srcdev-nuxt-components 9.5.7 → 9.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/.claude/skills/components/breadcrumb.md +5 -0
  2. package/.claude/skills/components/display-pill.md +2 -0
  3. package/.claude/skills/components/input-button.md +3 -0
  4. package/.claude/skills/components/responsive-header.md +49 -0
  5. package/.claude/skills/components/service-detail.md +32 -0
  6. package/.claude/skills/components/service-summary.md +8 -0
  7. package/.claude/skills/components/services-card-grid.md +5 -3
  8. package/.claude/skills/components/services-card.md +22 -12
  9. package/.claude/skills/components/slider-gallery.md +10 -4
  10. package/.claude/skills/components/tab-navigation.md +25 -9
  11. package/.claude/skills/index.md +1 -2
  12. package/.vscode/srcdev-component-responsive-header.code-snippets +10 -0
  13. package/.vscode/srcdev-component-service-detail.code-snippets +1 -0
  14. package/.vscode/srcdev-component-service-summary.code-snippets +3 -1
  15. package/.vscode/srcdev-component-services-card.code-snippets +38 -36
  16. package/.vscode/srcdev-component-services-grids.code-snippets +2 -1
  17. package/.vscode/srcdev-component-tab-navigation.code-snippets +1 -1
  18. package/app/components/01.atoms/display-pill/CONSUMER-STYLING.md +3 -0
  19. package/app/components/01.atoms/display-pill/DisplayPill.vue +5 -2
  20. package/app/components/01.atoms/navigation/breadcrumb/Breadcrumb.vue +16 -4
  21. package/app/components/01.atoms/navigation/breadcrumb/CONSUMER-STYLING.md +28 -5
  22. package/app/components/01.atoms/navigation/breadcrumb/stories/Breadcrumb.stories.ts +94 -0
  23. package/app/components/01.atoms/navigation/breadcrumb/tests/Breadcrumb.spec.ts +27 -0
  24. package/app/components/02.molecules/navigation/tab-navigation/CONSUMER-STYLING.md +52 -11
  25. package/app/components/02.molecules/navigation/tab-navigation/TabNavigation.vue +55 -23
  26. package/app/components/02.molecules/navigation/tab-navigation/stories/TabNavigation.stories.ts +176 -257
  27. package/app/components/02.molecules/navigation/tab-navigation/tests/TabNavigation.spec.ts +42 -2
  28. package/app/components/03.organisms/image-galleries/slider-gallery/CONSUMER-STYLING.md +19 -1
  29. package/app/components/03.organisms/image-galleries/slider-gallery/SliderGallery.vue +65 -7
  30. package/app/components/03.organisms/image-galleries/slider-gallery/stories/SliderGallery.stories.ts +201 -63
  31. package/app/components/03.organisms/image-galleries/slider-gallery/tests/SliderGallery.spec.ts +41 -2
  32. package/app/components/03.organisms/image-galleries/slider-gallery/tests/__snapshots__/SliderGallery.spec.ts.snap +6 -6
  33. package/app/components/03.organisms/responsive-header/CONSUMER-STYLING.md +4 -0
  34. package/app/components/03.organisms/responsive-header/NavigationItems.vue +33 -4
  35. package/app/components/03.organisms/responsive-header/ResponsiveHeader.vue +51 -4
  36. package/app/components/03.organisms/responsive-header/stories/ResponsiveHeader.stories.ts +52 -0
  37. package/app/components/03.organisms/responsive-header/tests/NavigationItems.spec.ts +20 -0
  38. package/app/components/03.organisms/responsive-header/tests/ResponsiveHeader.spec.ts +40 -0
  39. package/app/components/03.organisms/services/service-detail/CONSUMER-STYLING.md +92 -41
  40. package/app/components/03.organisms/services/service-detail/ServiceDetail.vue +198 -73
  41. package/app/components/03.organisms/services/service-detail/stories/ServiceDetail.stories.ts +157 -9
  42. package/app/components/03.organisms/services/service-detail/tests/ServiceDetail.spec.ts +58 -0
  43. package/app/components/03.organisms/services/service-detail/tests/__snapshots__/ServiceDetail.spec.ts.snap +4 -3
  44. package/app/components/03.organisms/services/service-summary/CONSUMER-STYLING.md +30 -0
  45. package/app/components/03.organisms/services/service-summary/ServiceSummary.vue +32 -8
  46. package/app/components/03.organisms/services/service-summary/stories/ServiceSummary.stories.ts +147 -0
  47. package/app/components/03.organisms/services/service-summary/tests/ServiceSummary.spec.ts +26 -0
  48. package/app/components/03.organisms/services/service-summary/tests/__snapshots__/ServiceSummary.spec.ts.snap +1 -1
  49. package/app/components/03.organisms/services/services-card/CONSUMER-STYLING.md +104 -63
  50. package/app/components/03.organisms/services/services-card/ServicesCard.vue +92 -66
  51. package/app/components/03.organisms/services/services-card/stories/ServicesCard.stories.ts +194 -27
  52. package/app/components/03.organisms/services/services-card/tests/ServicesCard.spec.ts +34 -8
  53. package/app/components/03.organisms/services/services-card/tests/__snapshots__/ServicesCard.spec.ts.snap +8 -8
  54. package/app/components/03.organisms/services/services-grids/CONSUMER-STYLING.md +9 -0
  55. package/app/components/03.organisms/services/services-grids/ServicesCardGrid.vue +2 -1
  56. package/app/components/03.organisms/services/services-grids/stories/ServiceSummaryGrid.stories.ts +46 -6
  57. package/app/components/03.organisms/services/services-grids/stories/ServicesCardGrid.stories.ts +89 -11
  58. package/app/components/03.organisms/services/services-grids/tests/ServicesCardGrid.spec.ts +8 -0
  59. package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServiceSummaryGrid.spec.ts.snap +3 -3
  60. package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServicesCardGrid.spec.ts.snap +24 -24
  61. package/app/components/05.forms/input-button/CONSUMER-STYLING.md +8 -0
  62. package/app/components/05.forms/input-button/InputButton.vue +3 -1
  63. package/app/composables/tests/useAnchorScroll.spec.ts +8 -0
  64. package/app/composables/useAnchorScroll.ts +1 -1
  65. package/nuxt.config.ts +1 -1
  66. package/package.json +1 -1
  67. package/.claude/skills/components/site-navigation.md +0 -145
  68. package/.vscode/srcdev-component-site-navigation.code-snippets +0 -38
  69. package/app/components/02.molecules/navigation/site-navigation/CONSUMER-STYLING.md +0 -109
  70. package/app/components/02.molecules/navigation/site-navigation/SiteNavigation.vue +0 -751
  71. package/app/components/02.molecules/navigation/site-navigation/stories/SiteNavigation.stories.ts +0 -356
  72. package/app/components/02.molecules/navigation/site-navigation/tests/SiteNavigation.spec.ts +0 -367
  73. package/app/components/02.molecules/navigation/site-navigation/tests/__snapshots__/SiteNavigation.spec.ts.snap +0 -30
