diff --git a/CLAUDE.md b/CLAUDE.md index 2e6b737f..1ab42780 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -52,6 +52,19 @@ The catalogue exists in **two forms**, and they must not drift: ## Non-obvious invariants +### Bootstrap lives in `@layer bootstrap` — and `!important` reverses that + +- For **normal** declarations, unlayered CSS (ours, and an integrator's) beats layered + Bootstrap. That is the whole point of `css/foundations/_bootstrap-layer.css`. +- For **`!important`** declarations the layer order is reversed (CSS Cascading 5 §6.4): a + layered important declaration beats an unlayered one. **Every Bootstrap utility is + `!important`**, so a `.w-100` in the markup silently defeats an unlayered + `.my-column { width: 350px !important }`. There is nothing to escalate to — the fix is to + drop the utility from the markup. +- This is the real reason `components/` may not use `!important`: ours would lose too. +- Integrators upgrading to v3 must audit every `!important` of theirs sitting on an element + that also carries a Bootstrap utility for the same property. The relation flipped. + ### `User` entity (MappedSuperclass) - `User` is `#[ORM\MappedSuperclass]` — instantiate directly with `new User()`, never via a factory. diff --git a/doc/macros.md b/doc/macros.md index 50b67c6c..71f1a2f1 100644 --- a/doc/macros.md +++ b/doc/macros.md @@ -26,10 +26,13 @@ To use this macro, you first need to import it in your Twig template: | Parameter | Type | Description | | :--- | :--- | :--- | -| `item` | `object` | The entity instance (used to generate the CSRF token for deletion). | +| `item` | `object` | The entity instance. Only used as the fallback CSRF token id (`delete`), which matches no shipped controller — pass `delete_token` instead. | | `edit_path` | `string` | (Optional) The URL for the "Edit" action. If omitted, the edit link will not be displayed. | | `delete_path` | `string` | (Optional) The URL for the "Delete" action. If omitted, the delete link will not be displayed. | | `delete_confirm_msg` | `string` | (Optional) A custom confirmation message for the deletion. Defaults to the translation of `text.confirm_delete`. | +| `status` | `hash` | (Optional) The online/offline toggle: `{ path, confirm, label }`. `confirm` may contain `%s`, replaced at runtime by the target state. | +| `delete_token` | `string` | (Optional) The CSRF token **value** for the delete form, e.g. `csrf_token('delete__post' ~ post.id)`. Each controller validates its own id, so the caller must compute it. | +| `extra` | `Markup` | (Optional) Caller-supplied markup inserted between the edit link and the status toggle — a preview link, a modal trigger, anything the fixed slots cannot express. Capture it with `{% set %}` so it is not escaped. | ### Example @@ -49,6 +52,30 @@ In a DataTable row: {% endblock %} ``` +### Adding an item the macro does not cover + +Capture the markup, then pass it by name — `extra` sits after `delete_token` in the +signature, so a named argument keeps the call readable: + +```twig +{% set extra %} + + {{ ux_icon('lucide:link') }} {{ 'text.preview'|trans }} + +{% endset %} + +{{ list.actions( + item, + path('admin_event_edit', {id: item.id}), + path('admin_event_delete', {id: item.id}), + extra: extra, + delete_token: csrf_token('delete__event' ~ item.id), +) }} +``` + +`{% set %}` yields a `Markup` object, which Twig prints as-is. A plain string passed here +would be escaped and show up as literal HTML. + ## Breadcrumb Macro The `breadcrumb` macro generates a navigation breadcrumb list from an array of items. diff --git a/doc/theming.md b/doc/theming.md index b6cb632b..758626df 100644 --- a/doc/theming.md +++ b/doc/theming.md @@ -60,6 +60,13 @@ Override only these — the semantic layer. Values below are the defaults. > The `-contrast` companion of each (`--aro-color-primary-contrast`…) is the text colour > drawn on top of it — set it too if your brand colour needs dark text. +> **Override the `-rgb` companion with its colour.** Every bridged colour has an +> `--aro-color-*-rgb` twin holding its bare `r, g, b` triplet (`--aro-color-secondary-rgb: +> 46, 79, 94`). Bootstrap's utilities (`.bg-*`, `.text-bg-*`, focus rings) consume the +> triplet, not the hex — re-theme a colour without its twin and those utilities keep the +> default palette. No override rule can catch them either: the layered `!important` on +> Bootstrap's utilities beats any unlayered one. The variable pair is the seam. + ### Semantic `--aro-color-success` `#63CEB3` · `--aro-color-danger` `#E52321` · diff --git a/doc/upgrade-v3.md b/doc/upgrade-v3.md index ac129edd..e390c9c9 100644 --- a/doc/upgrade-v3.md +++ b/doc/upgrade-v3.md @@ -104,6 +104,41 @@ A `data-toggle="modal"` left over from Bootstrap 4 raises no error — the butto nothing. Grep your admin templates for `data-toggle`, `data-target`, `data-dismiss` and add the `-bs-` (see above). +### Select2 AJAX payload + +The `processResults` shipped in `app.js` changed shape. Before: + +```js +results: data.items, +pagination: { more: (params.page * 20) < data.total_count } +``` + +Now: + +```js +results: data.results, +pagination: { more: data.pagination.more } +``` + +An endpoint still answering `{ total_count, items }` **fails silently in the worst way**: the +request fires, the response arrives, and the list renders empty — nothing in the console points +at the payload. Every AJAX Select2 endpoint must now answer: + +```json +{ + "results": [ { "id": 12, "full_name": "…" } ], + "pagination": { "more": true }, + "total_count": 137 +} +``` + +`full_name` may be an HTML fragment: `escapeMarkup` is the identity function and +`templateResult` reads `repo.full_name || repo.text`, so a row carrying neither renders blank. + +`Component\Select2\Select2` emits this shape for you — but only for Doctrine-backed lists (it +ends in a `QueryBuilder`, `Select2DataProviderInterface` included). A list fed by a search +engine or a remote API has to build the payload itself; the contract above is all it owes. + --- ## Rich-text editor — CKEditor → Quill diff --git a/docs/catalog.html b/docs/catalog.html index 161ecf95..0fc346a1 100644 --- a/docs/catalog.html +++ b/docs/catalog.html @@ -157,12 +157,21 @@ /* ------------------------------------------------------------------ Brand */ --aro-color-primary: #06BAB4; + /* + * `-rgb` companions. Bootstrap's legacy `rgba(var(--bs-*-rgb), a)` consumers need a + * bare comma triplet, and CSS cannot decompose a hex token into one (relative colour + * syntax cannot produce a naked triplet). So each colour the bridge translates carries + * its triplet as a token: override a colour, override its companion — the bridge maps + * both and hardcodes nothing. + */ + --aro-color-primary-rgb: 6, 186, 180; --aro-color-primary-hover: #05A29D; --aro-color-primary-contrast: #FFFFFF; /* Translucent teal tint — soft primary surface (e.g. the navbar user-icon circle). */ --aro-color-primary-subtle: rgba(122, 220, 218, 0.71); --aro-color-secondary: #2E4F5E; + --aro-color-secondary-rgb: 46, 79, 94; --aro-color-secondary-hover: #223C48; --aro-color-secondary-contrast: #FFFFFF; @@ -178,13 +187,18 @@ /* -------------------------------------------------------------- Semantic */ --aro-color-success: #63CEB3; + --aro-color-success-rgb: 99, 206, 179; --aro-color-danger: #E52321; + --aro-color-danger-rgb: 229, 35, 33; --aro-color-warning: #F25C05; + --aro-color-warning-rgb: 242, 92, 5; --aro-color-info: #E39B02; + --aro-color-info-rgb: 227, 155, 2; /* -------------------------------------------------------------- Surfaces */ --aro-color-bg: #F5F8FA; + --aro-color-bg-rgb: 245, 248, 250; --aro-color-surface: #FFFFFF; --aro-color-surface-muted: #EBEBEB; /* Select2 multi-select chip fill (lot B). */ /* Chrome that must read as inert beside the content it captions — the editor toolbar. @@ -195,6 +209,7 @@ /* ------------------------------------------------------------------ Text */ --aro-color-text: #333333; + --aro-color-text-rgb: 51, 51, 51; --aro-color-text-strong: #181C32; --aro-color-text-label: #3F4254; --aro-color-text-muted: #99A1B7; @@ -426,37 +441,41 @@ * `--bs-*-rgb` must be set alongside every colour. Bootstrap uses the RGB triplets inside * `rgba()` for subtle backgrounds, focus rings and `.text-bg-*`; setting only the hex * leaves those derivatives on Bootstrap's default blue, which produces components that - * are half-teal and half-blue — worse than not bridging at all. + * are half-teal and half-blue — worse than not bridging at all. The triplets come from + * the `-rgb` companion tokens in _tokens.css — never literals here — so an integrator who + * re-themes a colour re-themes every rgba() derivative by overriding the companion pair + * in one place. (The layered `!important` on Bootstrap's `.bg-*` utilities beats any + * unlayered override rule, so the variable is the only reliable seam.) */ :root { /* ------------------------------------------------------------ Theme colours */ --bs-primary: var(--aro-color-primary); - --bs-primary-rgb: 6, 186, 180; + --bs-primary-rgb: var(--aro-color-primary-rgb); --bs-secondary: var(--aro-color-secondary); - --bs-secondary-rgb: 46, 79, 94; + --bs-secondary-rgb: var(--aro-color-secondary-rgb); --bs-success: var(--aro-color-success); - --bs-success-rgb: 99, 206, 179; + --bs-success-rgb: var(--aro-color-success-rgb); --bs-danger: var(--aro-color-danger); - --bs-danger-rgb: 229, 35, 33; + --bs-danger-rgb: var(--aro-color-danger-rgb); --bs-warning: var(--aro-color-warning); - --bs-warning-rgb: 242, 92, 5; + --bs-warning-rgb: var(--aro-color-warning-rgb); --bs-info: var(--aro-color-info); - --bs-info-rgb: 227, 155, 2; + --bs-info-rgb: var(--aro-color-info-rgb); /* ------------------------------------------------------------------- Body */ --bs-body-bg: var(--aro-color-bg); - --bs-body-bg-rgb: 245, 248, 250; + --bs-body-bg-rgb: var(--aro-color-bg-rgb); --bs-body-color: var(--aro-color-text); - --bs-body-color-rgb: 51, 51, 51; + --bs-body-color-rgb: var(--aro-color-text-rgb); /* -------------------------------------------------------------- Emphasis */ @@ -466,9 +485,9 @@ /* ------------------------------------------------------------------ Links */ --bs-link-color: var(--aro-color-secondary); - --bs-link-color-rgb: 46, 79, 94; + --bs-link-color-rgb: var(--aro-color-secondary-rgb); --bs-link-hover-color: var(--aro-color-primary); - --bs-link-hover-color-rgb: 6, 186, 180; + --bs-link-hover-color-rgb: var(--aro-color-primary-rgb); /* --------------------------------------------------------------- Borders */ @@ -482,7 +501,7 @@ /* ---------------------------------------------------------- Focus ring */ - --bs-focus-ring-color: rgba(6, 186, 180, 0.25); + --bs-focus-ring-color: rgba(var(--aro-color-primary-rgb), 0.25); } /** @@ -575,9 +594,22 @@ color: var(--aro-color-secondary); } +/* Bootstrap's reboot leaves headings at 500; h1 carried 700 through v2 (Stisla set every + heading bold) and reads underweight without it. Only h1 is pinned — the smaller headings + are sized and weighted by the components that use them. */ +h1 { + font-weight: var(--aro-weight-bold); +} + +h2 { + font-weight: var(--aro-weight-semibold); +} + +/* The margin is load-bearing rhythm: !important so a scoped tweak cannot silently + compress the separator back to Bootstrap's default. */ hr { border-top-style: dashed; - margin: 2.5rem 0; + margin: 2.5rem 0 !important; } p { @@ -1391,6 +1423,157 @@ border-top: 0.5em solid var(--aro-color-primary); } +/* ./vendor/_spectrum.css */ +/** + * Spectrum — colour picker skin, for the `ColorType` form type. + * + * Loaded (plugin CSS + JS) by `Form/base.html.twig`, initialised on `.color-picker` in + * `js/app.js` — the class the `aropixel_admin_color_widget` block puts on the input. + * + * Restores the skin that lived in the deleted `css/custom.css` (v3.0.0) and was not + * carried over by the CSS reorganisation: without it the picker falls back to the raw + * plugin stylesheet, which does not match the admin at all. + * + * ⚠ Rendered in a popup on click, so absent from the visual baseline — validated by + * opening a colour field, not by diff. + * + * Restyled on tokens rather than transcribed: the resting control now wears the field + * surface of the other inputs, and the legacy `--main-bg-color` / Font Awesome chevron + * the v3.0.0 rules relied on are both gone. Geometry is kept as it was. + */ + +/* Resting control: the swatch + chevron box replacing the input. Reads as a field. */ +.sp-replacer { + margin: 0; + border: 1px solid var(--aro-field-border); + background: var(--aro-field-bg); + border-radius: var(--aro-radius-input); + padding: 5px; +} + +.sp-replacer:hover, +.sp-replacer.sp-active { + background: var(--aro-field-bg-focus); + border-color: var(--aro-color-border); +} + +.sp-preview { + width: 25px; + height: 22px; + border: none; + margin-right: var(--aro-space-1); + border-radius: var(--aro-radius-xs); +} + +/* Spectrum emits a `▼` glyph here; the v3.0.0 skin hid it behind a Font Awesome chevron, + which the admin no longer ships. Keep the glyph, dressed down to the muted text tone. */ +.sp-dd { + margin-top: 1px; + font-size: 10px; + line-height: 22px; + color: var(--aro-color-text-muted); +} + +/* Popup */ +.sp-container { + background-color: var(--aro-color-surface); + border: 1px solid var(--aro-color-border); + border-radius: var(--aro-radius-sm); + overflow: hidden; + box-shadow: var(--aro-shadow-dropdown); +} + +.sp-picker-container { + width: 220px; +} + +/* The plugin lays saturation / hue out in percentages; these two keep the hue bar clear + of the saturation square at the width set above. */ +.sp-hue { + border: none; + left: 86%; +} + +.sp-color { + border: 1px solid var(--aro-color-border); + right: 19%; +} + +.sp-slider { + height: 4px; + left: -2px; + right: -2px; + border: 1px solid var(--aro-color-border); + background-color: var(--aro-color-surface); + border-radius: var(--aro-radius-sm); +} + +/* Hex input inside the popup (`showInput: true`). */ +.sp-input { + border: 1px solid var(--aro-color-border); + border-radius: var(--aro-radius-xs); + padding: 6px 10px; + color: var(--aro-color-text); + box-shadow: none; + -webkit-appearance: none; + outline: none; +} + +.sp-input:focus { + border-color: var(--aro-color-border-focus); +} + +/* Buttons. The plugin ships its own gradients and text shadows, hence the flattening. */ +.sp-button-container { + float: none; + clear: right; +} + +.sp-cancel, +.sp-choose, +.sp-palette-toggle { + border: 0; + border-radius: var(--aro-radius-sm); + padding: 8px 6px; + float: left; + width: 48%; + box-sizing: border-box; + text-align: center; + font-size: var(--aro-text-xs); + line-height: 1.7; + outline: 0; +} + +.sp-container button { + background-color: var(--aro-color-primary); + background-image: none; + border: 1px solid var(--aro-color-primary); + color: var(--aro-color-primary-contrast); + text-shadow: none; +} + +.sp-container button:hover { + background-image: none; + background-color: var(--aro-color-primary-hover); + border-color: var(--aro-color-primary-hover); + text-shadow: none; +} + +/* `.sp-cancel` is an ``, so the colour needs `!important` (§7-1) to beat the plugin's + own `a.sp-cancel` rule, which is more specific than a class on its own. */ +.sp-cancel { + border: 1px solid var(--aro-color-border); + background-color: var(--aro-color-surface); + color: var(--aro-color-text) !important; +} + +.sp-cancel:hover { + border-color: var(--aro-color-border); + background-color: var(--aro-color-surface-subtle); + color: var(--aro-color-text) !important; + text-decoration: none; +} + /* ./components/_btn.css */ /** * Button — base, sizes and icon buttons. @@ -2375,6 +2558,17 @@ line-height: var(--aro-leading-tight); } + +/* + * Notification-style panel (.dropdown-list): the navbar dropdowns whose items are rows + * with avatar/time metadata. v2 sized it at 350px with no menu padding (rows carry their + * own); both were lost in the ablation and the panel collapsed to its content. + */ +.dropdown-menu.dropdown-list { + min-width: 350px; + padding: 0; +} + /* Active / focused item — relocated from the style.css tail (lot B). Unlayered, so it beats Bootstrap's layered `.dropdown-item` rules without `!important`. */ a.dropdown-item:focus, a.dropdown-item:active, a.dropdown-item.active { @@ -3503,6 +3697,16 @@ --bs-modal-header-padding: var(--aro-space-4) var(--aro-space-6); } + +/* + * A header with no title is just a close-button strip: the hairline under it would + * underline nothing and reads as a stray rule (e.g. a modal whose heading lives in the + * body). Detected structurally, so titled headers keep their separator. + */ +.modal-header:not(:has(.modal-title, h1, h2, h3, h4, h5, h6)) { + --bs-modal-header-border-width: 0; +} + /* * Size scale (design system): sm 400 · md 520 (default) · lg 720 · xl 960. Set on * `.modal-dialog`, so both plain modals and the Dialog component — which builds a @@ -3945,6 +4149,38 @@ .aro-alert--info { --aro-alert-accent: var(--aro-color-info); } .aro-alert--primary { --aro-alert-accent: var(--aro-color-primary); } +/* ---------------------------------------------------- Bootstrap .alert-* variants */ + +/* + * Solid presentation for Bootstrap's own alert vocabulary — flash messages and legacy + * templates that never migrated to .aro-alert: semantic fill, white text, no border, + * the look Tick&Live carried through v2. The .aro-alert component above keeps its + * tinted default; `.aro-alert--solid` gives the same tone there. + * + * Driven through Bootstrap's own --bs-alert-* variables (unlayered, so they outrank the + * layered defaults), like the card and dropdown components. + */ +.alert-success, .alert-danger, .alert-warning, .alert-info { + --bs-alert-color: var(--aro-color-text-inverted); + --bs-alert-border-color: transparent; + --bs-alert-link-color: var(--aro-color-text-inverted); +} + +.alert-success { --bs-alert-bg: var(--aro-color-success); } +.alert-danger { --bs-alert-bg: var(--aro-color-danger); } +.alert-warning { --bs-alert-bg: var(--aro-color-warning); } +.alert-info { --bs-alert-bg: var(--aro-color-info); } + +/* Content links go white with their panel (v2: `.alert:not(.alert-light) a`). */ +.alert-success a, .alert-danger a, .alert-warning a, .alert-info a { + color: inherit; +} + +/* A paragraph is the alert's own line of text, not prose: no trailing rhythm (v2 rule). */ +.alert p { + margin-bottom: 0; +} + /* ./components/_section-header.css */ /** * Section header — the identity block at the top of every screen. @@ -5545,6 +5781,17 @@ border-bottom: 3px solid var(--aro-color-primary); } +/* + * Pane content breathes: prose inside a tab reads against chrome on every side, and the + * 18px body leading that suits table cells cramps it here. Stisla shipped this pane rule + * at 24px with its own padding; 28px is the arbitrated value — the airier rhythm the + * admin actually wore (v2's prose rule) — kept as a literal like the body-leading one + * in _typography.css, pending the §4 density pass. + */ +.tab-content > .tab-pane { + line-height: 28px; +} + /* ./components/_bg-utilities.css */ /** * Background-colour utilities — cross-suite. diff --git a/docs/index.html b/docs/index.html index 161ecf95..0fc346a1 100644 --- a/docs/index.html +++ b/docs/index.html @@ -157,12 +157,21 @@ /* ------------------------------------------------------------------ Brand */ --aro-color-primary: #06BAB4; + /* + * `-rgb` companions. Bootstrap's legacy `rgba(var(--bs-*-rgb), a)` consumers need a + * bare comma triplet, and CSS cannot decompose a hex token into one (relative colour + * syntax cannot produce a naked triplet). So each colour the bridge translates carries + * its triplet as a token: override a colour, override its companion — the bridge maps + * both and hardcodes nothing. + */ + --aro-color-primary-rgb: 6, 186, 180; --aro-color-primary-hover: #05A29D; --aro-color-primary-contrast: #FFFFFF; /* Translucent teal tint — soft primary surface (e.g. the navbar user-icon circle). */ --aro-color-primary-subtle: rgba(122, 220, 218, 0.71); --aro-color-secondary: #2E4F5E; + --aro-color-secondary-rgb: 46, 79, 94; --aro-color-secondary-hover: #223C48; --aro-color-secondary-contrast: #FFFFFF; @@ -178,13 +187,18 @@ /* -------------------------------------------------------------- Semantic */ --aro-color-success: #63CEB3; + --aro-color-success-rgb: 99, 206, 179; --aro-color-danger: #E52321; + --aro-color-danger-rgb: 229, 35, 33; --aro-color-warning: #F25C05; + --aro-color-warning-rgb: 242, 92, 5; --aro-color-info: #E39B02; + --aro-color-info-rgb: 227, 155, 2; /* -------------------------------------------------------------- Surfaces */ --aro-color-bg: #F5F8FA; + --aro-color-bg-rgb: 245, 248, 250; --aro-color-surface: #FFFFFF; --aro-color-surface-muted: #EBEBEB; /* Select2 multi-select chip fill (lot B). */ /* Chrome that must read as inert beside the content it captions — the editor toolbar. @@ -195,6 +209,7 @@ /* ------------------------------------------------------------------ Text */ --aro-color-text: #333333; + --aro-color-text-rgb: 51, 51, 51; --aro-color-text-strong: #181C32; --aro-color-text-label: #3F4254; --aro-color-text-muted: #99A1B7; @@ -426,37 +441,41 @@ * `--bs-*-rgb` must be set alongside every colour. Bootstrap uses the RGB triplets inside * `rgba()` for subtle backgrounds, focus rings and `.text-bg-*`; setting only the hex * leaves those derivatives on Bootstrap's default blue, which produces components that - * are half-teal and half-blue — worse than not bridging at all. + * are half-teal and half-blue — worse than not bridging at all. The triplets come from + * the `-rgb` companion tokens in _tokens.css — never literals here — so an integrator who + * re-themes a colour re-themes every rgba() derivative by overriding the companion pair + * in one place. (The layered `!important` on Bootstrap's `.bg-*` utilities beats any + * unlayered override rule, so the variable is the only reliable seam.) */ :root { /* ------------------------------------------------------------ Theme colours */ --bs-primary: var(--aro-color-primary); - --bs-primary-rgb: 6, 186, 180; + --bs-primary-rgb: var(--aro-color-primary-rgb); --bs-secondary: var(--aro-color-secondary); - --bs-secondary-rgb: 46, 79, 94; + --bs-secondary-rgb: var(--aro-color-secondary-rgb); --bs-success: var(--aro-color-success); - --bs-success-rgb: 99, 206, 179; + --bs-success-rgb: var(--aro-color-success-rgb); --bs-danger: var(--aro-color-danger); - --bs-danger-rgb: 229, 35, 33; + --bs-danger-rgb: var(--aro-color-danger-rgb); --bs-warning: var(--aro-color-warning); - --bs-warning-rgb: 242, 92, 5; + --bs-warning-rgb: var(--aro-color-warning-rgb); --bs-info: var(--aro-color-info); - --bs-info-rgb: 227, 155, 2; + --bs-info-rgb: var(--aro-color-info-rgb); /* ------------------------------------------------------------------- Body */ --bs-body-bg: var(--aro-color-bg); - --bs-body-bg-rgb: 245, 248, 250; + --bs-body-bg-rgb: var(--aro-color-bg-rgb); --bs-body-color: var(--aro-color-text); - --bs-body-color-rgb: 51, 51, 51; + --bs-body-color-rgb: var(--aro-color-text-rgb); /* -------------------------------------------------------------- Emphasis */ @@ -466,9 +485,9 @@ /* ------------------------------------------------------------------ Links */ --bs-link-color: var(--aro-color-secondary); - --bs-link-color-rgb: 46, 79, 94; + --bs-link-color-rgb: var(--aro-color-secondary-rgb); --bs-link-hover-color: var(--aro-color-primary); - --bs-link-hover-color-rgb: 6, 186, 180; + --bs-link-hover-color-rgb: var(--aro-color-primary-rgb); /* --------------------------------------------------------------- Borders */ @@ -482,7 +501,7 @@ /* ---------------------------------------------------------- Focus ring */ - --bs-focus-ring-color: rgba(6, 186, 180, 0.25); + --bs-focus-ring-color: rgba(var(--aro-color-primary-rgb), 0.25); } /** @@ -575,9 +594,22 @@ color: var(--aro-color-secondary); } +/* Bootstrap's reboot leaves headings at 500; h1 carried 700 through v2 (Stisla set every + heading bold) and reads underweight without it. Only h1 is pinned — the smaller headings + are sized and weighted by the components that use them. */ +h1 { + font-weight: var(--aro-weight-bold); +} + +h2 { + font-weight: var(--aro-weight-semibold); +} + +/* The margin is load-bearing rhythm: !important so a scoped tweak cannot silently + compress the separator back to Bootstrap's default. */ hr { border-top-style: dashed; - margin: 2.5rem 0; + margin: 2.5rem 0 !important; } p { @@ -1391,6 +1423,157 @@ border-top: 0.5em solid var(--aro-color-primary); } +/* ./vendor/_spectrum.css */ +/** + * Spectrum — colour picker skin, for the `ColorType` form type. + * + * Loaded (plugin CSS + JS) by `Form/base.html.twig`, initialised on `.color-picker` in + * `js/app.js` — the class the `aropixel_admin_color_widget` block puts on the input. + * + * Restores the skin that lived in the deleted `css/custom.css` (v3.0.0) and was not + * carried over by the CSS reorganisation: without it the picker falls back to the raw + * plugin stylesheet, which does not match the admin at all. + * + * ⚠ Rendered in a popup on click, so absent from the visual baseline — validated by + * opening a colour field, not by diff. + * + * Restyled on tokens rather than transcribed: the resting control now wears the field + * surface of the other inputs, and the legacy `--main-bg-color` / Font Awesome chevron + * the v3.0.0 rules relied on are both gone. Geometry is kept as it was. + */ + +/* Resting control: the swatch + chevron box replacing the input. Reads as a field. */ +.sp-replacer { + margin: 0; + border: 1px solid var(--aro-field-border); + background: var(--aro-field-bg); + border-radius: var(--aro-radius-input); + padding: 5px; +} + +.sp-replacer:hover, +.sp-replacer.sp-active { + background: var(--aro-field-bg-focus); + border-color: var(--aro-color-border); +} + +.sp-preview { + width: 25px; + height: 22px; + border: none; + margin-right: var(--aro-space-1); + border-radius: var(--aro-radius-xs); +} + +/* Spectrum emits a `▼` glyph here; the v3.0.0 skin hid it behind a Font Awesome chevron, + which the admin no longer ships. Keep the glyph, dressed down to the muted text tone. */ +.sp-dd { + margin-top: 1px; + font-size: 10px; + line-height: 22px; + color: var(--aro-color-text-muted); +} + +/* Popup */ +.sp-container { + background-color: var(--aro-color-surface); + border: 1px solid var(--aro-color-border); + border-radius: var(--aro-radius-sm); + overflow: hidden; + box-shadow: var(--aro-shadow-dropdown); +} + +.sp-picker-container { + width: 220px; +} + +/* The plugin lays saturation / hue out in percentages; these two keep the hue bar clear + of the saturation square at the width set above. */ +.sp-hue { + border: none; + left: 86%; +} + +.sp-color { + border: 1px solid var(--aro-color-border); + right: 19%; +} + +.sp-slider { + height: 4px; + left: -2px; + right: -2px; + border: 1px solid var(--aro-color-border); + background-color: var(--aro-color-surface); + border-radius: var(--aro-radius-sm); +} + +/* Hex input inside the popup (`showInput: true`). */ +.sp-input { + border: 1px solid var(--aro-color-border); + border-radius: var(--aro-radius-xs); + padding: 6px 10px; + color: var(--aro-color-text); + box-shadow: none; + -webkit-appearance: none; + outline: none; +} + +.sp-input:focus { + border-color: var(--aro-color-border-focus); +} + +/* Buttons. The plugin ships its own gradients and text shadows, hence the flattening. */ +.sp-button-container { + float: none; + clear: right; +} + +.sp-cancel, +.sp-choose, +.sp-palette-toggle { + border: 0; + border-radius: var(--aro-radius-sm); + padding: 8px 6px; + float: left; + width: 48%; + box-sizing: border-box; + text-align: center; + font-size: var(--aro-text-xs); + line-height: 1.7; + outline: 0; +} + +.sp-container button { + background-color: var(--aro-color-primary); + background-image: none; + border: 1px solid var(--aro-color-primary); + color: var(--aro-color-primary-contrast); + text-shadow: none; +} + +.sp-container button:hover { + background-image: none; + background-color: var(--aro-color-primary-hover); + border-color: var(--aro-color-primary-hover); + text-shadow: none; +} + +/* `.sp-cancel` is an ``, so the colour needs `!important` (§7-1) to beat the plugin's + own `a.sp-cancel` rule, which is more specific than a class on its own. */ +.sp-cancel { + border: 1px solid var(--aro-color-border); + background-color: var(--aro-color-surface); + color: var(--aro-color-text) !important; +} + +.sp-cancel:hover { + border-color: var(--aro-color-border); + background-color: var(--aro-color-surface-subtle); + color: var(--aro-color-text) !important; + text-decoration: none; +} + /* ./components/_btn.css */ /** * Button — base, sizes and icon buttons. @@ -2375,6 +2558,17 @@ line-height: var(--aro-leading-tight); } + +/* + * Notification-style panel (.dropdown-list): the navbar dropdowns whose items are rows + * with avatar/time metadata. v2 sized it at 350px with no menu padding (rows carry their + * own); both were lost in the ablation and the panel collapsed to its content. + */ +.dropdown-menu.dropdown-list { + min-width: 350px; + padding: 0; +} + /* Active / focused item — relocated from the style.css tail (lot B). Unlayered, so it beats Bootstrap's layered `.dropdown-item` rules without `!important`. */ a.dropdown-item:focus, a.dropdown-item:active, a.dropdown-item.active { @@ -3503,6 +3697,16 @@ --bs-modal-header-padding: var(--aro-space-4) var(--aro-space-6); } + +/* + * A header with no title is just a close-button strip: the hairline under it would + * underline nothing and reads as a stray rule (e.g. a modal whose heading lives in the + * body). Detected structurally, so titled headers keep their separator. + */ +.modal-header:not(:has(.modal-title, h1, h2, h3, h4, h5, h6)) { + --bs-modal-header-border-width: 0; +} + /* * Size scale (design system): sm 400 · md 520 (default) · lg 720 · xl 960. Set on * `.modal-dialog`, so both plain modals and the Dialog component — which builds a @@ -3945,6 +4149,38 @@ .aro-alert--info { --aro-alert-accent: var(--aro-color-info); } .aro-alert--primary { --aro-alert-accent: var(--aro-color-primary); } +/* ---------------------------------------------------- Bootstrap .alert-* variants */ + +/* + * Solid presentation for Bootstrap's own alert vocabulary — flash messages and legacy + * templates that never migrated to .aro-alert: semantic fill, white text, no border, + * the look Tick&Live carried through v2. The .aro-alert component above keeps its + * tinted default; `.aro-alert--solid` gives the same tone there. + * + * Driven through Bootstrap's own --bs-alert-* variables (unlayered, so they outrank the + * layered defaults), like the card and dropdown components. + */ +.alert-success, .alert-danger, .alert-warning, .alert-info { + --bs-alert-color: var(--aro-color-text-inverted); + --bs-alert-border-color: transparent; + --bs-alert-link-color: var(--aro-color-text-inverted); +} + +.alert-success { --bs-alert-bg: var(--aro-color-success); } +.alert-danger { --bs-alert-bg: var(--aro-color-danger); } +.alert-warning { --bs-alert-bg: var(--aro-color-warning); } +.alert-info { --bs-alert-bg: var(--aro-color-info); } + +/* Content links go white with their panel (v2: `.alert:not(.alert-light) a`). */ +.alert-success a, .alert-danger a, .alert-warning a, .alert-info a { + color: inherit; +} + +/* A paragraph is the alert's own line of text, not prose: no trailing rhythm (v2 rule). */ +.alert p { + margin-bottom: 0; +} + /* ./components/_section-header.css */ /** * Section header — the identity block at the top of every screen. @@ -5545,6 +5781,17 @@ border-bottom: 3px solid var(--aro-color-primary); } +/* + * Pane content breathes: prose inside a tab reads against chrome on every side, and the + * 18px body leading that suits table cells cramps it here. Stisla shipped this pane rule + * at 24px with its own padding; 28px is the arbitrated value — the airier rhythm the + * admin actually wore (v2's prose rule) — kept as a literal like the body-leading one + * in _typography.css, pending the §4 density pass. + */ +.tab-content > .tab-pane { + line-height: 28px; +} + /* ./components/_bg-utilities.css */ /** * Background-colour utilities — cross-suite. diff --git a/src/Form/Type/Image/InstanceToData.php b/src/Form/Type/Image/InstanceToData.php index f37d14b2..af7b59d5 100644 --- a/src/Form/Type/Image/InstanceToData.php +++ b/src/Form/Type/Image/InstanceToData.php @@ -39,6 +39,13 @@ public function getFileName(mixed $data): mixed return $value; } + /* + * Attributes and crops are optional: ImageMapper guards their use, and ImageType + * accepts arbitrary data objects (only `filename_value` is configurable). Reading + * them strictly broke every integration passing a domain object without an + * `attributes` property — v2 only read attributes in the gallery, whose items + * always have one. Tolerant read: null when the path is not readable. + */ public function getAttributes(mixed $data): mixed { $value = $data; @@ -46,7 +53,9 @@ public function getAttributes(mixed $data): mixed // invalid data type if ($data && !\is_string($data)) { $propertyAccessor = PropertyAccess::createPropertyAccessor(); - $value = $propertyAccessor->getValue($data, $this->attributesValue); + $value = $propertyAccessor->isReadable($data, $this->attributesValue) + ? $propertyAccessor->getValue($data, $this->attributesValue) + : null; } return $value; @@ -59,7 +68,9 @@ public function getCrops(mixed $data): mixed // invalid data type if (!\is_string($data)) { $propertyAccessor = PropertyAccess::createPropertyAccessor(); - $value = $propertyAccessor->getValue($data, $this->cropsValue); + $value = $propertyAccessor->isReadable($data, $this->cropsValue) + ? $propertyAccessor->getValue($data, $this->cropsValue) + : null; } return $value; diff --git a/src/Resources/public/css/components/_alert.css b/src/Resources/public/css/components/_alert.css index ca7c584e..e3926c56 100644 --- a/src/Resources/public/css/components/_alert.css +++ b/src/Resources/public/css/components/_alert.css @@ -177,3 +177,35 @@ .aro-alert--warning { --aro-alert-accent: var(--aro-color-warning); } .aro-alert--info { --aro-alert-accent: var(--aro-color-info); } .aro-alert--primary { --aro-alert-accent: var(--aro-color-primary); } + +/* ---------------------------------------------------- Bootstrap .alert-* variants */ + +/* + * Solid presentation for Bootstrap's own alert vocabulary — flash messages and legacy + * templates that never migrated to .aro-alert: semantic fill, white text, no border, + * the look Tick&Live carried through v2. The .aro-alert component above keeps its + * tinted default; `.aro-alert--solid` gives the same tone there. + * + * Driven through Bootstrap's own --bs-alert-* variables (unlayered, so they outrank the + * layered defaults), like the card and dropdown components. + */ +.alert-success, .alert-danger, .alert-warning, .alert-info { + --bs-alert-color: var(--aro-color-text-inverted); + --bs-alert-border-color: transparent; + --bs-alert-link-color: var(--aro-color-text-inverted); +} + +.alert-success { --bs-alert-bg: var(--aro-color-success); } +.alert-danger { --bs-alert-bg: var(--aro-color-danger); } +.alert-warning { --bs-alert-bg: var(--aro-color-warning); } +.alert-info { --bs-alert-bg: var(--aro-color-info); } + +/* Content links go white with their panel (v2: `.alert:not(.alert-light) a`). */ +.alert-success a, .alert-danger a, .alert-warning a, .alert-info a { + color: inherit; +} + +/* A paragraph is the alert's own line of text, not prose: no trailing rhythm (v2 rule). */ +.alert p { + margin-bottom: 0; +} diff --git a/src/Resources/public/css/components/_dropdown.css b/src/Resources/public/css/components/_dropdown.css index 7c229927..161eb7aa 100644 --- a/src/Resources/public/css/components/_dropdown.css +++ b/src/Resources/public/css/components/_dropdown.css @@ -79,6 +79,17 @@ line-height: var(--aro-leading-tight); } + +/* + * Notification-style panel (.dropdown-list): the navbar dropdowns whose items are rows + * with avatar/time metadata. v2 sized it at 350px with no menu padding (rows carry their + * own); both were lost in the ablation and the panel collapsed to its content. + */ +.dropdown-menu.dropdown-list { + min-width: 350px; + padding: 0; +} + /* Active / focused item — relocated from the style.css tail (lot B). Unlayered, so it beats Bootstrap's layered `.dropdown-item` rules without `!important`. */ a.dropdown-item:focus, a.dropdown-item:active, a.dropdown-item.active { diff --git a/src/Resources/public/css/components/_modal.css b/src/Resources/public/css/components/_modal.css index 9f23ac92..45a2f039 100644 --- a/src/Resources/public/css/components/_modal.css +++ b/src/Resources/public/css/components/_modal.css @@ -16,6 +16,16 @@ --bs-modal-header-padding: var(--aro-space-4) var(--aro-space-6); } + +/* + * A header with no title is just a close-button strip: the hairline under it would + * underline nothing and reads as a stray rule (e.g. a modal whose heading lives in the + * body). Detected structurally, so titled headers keep their separator. + */ +.modal-header:not(:has(.modal-title, h1, h2, h3, h4, h5, h6)) { + --bs-modal-header-border-width: 0; +} + /* * Size scale (design system): sm 400 · md 520 (default) · lg 720 · xl 960. Set on * `.modal-dialog`, so both plain modals and the Dialog component — which builds a diff --git a/src/Resources/public/css/components/_tabs.css b/src/Resources/public/css/components/_tabs.css index 22c66fa2..bf9c653d 100644 --- a/src/Resources/public/css/components/_tabs.css +++ b/src/Resources/public/css/components/_tabs.css @@ -13,3 +13,14 @@ .nav-tabs.tab-underlined .nav-link.active { border-bottom: 3px solid var(--aro-color-primary); } + +/* + * Pane content breathes: prose inside a tab reads against chrome on every side, and the + * 18px body leading that suits table cells cramps it here. Stisla shipped this pane rule + * at 24px with its own padding; 28px is the arbitrated value — the airier rhythm the + * admin actually wore (v2's prose rule) — kept as a literal like the body-leading one + * in _typography.css, pending the §4 density pass. + */ +.tab-content > .tab-pane { + line-height: 28px; +} diff --git a/src/Resources/public/css/foundations/_bootstrap-bridge.css b/src/Resources/public/css/foundations/_bootstrap-bridge.css index d9e17c5d..3dc71761 100644 --- a/src/Resources/public/css/foundations/_bootstrap-bridge.css +++ b/src/Resources/public/css/foundations/_bootstrap-bridge.css @@ -14,37 +14,41 @@ * `--bs-*-rgb` must be set alongside every colour. Bootstrap uses the RGB triplets inside * `rgba()` for subtle backgrounds, focus rings and `.text-bg-*`; setting only the hex * leaves those derivatives on Bootstrap's default blue, which produces components that - * are half-teal and half-blue — worse than not bridging at all. + * are half-teal and half-blue — worse than not bridging at all. The triplets come from + * the `-rgb` companion tokens in _tokens.css — never literals here — so an integrator who + * re-themes a colour re-themes every rgba() derivative by overriding the companion pair + * in one place. (The layered `!important` on Bootstrap's `.bg-*` utilities beats any + * unlayered override rule, so the variable is the only reliable seam.) */ :root { /* ------------------------------------------------------------ Theme colours */ --bs-primary: var(--aro-color-primary); - --bs-primary-rgb: 6, 186, 180; + --bs-primary-rgb: var(--aro-color-primary-rgb); --bs-secondary: var(--aro-color-secondary); - --bs-secondary-rgb: 46, 79, 94; + --bs-secondary-rgb: var(--aro-color-secondary-rgb); --bs-success: var(--aro-color-success); - --bs-success-rgb: 99, 206, 179; + --bs-success-rgb: var(--aro-color-success-rgb); --bs-danger: var(--aro-color-danger); - --bs-danger-rgb: 229, 35, 33; + --bs-danger-rgb: var(--aro-color-danger-rgb); --bs-warning: var(--aro-color-warning); - --bs-warning-rgb: 242, 92, 5; + --bs-warning-rgb: var(--aro-color-warning-rgb); --bs-info: var(--aro-color-info); - --bs-info-rgb: 227, 155, 2; + --bs-info-rgb: var(--aro-color-info-rgb); /* ------------------------------------------------------------------- Body */ --bs-body-bg: var(--aro-color-bg); - --bs-body-bg-rgb: 245, 248, 250; + --bs-body-bg-rgb: var(--aro-color-bg-rgb); --bs-body-color: var(--aro-color-text); - --bs-body-color-rgb: 51, 51, 51; + --bs-body-color-rgb: var(--aro-color-text-rgb); /* -------------------------------------------------------------- Emphasis */ @@ -54,9 +58,9 @@ /* ------------------------------------------------------------------ Links */ --bs-link-color: var(--aro-color-secondary); - --bs-link-color-rgb: 46, 79, 94; + --bs-link-color-rgb: var(--aro-color-secondary-rgb); --bs-link-hover-color: var(--aro-color-primary); - --bs-link-hover-color-rgb: 6, 186, 180; + --bs-link-hover-color-rgb: var(--aro-color-primary-rgb); /* --------------------------------------------------------------- Borders */ @@ -70,7 +74,7 @@ /* ---------------------------------------------------------- Focus ring */ - --bs-focus-ring-color: rgba(6, 186, 180, 0.25); + --bs-focus-ring-color: rgba(var(--aro-color-primary-rgb), 0.25); } /** diff --git a/src/Resources/public/css/foundations/_bootstrap-layer.css b/src/Resources/public/css/foundations/_bootstrap-layer.css index 1871f901..fc9f922e 100644 --- a/src/Resources/public/css/foundations/_bootstrap-layer.css +++ b/src/Resources/public/css/foundations/_bootstrap-layer.css @@ -5,20 +5,33 @@ * to bootstrap.min.css. @import only takes effect at the top of a stylesheet, so * this cannot be folded into style.css, which loads last and carries rules of its own. * - * Unlayered CSS always beats layered CSS, whatever the specificity. Putting Bootstrap — - * and only Bootstrap — in a layer therefore means every rule we write wins over it by - * construction, without `!important` and without specificity games. That is what makes - * the "zero `!important` in components/" rule of §7 enforceable rather than aspirational. + * Unlayered CSS beats layered CSS for **normal** declarations, whatever the specificity. + * Putting Bootstrap — and only Bootstrap — in a layer therefore means every rule we write + * wins over it by construction, without `!important` and without specificity games. That is + * what makes the "zero `!important` in components/" rule of §7 enforceable rather than + * aspirational. * - * It also settles the override contract of §5 for free: an integrator's own stylesheet is - * unlayered too, so it keeps winning over ours. + * It also settles the override contract of §5 for normal declarations: an integrator's own + * stylesheet is unlayered too, so it keeps winning over ours. * - * Two traps worth naming: + * Three traps worth naming: * - * 1. Layering *our* CSS instead would have the opposite effect — it would put us below + * 1. `!important` reverses the layer order (CSS Cascading 5 §6.4). An important declaration + * inside `@layer bootstrap` therefore BEATS an unlayered important declaration — the + * exact opposite of the normal case. Every Bootstrap utility is `!important` by design, + * so an integrator rule like + * + * .my-column { width: 350px !important } (unlayered) + * + * now loses to `.w-100` sitting on the same element. Verified in Chromium: the element + * computes to 100%. Integrators upgrading to v3 must drop the Bootstrap utility from the + * markup (right answer) rather than escalate — there is nothing left to escalate to. + * This is also why §7 forbids `!important` in components/: ours would lose too. + * + * 2. Layering *our* CSS instead would have the opposite effect — it would put us below * Bootstrap, which is strictly worse than doing nothing. * - * 2. Timing matters. Enabling this while Stisla was still loaded was tried and reverted: + * 3. Timing matters. Enabling this while Stisla was still loaded was tried and reverted: * layering Bootstrap does not only let *our* CSS win, it lets **everything unlayered** * win, and that included 5 118 lines of Stisla. Measured effect at the time: 40 of 48 * screens shifted, because Stisla rules that Bootstrap used to beat on specificity diff --git a/src/Resources/public/css/foundations/_tokens.css b/src/Resources/public/css/foundations/_tokens.css index 340b6a29..3d2c7c76 100644 --- a/src/Resources/public/css/foundations/_tokens.css +++ b/src/Resources/public/css/foundations/_tokens.css @@ -21,12 +21,21 @@ /* ------------------------------------------------------------------ Brand */ --aro-color-primary: #06BAB4; + /* + * `-rgb` companions. Bootstrap's legacy `rgba(var(--bs-*-rgb), a)` consumers need a + * bare comma triplet, and CSS cannot decompose a hex token into one (relative colour + * syntax cannot produce a naked triplet). So each colour the bridge translates carries + * its triplet as a token: override a colour, override its companion — the bridge maps + * both and hardcodes nothing. + */ + --aro-color-primary-rgb: 6, 186, 180; --aro-color-primary-hover: #05A29D; --aro-color-primary-contrast: #FFFFFF; /* Translucent teal tint — soft primary surface (e.g. the navbar user-icon circle). */ --aro-color-primary-subtle: rgba(122, 220, 218, 0.71); --aro-color-secondary: #2E4F5E; + --aro-color-secondary-rgb: 46, 79, 94; --aro-color-secondary-hover: #223C48; --aro-color-secondary-contrast: #FFFFFF; @@ -42,13 +51,18 @@ /* -------------------------------------------------------------- Semantic */ --aro-color-success: #63CEB3; + --aro-color-success-rgb: 99, 206, 179; --aro-color-danger: #E52321; + --aro-color-danger-rgb: 229, 35, 33; --aro-color-warning: #F25C05; + --aro-color-warning-rgb: 242, 92, 5; --aro-color-info: #E39B02; + --aro-color-info-rgb: 227, 155, 2; /* -------------------------------------------------------------- Surfaces */ --aro-color-bg: #F5F8FA; + --aro-color-bg-rgb: 245, 248, 250; --aro-color-surface: #FFFFFF; --aro-color-surface-muted: #EBEBEB; /* Select2 multi-select chip fill (lot B). */ /* Chrome that must read as inert beside the content it captions — the editor toolbar. @@ -59,6 +73,7 @@ /* ------------------------------------------------------------------ Text */ --aro-color-text: #333333; + --aro-color-text-rgb: 51, 51, 51; --aro-color-text-strong: #181C32; --aro-color-text-label: #3F4254; --aro-color-text-muted: #99A1B7; diff --git a/src/Resources/public/css/foundations/_typography.css b/src/Resources/public/css/foundations/_typography.css index 74284484..05c062c6 100644 --- a/src/Resources/public/css/foundations/_typography.css +++ b/src/Resources/public/css/foundations/_typography.css @@ -41,9 +41,22 @@ a:where(:not(.btn):not(.page-link)):focus { color: var(--aro-color-secondary); } +/* Bootstrap's reboot leaves headings at 500; h1 carried 700 through v2 (Stisla set every + heading bold) and reads underweight without it. Only h1 is pinned — the smaller headings + are sized and weighted by the components that use them. */ +h1 { + font-weight: var(--aro-weight-bold); +} + +h2 { + font-weight: var(--aro-weight-semibold); +} + +/* The margin is load-bearing rhythm: !important so a scoped tweak cannot silently + compress the separator back to Bootstrap's default. */ hr { border-top-style: dashed; - margin: 2.5rem 0; + margin: 2.5rem 0 !important; } p { diff --git a/src/Resources/public/css/style.css b/src/Resources/public/css/style.css index ebdd701b..bd9f4cba 100644 --- a/src/Resources/public/css/style.css +++ b/src/Resources/public/css/style.css @@ -26,6 +26,7 @@ @import url("./vendor/_tagsinput.css"); @import url("./vendor/_pwstrength.css"); @import url("./vendor/_pickers.css"); +@import url("./vendor/_spectrum.css"); @import url("./components/_btn.css"); @import url("./components/_btn-variants.css"); diff --git a/src/Resources/public/css/vendor/_spectrum.css b/src/Resources/public/css/vendor/_spectrum.css new file mode 100644 index 00000000..4725501b --- /dev/null +++ b/src/Resources/public/css/vendor/_spectrum.css @@ -0,0 +1,149 @@ +/** + * Spectrum — colour picker skin, for the `ColorType` form type. + * + * Loaded (plugin CSS + JS) by `Form/base.html.twig`, initialised on `.color-picker` in + * `js/app.js` — the class the `aropixel_admin_color_widget` block puts on the input. + * + * Restores the skin that lived in the deleted `css/custom.css` (v3.0.0) and was not + * carried over by the CSS reorganisation: without it the picker falls back to the raw + * plugin stylesheet, which does not match the admin at all. + * + * ⚠ Rendered in a popup on click, so absent from the visual baseline — validated by + * opening a colour field, not by diff. + * + * Restyled on tokens rather than transcribed: the resting control now wears the field + * surface of the other inputs, and the legacy `--main-bg-color` / Font Awesome chevron + * the v3.0.0 rules relied on are both gone. Geometry is kept as it was. + */ + +/* Resting control: the swatch + chevron box replacing the input. Reads as a field. */ +.sp-replacer { + margin: 0; + border: 1px solid var(--aro-field-border); + background: var(--aro-field-bg); + border-radius: var(--aro-radius-input); + padding: 5px; +} + +.sp-replacer:hover, +.sp-replacer.sp-active { + background: var(--aro-field-bg-focus); + border-color: var(--aro-color-border); +} + +.sp-preview { + width: 25px; + height: 22px; + border: none; + margin-right: var(--aro-space-1); + border-radius: var(--aro-radius-xs); +} + +/* Spectrum emits a `▼` glyph here; the v3.0.0 skin hid it behind a Font Awesome chevron, + which the admin no longer ships. Keep the glyph, dressed down to the muted text tone. */ +.sp-dd { + margin-top: 1px; + font-size: 10px; + line-height: 22px; + color: var(--aro-color-text-muted); +} + +/* Popup */ +.sp-container { + background-color: var(--aro-color-surface); + border: 1px solid var(--aro-color-border); + border-radius: var(--aro-radius-sm); + overflow: hidden; + box-shadow: var(--aro-shadow-dropdown); +} + +.sp-picker-container { + width: 220px; +} + +/* The plugin lays saturation / hue out in percentages; these two keep the hue bar clear + of the saturation square at the width set above. */ +.sp-hue { + border: none; + left: 86%; +} + +.sp-color { + border: 1px solid var(--aro-color-border); + right: 19%; +} + +.sp-slider { + height: 4px; + left: -2px; + right: -2px; + border: 1px solid var(--aro-color-border); + background-color: var(--aro-color-surface); + border-radius: var(--aro-radius-sm); +} + +/* Hex input inside the popup (`showInput: true`). */ +.sp-input { + border: 1px solid var(--aro-color-border); + border-radius: var(--aro-radius-xs); + padding: 6px 10px; + color: var(--aro-color-text); + box-shadow: none; + -webkit-appearance: none; + outline: none; +} + +.sp-input:focus { + border-color: var(--aro-color-border-focus); +} + +/* Buttons. The plugin ships its own gradients and text shadows, hence the flattening. */ +.sp-button-container { + float: none; + clear: right; +} + +.sp-cancel, +.sp-choose, +.sp-palette-toggle { + border: 0; + border-radius: var(--aro-radius-sm); + padding: 8px 6px; + float: left; + width: 48%; + box-sizing: border-box; + text-align: center; + font-size: var(--aro-text-xs); + line-height: 1.7; + outline: 0; +} + +.sp-container button { + background-color: var(--aro-color-primary); + background-image: none; + border: 1px solid var(--aro-color-primary); + color: var(--aro-color-primary-contrast); + text-shadow: none; +} + +.sp-container button:hover { + background-image: none; + background-color: var(--aro-color-primary-hover); + border-color: var(--aro-color-primary-hover); + text-shadow: none; +} + +/* `.sp-cancel` is an ``, so the colour needs `!important` (§7-1) to beat the plugin's + own `a.sp-cancel` rule, which is more specific than a class on its own. */ +.sp-cancel { + border: 1px solid var(--aro-color-border); + background-color: var(--aro-color-surface); + color: var(--aro-color-text) !important; +} + +.sp-cancel:hover { + border-color: var(--aro-color-border); + background-color: var(--aro-color-surface-subtle); + color: var(--aro-color-text) !important; + text-decoration: none; +} diff --git a/src/Resources/public/js/module/dialog/confirm-dialog.js b/src/Resources/public/js/module/dialog/confirm-dialog.js index 58d10d8b..eb53dfbd 100644 --- a/src/Resources/public/js/module/dialog/confirm-dialog.js +++ b/src/Resources/public/js/module/dialog/confirm-dialog.js @@ -39,6 +39,17 @@ export class ConfirmDialog { constructor(options = {}) { // Translated fallbacks rendered by base.html.twig; safe if absent. const i18n = (typeof window !== 'undefined' && window.aroDialogI18n) || {}; + + // Callers read their labels from data-attributes, which yield `undefined` when the + // attribute is absent. Object.assign copies that `undefined` over the default, and + // `textContent = undefined` empties the node — blank buttons. Drop the holes first. + const given = {}; + for (const key of Object.keys(options)) { + if (options[key] !== undefined) { + given[key] = options[key]; + } + } + const o = Object.assign({ intent: 'danger', size: 'md', @@ -47,7 +58,7 @@ export class ConfirmDialog { confirmLabel: i18n.confirm || 'Confirm', cancelLabel: i18n.cancel || 'Cancel', onConfirm: () => {}, - }, options); + }, given); // Size scale shared with modals: sm 400 · md 520 (default) · lg 720 · xl 960. const sizeClass = { sm: 'modal-sm', lg: 'modal-lg', xl: 'modal-xl' }[o.size] || ''; diff --git a/src/Resources/views/Macro/actions.html.twig b/src/Resources/views/Macro/actions.html.twig index d911b449..40f42fef 100644 --- a/src/Resources/views/Macro/actions.html.twig +++ b/src/Resources/views/Macro/actions.html.twig @@ -10,12 +10,18 @@ # compute it — e.g. `csrf_token('delete__post' ~ post.id)`. Without it the macro falls back # to `delete`, which matches no shipped controller and is only a placeholder. # + # `extra` (optional) is caller-supplied markup inserted between edit and status — the place + # for a preview link, a "see more" modal trigger, anything the fixed slots cannot express. + # Capture it so it stays Markup and is not escaped, then pass it by name: + # {% set extra %}{% endset %} + # {{ list.actions(item, edit, del, msg, extra: extra) }} + # # Delete confirmation is the Dialog component: `a.delete[data-confirm]` is caught in app.js, # which opens a danger `ConfirmDialog` (css/components/_dialog.css) and, on confirm, submits # the sibling `
`. `data-confirm-title` is the bold question, `data-confirm` the detail # line under it; button labels come translated via `data-confirm-label` / `data-cancel-label`. #} -{% macro actions(item, edit_path, delete_path, delete_confirm_msg = null, status = null, delete_token = null) %} +{% macro actions(item, edit_path, delete_path, delete_confirm_msg = null, status = null, delete_token = null, extra = null) %}