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.
- package/.claude/skills/components/breadcrumb.md +5 -0
- package/.claude/skills/components/display-pill.md +2 -0
- package/.claude/skills/components/input-button.md +3 -0
- package/.claude/skills/components/responsive-header.md +49 -0
- package/.claude/skills/components/service-detail.md +32 -0
- package/.claude/skills/components/service-summary.md +8 -0
- package/.claude/skills/components/services-card-grid.md +5 -3
- package/.claude/skills/components/services-card.md +22 -12
- package/.claude/skills/components/slider-gallery.md +10 -4
- package/.claude/skills/components/tab-navigation.md +25 -9
- package/.claude/skills/index.md +1 -2
- package/.vscode/srcdev-component-responsive-header.code-snippets +10 -0
- package/.vscode/srcdev-component-service-detail.code-snippets +1 -0
- package/.vscode/srcdev-component-service-summary.code-snippets +3 -1
- package/.vscode/srcdev-component-services-card.code-snippets +38 -36
- package/.vscode/srcdev-component-services-grids.code-snippets +2 -1
- package/.vscode/srcdev-component-tab-navigation.code-snippets +1 -1
- package/app/components/01.atoms/display-pill/CONSUMER-STYLING.md +3 -0
- package/app/components/01.atoms/display-pill/DisplayPill.vue +5 -2
- package/app/components/01.atoms/navigation/breadcrumb/Breadcrumb.vue +16 -4
- package/app/components/01.atoms/navigation/breadcrumb/CONSUMER-STYLING.md +28 -5
- package/app/components/01.atoms/navigation/breadcrumb/stories/Breadcrumb.stories.ts +94 -0
- package/app/components/01.atoms/navigation/breadcrumb/tests/Breadcrumb.spec.ts +27 -0
- package/app/components/02.molecules/navigation/tab-navigation/CONSUMER-STYLING.md +52 -11
- package/app/components/02.molecules/navigation/tab-navigation/TabNavigation.vue +55 -23
- package/app/components/02.molecules/navigation/tab-navigation/stories/TabNavigation.stories.ts +176 -257
- package/app/components/02.molecules/navigation/tab-navigation/tests/TabNavigation.spec.ts +42 -2
- package/app/components/03.organisms/image-galleries/slider-gallery/CONSUMER-STYLING.md +19 -1
- package/app/components/03.organisms/image-galleries/slider-gallery/SliderGallery.vue +65 -7
- package/app/components/03.organisms/image-galleries/slider-gallery/stories/SliderGallery.stories.ts +201 -63
- package/app/components/03.organisms/image-galleries/slider-gallery/tests/SliderGallery.spec.ts +41 -2
- package/app/components/03.organisms/image-galleries/slider-gallery/tests/__snapshots__/SliderGallery.spec.ts.snap +6 -6
- package/app/components/03.organisms/responsive-header/CONSUMER-STYLING.md +4 -0
- package/app/components/03.organisms/responsive-header/NavigationItems.vue +33 -4
- package/app/components/03.organisms/responsive-header/ResponsiveHeader.vue +51 -4
- package/app/components/03.organisms/responsive-header/stories/ResponsiveHeader.stories.ts +52 -0
- package/app/components/03.organisms/responsive-header/tests/NavigationItems.spec.ts +20 -0
- package/app/components/03.organisms/responsive-header/tests/ResponsiveHeader.spec.ts +40 -0
- package/app/components/03.organisms/services/service-detail/CONSUMER-STYLING.md +92 -41
- package/app/components/03.organisms/services/service-detail/ServiceDetail.vue +198 -73
- package/app/components/03.organisms/services/service-detail/stories/ServiceDetail.stories.ts +157 -9
- package/app/components/03.organisms/services/service-detail/tests/ServiceDetail.spec.ts +58 -0
- package/app/components/03.organisms/services/service-detail/tests/__snapshots__/ServiceDetail.spec.ts.snap +4 -3
- package/app/components/03.organisms/services/service-summary/CONSUMER-STYLING.md +30 -0
- package/app/components/03.organisms/services/service-summary/ServiceSummary.vue +32 -8
- package/app/components/03.organisms/services/service-summary/stories/ServiceSummary.stories.ts +147 -0
- package/app/components/03.organisms/services/service-summary/tests/ServiceSummary.spec.ts +26 -0
- package/app/components/03.organisms/services/service-summary/tests/__snapshots__/ServiceSummary.spec.ts.snap +1 -1
- package/app/components/03.organisms/services/services-card/CONSUMER-STYLING.md +104 -63
- package/app/components/03.organisms/services/services-card/ServicesCard.vue +92 -66
- package/app/components/03.organisms/services/services-card/stories/ServicesCard.stories.ts +194 -27
- package/app/components/03.organisms/services/services-card/tests/ServicesCard.spec.ts +34 -8
- package/app/components/03.organisms/services/services-card/tests/__snapshots__/ServicesCard.spec.ts.snap +8 -8
- package/app/components/03.organisms/services/services-grids/CONSUMER-STYLING.md +9 -0
- package/app/components/03.organisms/services/services-grids/ServicesCardGrid.vue +2 -1
- package/app/components/03.organisms/services/services-grids/stories/ServiceSummaryGrid.stories.ts +46 -6
- package/app/components/03.organisms/services/services-grids/stories/ServicesCardGrid.stories.ts +89 -11
- package/app/components/03.organisms/services/services-grids/tests/ServicesCardGrid.spec.ts +8 -0
- package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServiceSummaryGrid.spec.ts.snap +3 -3
- package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServicesCardGrid.spec.ts.snap +24 -24
- package/app/components/05.forms/input-button/CONSUMER-STYLING.md +8 -0
- package/app/components/05.forms/input-button/InputButton.vue +3 -1
- package/app/composables/tests/useAnchorScroll.spec.ts +8 -0
- package/app/composables/useAnchorScroll.ts +1 -1
- package/nuxt.config.ts +1 -1
- package/package.json +1 -1
- package/.claude/skills/components/site-navigation.md +0 -145
- package/.vscode/srcdev-component-site-navigation.code-snippets +0 -38
- package/app/components/02.molecules/navigation/site-navigation/CONSUMER-STYLING.md +0 -109
- package/app/components/02.molecules/navigation/site-navigation/SiteNavigation.vue +0 -751
- package/app/components/02.molecules/navigation/site-navigation/stories/SiteNavigation.stories.ts +0 -356
- package/app/components/02.molecules/navigation/site-navigation/tests/SiteNavigation.spec.ts +0 -367
- 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-
|
|
96
|
-
--
|
|
97
|
-
--description-text-colour:
|
|
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
|
|
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-
|
|
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-
|
|
40
|
-
- The eyebrow's gradient fill is turned off over the image; it takes the text colour (or `--image-
|
|
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-
|
|
163
|
-
--
|
|
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
|
|
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 `--
|
|
199
|
-
- `.services-card`, `.image-wrapper`, `.details
|
|
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(--
|
|
150
|
-
| `--tab-nav-link-hover-color` | `var(--
|
|
151
|
-
| `--tab-nav-link-active-color` |
|
|
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(--
|
|
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,
|
|
170
|
-
| `--tab-nav-panel-border-color` |
|
|
171
|
-
| `--tab-nav-panel-item-border` |
|
|
172
|
-
| `--tab-nav-panel-link-color` |
|
|
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` |
|
|
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 |
|
package/.claude/skills/index.md
CHANGED
|
@@ -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
|
-
├──
|
|
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
|
}
|
|
@@ -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-
|
|
98
|
-
" --image-
|
|
99
|
-
" --image-
|
|
100
|
-
" --image-
|
|
101
|
-
" --
|
|
102
|
-
" --
|
|
103
|
-
" --details-
|
|
104
|
-
" --details-
|
|
105
|
-
" --eyebrow-
|
|
106
|
-
" --
|
|
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
|
-
" --
|
|
123
|
-
" --footer-
|
|
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-
|
|
127
|
-
" --image-
|
|
128
|
-
" --image-
|
|
129
|
-
" --image-
|
|
130
|
-
" --image-
|
|
131
|
-
" --image-
|
|
132
|
-
" --image-
|
|
133
|
-
" --image-
|
|
134
|
-
" --image-
|
|
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
|
-
" --
|
|
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:
|
|
132
|
-
|
|
132
|
+
display: block;
|
|
133
|
+
min-inline-size: 0;
|
|
134
|
+
overflow-x: clip;
|
|
135
|
+
text-overflow: ellipsis;
|
|
133
136
|
}
|
|
134
137
|
|
|
135
138
|
&.sm {
|