@@ -47,4 +47,9 @@ a `to` is a real link; the library never guesses which item is "current" from it
47
47
  - `ServiceDetail` uses this component internally for its hero banner breadcrumb, overriding
48
48
  `--breadcrumb-colour` to `white` so it reads over the hero image. See
49
49
  [service-detail.md](service-detail.md).
50
+ - Items with a blank `label` are skipped (no empty crumb, no dangling separator), and nothing renders when
51
+ none are left, so there is no empty `<nav>` landmark. An empty `separator` renders no separator element.
52
+ Added 2026-10-06.
53
+ - Long labels wrap (unbroken ones too). Set `--breadcrumb-item-line-clamp` (default `none`) to cap each
54
+ label's lines with an ellipsis, e.g. `1` for single-line crumbs. Added 2026-10-06.
50
55
  - See `CONSUMER-STYLING.md` in the component's own folder for the full `--breadcrumb-*` token API.
@@ -98,6 +98,8 @@ Colour resolves as **variant token → base token → variant default**: `--disp
98
98
 
99
99
  ## Notes
100
100
 
101
+ - Since 2026-10-06 a pill never grows past its container: a label too long for the space ends in an ellipsis (it still never wraps).
102
+
101
103
  - `border-radius` defaults to `100vw` — this always collapses to the tightest possible radius on the shorter axis, making it geometry-independent (no magic number needed).
102
104
  - `cursor: default` and `user-select: none` are applied unconditionally. `cursor: pointer` is applied automatically via `:is(button, a)` when `tag` is interactive.
103
105
  - All variant colour pairs use a light-tint background (`--color-01`) with a dark text colour (`--color-09`/`--color-10`) to ensure WCAG AA contrast. The project colour scale runs `00` (lightest) → `10` (darkest) — the opposite of Tailwind.
@@ -66,6 +66,9 @@ Colour tokens are `--input-button-{variant}-{property}` with `--theme-*` fallbac
66
66
  typography are the global `--button-*` tokens. Full reference:
67
67
  `app/components/05.forms/input-button/CONSUMER-STYLING.md`.
68
68
 
69
+ Labels stay on one line by default. Set `--input-button-text-white-space: normal` to let a long
70
+ label wrap inside a narrow container (added 2026-10-06; `ServicesCardGrid` does this for its CTAs).
71
+
69
72
  ## Notes
70
73
 
71
74
  - Before 2026-09-28, `readonly` only set `pointer-events: none`, so keyboard users could still
@@ -112,8 +112,57 @@ buggy toggle-based version too.
112
112
  | `overflowMenuAriaLabel` | `string` | `"Overflow navigation menu"` | Forwarded to `NavigationItems`' `ariaLabel` — override for localisation. |
113
113
  | `overflowButtonLabel` | `string` | `"More navigation"` | Accessible name of the overflow/burger button, which shows only icons. |
114
114
  | `submenuAriaLabel` | `string` | `"{title} submenu"` | aria-label on each dropdown summary, top bar and overflow panel; `{title}` is replaced with `childLinksTitle` (or `name`). Forwarded to `NavigationItems`. |
115
+ | `anchorScrollOffset` | `number \| (() => number)` | `undefined` (0) | Pixels left above a section when a `#anchor` link scrolls to it, e.g. a sticky header's height. Pass a getter to read it at click time. |
115
116
  | `styleClassPassthrough` | `string \| string[]` | `[]` | Extra CSS classes applied to the root `.navigation` element. |
116
117
 
