srcdev-nuxt-components 9.6.0 → 9.6.2
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/component-compliance-checklist.md +22 -3
- package/.claude/skills/component-dynamic-slots.md +3 -3
- package/.claude/skills/component-export-types.md +10 -16
- package/.claude/skills/component-naming.md +1 -1
- package/.claude/skills/components/alert-content.md +9 -1
- package/.claude/skills/components/container-glow.md +21 -8
- package/.claude/skills/components/display-avatar.md +27 -10
- package/.claude/skills/components/display-chip.md +38 -24
- package/.claude/skills/components/display-pill.md +58 -10
- package/.claude/skills/components/display-prompt.md +8 -5
- package/.claude/skills/components/display-toast.md +21 -4
- package/.claude/skills/components/glowing-border.md +20 -2
- package/.claude/skills/components/google-reviews.md +2 -2
- package/.claude/skills/components/profile-section.md +59 -21
- package/.claude/skills/components/responsive-header.md +11 -1
- package/.claude/skills/components/select-menu.md +54 -5
- package/.claude/skills/components/site-header.md +2 -2
- package/.claude/skills/components/social-icons-list.md +2 -0
- package/.claude/skills/components/tabbed-content.md +300 -0
- package/.claude/skills/composable-anchor-scroll.md +16 -15
- package/.claude/skills/index.md +8 -7
- package/.claude/skills/storybook-add-story.md +55 -0
- package/.claude/skills/theming-component-token-pattern.md +48 -0
- package/.claude/skills/theming-status-tokens.md +60 -0
- package/.vscode/srcdev-component-display-avatar.code-snippets +9 -1
- package/.vscode/srcdev-component-display-chip.code-snippets +3 -2
- package/.vscode/srcdev-component-display-pill.code-snippets +10 -0
- package/.vscode/srcdev-component-profile-section.code-snippets +27 -4
- package/.vscode/srcdev-component-responsive-header.code-snippets +6 -0
- package/.vscode/srcdev-component-select-menu.code-snippets +28 -0
- package/.vscode/{srcdev-component-tabs-core.code-snippets → srcdev-component-tabbed-content.code-snippets} +35 -16
- package/app/assets/styles/setup/03.theming/_status.css +24 -0
- package/app/assets/styles/setup/03.theming/index.css +1 -0
- package/app/components/01.atoms/animations/container-glow/CONSUMER-STYLING.md +39 -35
- package/app/components/01.atoms/animations/container-glow/ContainerGlow.vue +25 -15
- package/app/components/01.atoms/animations/container-glow/stories/ContainerGlow.stories.ts +130 -24
- package/app/components/01.atoms/animations/container-glow/tests/ContainerGlow.spec.ts +45 -0
- package/app/components/01.atoms/animations/container-glow/tests/__snapshots__/ContainerGlow.spec.ts.snap +2 -2
- package/app/components/01.atoms/animations/glowing-border/CONSUMER-STYLING.md +76 -19
- package/app/components/01.atoms/animations/glowing-border/GlowingBorder.vue +12 -10
- package/app/components/01.atoms/animations/glowing-border/stories/GlowingBorder.stories.ts +201 -42
- package/app/components/01.atoms/animations/glowing-border/tests/GlowingBorder.spec.ts +15 -4
- package/app/components/01.atoms/animations/glowing-border/tests/__snapshots__/GlowingBorder.spec.ts.snap +1 -1
- package/app/components/01.atoms/display-pill/CONSUMER-STYLING.md +75 -9
- package/app/components/01.atoms/display-pill/DisplayPill.vue +57 -24
- package/app/components/01.atoms/display-pill/stories/DisplayPill.stories.ts +240 -16
- package/app/components/01.atoms/display-pill/tests/DisplayPill.spec.ts +43 -14
- package/app/components/01.atoms/display-pill/tests/__snapshots__/DisplayPill.spec.ts.snap +3 -3
- package/app/components/01.atoms/tabbed-content/CONSUMER-STYLING.md +201 -0
- package/app/components/01.atoms/tabbed-content/TabbedContent.vue +750 -0
- package/app/components/01.atoms/tabbed-content/stories/TabbedContent.stories.ts +279 -0
- package/app/components/01.atoms/tabbed-content/tests/TabbedContent.spec.ts +816 -0
- package/app/components/02.molecules/alert-content/AlertContent.vue +11 -2
- package/app/components/02.molecules/alert-content/AlertContentInner.vue +19 -0
- package/app/components/02.molecules/alert-content/CONSUMER-STYLING.md +17 -4
- package/app/components/02.molecules/alert-content/stories/AlertContent.stories.ts +200 -17
- package/app/components/02.molecules/alert-content/tests/AlertContent.spec.ts +33 -0
- package/app/components/02.molecules/display-avatar/CONSUMER-STYLING.md +33 -15
- package/app/components/02.molecules/display-avatar/DisplayAvatar.vue +81 -36
- package/app/components/02.molecules/display-avatar/stories/DisplayAvatar.stories.ts +108 -6
- package/app/components/02.molecules/display-avatar/tests/DisplayAvatar.spec.ts +104 -13
- package/app/components/02.molecules/display-avatar/tests/__snapshots__/DisplayAvatar.spec.ts.snap +4 -3
- package/app/components/02.molecules/display-chip/CONSUMER-STYLING.md +57 -19
- package/app/components/02.molecules/display-chip/DisplayChip.vue +72 -54
- package/app/components/02.molecules/display-chip/stories/DisplayChip.stories.ts +103 -6
- package/app/components/02.molecules/display-chip/tests/DisplayChip.spec.ts +98 -29
- package/app/components/02.molecules/display-chip/tests/__snapshots__/DisplayChip.spec.ts.snap +8 -4
- package/app/components/02.molecules/profile-section/CONSUMER-STYLING.md +103 -13
- package/app/components/02.molecules/profile-section/ProfileSection.vue +123 -23
- package/app/components/02.molecules/profile-section/stories/ProfileSection.stories.ts +353 -115
- package/app/components/02.molecules/profile-section/tests/ProfileSection.spec.ts +144 -11
- package/app/components/02.molecules/profile-section/tests/__snapshots__/ProfileSection.spec.ts.snap +6 -11
- package/app/components/02.molecules/prompt/CONSUMER-STYLING.md +9 -1
- package/app/components/02.molecules/prompt/DisplayPrompt.vue +2 -2
- package/app/components/02.molecules/prompt/stories/DisplayPrompt.stories.ts +208 -71
- package/app/components/02.molecules/prompt/tests/DisplayPrompt.spec.ts +19 -6
- package/app/components/02.molecules/select-menu/CONSUMER-STYLING.md +66 -8
- package/app/components/02.molecules/select-menu/SelectMenu.vue +141 -18
- package/app/components/02.molecules/select-menu/stories/SelectMenu.stories.ts +232 -2
- package/app/components/02.molecules/select-menu/tests/SelectMenu.spec.ts +151 -0
- package/app/components/02.molecules/select-menu/tests/__snapshots__/SelectMenu.spec.ts.snap +6 -2
- package/app/components/02.molecules/social-icons-list/CONSUMER-STYLING.md +6 -2
- package/app/components/02.molecules/social-icons-list/SocialIconsList.vue +1 -0
- package/app/components/02.molecules/toast/CONSUMER-STYLING.md +91 -27
- package/app/components/02.molecules/toast/DisplayToast.vue +109 -50
- package/app/components/02.molecules/toast/DisplayToastProvider.vue +109 -45
- package/app/components/02.molecules/toast/stories/DisplayToast.stories.ts +23 -0
- package/app/components/02.molecules/toast/stories/DisplayToastProvider.stories.ts +73 -0
- package/app/components/02.molecules/toast/tests/DisplayToast.spec.ts +132 -18
- package/app/components/02.molecules/toast/tests/DisplayToastProvider.spec.ts +105 -39
- package/app/components/03.organisms/google-reviews/tests/__snapshots__/GoogleReviewCard.spec.ts.snap +1 -1
- package/app/components/03.organisms/google-reviews/tests/__snapshots__/GoogleReviews.spec.ts.snap +6 -6
- package/app/components/03.organisms/responsive-header/CONSUMER-STYLING.md +38 -11
- package/app/components/03.organisms/responsive-header/NavigationItems.vue +27 -12
- package/app/components/03.organisms/responsive-header/ResponsiveHeader.vue +35 -20
- package/app/components/03.organisms/responsive-header/stories/ResponsiveHeader.stories.ts +1 -1
- package/app/components/03.organisms/services/service-detail/tests/__snapshots__/ServiceDetail.spec.ts.snap +1 -1
- package/app/components/03.organisms/services/service-summary/tests/__snapshots__/ServiceSummary.spec.ts.snap +1 -1
- package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServiceSummaryGrid.spec.ts.snap +3 -3
- package/app/composables/useTabs.ts +148 -206
- package/app/types/components/display-chip.d.ts +8 -4
- package/app/types/components/display-toast.d.ts +2 -0
- package/app/types/components/index.ts +0 -1
- package/app/types/components/select-menu.d.ts +2 -0
- package/app/utils/pausableTimeout.ts +41 -0
- package/nuxt.config.ts +1 -1
- package/package.json +1 -1
- package/.claude/skills/components/tab-navigation.md +0 -288
- package/.claude/skills/components/tabs-core.md +0 -187
- package/.vscode/srcdev-component-tab-navigation.code-snippets +0 -30
- package/app/components/01.atoms/navigation/tabs/CONSUMER-STYLING.md +0 -120
- package/app/components/01.atoms/navigation/tabs/TabsCore.vue +0 -272
- package/app/components/01.atoms/navigation/tabs/stories/TabsCore.stories.ts +0 -199
- package/app/components/01.atoms/navigation/tabs/tests/TabsCore.spec.ts +0 -274
- package/app/components/02.molecules/navigation/tab-navigation/CONSUMER-STYLING.md +0 -191
- package/app/components/02.molecules/navigation/tab-navigation/TabNavigation.vue +0 -550
- package/app/components/02.molecules/navigation/tab-navigation/stories/TabNavigation.stories.ts +0 -259
- package/app/components/02.molecules/navigation/tab-navigation/tests/TabNavigation.spec.ts +0 -379
- package/app/components/02.molecules/navigation/tab-navigation/tests/__snapshots__/TabNavigation.spec.ts.snap +0 -33
- package/app/composables/useNavCollapse.ts +0 -86
- package/app/types/components/nav-item.d.ts +0 -11
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# DisplayPrompt
|
|
2
2
|
|
|
3
|
+
> **Changed 2026-10-08:** the root's `.closed` class is now `data-state="open" | "closed"`, so a consumer's own `.closed` class can't collide with it.
|
|
4
|
+
|
|
3
5
|
> **Changed 2026-09-28:** root class `.display-prompt-core` → `.display-prompt` (and `data-test-id` `display-prompt-core-{theme}` → `display-prompt-{theme}`). The documented `outlined` passthrough modifier never had any CSS behind it and was removed from this doc.
|
|
4
6
|
|
|
5
7
|
## Overview
|
|
@@ -27,7 +29,7 @@ the DOM. Dismiss can be controlled locally (closes itself) or by a parent via `v
|
|
|
27
29
|
|
|
28
30
|
| Slot | Description |
|
|
29
31
|
|---|---|
|
|
30
|
-
| `#title` |
|
|
32
|
+
| `#title` | Bold heading text. The title `<p>` is omitted when this slot isn't provided. |
|
|
31
33
|
| `#content` | Body text below the title. The `<p>` element is omitted when this slot is empty. |
|
|
32
34
|
| `#customDecoratorIcon` | Replaces the default theme icon. |
|
|
33
35
|
| `#customCloseIcon` | Replaces the default × close icon inside the dismiss button. |
|
|
@@ -52,10 +54,10 @@ Two modes depending on whether `v-model` is bound:
|
|
|
52
54
|
|
|
53
55
|
| Scenario | What happens on close |
|
|
54
56
|
|---|---|
|
|
55
|
-
| No `v-model` (or `v-model="false"`) | Sets internal `componentOpen = false` →
|
|
57
|
+
| No `v-model` (or `v-model="false"`) | Sets internal `componentOpen = false` → `data-state="closed"` on the root → collapses via CSS |
|
|
56
58
|
| `v-model="true"` | Emits `update:modelValue = false`; internal state unchanged — parent controls visibility |
|
|
57
59
|
|
|
58
|
-
|
|
60
|
+
`data-state="closed"` triggers a CSS grid row animation (`grid-template-rows: 1fr → 0fr`) with
|
|
59
61
|
`opacity: 0` and `pointer-events: none`, and the root becomes `inert` so the dismiss button leaves the
|
|
60
62
|
tab order and the accessibility tree. The transition is off under `prefers-reduced-motion: reduce`;
|
|
61
63
|
its duration is `--display-prompt-transition-duration` (`200ms`).
|
|
@@ -151,5 +153,6 @@ glass effect to be visible.
|
|
|
151
153
|
- The root gets `tabindex="-1"` only when `useAutoFocus` is on, so it can take programmatic focus without joining the tab order (it used to be `tabindex="0"` always).
|
|
152
154
|
- `useAutoFocus` focuses the root element on mount (useful when injecting a prompt in response to a
|
|
153
155
|
user action that has already moved focus elsewhere).
|
|
154
|
-
-
|
|
155
|
-
|
|
156
|
+
- Long titles/messages wrap (unbroken strings included). To cap them, set AlertContent's
|
|
157
|
+
`--alert-content-title-line-clamp` / `--alert-content-text-line-clamp` on an ancestor. The
|
|
158
|
+
`StressTest` story shows the worst-case content.
|
|
@@ -53,6 +53,7 @@ interface DisplayToastConfig {
|
|
|
53
53
|
title?: string // bold title line
|
|
54
54
|
description?: string // smaller description line
|
|
55
55
|
customIcon?: string // icon name override (e.g. "akar-icons:check-box")
|
|
56
|
+
dismissLabel?: string // screen-reader text for the close button — default: "Close"
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
59
|
```
|
|
@@ -111,6 +112,17 @@ const toastVisible = ref(false)
|
|
|
111
112
|
- `onBeforeRouteLeave` dismisses the toast on navigation. This emits a Vue Router warning in Vitest (no active route record) — harmless.
|
|
112
113
|
- `returnFocusTo` accepts an `HTMLElement` or a component instance with `$el`.
|
|
113
114
|
- The toast uses `<Teleport to="body">`. In tests, query `document.querySelector(".display-toast")` not `wrapper.find()`.
|
|
115
|
+
- Position, alignment and state are `data-position`, `data-alignment` (`full-width` when `fullWidth`) and `data-state="show|hide"`, not classes (changed 2026-10-08: the old bare `.top`/`.hide`/... classes could collide with a consumer's utility classes, since the toast is teleported into `<body>`).
|
|
116
|
+
- In tests, unmount every toast in `afterEach` before clearing `document.body`: a leftover instance's pending dismiss timer otherwise re-renders into the removed container (`insertBefore` of null) during a later test's timer advance.
|
|
117
|
+
|
|
118
|
+
### Auto-dismiss pause
|
|
119
|
+
|
|
120
|
+
Auto-dismiss pauses while hovered, or while keyboard focus is on or inside the toast, and resumes with
|
|
121
|
+
the remaining time (`createPausableTimeout` in `app/utils/pausableTimeout.ts`, plus
|
|
122
|
+
`focusPausesAutoDismiss`). The toast's own programmatic focus only pauses it when the root matches
|
|
123
|
+
`:focus-visible` (keyboard modality), so mouse users still get auto-dismiss. `data-paused` freezes the
|
|
124
|
+
progress bar via `animation-play-state`. In jsdom `:focus-visible` matches any focus, so tests that
|
|
125
|
+
depend on modality spy on `Element.prototype.matches` (see `DisplayToast.spec.ts`).
|
|
114
126
|
|
|
115
127
|
---
|
|
116
128
|
|
|
@@ -208,12 +220,15 @@ Stacked dismissals use a FLIP animation — remaining toasts slide smoothly to f
|
|
|
208
220
|
|
|
209
221
|
### CSS / styling
|
|
210
222
|
|
|
211
|
-
|
|
223
|
+
Both components teleport to `<body>`, so tokens set on a page or layout class don't reach them: set
|
|
224
|
+
them globally. Own tokens: `--display-toast(-provider)-max-width` (default `min(48rem, 100% - 2 * gutter)`),
|
|
225
|
+
`-progress-colour`, `-focus-ring-colour`, `-z-index`. Card styling comes from `--alert-content-*`.
|
|
226
|
+
Full list in `CONSUMER-STYLING.md`.
|
|
212
227
|
|
|
213
228
|
```css
|
|
214
|
-
|
|
215
|
-
--
|
|
216
|
-
--
|
|
229
|
+
:where(html) {
|
|
230
|
+
--display-toast-provider-max-width: min(36rem, 100% - 48px);
|
|
231
|
+
--display-toast-provider-progress-colour: var(--brand-accent);
|
|
217
232
|
}
|
|
218
233
|
```
|
|
219
234
|
|
|
@@ -234,5 +249,7 @@ The inner content layout is handled by the shared `AlertContentInner` molecule
|
|
|
234
249
|
- `useToastQueue` state persists across route changes — no `onBeforeRouteLeave` cleanup needed.
|
|
235
250
|
- `crypto.randomUUID()` generates toast IDs (`toast-<uuid>`). IDs are returned by `show()` for targeted `dismiss(id)` calls.
|
|
236
251
|
- In tests, query via `document.querySelector(".display-toast-provider-item")` — the Teleport renders outside the component wrapper.
|
|
252
|
+
- Every mounted provider reacts to the shared `useToastQueue` state, so tests must unmount providers in `afterEach`; otherwise an earlier test's provider runs its own timer for a new toast and dismisses it.
|
|
253
|
+
- Each item pauses independently (hover / keyboard focus, same rule as the standalone toast), tracked per toast id.
|
|
237
254
|
- The progress bar (`.display-toast-provider-progress`) is only rendered when `autoDismiss: true`.
|
|
238
255
|
- Dismissal can be triggered by: close button click, Escape key (when the toast item has focus), `dismiss(id)`, auto-dismiss timer, or `clear()`.
|
|
@@ -17,7 +17,7 @@ a configurable root element via the `tag` prop.
|
|
|
17
17
|
| Prop | Type | Default | Description |
|
|
18
18
|
|------|------|---------|-------------|
|
|
19
19
|
| `tag` | `"div" \| "p" \| "span" \| "section" \| "article" \| "aside" \| "header" \| "footer" \| "main" \| "nav" \| "ul" \| "ol"` | `"div"` | Root element tag. |
|
|
20
|
-
| `variant` | `"subtle" \| "vivid" \| "silver" \| "steel" \| "green"` | `"subtle"` | Glow colour variant. |
|
|
20
|
+
| `variant` | `"subtle" \| "vivid" \| "silver" \| "steel" \| "green"` | `"subtle"` | Glow colour variant, rendered as `data-variant` on the root (not a class). |
|
|
21
21
|
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the root element. |
|
|
22
22
|
|
|
23
23
|
## Slots
|
|
@@ -36,7 +36,7 @@ a configurable root element via the `tag` prop.
|
|
|
36
36
|
|
|
37
37
|
## Styling
|
|
38
38
|
|
|
39
|
-
Border width, radius, surface colour, animation duration, and every variant's 5 gradient colour
|
|
39
|
+
Border width, radius, surface colour (defaults to `--theme-surface-subtle`), text colour (`--theme-text`), animation duration, and every variant's 5 gradient colour
|
|
40
40
|
stops are public CSS custom properties — see `CONSUMER-STYLING.md` for the full token table.
|
|
41
41
|
|
|
42
42
|
```vue
|
|
@@ -48,6 +48,14 @@ stops are public CSS custom properties — see `CONSUMER-STYLING.md` for the ful
|
|
|
48
48
|
</GlowingBorder>
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
## Highlighting a single control
|
|
52
|
+
|
|
53
|
+
Wrap just an `InputButton` or `InputTextCore` so the glow becomes the control's border (a design
|
|
54
|
+
call-out for something "special"). Set `--glowing-border-radius` to the control's radius plus the
|
|
55
|
+
glow width, `--glowing-border-surface: transparent`, `--glowing-border-overflow: visible` (or the
|
|
56
|
+
control's focus ring is clipped), shrink-to-fit display, and `--input-text-border: transparent` for
|
|
57
|
+
the input. Full recipe in `CONSUMER-STYLING.md`; `HighlightedControls` story.
|
|
58
|
+
|
|
51
59
|
## Motion
|
|
52
60
|
|
|
53
61
|
The glow animation stops automatically under `prefers-reduced-motion: reduce`.
|
|
@@ -64,3 +72,13 @@ The glow animation stops automatically under `prefers-reduced-motion: reduce`.
|
|
|
64
72
|
override surface.
|
|
65
73
|
- 2026-09-20: added `green` variant (vibrant, saturated greens: `#00ff87`, `#39ff14`, `#00c853`,
|
|
66
74
|
`#64dd17`, `#00e676`), each stop overridable via `--glowing-border-green-color-{1-5}`.
|
|
75
|
+
- 2026-10-08 stress-test pass (`StressTest` story): the variant moved from a bare root class
|
|
76
|
+
(`.vivid`, `.green`...) to `data-variant`, since those names collide with common consumer
|
|
77
|
+
classes; `@keyframes glow` renamed to `glowing-border-rotate` for the same reason. Added
|
|
78
|
+
`overflow-wrap: anywhere` so long unbroken text wraps rather than being clipped by
|
|
79
|
+
`overflow: hidden`. `--glowing-border-surface` default moved from `canvas` to
|
|
80
|
+
`--theme-surface-subtle`, and a new `--glowing-border-text-colour` token defaults to
|
|
81
|
+
`--theme-text`. Stories gained story-only canvas and surface background controls with dark
|
|
82
|
+
values; the hardcoded dark stage was removed.
|
|
83
|
+
- 2026-10-08: added `--glowing-border-overflow` (default `hidden`) so a wrapped control's hover/focus
|
|
84
|
+
outlines can show, plus the `HighlightedControls` story and recipe.
|
|
@@ -132,8 +132,8 @@ replaced.
|
|
|
132
132
|
- Survives hostile data (see the `StressTest` story): long and unbroken text wraps inside its card,
|
|
133
133
|
reviewer names are cut to 2 lines (`--google-reviews-author-line-clamp`),
|
|
134
134
|
initials take whole characters (emoji-safe), star fill is clamped to 0–5, review text renders as
|
|
135
|
-
plain text. A broken photo URL
|
|
136
|
-
|
|
135
|
+
plain text. A broken photo URL falls back to the reviewer's initials (`DisplayAvatar`'s image-error
|
|
136
|
+
fallback).
|
|
137
137
|
|
|
138
138
|
## Variants
|
|
139
139
|
|
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ProfileSection
|
|
3
|
-
description: ProfileSection molecule — props, dynamic profile-info slots, eyebrowText/heroText/profileLinks slots, layout, accessibility
|
|
3
|
+
description: ProfileSection molecule — props, dynamic profile-info slots, eyebrowText/heroText/profileLinks slots, container-query layout, accessibility
|
|
4
4
|
type: reference
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# ProfileSection
|
|
8
8
|
|
|
9
|
+
> **Changed 2026-10-14 (breaking):** New `headerPlacement` prop (`above`, `picture-bottom`, `picture-top`) can put the header over the bottom or top of the picture. `profileInfoCount` removed — only the `profile-info-N` slots you give are rendered (it used to pad with empty blocks when none were given). The two-column switch is a container query on the component's own width, not a viewport `@media` query, and both columns are `minmax(0, …)`. The `<header>` is only rendered when a header slot is given. Highlight colours go through new `--profile-section-*` tokens. An invalid `profilePicture.width`/`height` (0, negative, `NaN`) falls back to 828×1104.
|
|
10
|
+
>
|
|
9
11
|
> **Classes renamed 2026-09-27:** `.picture` → `.profile-section-picture`, `.profile-picture` → `.profile-section-image`, `.profile-info` → `.profile-section-info`, `.profile-info-content` → `.profile-section-info-content`, `.profile-info-block` → `.profile-section-info-block`, `.profile-links` → `.profile-section-links`.
|
|
10
12
|
|
|
11
13
|
## Overview
|
|
12
14
|
|
|
13
|
-
`ProfileSection` is a molecule that renders a practitioner/author profile: a header area (eyebrow + heading), a profile picture, and a flexible set of bio/info blocks alongside optional profile links. It is landmark-aware — when `tag` is `section
|
|
15
|
+
`ProfileSection` is a molecule that renders a practitioner/author profile: a header area (eyebrow + heading), a profile picture, and a flexible set of bio/info blocks alongside optional profile links. It is landmark-aware — when `tag` is `section` or `article`, the root element automatically gets `aria-labelledby` wired to the heading inside the `#heroText` slot (**you must bind `headingId` yourself, and a console warning fires if you forget** — see [component-aria-landmark.md](../component-aria-landmark.md)). `tag="main"` is never auto-labelled.
|
|
14
16
|
|
|
15
17
|
## Props
|
|
16
18
|
|
|
17
19
|
| Prop | Type | Default | Description |
|
|
18
20
|
|------|------|---------|-------------|
|
|
19
|
-
| `profilePicture` | `ProfilePicture` (`{ src; alt; width?; height? }`, exported from the layer types) | — | **Required.** Rendered via `NuxtImg
|
|
21
|
+
| `profilePicture` | `ProfilePicture` (`{ src; alt; width?; height? }`, exported from the layer types) | — | **Required.** Rendered via `NuxtImg`. `width`/`height` default to `828`/`1104` (3:4, a Vercel-allowed width), and a missing or invalid value (0, negative, `NaN`, `Infinity`) uses the default. |
|
|
20
22
|
| `tag` | `"div" \| "section" \| "article" \| "main"` | `"div"` | HTML element rendered as the root. |
|
|
21
|
-
| `
|
|
23
|
+
| `headerPlacement` | `"above" \| "picture-bottom" \| "picture-top"` | `"above"` | Where the `#eyebrowText`/`#heroText` header goes: above the grid, or over the bottom or top of the picture on a scrim. Template form: `header-placement`. |
|
|
22
24
|
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the root element. |
|
|
23
25
|
|
|
24
26
|
## Slots
|
|
@@ -28,17 +30,19 @@ type: reference
|
|
|
28
30
|
| `#eyebrowText` | Optional eyebrow label above the heading. Use `EyebrowText`. |
|
|
29
31
|
| `#heroText` | Heading slot. Receives `headingId` as a slot prop — bind it to the heading's `id` for accessible `aria-labelledby`. |
|
|
30
32
|
| `#profile-info-1` … `#profile-info-N` | Bio / info blocks. Provide as many numbered slots as needed. They are rendered in ascending numeric order. |
|
|
31
|
-
| `#profileLinks` | Links / actions shown at the bottom-right of the info column (e.g. social icons, booking CTA). |
|
|
33
|
+
| `#profileLinks` | Links / actions shown at the bottom-right of the info column (e.g. social icons, booking CTA). Wraps onto more lines when there are many. |
|
|
34
|
+
|
|
35
|
+
The `<header class="profile-section-header">` is only rendered when `#eyebrowText` or `#heroText` is given. It renders above the grid, or inside the picture when `header-placement` is `picture-bottom` or `picture-top`.
|
|
32
36
|
|
|
33
37
|
## Basic usage
|
|
34
38
|
|
|
35
39
|
```vue
|
|
36
40
|
<ProfileSection
|
|
37
41
|
tag="section"
|
|
38
|
-
:profile-picture="{ src: '/images/profile/
|
|
42
|
+
:profile-picture="{ src: '/images/profile/alex.jpg', alt: 'Alex Morgan, hair stylist' }"
|
|
39
43
|
>
|
|
40
44
|
<template #eyebrowText>
|
|
41
|
-
<EyebrowText font-size="large" text="About
|
|
45
|
+
<EyebrowText font-size="large" text-content="About Alex" />
|
|
42
46
|
</template>
|
|
43
47
|
|
|
44
48
|
<template #heroText="{ headingId }">
|
|
@@ -47,8 +51,8 @@ type: reference
|
|
|
47
51
|
:id="headingId"
|
|
48
52
|
font-size="title"
|
|
49
53
|
:text-content="[
|
|
50
|
-
{ text: '
|
|
51
|
-
{ text: '
|
|
54
|
+
{ text: 'Alex Morgan', styleClass: 'normal' },
|
|
55
|
+
{ text: 'Hair stylist', styleClass: 'accent' },
|
|
52
56
|
]"
|
|
53
57
|
/>
|
|
54
58
|
</template>
|
|
@@ -58,7 +62,7 @@ type: reference
|
|
|
58
62
|
</template>
|
|
59
63
|
|
|
60
64
|
<template #profile-info-2>
|
|
61
|
-
<p>
|
|
65
|
+
<p class="location">Based in <span class="highlight">Northtown</span>…</p>
|
|
62
66
|
</template>
|
|
63
67
|
|
|
64
68
|
<template #profileLinks>
|
|
@@ -69,11 +73,12 @@ type: reference
|
|
|
69
73
|
|
|
70
74
|
## Dynamic profile-info slots
|
|
71
75
|
|
|
72
|
-
The `profile-info-N` slots are discovered at runtime by filtering `useSlots()` for keys matching `/^profile-info-\d
|
|
76
|
+
The `profile-info-N` slots are discovered at runtime by filtering `useSlots()` for keys matching `/^profile-info-\d+$/`, and rendered in ascending numeric order.
|
|
73
77
|
|
|
74
78
|
- If you provide `#profile-info-1`, `#profile-info-2`, `#profile-info-3` — all three render in order.
|
|
75
|
-
- If you provide no
|
|
76
|
-
- Gaps are supported — you can provide `#profile-info-1` and `#profile-info-3` without `#profile-info-2`;
|
|
79
|
+
- If you provide none, no info blocks render.
|
|
80
|
+
- Gaps are supported — you can provide `#profile-info-1` and `#profile-info-3` without `#profile-info-2`; `#profile-info-12` sorts after `#profile-info-2`.
|
|
81
|
+
- Discovery runs every render, so slots added or removed after mount (a `v-if` on the slot template) appear and disappear.
|
|
77
82
|
|
|
78
83
|
## heroText slot prop
|
|
79
84
|
|
|
@@ -89,10 +94,41 @@ If you use a different heading component, bind `headingId` to whatever prop rend
|
|
|
89
94
|
|
|
90
95
|
## Layout
|
|
91
96
|
|
|
92
|
-
- **
|
|
93
|
-
- **768px
|
|
94
|
-
-
|
|
95
|
-
-
|
|
97
|
+
- The root is a size container named `profile-section`. Layout follows **the component's own width**, not the viewport.
|
|
98
|
+
- **Under 768px wide**: single column — picture stacked above info, `--profile-section-gap` (`2rem`).
|
|
99
|
+
- **768px wide or more**: two columns — `minmax(0, var(--profile-section-picture-width))` (`38.4rem`) picture column, `minmax(0, 1fr)` info column, `--profile-section-gap-wide` (`4rem`) gap.
|
|
100
|
+
- Being an inline-size container, the root can't size itself from its content: give it a width in a flex row or other shrink-to-fit context.
|
|
101
|
+
- The root sets `overflow-wrap: anywhere`, so unbroken text and URLs in any slot wrap rather than widening the grid.
|
|
102
|
+
- Picture frame (`.profile-section-picture`): `--profile-section-picture-aspect-ratio` (`3 / 4`), `--profile-section-picture-border-radius` (`0.8rem`), `overflow: hidden`. The `NuxtImg` fills the frame with `object-fit: cover`. Full token list: `CONSUMER-STYLING.md` in the component folder.
|
|
103
|
+
- Profile links: `flex-wrap: wrap; align-items: end; justify-content: flex-end` — right-aligned to the bottom of the info column.
|
|
104
|
+
- To change the picture column width, set `--profile-section-picture-width`; don't override `grid-template-columns` in a viewport `@media` query, which no longer lines up with the component's own switch.
|
|
105
|
+
|
|
106
|
+
## Header in picture
|
|
107
|
+
|
|
108
|
+
With `header-placement="picture-bottom"` or `"picture-top"`, the `<header>` renders inside `.profile-section-picture`, before the image in the DOM (so it is read first), and sits at the bottom or top of the frame on a scrim that fades away from that edge. It follows ServicesCard's `titles-within-image-wrapper`:
|
|
109
|
+
|
|
110
|
+
- Scrim and text come from `--profile-section-picture-header-*` tokens: scrim colour (`#000`) and opacity (`75%`), a solid stop (`0%`, how far the scrim stays full strength before fading) and a target colour (`transparent`, what it fades into, set it to roughly the photo's colour where the fade ends so it blends instead of leaving a grey band), text colour (`contrast-color()` of the scrim colour where supported, else `#fff`), eyebrow colour, padding (`2.4rem`), gap (`0.4rem`). Full list in `CONSUMER-STYLING.md`.
|
|
111
|
+
- The frame drops `overflow: hidden` in this mode, so its aspect ratio is a minimum: a long header grows the frame rather than being clipped. The image is absolutely positioned and still fills the frame.
|
|
112
|
+
- `aria-labelledby` still points at the heading.
|
|
113
|
+
- Drop spacing utilities like `mb-20` from the heading in this mode; the header's padding handles spacing.
|
|
114
|
+
|
|
115
|
+
```vue
|
|
116
|
+
<ProfileSection tag="section" header-placement="picture-bottom" :profile-picture="picture">
|
|
117
|
+
<template #eyebrowText>
|
|
118
|
+
<EyebrowText tag="p" font-size="medium" text-content="About Alex" />
|
|
119
|
+
</template>
|
|
120
|
+
<template #heroText="{ headingId }">
|
|
121
|
+
<HeroText :id="headingId" tag="h2" font-size="heading" :text-content="[{ text: 'Alex Morgan', styleClass: 'normal' }]" />
|
|
122
|
+
</template>
|
|
123
|
+
...
|
|
124
|
+
</ProfileSection>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Two stories show themes built only from tokens: `Header In Picture (Dark, Bottom)`, ServicesCard-style, and `Header In Picture (Light, Top)`. A light scrim needs a dark `--profile-section-picture-header-text-colour`; `contrast-color()` picks one where supported, but set it for older browsers. `--profile-section-info-text-colour` sets the info column's text colour for a themed background.
|
|
128
|
+
|
|
129
|
+
## Content hooks
|
|
130
|
+
|
|
131
|
+
A `.highlight` inside an element with class `location` gets `--profile-section-highlight-colour` (default `var(--colour-text-accent)`); inside `services` it gets `--profile-section-link-colour` / `--profile-section-link-colour-hover` (defaults `var(--colour-link-default)` / `var(--colour-link-hover)`). Both use `--profile-section-highlight-font-weight` (`600`). The `location`/`services` class can sit on the `<p>` itself or a wrapper.
|
|
96
132
|
|
|
97
133
|
## CSS notes
|
|
98
134
|
|
|
@@ -106,8 +142,6 @@ If you use a different heading component, bind `headingId` to whatever prop rend
|
|
|
106
142
|
}
|
|
107
143
|
```
|
|
108
144
|
|
|
109
|
-
- Accent-coloured text within `.location` or `.services` info blocks can use the `.highlight` class for `var(--colour-text-accent)` or `var(--colour-link-default)` colouring respectively.
|
|
110
|
-
|
|
111
145
|
## Consumer styling scaffold
|
|
112
146
|
|
|
113
147
|
```vue
|
|
@@ -121,6 +155,8 @@ If you use a different heading component, bind `headingId` to whatever prop rend
|
|
|
121
155
|
<style>
|
|
122
156
|
.profile-section {
|
|
123
157
|
&.my-profile {
|
|
158
|
+
--profile-section-picture-width: 32rem;
|
|
159
|
+
|
|
124
160
|
.profile-section-header {
|
|
125
161
|
margin-block-end: 3.2rem;
|
|
126
162
|
}
|
|
@@ -131,6 +167,8 @@ If you use a different heading component, bind `headingId` to whatever prop rend
|
|
|
131
167
|
|
|
132
168
|
## Notes
|
|
133
169
|
|
|
134
|
-
- `NuxtImg` is used for the profile picture — provide a real `src` path. A
|
|
135
|
-
-
|
|
170
|
+
- `NuxtImg` is used for the profile picture — provide a real `src` path. A broken image shows its alt text inside the 3:4 frame.
|
|
171
|
+
- Pass `width`/`height` in `profilePicture` when your image isn't 3:4 at 828px wide, so IPX/Vercel request the right size.
|
|
172
|
+
- The `With Social Icons` story shows a `SocialIconsList` in `#profileLinks`; size it with `--social-icons-list-icon-size` and `--social-icons-list-gap` on your own wrapper.
|
|
173
|
+
- The `StressTest` story covers hostile content (broken image, unbroken text, RTL, out-of-order slots, many links) at every canvas width.
|
|
136
174
|
- Component is auto-imported in Nuxt — no import needed.
|
|
@@ -132,6 +132,9 @@ Route links and anchor links can be mixed in the same data. See the "Same-Page A
|
|
|
132
132
|
|
|
133
133
|
## Migrating from TabNavigation
|
|
134
134
|
|
|
135
|
+
> `TabNavigation` was deprecated 2026-10-06 and **removed 2026-10-07**, along with its `NavItem` /
|
|
136
|
+
> `NavItemData` types and the `useNavCollapse` composable. Type the new data as `ResponsiveHeaderProp`.
|
|
137
|
+
|
|
135
138
|
`ResponsiveHeader` covers everything `TabNavigation` does, and collapses progressively (only the items
|
|
136
139
|
that don't fit move into the overflow menu) instead of all-or-nothing. To switch:
|
|
137
140
|
|
|
@@ -149,8 +152,12 @@ that don't fit move into the overflow menu) instead of all-or-nothing. To switch
|
|
|
149
152
|
| `anchor-scroll-offset` | `anchor-scroll-offset` |
|
|
150
153
|
| 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
154
|
| `--tab-nav-link-color` | `--responsive-header-link-color` |
|
|
155
|
+
| `--tab-nav-link-hover-color` / `--tab-nav-link-active-color` | `--responsive-header-link-color-hover` / `-active` (added 2026-10-07) |
|
|
156
|
+
| `--tab-nav-decorator-hovered-bg` | `--responsive-nav-decorator-hovered-bg` |
|
|
152
157
|
| `--tab-nav-decorator-indicator-color` | `--responsive-nav-decorator-indicator-color` |
|
|
153
|
-
| `--tab-nav
|
|
158
|
+
| `--tab-nav-panel-link-color` / `-hover-color` / `-active-color` | `--overflow-nav-link-color` / `-color-hover` / `-color-active` |
|
|
159
|
+
| `--tab-nav-panel-bg` / `--tab-nav-panel-border-color` / `--tab-nav-panel-item-border` | `--responsive-header-overflow-nav-bg` / `--responsive-header-overflow-nav-border` (full shorthand) / `--overflow-nav-link-border-color` |
|
|
160
|
+
| `--tab-nav-burger-color` | `--responsive-header-overflow-btn-icon-color` |
|
|
154
161
|
|
|
155
162
|
```vue
|
|
156
163
|
<!-- Before -->
|
|
@@ -203,9 +210,12 @@ All tokens are read via `var(--token, default)` — see the full list in the com
|
|
|
203
210
|
|---|---|---|
|
|
204
211
|
| `--responsive-header-link-font-size` | `inherit` | Nav-link font-size. **Set this to a fixed value** — see the measurement-pipeline note above for why leaving it `inherit` from a fluid ancestor is a footgun. |
|
|
205
212
|
| `--responsive-header-link-color` | `inherit` | Link/summary text colour. |
|
|
213
|
+
| `--responsive-header-link-color-hover` / `-active` | the resting colour | Per-state link colour in the top bar (added 2026-10-07). Hover falls back to the item's current state. `--overflow-nav-link-color-hover` / `-active` do the same in the overflow panel. |
|
|
206
214
|
| `--responsive-header-bg` / `--responsive-header-padding-*` / `--responsive-header-border*` | transparent / `0` / `none` | Root element theming. |
|
|
207
215
|
| `--responsive-header-overflow-btn-*` | various | Overflow burger button sizing/colour. |
|
|
208
216
|
| `--responsive-header-sub-nav-*` / `--responsive-header-overflow-nav-*` | various | Top-bar dropdown panel and overflow-panel container theming. |
|
|
217
|
+
|
|
218
|
+
Defaults are for a **light** header (changed 2026-10-07): panels use `--page-bg`, borders and hover tints are `color-mix()`es of `--theme-text`, the overflow button is transparent with a `--theme-border-focus` hover outline. They used to assume a dark bar (`Canvas`, translucent white borders and tints). A dark header sets the tokens.
|
|
209
219
|
| `--responsive-nav-decorator-indicator-color` / `--responsive-nav-decorator-hovered-*` | `currentColor` / inherits | The sliding active/hover indicator bar under the main nav. |
|
|
210
220
|
|
|
211
221
|
---
|
|
@@ -21,14 +21,18 @@ open between picks.
|
|
|
21
21
|
|
|
22
22
|
| Prop | Type | Default | Notes |
|
|
23
23
|
|---|---|---|---|
|
|
24
|
-
| `options` | `SelectMenuOption[]` | — | **Required.** `{ value: string \| number; label: string; icon?: string }[]`. |
|
|
24
|
+
| `options` | `SelectMenuOption[]` | — | **Required.** `{ value: string \| number; label: string; icon?: string; dotColor?: string }[]`. `dotColor` (any CSS colour) colours the trigger's status dot while that option is selected (`indicator="dot"`, single-select) and shows a matching dot beside the option in the list when it has no `icon`. |
|
|
25
25
|
| `label` | `string` | — | **Required.** Accessible name for the trigger + listbox (`aria-label`). Also the trigger's fallback text when nothing is selected and no `placeholder` is set — this doubles as a visible category tag, e.g. `"Choose a service"`. |
|
|
26
26
|
| `placeholder` | `string` | `undefined` | Trigger text shown when nothing is selected. Falls back to `label` when omitted. |
|
|
27
|
-
| `showIcon` | `boolean` | `true` | Show the selected option's icon
|
|
27
|
+
| `showIcon` | `boolean` | `true` | Show the leading icon in the trigger: the selected option's icon (single-select), otherwise `triggerIcon`. |
|
|
28
|
+
| `triggerIcon` | `string` | `undefined` | Fixed leading icon, e.g. a filter category icon. Shown in both modes; in single-select a selected option's own icon replaces it. |
|
|
29
|
+
| `indicator` | `"none" \| "count" \| "dot"` | `"none"` | Marker after the trigger text, visible only while something is selected; otherwise kept in the layout with `visibility: hidden` (`.select-menu-trigger-indicator-empty`) so the trigger width doesn't jump. `count` = badge with the number selected (for `multiple`); `dot` = status dot (for single-select filters, flags the filter as active). `aria-hidden`. |
|
|
30
|
+
| `selectedCountLabel` | `string` | `"{count} selected"` | Appended to the trigger's `aria-label` while the indicator shows (`"Category, 3 selected"`). `{count}` is replaced. Override for translation. |
|
|
28
31
|
| `showLabel` | `boolean` | `true` | Show the selected option's label (or placeholder/label fallback) text in the trigger. Set `false` for an icon-only compact trigger. |
|
|
29
32
|
| `showChevron` | `boolean` | `true` | Show the trailing chevron in the trigger. |
|
|
30
|
-
| `multiple` | `boolean` | `false` | Allow selecting more than one option. Each option gets a checkbox indicator, `v-model` becomes an array, and picking an option leaves the popover open so more can be toggled.
|
|
33
|
+
| `multiple` | `boolean` | `false` | Allow selecting more than one option. Each option gets a checkbox indicator, `v-model` becomes an array, and picking an option leaves the popover open so more can be toggled. No option icon in the trigger when `true` (no single option to represent); `triggerIcon` still shows. |
|
|
31
34
|
| `showSelectionInTrigger` | `boolean` | `false` | Multiple mode only. When `true`, the trigger text updates to a comma-separated list of the currently checked options instead of staying fixed on `placeholder`/`label`. No effect outside `multiple`. |
|
|
35
|
+
| `reserveLabelWidth` | `boolean` | `false` | Single-select only. Renders hidden copies of the placeholder and every option label in the same grid cell as the visible text, so the trigger is always as wide as the longest one and doesn't resize when the selection changes. Off by default, since sometimes a trigger that hugs its text is wanted. No effect with `multiple`. |
|
|
32
36
|
| `inputVariant` | `InputUiVariant` | `"normal"` | Trigger border style, same prop as `InputSelect`: `"normal"` (bordered box) or `"underlined"` (bottom border only, square corners), added as a class on the root. `"outlined"` has no CSS, same as the `05.forms` inputs. |
|
|
33
37
|
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes on the root `<div>`. |
|
|
34
38
|
|
|
@@ -132,6 +136,39 @@ Set `showSelectionInTrigger` to opt out of that and have the trigger update inst
|
|
|
132
136
|
|
|
133
137
|
---
|
|
134
138
|
|
|
139
|
+
## Filter trigger: icon + count or status dot
|
|
140
|
+
|
|
141
|
+
```vue
|
|
142
|
+
<!-- Multi-select category filter: list icon, "Category", badge "3", chevron -->
|
|
143
|
+
<SelectMenu
|
|
144
|
+
v-model="categories"
|
|
145
|
+
:options="categoryOptions"
|
|
146
|
+
label="Category"
|
|
147
|
+
multiple
|
|
148
|
+
trigger-icon="lucide:list"
|
|
149
|
+
indicator="count"
|
|
150
|
+
/>
|
|
151
|
+
|
|
152
|
+
<!-- Single-select status filter: dot appears once a status is picked, in that option's dotColor
|
|
153
|
+
statusOptions = [{ value: "active", label: "Active", dotColor: "var(--status-success)" }, ...] -->
|
|
154
|
+
<SelectMenu
|
|
155
|
+
v-model="status"
|
|
156
|
+
:options="statusOptions"
|
|
157
|
+
label="Status"
|
|
158
|
+
trigger-icon="lucide:circle-dot"
|
|
159
|
+
indicator="dot"
|
|
160
|
+
/>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
The indicator is only visible while something is selected, so an unfiltered trigger looks plain,
|
|
164
|
+
but its space is always reserved so the trigger doesn't resize as the filter is set and cleared.
|
|
165
|
+
Per-option `dotColor` beats `--select-menu-trigger-dot-color`, which is the fallback (and the only
|
|
166
|
+
colour in `multiple` mode).
|
|
167
|
+
Badge/dot tokens (`--select-menu-trigger-count-*`, `--select-menu-trigger-dot-*`) are in
|
|
168
|
+
`CONSUMER-STYLING.md`.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
135
172
|
## Filter bar (multiple categories)
|
|
136
173
|
|
|
137
174
|
`SelectMenu` is single-select per instance — a faceted filter bar is built by placing one instance
|
|
@@ -167,6 +204,13 @@ Quick reference:
|
|
|
167
204
|
}
|
|
168
205
|
```
|
|
169
206
|
|
|
207
|
+
### Long content
|
|
208
|
+
|
|
209
|
+
The root caps at its container width, so a long trigger label truncates with an ellipsis. The
|
|
210
|
+
popover caps at `--select-menu-popover-max-width` (default `calc(100vw - 3.2rem)`) and option labels
|
|
211
|
+
wrap; `--select-menu-item-label-line-clamp` (default `none`) caps them, `1` for single-line
|
|
212
|
+
ellipsis. The `StressTest` story covers this at every canvas width.
|
|
213
|
+
|
|
170
214
|
---
|
|
171
215
|
|
|
172
216
|
## Notes
|
|
@@ -188,7 +232,8 @@ Quick reference:
|
|
|
188
232
|
drop the whole rule.
|
|
189
233
|
Confirmed broken on a Safari 16 iPad before this (trigger rendered, menu never opened).
|
|
190
234
|
- **Left-aligned popover** (`left: anchor(left)`) — unlike `ActionMenu` which right-aligns. Matches
|
|
191
|
-
native `<select>` dropdown behaviour. Flips above the trigger near the bottom of the viewport
|
|
235
|
+
native `<select>` dropdown behaviour. Flips above the trigger near the bottom of the viewport,
|
|
236
|
+
and to right-aligned when it would overflow the right edge (anchor-positioning browsers only).
|
|
192
237
|
- **Keyboard navigation** follows the WAI-ARIA listbox pattern: `ArrowDown`/`ArrowUp` move between
|
|
193
238
|
options (wraps around), `Home`/`End` jump to first/last, `Enter`/`Space` select the focused
|
|
194
239
|
option, `Tab` closes without stealing focus back to the trigger (matches `ActionMenu`'s
|
|
@@ -209,7 +254,7 @@ Quick reference:
|
|
|
209
254
|
weight doesn't resize the popover. Hover/focus has `-text-color-hover` and `-border-hover`, which
|
|
210
255
|
fall back to the row's *current* state (selected or resting), not always to resting. There's no
|
|
211
256
|
hover font-weight token on purpose: the width reserve only covers one alternate weight.
|
|
212
|
-
- **Chevron
|
|
257
|
+
- **Chevron flip** (`scaleY(-1)`, was `rotate(180deg)` until 2026-10-07) keys off the trigger's `aria-expanded` (from the composable's `isOpen`), not
|
|
213
258
|
`:has(...:popover-open)`, so it also works without the Popover API (changed 2026-10-05).
|
|
214
259
|
- **No native `<select>`/`appearance: base-select` involved** — this is a from-scratch popover
|
|
215
260
|
listbox, not a styled native select, so it doesn't inherit `InputSelectCore`'s WebKit
|
|
@@ -217,3 +262,7 @@ Quick reference:
|
|
|
217
262
|
- **Not a form field** — `SelectMenu` has no `fieldHasError`/`theme`/`inputVariant` props like the
|
|
218
263
|
`05.forms` inputs. Use `InputSelectCore` instead for an actual form field that needs validation
|
|
219
264
|
state and native `<select>` semantics.
|
|
265
|
+
- **2026-10-08 stress-test pass**: root `max-inline-size: 100%` (a long trigger label overflowed
|
|
266
|
+
narrow containers instead of truncating), popover `--select-menu-popover-max-width` and
|
|
267
|
+
`flip-inline` fallbacks (a long option made the menu wider than the viewport), option labels wrap
|
|
268
|
+
with a `--select-menu-item-label-line-clamp` token instead of `nowrap`.
|
|
@@ -99,6 +99,6 @@ const responsiveNavLinks = {
|
|
|
99
99
|
|
|
100
100
|
## Migration notes
|
|
101
101
|
|
|
102
|
-
- Consuming layouts previously assembling `PageRow` + `SkipLinks` + `ResponsiveHeader` (or `TabNavigation`) by hand can switch to `SiteHeader` directly; the two `styleClassPassthrough` props map 1:1 onto the two independent passthrough hooks that pattern used.
|
|
103
|
-
- `TabNavigation`
|
|
102
|
+
- Consuming layouts previously assembling `PageRow` + `SkipLinks` + `ResponsiveHeader` (or the removed `TabNavigation`) by hand can switch to `SiteHeader` directly; the two `styleClassPassthrough` props map 1:1 onto the two independent passthrough hooks that pattern used.
|
|
103
|
+
- `TabNavigation` (removed 2026-10-07) took `NavItemData` (`{ main: [{ text, href, iconName, cssName }] }`, also removed), which is **not** the same shape as `ResponsiveHeaderProp` (`{ groupKey: [{ name, path, childLinksTitle, childLinks }] }`): migrating means reshaping the nav data, not just swapping the component. See "Migrating from TabNavigation" in `responsive-header.md`. Its `navAlign` prop has no `SiteHeader` equivalent since `ResponsiveHeader`'s layout is fixed (branding column + nav column).
|
|
104
104
|
- Requires the consuming layout's main/footer regions to carry `id="main-content"` / `id="footer-content"` — same requirement as using `SkipLinks` directly.
|
|
@@ -57,6 +57,8 @@ import type { ISocialIcon } from "srcdev-nuxt-components";
|
|
|
57
57
|
| YouTube | `logos:youtube-icon` | `https://www.youtube.com/@` |
|
|
58
58
|
| TikTok | `logos:tiktok-icon` | `https://www.tiktok.com/@` |
|
|
59
59
|
|
|
60
|
+
`logos:instagram-icon`, `logos:x` and other dark brand icons vanish on a dark background, and their colours can't be changed. On a dark background use a mono icon such as `mdi:instagram` (any set that draws in `currentColor`) and colour it with `--social-icons-list-icon-colour` (default `currentColor`). Installed mono Instagram icons: `mdi:instagram`, `bi:instagram`, `lucide:instagram`, `akar-icons:instagram-fill`, `radix-icons:instagram-logo`.
|
|
61
|
+
|
|
60
62
|
---
|
|
61
63
|
|
|
62
64
|
## Usage example
|