118
+ ## Same-page anchor links (since 2026-10-06)
119
+
120
+ Any item or child link whose `path` starts with `#` is a same-page section link:
121
+
122
+ - It renders as a plain `<a href="#section">`, not a `NuxtLink`, so Vue Router stays out of the scroll.
123
+ - Clicking it smooth-scrolls to the element with that id (`useAnchorScroll`), leaving
124
+ `anchorScrollOffset` pixels above it, and pushes the hash to the URL. Under
125
+ `prefers-reduced-motion: reduce` the browser's instant jump is used instead.
126
+ - It is active (`li.is-active`, the indicator) by hash, not by route. On load, the URL's hash wins;
127
+ with none, the first anchor item in the data is active. Both the top bar and the overflow menu
128
+ share that state.
129
+ - Clicking one closes any open dropdown and the overflow menu (route links close them by navigating).
130
+
131
+ Route links and anchor links can be mixed in the same data. See the "Same-Page Anchor Links" story.
132
+
133
+ ## Migrating from TabNavigation
134
+
135
+ `ResponsiveHeader` covers everything `TabNavigation` does, and collapses progressively (only the items
136
+ that don't fit move into the overflow menu) instead of all-or-nothing. To switch:
137
+
138
+ | TabNavigation | ResponsiveHeader |
139
+ |---|---|
140
+ | `:nav-item-data="{ main: [...] }"` | `:responsive-nav-links="{ main: [...] }"` |
141
+ | item `text` | item `name` |
142
+ | item `href` (route or `#anchor`) | item `path` (route or `#anchor`, same behaviour) |
143
+ | item `iconName` | item `iconName` |
144
+ | item `isExternal` | drop it: `NuxtLink` treats a full URL as external |
145
+ | item `cssName` | no equivalent; drop it (no consumer styles these classes) |
146
+ | `nav-align="right"` | `--responsive-header-main-nav-justify-content: safe end` (CSS token) |
147
+ | `aria-label` | `main-nav-aria-label` |
148
+ | `open-menu-label` / `close-menu-label` | `overflow-button-label` (one label; the button is a `<summary>`) |
149
+ | `anchor-scroll-offset` | `anchor-scroll-offset` |
150
+ | collapse everything to a burger when items don't fit | default is per-item overflow; add `collapse-at-main-nav-intersection` for the old all-or-nothing burger |
151
+ | `--tab-nav-link-color` | `--responsive-header-link-color` |
152
+ | `--tab-nav-decorator-indicator-color` | `--responsive-nav-decorator-indicator-color` |
153
+ | `--tab-nav-*` panel and burger tokens | the overflow tokens in `CONSUMER-STYLING.md` and NavigationItems' `--overflow-nav-*` |
154
+
155
+ ```vue
156
+ <!-- Before -->
157
+ <TabNavigation nav-align="right" :nav-item-data="{ main: [{ text: 'About', href: '#about' }] }" />
158
+
159
+ <!-- After -->
160
+ <ResponsiveHeader
161
+ :responsive-nav-links="{ main: [{ name: 'About', path: '#about' }] }"
162
+ style="--responsive-header-main-nav-justify-content: safe end"
163
+ />
164
+ ```
165
+
117
166
  ## Data edge cases (since 2026-10-05)
118
167
 
119
168
  - A dropdown with no `childLinksTitle` shows its `name` instead (it used to render an empty
@@ -36,6 +36,9 @@ always rendered.
36
36
  | `headerTag` | `"h1" \| "h2" \| "h3"` | `"h1"` | no |
37
37
  | `subheadingTag` | `"h2" \| "h3"` | `"h2"` | no |
38
38
  | `breadcrumbItems` | `BreadcrumbItem[]` | auto-built from `serviceData` | no |
39
+ | `breadcrumbAriaLabel` | `string` | Breadcrumb's default | no |
40
+ | `pricePrefix` | `string` | `"From"` | no |
41
+ | `idealForIcon` | `string` | `"mdi:diamond-stone"` | no |
39
42
  | `heroImageVariant` | `"full" \| "popout" \| "content" \| "inset-content"` | `"full"` | no |
40
43
  | `heroContentVariant` | `"full" \| "popout" \| "content" \| "inset-content"` | `"content"` | no |
41
44
  | `bodyVariant` | `"full" \| "popout" \| "content" \| "inset-content"` | `"content"` | no |
@@ -90,6 +93,35 @@ Defaults to a plain, non-linked two-item trail built from `serviceData.category`
90
93
  />
91
94
  ```
92
95
 
96
+ > **Changed 2026-10-06:** the hero price prefix (`pricePrefix`), breadcrumb label and ideal-for icon are
97
+ > props (the `From` and the icon were hardcoded). Sections with empty data, and headings set to `""`, are
98
+ > left out instead of rendering an orphan heading; empty fields render nothing; with no title, a landmark
99
+ > `tag` drops `aria-labelledby`. FAQ questions are one level below `subheadingTag` (were always `h3`).
100
+ > The hero tokens are now a real minimum height, so a long title grows the hero instead of being clipped.
101
+ > Ideal-for and related cards default to `--theme-surface-subtle` (were dark slate with no text colour).
102
+ > The `final-cta` slot sits in `.service-detail__final-cta-action`, which never shrinks, so long copy can't
103
+ > push its button out of the banner. Sidebar row labels size to their words (up to half the row); values wrap.
104
+
105
+ ## Line-clamp tokens
106
+
107
+ All default to `none` (show everything). `1` is single-line ellipsis; any other number caps the lines
108
+ with an ellipsis on the last. Set them in the consumer's own CSS, e.g.
109
+ `.service-page { --service-detail-hero-title-line-clamp: 3; }`.
110
+
111
+ | Token | Clamps |
112
+ |---|---|
113
+ | `--breadcrumb-item-line-clamp` | Each hero breadcrumb label (Breadcrumb's own token) |
114
+ | `--service-detail-hero-eyebrow-line-clamp` | Hero eyebrow (`subtitle`) |
115
+ | `--service-detail-hero-title-line-clamp` | Hero title |
116
+ | `--service-detail-sidebar-label-line-clamp` | "Book This Service" / "You May Also Like" labels |
117
+ | `--service-detail-sidebar-value-line-clamp` | Sidebar row values (price, duration, location) |
118
+ | `--service-detail-related-title-line-clamp` | Related service titles (default item only, not the `related-service` slot) |
119
+ | `--service-detail-final-cta-heading-line-clamp` | Final CTA heading |
120
+ | `--service-detail-final-cta-body-line-clamp` | Final CTA body |
121
+
122
+ The long-form body sections (description, process, FAQs) have no clamp on purpose: this is the page
123
+ people come to read in full.
124
+
93
125
  ## Slots
94
126
 
95
127
  | Slot | Slot props | Purpose |
@@ -25,6 +25,7 @@ Usually consumed via [ServiceSummaryGrid](service-summary-grid.md) rather than d
25
25
  | `index` | `number` | `0` | no |
26
26
  | `alignment` | `"start" \| "center" \| "end"` | `"center"` | no |
27
27
  | `reverse` | `boolean` | `false` | no |
28
+ | `pricePrefix` | `string` | `"From"` | no |
28
29
  | `styleClassPassthrough` | `string \| string[]` | `[]` | no |
29
30
 
30
31
  ### `index` and image loading
@@ -57,6 +58,13 @@ same pattern `ServiceDetail`'s hero uses — rather than the icon+text row the o
57
58
  had. There are no icon-customisation props (`durationIcon`/`priceIcon` are gone along with the
58
59
  icon row).
59
60
 
61
+ The price pill reads `` `${pricePrefix} ${price}` `` (trimmed): pass a translated `pricePrefix`, or `""`
62
+ for the price alone. A pill is only rendered when its value is non-empty, and the pill row is left out
63
+ when both are empty. Empty `subtitle`, `title` and `whatIsIt` render nothing either; with no title, a
64
+ landmark `tag` drops its `aria-labelledby` (there is no heading to point at). Added 2026-10-06, along
65
+ with `--service-summary-column-min-width` and `--service-summary-body-line-clamp`. Before, the `From`
66
+ prefix was hardcoded and empty fields rendered empty elements, including an empty heading.
67
+
60
68
  ## Row height and image aspect ratio
61
69
 
62
70
  By default `.service-summary` has no fixed height — the row sizes to its content: the image
@@ -40,6 +40,7 @@ Set on `.services-card-grid` (or scoped to a page class):
40
40
  | Token | Default | Controls |
41
41
  | ---------------------- | --------- | ------------------------------------- |
42
42
  | `--services-card-grid-gap` | `4rem` | Gap between grid cells |
43
+ | `--services-card-grid-button-text-white-space` | `normal` | CTA label wrapping; `nowrap` keeps labels on one line |
43
44
  | `--services-card-grid-column-min-width` | `250px` | Minimum column width before wrapping |
44
45
 
45
46
  ## Consumer page boilerplate
@@ -92,9 +93,9 @@ if (servicesData.value.length === 0) {
92
93
  --services-card-grid-column-min-width: 250px;
93
94
 
94
95
  .services-card {
95
- --eyebrow-text-padding-block: 0.8rem 0;
96
- --hero-text-padding-block: 2rem 1rem;
97
- --description-text-colour: var(--colour-text-secondary);
96
+ --services-card-eyebrow-padding-block: 0.8rem 0;
97
+ --services-card-title-padding-block: 2rem 1rem;
98
+ --services-card-description-text-colour: inherit;
98
99
  }
99
100
  }
100
101
  }
@@ -107,4 +108,5 @@ if (servicesData.value.length === 0) {
107
108
  - The `Service` type is imported from `~/types/types.services`.
108
109
  - Uses `repeat(auto-fit, minmax(min(var(--services-card-grid-column-min-width, 250px), 100%), 1fr))` (the `min(…, 100%)` stops a single column overflowing containers narrower than the minimum, fixed 2026-09-28) — columns grow to fill available space and wrap when below the minimum width.
109
110
  - The `#actions` slot template is passed down into each `ServicesCard`; `serviceData` is the scoped prop for the current iteration item.
111
+ - CTA labels (`buttonTextPrefix` + title, trimmed) wrap inside the card since 2026-10-06; before, InputButton's `nowrap` let a long or translated title run past the card edge and get clipped.
110
112
  - Data fetching is the page's responsibility — pass an empty array as fallback while loading (`servicesData ?? []`).
@@ -4,6 +4,16 @@
4
4
 
5
5
  `ServicesCard` renders a single service as a portrait card: image, subtitle (eyebrow), title, short description, an optional duration/price meta row, and an `actions` slot for any CTA content. The component owns the layout and data display; all routing and button decisions are delegated to the consumer via the slot.
6
6
 
7
+ > **Changed 2026-10-06:** every inner class and card-specific token is now prefixed `services-card-`
8
+ > (`.footer` → `.services-card-footer`, `--meta-font-size` → `--services-card-meta-font-size`,
9
+ > `--image-wrapper-*` → `--services-card-image-*`, and so on; the full old-to-new list is in
10
+ > `CONSUMER-STYLING.md` under State hooks). Bare names like `.footer`, `.description` and
11
+ > `--meta-*` collided with consumer CSS, since the card renders inline. Also: empty `subtitle`,
12
+ > `title` or `shortDescription` no longer render an empty element (no empty heading); long unbroken
13
+ > text wraps; a long duration/price wraps onto its own line; `--services-card-description-line-clamp`
14
+ > defaults to `none`; new `--services-card-meta-gap`; outline hover tokens fall back to the resting ones; a whole-card link now shows a focus ring by default
15
+ > (`--services-card-outline-colour-focus`, falling back to the hover colour then `--theme-ring`).
16
+
7
17
  ## Props
8
18
 
9
19
  | Prop | Type | Default | Required |
@@ -33,11 +43,11 @@ If an `actions` slot is provided, the card stays as a static `tag` element (no h
33
43
 
34
44
  ### Titles over the image (`titlesWithinImageWrapper`)
35
45
 
36
- When `true`, the eyebrow and title move out of `.details-wrapper` and sit over the bottom of the image instead. The image wrapper is a `GridStack`: the image is layer 1, and `.image-wrapper-details` (eyebrow + title) is layer 2. The description, meta row and actions stay below the image.
46
+ When `true`, the eyebrow and title move out of `.services-card-details` and sit over the bottom of the image instead. The image wrapper is a `GridStack`: the image is layer 1, and `.services-card-image-details` (eyebrow + title) is layer 2. The description, meta row and actions stay below the image.
37
47
 
38
- - A gradient scrim behind the text keeps it readable. Its colour comes from `--image-wrapper-details-scrim-colour` (default black), and `--image-wrapper-details-scrim` replaces the whole background (`none` removes it).
39
- - Text colour uses `contrast-color()` against the scrim colour where supported (black or white, whichever reads better), falling back to white. It reads the scrim colour only, not the photo, so if you remove the scrim, set `--image-wrapper-details-text-colour` yourself.
40
- - The eyebrow's gradient fill is turned off over the image; it takes the text colour (or `--image-wrapper-details-eyebrow-text-colour`).
48
+ - A gradient scrim behind the text keeps it readable. Its colour comes from `--services-card-image-details-scrim-colour` (default black), and `--services-card-image-details-scrim` replaces the whole background (`none` removes it).
49
+ - Text colour uses `contrast-color()` against the scrim colour where supported (black or white, whichever reads better), falling back to white. It reads the scrim colour only, not the photo, so if you remove the scrim, set `--services-card-image-details-text-colour` yourself.
50
+ - The eyebrow's gradient fill is turned off over the image; it takes the text colour (or `--services-card-image-details-eyebrow-colour`).
41
51
  - The eyebrow's default `fontSize` is `"medium"` in this mode (vs `"large"` below the image). `eyebrowConfig`/`heroConfig` still apply.
42
52
 
43
53
  See "Titles over the image" in `CONSUMER-STYLING.md` for the token list.
@@ -158,10 +168,10 @@ scaffold a style block using `styleClassPassthrough` instead — see
158
168
  .page-my-page {
159
169
  .services-card {
160
170
  --services-card-gap: 1.4rem;
161
- --description-line-clamp: 3;
162
- --eyebrow-text-padding-block: 0.8rem 0;
163
- --hero-text-padding-block: 2rem 1rem;
164
- --meta-border-colour: var(--brand-border);
171
+ --services-card-description-line-clamp: 3;
172
+ --services-card-eyebrow-padding-block: 0.8rem 0;
173
+ --services-card-title-padding-block: 2rem 1rem;
174
+ --services-card-meta-border-colour: var(--brand-border);
165
175
  }
166
176
  }
167
177
  </style>
@@ -193,9 +203,9 @@ No `actions` slot — the entire card renders as an anchor (`NuxtLink` for the i
193
203
 
194
204
  - Component is auto-imported in Nuxt — no import needed.
195
205
  - The `Service` type is imported from `~/types/types.services`.
196
- - Root markup is two rows (`grid-template-rows: auto 1fr`): `.image-wrapper`, then a `.details-wrapper` (`display: flex; flex-direction: column`) holding the eyebrow, title, description, and a `.footer` wrapper. Description length is controlled by `--description-line-clamp` (default effectively unclamped) rather than a fixed-height grid row.
197
- - `.footer` groups the meta row and the `actions` slot and gets `margin-block-start: auto`, pinning them to the bottom of the card. Combined with the root's `1fr` details row, this means when `ServicesCardGrid`'s default `align-items: stretch` makes a card taller than its own content (to match a taller sibling in the same row), the extra height goes to `.footer`'s top margin rather than leaving whitespace below the actions slot — so the meta row and actions/button line up across a row of cards regardless of each card's description length. This does the visual job of CSS subgrid without needing a subgrid chain across `ServicesCard`/`ServicesCardGrid` (which would also break whenever cards in a row don't all render the same optional rows — `.meta` and `actions` are both conditional).
198
- - Image has a `3/4` aspect ratio with a subtle scale-on-hover effect, triggered by hovering the image only (not the whole card). When the card is a whole-card link styled with its own hover effect (e.g. `--services-card-transform-hover`), set `--image-wrapper-border-image-zoom-transform: none` so the two effects don't stack; the "Dark Overlay Cards" story does this. Keep the zoom for the `actions`-slot layout, where the card itself has no hover state. The hover is on `.image-wrapper`, not the image, so it still fires when the overlaid titles cover the image. `.image-wrapper-details` is `position: relative; z-index: 1` because the image's hover `transform` would otherwise paint over it, and `.image-wrapper` sets `isolation: isolate` so that z-index stays inside the card.
199
- - `.services-card`, `.image-wrapper`, `.details-wrapper`, and `.footer` all set `min-inline-size: 0`. Without it, an unbreakable child — most commonly a long `actions` slot button label, since `InputButton`'s `.button-text` is `white-space: nowrap` with no ellipsis — forces its content's min-content width up through the flex/grid chain and widens that one card's grid column in `ServicesCardGrid` wider than its siblings (the image just rides along on the widened column; it isn't the actual cause). If you see one card/column wider than the rest with cut-off content, check for a long unbreakable string in a slot before assuming it's an image sizing issue.
206
+ - Root markup is two rows (`grid-template-rows: auto 1fr`): `.services-card-image-wrapper`, then a `.services-card-details` (`display: flex; flex-direction: column`) holding the eyebrow, title, description, and a `.services-card-footer` wrapper. Description length is controlled by `--services-card-description-line-clamp` (default `none`) rather than a fixed-height grid row.
207
+ - `.services-card-footer` groups the meta row and the `actions` slot and gets `margin-block-start: auto`, pinning them to the bottom of the card. Combined with the root's `1fr` details row, this means when `ServicesCardGrid`'s default `align-items: stretch` makes a card taller than its own content (to match a taller sibling in the same row), the extra height goes to `.services-card-footer`'s top margin rather than leaving whitespace below the actions slot — so the meta row and actions/button line up across a row of cards regardless of each card's description length. This does the visual job of CSS subgrid without needing a subgrid chain across `ServicesCard`/`ServicesCardGrid` (which would also break whenever cards in a row don't all render the same optional rows — `.services-card-meta` and `actions` are both conditional).
208
+ - Image has a `3/4` aspect ratio with a subtle scale-on-hover effect, triggered by hovering the image only (not the whole card). When the card is a whole-card link styled with its own hover effect (e.g. `--services-card-transform-hover`), set `--services-card-image-zoom-transform: none` so the two effects don't stack; the "Dark Overlay Cards" story does this. Keep the zoom for the `actions`-slot layout, where the card itself has no hover state. The hover is on `.services-card-image-wrapper`, not the image, so it still fires when the overlaid titles cover the image. `.services-card-image-details` is `position: relative; z-index: 1` because the image's hover `transform` would otherwise paint over it, and `.services-card-image-wrapper` sets `isolation: isolate` so that z-index stays inside the card.
209
+ - `.services-card`, `.services-card-image-wrapper`, `.services-card-details`, and `.services-card-footer` all set `min-inline-size: 0`. Without it, an unbreakable child — most commonly a long `actions` slot button label, since `InputButton`'s `.button-text` is `white-space: nowrap` with no ellipsis — forces its content's min-content width up through the flex/grid chain and widens that one card's grid column in `ServicesCardGrid` wider than its siblings (the image just rides along on the widened column; it isn't the actual cause). If you see one card/column wider than the rest with cut-off content, check for a long unbreakable string in a slot before assuming it's an image sizing issue.
200
210
  - Usually consumed via `ServicesCardGrid` rather than directly.
201
211
  - The "Dark Overlay Cards" story shows a full restyle using tokens only: dark panel, flush image, titles over the image, hover lift (`--services-card-transform-hover`), and EyebrowText typography tokens (`--eyebrow-text-font-style`, `-letter-spacing`, etc.) set on the card so they inherit into its eyebrow.
@@ -5,7 +5,13 @@
5
5
  `SliderGallery` (`03.organisms/image-galleries/slider-gallery`) is a full-screen image carousel:
6
6
  a large slide with staggered text, a thumbnail strip that animates into place, prev/next arrows, a
7
7
  progress bar, and optional auto-advance. It's `position: absolute; inset: 0` at `100svh × 100vw`,
8
- so place it inside a positioned container sized for it (usually a full-viewport hero).
8
+ so place it inside a positioned container sized for it (usually a full-viewport hero). To fill a
9
+ smaller positioned container instead, set `--slider-gallery-width: 100%` and `--slider-gallery-height: 100%`.
10
+
11
+ > **Changed 2026-10-06:** worst-case timings are clamped: `animationDuration` below 0 (or not a
12
+ > finite number) becomes 0, and auto-advance waits at least `max(1000, animationDuration, autoRunInterval)`
13
+ > ms. Before, an `autoRunInterval` at or below `animationDuration` stopped auto-advance after the first
14
+ > slide. Long unbroken text now wraps everywhere, and thumbnail text is clamped to 2 lines by default.
9
15
 
10
16
  > **Changed 2026-09-28:** every inner class is now `slider-gallery-*` (was bare `.list`, `.item`,
11
17
  > `.content`, `.title`, `.thumbnail`, `.arrows`, `.time` and so on, which collided with consumer CSS
@@ -20,8 +26,8 @@ so place it inside a positioned container sized for it (usually a full-viewport
20
26
  |---|---|---|---|
21
27
  | `v-model:gallery-data` | `IGalleryData[]` | required | Slides (see type below) |
22
28
  | `autoRun` | `boolean` | `true` | Auto-advance. Paused while hovered or focused; never runs under `prefers-reduced-motion: reduce` |
23
- | `autoRunInterval` | `number` | `7000` | ms between auto-advances |
24
- | `animationDuration` | `number` | `3000` | ms a transition locks the controls (also drives the progress bar) |
29
+ | `autoRunInterval` | `number` | `7000` | ms between auto-advances. Never shorter than `animationDuration` or 1000ms |
30
+ | `animationDuration` | `number` | `3000` | ms a transition locks the controls (also drives the progress bar). Negative values become 0 |
25
31
  | `ariaLabel` | `string` | `"Image gallery"` | Carousel region label |
26
32
  | `loadingText` | `string` | `"Loading gallery..."` | Loading-state copy |
27
33
  | `seeMoreText` | `string` | `"SEE MORE"` | Link text for slides with an `href` |
@@ -78,7 +84,7 @@ const slides = ref<IGalleryData[]>([
78
84
  ## Styling
79
85
 
80
86
  Public tokens (`--slider-gallery-*`) cover height, z-index, accent (spinner and progress), text
81
- colours, thumbnail size/border/radius/overlay, arrow row position (base, tablet, desktop), arrow size, colours, border, outline (resting, hover and focus) and CTA colours. Full reference:
87
+ width, colours, line clamps for each text element (`--slider-gallery-*-line-clamp`; thumbnails default to 2 lines), thumbnail size/border/radius/overlay, arrow row position (base, tablet, desktop), arrow size, colours, border, outline (resting, hover and focus) and CTA colours. Full reference:
82
88
  `app/components/03.organisms/image-galleries/slider-gallery/CONSUMER-STYLING.md`.
83
89
 
84
90
  ## Notes
@@ -1,5 +1,11 @@
1
1
  # TabNavigation
2
2
 
3
+ > **Deprecated (2026-10-06): use [ResponsiveHeader](responsive-header.md) for new work.** It does
4
+ > everything TabNavigation does, including same-page `#anchor` links with `anchorScrollOffset`, and
5
+ > collapses item by item instead of all-or-nothing. Follow "Migrating from TabNavigation" in that doc.
6
+ > TabNavigation stays until existing consumers have moved, then it will be removed. It logs a
7
+ > dev-mode console warning on mount.
8
+
3
9
  ## Overview
4
10
 
5
11
  A responsive horizontal navigation bar with CSS anchor-positioning indicators, smooth anchor
@@ -41,10 +47,20 @@ interface NavItemData {
41
47
  | `nav-align` | `"left" \| "center" \| "right"` | `"left"` | Positions the tab list within the nav container |
42
48
  | `anchor-scroll-offset` | `number \| (() => number)` | — | Pixel offset subtracted from scroll target; pass a getter so it is re-evaluated at click time |
43
49
  | `aria-label` | `string` | `"Site navigation"` | aria-label on the root `<nav>` landmark — override for localisation |
50
+ | `open-menu-label` | `string` | `"Open navigation menu"` | Burger button label while closed — override for localisation |
51
+ | `close-menu-label` | `string` | `"Close navigation menu"` | Burger button label while open — override for localisation |
44
52
  | `:style-class-passthrough` | `string \| string[]` | `[]` | Extra classes on the root `<nav>` element |
45
53
 
46
54
  ---
47
55
 
56
+ > **Changed 2026-10-06:** the burger labels are props (were hardcoded English). Items with blank `text`
57
+ > are skipped (a link with no text has no accessible name), and nothing renders when none are left.
58
+ > Items are keyed by position, so duplicate or missing `href`s no longer break rendering. The mobile
59
+ > panel's id comes from `useId()` (was a fixed `tab-nav-panel`, which duplicated with two navs on a
60
+ > page). Links show a focus ring (`--tab-nav-focus-ring-*`), and long panel labels wrap. Colour
61
+ > defaults are light-theme now (`--theme-text` links, `--theme-accent` hover and indicator, `--page-bg`
62
+ > panel); they used to assume a dark header. Dark headers or dark mode set the colour tokens.
63
+
48
64
  ## Basic usage — route links
49
65
 
50
66
  ```vue
@@ -146,9 +162,9 @@ the component source folder for the full token reference.
146
162
 
147
163
  | Token | Default | Controls |
148
164
  |---|---|---|
149
- | `--tab-nav-link-color` | `var(--slate-01, currentColor)` | Link text colour |
150
- | `--tab-nav-link-hover-color` | `var(--slate-04, currentColor)` | Link hover colour |
151
- | `--tab-nav-link-active-color` | `var(--slate-01, currentColor)` | Active link colour |
165
+ | `--tab-nav-link-color` | `var(--theme-text)` | Link text colour |
166
+ | `--tab-nav-link-hover-color` | `var(--theme-accent)` | Link hover colour |
167
+ | `--tab-nav-link-active-color` | the link colour | Active link colour |
152
168
  | `--tab-nav-link-size` | `1.6rem` | Font size |
153
169
  | `--tab-nav-link-weight` | `400` | Font weight |
154
170
  | `--tab-nav-link-tracking` | `0.06em` | Letter spacing |
@@ -159,17 +175,17 @@ the component source folder for the full token reference.
159
175
 
160
176
  | Token | Default | Controls |
161
177
  |---|---|---|
162
- | `--tab-nav-decorator-indicator-color` | `var(--slate-01, currentColor)` | Active underline bar colour |
178
+ | `--tab-nav-decorator-indicator-color` | `var(--theme-accent)` | Active underline bar colour |
163
179
  | `--tab-nav-decorator-hovered-bg` | `transparent` | Hover pill background (set non-transparent to enable) |
164
180
 
165
181
  ### Mobile panel
166
182
 
167
183
  | Token | Default | Controls |
168
184
  |---|---|---|
169
- | `--tab-nav-panel-bg` | `var(--page-bg, #1a1614)` | Panel background |
170
- | `--tab-nav-panel-border-color` | `color-mix(in oklch, var(--slate-01) 35%, transparent)` | Panel top border |
171
- | `--tab-nav-panel-item-border` | `color-mix(in oklch, var(--slate-01) 8%, transparent)` | Item dividers |
172
- | `--tab-nav-panel-link-color` | `var(--slate-01, currentColor)` | Panel link colour |
185
+ | `--tab-nav-panel-bg` | `var(--page-bg, var(--theme-surface-subtle))` | Panel background |
186
+ | `--tab-nav-panel-border-color` | the link colour at 20% | Panel top border |
187
+ | `--tab-nav-panel-item-border` | the panel link colour at 12% | Item dividers |
188
+ | `--tab-nav-panel-link-color` | the link colour | Panel link colour |
173
189
  | `--tab-nav-panel-padding-block` | `1.4rem` | Link vertical padding |
174
190
  | `--tab-nav-panel-padding-inline` | `1.5rem` | Link horizontal padding |
175
191
  | `--tab-nav-panel-slide-duration` | `350ms` | Panel open/close duration |
@@ -179,7 +195,7 @@ the component source folder for the full token reference.
179
195
 
180
196
  | Token | Default | Controls |
181
197
  |---|---|---|
182
- | `--tab-nav-burger-color` | `var(--slate-01, currentColor)` | Bar colour |
198
+ | `--tab-nav-burger-color` | the link colour | Bar colour |
183
199
  | `--tab-nav-burger-width` | `22px` | Bar width |
184
200
  | `--tab-nav-burger-height` | `1.5px` | Bar height |
185
201
  | `--tab-nav-burger-gap` | `5px` | Gap between bars |
@@ -107,8 +107,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
107
107
  ├── wipe-away-vertical.md — WipeAwayVertical: scroll-driven vertical wipe-away effect, pure-CSS grid overlay (no JS), indexed dynamic slots, sticky-centering-via-calc gotcha, CSS tokens
108
108
  ├── marquee-scroller.md — MarqueeScroller: infinite logo/badge scroller, per-item dynamic slots (marqueeData id), hover/focus/keyboard pause, reduced-motion, CSS tokens
109
109
  ├── rotating-carousel-image.md — RotatingCarouselImage: 3D rotating image carousel, scroll-parallax tilt, focus/keyboard/hover pause, reduced-motion, CSS tokens
110
- ├── site-navigation.md — SiteNavigation: responsive nav with auto-collapse, burger menu, decorator indicators, CSS token API
111
- ├── tab-navigation.md — TabNavigation: horizontal tab nav with CSS anchor-positioning indicators, anchor scroll, burger collapse, full CSS token API
110
+ ├── tab-navigation.md — TabNavigation (DEPRECATED, use ResponsiveHeader): horizontal tab nav with CSS anchor-positioning indicators, anchor scroll, burger collapse, full CSS token API
112
111
  ├── social-icons-list.md — SocialIconsList: data-driven social icon links, ISocialIcon type, logos: icon names, CSS tokens
113
112
  ├── display-qr-code.md — DisplayQrCode: QR code SVG from a string value, colour/size/variant/radius props, currentColor default
114
113
  ├── capture-qr-code.md — CaptureQrCode: live camera scanner, error state, visibility/route/KeepAlive lifecycle, media stream cleanup
@@ -56,5 +56,15 @@
56
56
  " --responsive-header-link-font-size: ${4:1.4rem};",
57
57
  "}"
58
58
  ]
59
+ },
60
+ "SRCDEV ResponsiveHeader Anchor Links": {
61
+ "description": "ResponsiveHeader for a one-page site: #anchor items smooth-scroll to sections, offset for a sticky header",
62
+ "scope": "vue,html",
63
+ "body": [
64
+ "<ResponsiveHeader",
65
+ " :responsive-nav-links=\"{ main: [{ name: '$1Home', path: '#$2home' }, { name: '$3About', path: '#$4about' }] }\"",
66
+ " :anchor-scroll-offset=\"${5:80}\"",
67
+ "/>"
68
+ ]
59
69
  }
60
70
  }
@@ -7,6 +7,7 @@
7
7
  " :service-data=\"$1service\"",
8
8
  " location=\"$2Mobile — across Bath\"",
9
9
  " :related-services=\"$3relatedServices\"",
10
+ " price-prefix=\"${7:From}\"",
10
11
  ">",
11
12
  " <template #book-cta>",
12
13
  " <button type=\"button\">$4Book Now</button>",
@@ -18,7 +18,7 @@
18
18
  "description": "ServiceSummary with image/content columns swapped",
19
19
  "scope": "vue,html",
20
20
  "body": [
21
- "<ServiceSummary :service-data=\"$1service\" :reverse=\"$2true\">",
21
+ "<ServiceSummary :service-data=\"$1service\" :reverse=\"$2true\" price-prefix=\"${3:From}\">",
22
22
  " <template #summary-link=\"{ serviceData }\">",
23
23
  " <NuxtLink :to=\"`/services/${serviceData.slug}`\">$3More about {{ serviceData.title }} →</NuxtLink>",
24
24
  " </template>",
@@ -55,6 +55,8 @@
55
55
  " --service-summary-image-padding-block-mobile: ;",
56
56
  " --service-summary-image-padding-inline-mobile: ;",
57
57
  " --service-summary-pills-gap: ;",
58
+ " --service-summary-column-min-width: ;",
59
+ " --service-summary-body-line-clamp: ;",
58
60
  " --service-summary-pill-border-colour: ;",
59
61
  "",
60
62
  " .service-summary__image-wrapper {}",
@@ -85,6 +85,7 @@
85
85
  " --services-card-outline-width: ;",
86
86
  " --services-card-outline-colour: ;",
87
87
  " --services-card-outline-colour-hover: ;",
88
+ " --services-card-outline-colour-focus: ;",
88
89
  " --services-card-outline-offset: ;",
89
90
  " --services-card-outline-offset-hover: ;",
90
91
  " --services-card-background-color-hover: ;",
@@ -94,44 +95,45 @@
94
95
  " --services-card-background-color: ;",
95
96
  " --services-card-padding-block: ;",
96
97
  " --services-card-padding-inline: ;",
97
- " --image-wrapper-aspect-ratio: ;",
98
- " --image-wrapper-border-radius: ;",
99
- " --image-wrapper-padding-block: ;",
100
- " --image-wrapper-padding-inline: ;",
101
- " --image-wrapper-border-image-zoom-transform: ;",
102
- " --details-wrapper-grid-gap: ;",
103
- " --details-wrapper-padding-block: ;",
104
- " --details-wrapper-padding-inline: ;",
105
- " --eyebrow-text-padding-block: ;",
106
- " --hero-text-padding-block: ;",
107
- " --description-text-colour: ;",
108
- " --description-padding-block: ;",
109
- " --description-line-height: ;",
110
- " --description-font-weight: ;",
111
- " --description-line-clamp: ;",
112
- " --meta-padding-block: ;",
113
- " --meta-border-colour: ;",
114
- " --meta-text-colour: ;",
115
- " --meta-duration-text-colour: ;",
116
- " --meta-price-text-colour: ;",
117
- " --meta-duration-font-weight: ;",
118
- " --meta-price-font-weight: ;",
119
- " --meta-letter-spacing: ;",
120
- " --meta-font-size: ;",
121
- " --meta-text-transform: ;",
122
- " --footer-padding-block: ;",
123
- " --footer-wrapper-grid-gap: ;",
98
+ " --services-card-image-aspect-ratio: ;",
99
+ " --services-card-image-border-radius: ;",
100
+ " --services-card-image-padding-block: ;",
101
+ " --services-card-image-padding-inline: ;",
102
+ " --services-card-image-zoom-transform: ;",
103
+ " --services-card-details-gap: ;",
104
+ " --services-card-details-padding-block: ;",
105
+ " --services-card-details-padding-inline: ;",
106
+ " --services-card-eyebrow-padding-block: ;",
107
+ " --services-card-title-padding-block: ;",
108
+ " --services-card-description-text-colour: ;",
109
+ " --services-card-description-padding-block: ;",
110
+ " --services-card-description-line-height: ;",
111
+ " --services-card-description-font-weight: ;",
112
+ " --services-card-description-line-clamp: ;",
113
+ " --services-card-meta-padding-block: ;",
114
+ " --services-card-meta-border-colour: ;",
115
+ " --services-card-meta-text-colour: ;",
116
+ " --services-card-meta-duration-text-colour: ;",
117
+ " --services-card-meta-price-text-colour: ;",
118
+ " --services-card-meta-duration-font-weight: ;",
119
+ " --services-card-meta-price-font-weight: ;",
120
+ " --services-card-meta-letter-spacing: ;",
121
+ " --services-card-meta-font-size: ;",
122
+ " --services-card-meta-text-transform: ;",
123
+ " --services-card-meta-gap: ;",
124
+ " --services-card-footer-padding-block: ;",
125
+ " --services-card-footer-gap: ;",
124
126
  "",
125
127
  " /* Titles over the image (titlesWithinImageWrapper) */",
126
- " --image-wrapper-details-scrim-colour: ;",
127
- " --image-wrapper-details-scrim: ;",
128
- " --image-wrapper-details-text-colour: ;",
129
- " --image-wrapper-details-eyebrow-text-colour: ;",
130
- " --image-wrapper-details-gap: ;",
131
- " --image-wrapper-details-eyebrow-text-padding-block: ;",
132
- " --image-wrapper-details-eyebrow-text-padding-inline: ;",
133
- " --image-wrapper-details-hero-text-padding-block: ;",
134
- " --image-wrapper-details-hero-text-padding-inline: ;",
128
+ " --services-card-image-details-scrim-colour: ;",
129
+ " --services-card-image-details-scrim: ;",
130
+ " --services-card-image-details-text-colour: ;",
131
+ " --services-card-image-details-eyebrow-colour: ;",
132
+ " --services-card-image-details-gap: ;",
133
+ " --services-card-image-details-eyebrow-padding-block: ;",
134
+ " --services-card-image-details-eyebrow-padding-inline: ;",
135
+ " --services-card-image-details-title-padding-block: ;",
136
+ " --services-card-image-details-title-padding-inline: ;",
135
137
  " }",
136
138
  "}"
137
139
  ]
@@ -29,7 +29,8 @@
29
29
  ".$1my-page {",
30
30
  " --services-card-grid-column-min-width: $2;",
31
31
  " --services-card-grid-gap: $3;",
32
- " --service-summary-grid-row-gap: $4;",
32
+ " --services-card-grid-button-text-white-space: $4;",
33
+ " --service-summary-grid-row-gap: $5;",
33
34
  "}"
34
35
  ]
35
36
  }
@@ -10,7 +10,7 @@
10
10
  "description": "TabNavigation with a localised aria-label",
11
11
  "scope": "vue,html",
12
12
  "body": [
13
- "<TabNavigation :nav-item-data=\"$1navData\" aria-label=\"${2:Site navigation}\" />"
13
+ "<TabNavigation :nav-item-data=\"$1navData\" aria-label=\"${2:Site navigation}\" open-menu-label=\"${3:Open navigation menu}\" close-menu-label=\"${4:Close navigation menu}\" />"
14
14
  ]
15
15
  },
16
16
  "SRCDEV TabNavigation Nav Data Script": {
@@ -73,6 +73,9 @@ keeps the same proportions. Tune a **ratio** to change spacing across all sizes,
73
73
  Private tokens (not public API): `--_background`, `--_text-colour`, `--_font-size`, `--_padding-inline`,
74
74
  `--_padding-block`, `--_padding-inline-icon` (derived from `--_icon-size`), `--_icon-size`, swapped by the size and variant classes.
75
75
 
76
+ > **Changed 2026-10-06**: a pill is capped at its container's width (`max-inline-size: 100%`), and a
77
+ > label too long for it ends in an ellipsis instead of overflowing. Short labels are unaffected.
78
+
76
79
  ## State hooks
77
80
 
78
81
  - Size class: `.sm`, `.md`, `.lg`. Variant class: `.default`, `.primary`, `.success`, `.warning`,
@@ -105,6 +105,7 @@ watch(
105
105
  line-height: 1;
106
106
  white-space: nowrap;
107
107
  width: fit-content;
108
+ max-inline-size: 100%;
108
109
  cursor: default;
109
110
  user-select: none;
110
111
 
@@ -128,8 +129,10 @@ watch(
128
129
  }
129
130
 
130
131
  .display-pill-label {
131
- display: inline-flex;
132
- align-items: center;
132
+ display: block;
133
+ min-inline-size: 0;
134
+ overflow-x: clip;
135
+ text-overflow: ellipsis;
133
136
  }
134
137
 
135
138
  &.sm {