srcdev-nuxt-components 9.6.0 → 9.6.1
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 +8 -2
- 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 +13 -7
- package/.claude/skills/components/display-chip.md +37 -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/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/tabbed-content.md +300 -0
- package/.claude/skills/composable-anchor-scroll.md +16 -15
- package/.claude/skills/index.md +4 -4
- package/.claude/skills/storybook-add-story.md +53 -0
- package/.claude/skills/theming-status-tokens.md +60 -0
- package/.vscode/srcdev-component-display-avatar.code-snippets +2 -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-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 +7 -5
- package/app/components/02.molecules/display-avatar/DisplayAvatar.vue +6 -2
- package/app/components/02.molecules/display-avatar/stories/DisplayAvatar.stories.ts +24 -3
- package/app/components/02.molecules/display-avatar/tests/DisplayAvatar.spec.ts +19 -0
- package/app/components/02.molecules/display-avatar/tests/__snapshots__/DisplayAvatar.spec.ts.snap +2 -1
- package/app/components/02.molecules/display-chip/CONSUMER-STYLING.md +52 -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/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/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/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
|
@@ -285,6 +285,59 @@ prop and try to pass it. Make it unmistakable:
|
|
|
285
285
|
|
|
286
286
|
`PriceList.stories.ts` and `GoogleReviews.stories.ts` (line-clamp controls) follow this.
|
|
287
287
|
|
|
288
|
+
## Link to the stories of components that use this one
|
|
289
|
+
|
|
290
|
+
When the component you're working on is a building block of other components (it's rendered
|
|
291
|
+
inside them, or they're a specialised version of it), its stories only show it in isolation. Add a
|
|
292
|
+
short note to each story that names the components using it, says what they add, and links to their
|
|
293
|
+
stories, so a reader can see it in real use. Examples: `AlertContent` is the panel inside
|
|
294
|
+
`DisplayToast`, `DisplayPrompt` and `CookieConsentBanner`; `AlertContentInner` is shared by
|
|
295
|
+
`AlertContent` and `AlertMaskedContent`.
|
|
296
|
+
|
|
297
|
+
- **Say how they're related, not just "see also".** Name the parent and what it adds, e.g. "The
|
|
298
|
+
#actions slot wired to real accept and reject handlers: see the CookieConsentBanner story."
|
|
299
|
+
- **Link to the story that shows the relevant feature**, not just any story. A story about a slot
|
|
300
|
+
or behaviour links to the parent story that exercises it. Other stories can share one general
|
|
301
|
+
note.
|
|
302
|
+
- **Check the parent story actually shows what the note says.** Read its component before writing
|
|
303
|
+
the note. `DisplayToast` doesn't fill `AlertContent`'s `#actions` slot, so a toast note that
|
|
304
|
+
promised action buttons would send people to the wrong place.
|
|
305
|
+
- **Put the note in the canvas**, as a `<p>` above the component, so it's visible on the Canvas tab
|
|
306
|
+
as well as Docs. If the story has a `parameters.docs.description`, add the link there too.
|
|
307
|
+
- Find the parent's story id from its `title` and export name: `"Molecules/CookieConsentBanner"` +
|
|
308
|
+
`Default` gives `molecules-cookieconsentbanner--default` (lowercase, `/` and spaces become `-`,
|
|
309
|
+
then `--` and the kebab-cased export name).
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
// Links in the canvas open in the Storybook manager (target="_top"), not inside the preview iframe.
|
|
313
|
+
const storyNoteStyle = "margin: 0 0 1.6rem; font-size: 1.4rem;";
|
|
314
|
+
const cookieNote = `
|
|
315
|
+
<p style="${storyNoteStyle}">
|
|
316
|
+
For the #actions slot wired up to real accept and reject handlers, open the
|
|
317
|
+
<a href="/?path=/story/molecules-cookieconsentbanner--default" target="_top">CookieConsentBanner</a> story.
|
|
318
|
+
</p>
|
|
319
|
+
`;
|
|
320
|
+
|
|
321
|
+
// In the story template:
|
|
322
|
+
template: `
|
|
323
|
+
<div style="max-width: 600px; padding: 2rem;">
|
|
324
|
+
${cookieNote}
|
|
325
|
+
<AlertContent v-bind="componentArgs">...</AlertContent>
|
|
326
|
+
</div>
|
|
327
|
+
`,
|
|
328
|
+
|
|
329
|
+
// In parameters.docs.description (markdown): use "?path=..." with no leading slash.
|
|
330
|
+
story: "See the [CookieConsentBanner](?path=/story/molecules-cookieconsentbanner--default) story. ...",
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
- Canvas links need `target="_top"` and a leading `/` (`/?path=...`). Without `target="_top"` the
|
|
334
|
+
whole Storybook UI loads inside the preview frame. The leading `/` assumes Storybook is served
|
|
335
|
+
from the root of its domain, which holds locally and on the deployed Storybook.
|
|
336
|
+
- Define each note once as a constant at the top of the file and interpolate it, rather than
|
|
337
|
+
repeating the markup in every story.
|
|
338
|
+
|
|
339
|
+
`AlertContent.stories.ts` follows this.
|
|
340
|
+
|
|
288
341
|
## Notes
|
|
289
342
|
|
|
290
343
|
- **Colour scheme:** `.storybook/preview.ts` pins every story to the light scheme
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Status tokens
|
|
2
|
+
|
|
3
|
+
Global status colours, declared on `:root` in `app/assets/styles/setup/03.theming/_status.css`.
|
|
4
|
+
Components that show a status (a dot, a badge, a pill) fall back to these, so a consumer restyles
|
|
5
|
+
every status indicator in the library from one place.
|
|
6
|
+
|
|
7
|
+
## The set
|
|
8
|
+
|
|
9
|
+
Each status has three roles:
|
|
10
|
+
|
|
11
|
+
| Role | Token | Use |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| Solid | `--status-<name>` | Dots, markers, small fills (mid-ramp step, visible on light and dark) |
|
|
14
|
+
| Surface | `--status-<name>-surface` | Tinted background of a badge or pill |
|
|
15
|
+
| Text | `--status-<name>-text` | Text/icon on that surface |
|
|
16
|
+
|
|
17
|
+
| Status | Solid | Surface / text | Meaning |
|
|
18
|
+
|---|---|---|---|
|
|
19
|
+
| `success` | `--green-06` | `--green-01` / `--green-10` | Good, active, online, complete |
|
|
20
|
+
| `warning` | `--orange-05` | `--orange-01` / `--orange-10` | Needs attention, pending, idle |
|
|
21
|
+
| `danger` | `--red-06` | `--red-01` / `--red-10` | Error, blocked, do not disturb |
|
|
22
|
+
| `info` | `--blue-06` | `--blue-01` / `--blue-09` | Informational, in progress, new |
|
|
23
|
+
| `neutral` | `--slate-05` | `--slate-01` / `--slate-09` | Inactive, offline, archived, draft |
|
|
24
|
+
|
|
25
|
+
Keep the set to these five. A domain-specific state (`pending`, `archived`, `in-progress`) maps
|
|
26
|
+
onto one of them rather than getting its own token; if a consumer genuinely needs a sixth colour,
|
|
27
|
+
they define it in their app and pass it wherever the component takes a colour (e.g. `dotColor`).
|
|
28
|
+
|
|
29
|
+
## Who reads them
|
|
30
|
+
|
|
31
|
+
The component's own token always wins, then the global status token:
|
|
32
|
+
|
|
33
|
+
| Component | Mapping |
|
|
34
|
+
|---|---|
|
|
35
|
+
| `DisplayChip` | `online` → success, `idle` → warning, `dnd` → danger, no class → neutral (solid) |
|
|
36
|
+
| `DisplayPill` | `success`/`warning`/`danger` variants → surface + text. `primary` (brand) and `neutral` (dark, inverted) do **not** read the set |
|
|
37
|
+
| `SelectMenu` | Nothing automatic; options pass `dotColor: "var(--status-success)"` etc. |
|
|
38
|
+
|
|
39
|
+
When adding a component that shows a status, default its colour tokens to these
|
|
40
|
+
(`var(--component-colour-online, var(--status-success))`), never to a palette step or hex value
|
|
41
|
+
directly.
|
|
42
|
+
|
|
43
|
+
## Overriding
|
|
44
|
+
|
|
45
|
+
```css
|
|
46
|
+
/* consuming app, unlayered */
|
|
47
|
+
:root {
|
|
48
|
+
--status-success: #1f9d55;
|
|
49
|
+
--status-success-surface: #e6f6ec;
|
|
50
|
+
--status-success-text: #0f5132;
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
One component only: set its own token instead (`--display-chip-colour-online: hotpink`).
|
|
55
|
+
|
|
56
|
+
## Dark mode
|
|
57
|
+
|
|
58
|
+
Light values only, like every other layer token (no `light-dark()`). Storybook's dark values live
|
|
59
|
+
in `.storybook/color-scheme.css`: surfaces move to step `09`, text to step `02`; solids stay put.
|
|
60
|
+
A consumer app with a dark scheme copies those overrides (see `theming-dark-mode.md`).
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
" src=\"${1:/images/profile.jpg}\"",
|
|
22
22
|
" alt=\"${2:Jane Smith}\"",
|
|
23
23
|
" :chip=\"{ size: '${3:12px}', maskWidth: '${4:4px}', offset: '${5:0px}', angle: '${6:45deg}' }\"",
|
|
24
|
-
"
|
|
24
|
+
" status=\"${7|online,idle,dnd,offline|}\"",
|
|
25
|
+
" status-label=\"${8:Online}\"",
|
|
25
26
|
"/>"
|
|
26
27
|
]
|
|
27
28
|
}
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
"<DisplayChip",
|
|
7
7
|
" shape=\"${1|circle,square|}\"",
|
|
8
8
|
" :config=\"{ size: '${2:12px}', maskWidth: '${3:4px}', offset: '${4:0px}', angle: '${5:135deg}' }\"",
|
|
9
|
-
"
|
|
9
|
+
" status=\"${6|online,idle,dnd,offline|}\"",
|
|
10
|
+
" status-label=\"${7:Online}\"",
|
|
10
11
|
">",
|
|
11
|
-
" $
|
|
12
|
+
" $8",
|
|
12
13
|
"</DisplayChip>"
|
|
13
14
|
]
|
|
14
15
|
},
|
|
@@ -34,5 +34,15 @@
|
|
|
34
34
|
" --display-pill-border-colour: ${4:currentColor};",
|
|
35
35
|
"}"
|
|
36
36
|
]
|
|
37
|
+
},
|
|
38
|
+
"SRCDEV DisplayPill Two-Tone Ring": {
|
|
39
|
+
"description": "DisplayPill dark border plus light ring in each variant's own colours",
|
|
40
|
+
"scope": "css",
|
|
41
|
+
"body": [
|
|
42
|
+
".${1:tag-list} {",
|
|
43
|
+
" --display-pill-border-colour: currentColor;",
|
|
44
|
+
" --display-pill-ring-width: ${2:0.2rem};",
|
|
45
|
+
"}"
|
|
46
|
+
]
|
|
37
47
|
}
|
|
38
48
|
}
|
|
@@ -51,6 +51,12 @@
|
|
|
51
51
|
"body": [
|
|
52
52
|
".$1site-header-nav {",
|
|
53
53
|
" --responsive-header-link-color: $2inherit;",
|
|
54
|
+
" --responsive-header-link-color-hover: ;",
|
|
55
|
+
" --responsive-header-link-color-active: ;",
|
|
56
|
+
" --responsive-nav-decorator-indicator-color: ;",
|
|
57
|
+
" --overflow-nav-link-color: ;",
|
|
58
|
+
" --overflow-nav-link-color-hover: ;",
|
|
59
|
+
" --overflow-nav-link-color-active: ;",
|
|
54
60
|
" --responsive-header-bg: $3transparent;",
|
|
55
61
|
" /* Fixed, NOT var(--step-*) — see responsive-header.md's measurement-pipeline note. */",
|
|
56
62
|
" --responsive-header-link-font-size: ${4:1.4rem};",
|
|
@@ -61,6 +61,34 @@
|
|
|
61
61
|
"/>"
|
|
62
62
|
]
|
|
63
63
|
},
|
|
64
|
+
"SRCDEV SelectMenu Filter Count": {
|
|
65
|
+
"description": "SelectMenu — multi-select filter with a category icon and a selected-count badge",
|
|
66
|
+
"scope": "vue,html",
|
|
67
|
+
"body": [
|
|
68
|
+
"<SelectMenu",
|
|
69
|
+
" v-model=\"${1:selectedValues}\"",
|
|
70
|
+
" :options=\"${2:options}\"",
|
|
71
|
+
" label=\"${3:Category}\"",
|
|
72
|
+
" multiple",
|
|
73
|
+
" trigger-icon=\"${4:lucide:list}\"",
|
|
74
|
+
" indicator=\"count\"",
|
|
75
|
+
"/>"
|
|
76
|
+
]
|
|
77
|
+
},
|
|
78
|
+
"SRCDEV SelectMenu Filter Status Dot": {
|
|
79
|
+
"description": "SelectMenu — single-select filter with a category icon and an active-filter status dot, fixed width across selections",
|
|
80
|
+
"scope": "vue,html",
|
|
81
|
+
"body": [
|
|
82
|
+
"<SelectMenu",
|
|
83
|
+
" v-model=\"${1:status}\"",
|
|
84
|
+
" :options=\"${2:statusOptions}\"",
|
|
85
|
+
" label=\"${3:Status}\"",
|
|
86
|
+
" trigger-icon=\"${4:lucide:circle-dot}\"",
|
|
87
|
+
" indicator=\"dot\"",
|
|
88
|
+
" reserve-label-width",
|
|
89
|
+
"/>"
|
|
90
|
+
]
|
|
91
|
+
},
|
|
64
92
|
"SRCDEV SelectMenu Multi-Select Showing Selection": {
|
|
65
93
|
"description": "SelectMenu — multi-select where the trigger updates to list checked options",
|
|
66
94
|
"scope": "vue,html",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"SRCDEV
|
|
3
|
-
"description": "
|
|
2
|
+
"SRCDEV TabbedContent Basic": {
|
|
3
|
+
"description": "TabbedContent — horizontal tablist with indexed trigger/content slots",
|
|
4
4
|
"scope": "vue,html",
|
|
5
5
|
"body": [
|
|
6
|
-
"<
|
|
6
|
+
"<TabbedContent :item-count=\"${1:3}\" aria-label=\"${8:Product details}\">",
|
|
7
7
|
" <template #tab-0-trigger>${2:Overview}</template>",
|
|
8
8
|
" <template #tab-0-content>",
|
|
9
9
|
" $3",
|
|
@@ -16,39 +16,39 @@
|
|
|
16
16
|
" <template #tab-2-content>",
|
|
17
17
|
" $7",
|
|
18
18
|
" </template>",
|
|
19
|
-
"</
|
|
19
|
+
"</TabbedContent>"
|
|
20
20
|
]
|
|
21
21
|
},
|
|
22
|
-
"SRCDEV
|
|
23
|
-
"description": "
|
|
22
|
+
"SRCDEV TabbedContent Vertical": {
|
|
23
|
+
"description": "TabbedContent with axis=\"y\" for a vertical tab column",
|
|
24
24
|
"scope": "vue,html",
|
|
25
25
|
"body": [
|
|
26
|
-
"<
|
|
26
|
+
"<TabbedContent axis=\"y\" :item-count=\"${1:3}\" aria-label=\"${2:Settings}\">",
|
|
27
27
|
" <template #tab-0-trigger>$3</template>",
|
|
28
28
|
" <template #tab-0-content>$4</template>",
|
|
29
|
-
"</
|
|
29
|
+
"</TabbedContent>"
|
|
30
30
|
]
|
|
31
31
|
},
|
|
32
|
-
"SRCDEV
|
|
33
|
-
"description": "
|
|
32
|
+
"SRCDEV TabbedContent Indicators Off": {
|
|
33
|
+
"description": "TabbedContent with the moving hover/active/underline decorators all disabled",
|
|
34
34
|
"scope": "vue,html",
|
|
35
35
|
"body": [
|
|
36
|
-
"<
|
|
36
|
+
"<TabbedContent",
|
|
37
37
|
" :item-count=\"${1:3}\"",
|
|
38
38
|
" :track-hover=\"false\"",
|
|
39
39
|
" :track-active=\"false\"",
|
|
40
40
|
" :track-indicator=\"false\"",
|
|
41
41
|
">",
|
|
42
42
|
" $2",
|
|
43
|
-
"</
|
|
43
|
+
"</TabbedContent>"
|
|
44
44
|
]
|
|
45
45
|
},
|
|
46
|
-
"SRCDEV
|
|
47
|
-
"description": "CSS override scaffold for
|
|
46
|
+
"SRCDEV TabbedContent CSS Override": {
|
|
47
|
+
"description": "CSS override scaffold for TabbedContent — scope to a page or section class",
|
|
48
48
|
"scope": "css",
|
|
49
49
|
"body": [
|
|
50
50
|
".$1my-page {",
|
|
51
|
-
" .
|
|
51
|
+
" .tabbed-content {",
|
|
52
52
|
" --tabs-nav-border: ;",
|
|
53
53
|
" --tabs-hover-indicator-colour: ;",
|
|
54
54
|
" --tabs-hover-indicator-text-colour: ;",
|
|
@@ -56,6 +56,8 @@
|
|
|
56
56
|
" --tabs-active-indicator-text-colour: ;",
|
|
57
57
|
" --tabs-underline-indicator-colour: ;",
|
|
58
58
|
" --tabs-underline-indicator-height: ;",
|
|
59
|
+
" --tabs-hover-underline-indicator-colour: ;",
|
|
60
|
+
" --tabs-hover-underline-indicator-height: ;",
|
|
59
61
|
" --tabs-list-item-opacity: ;",
|
|
60
62
|
" --tabs-list-item-colour: ;",
|
|
61
63
|
" --tabs-list-item-colour-selected: ;",
|
|
@@ -63,16 +65,33 @@
|
|
|
63
65
|
" --tabs-list-item-font-weight: ;",
|
|
64
66
|
" --tabs-list-item-padding-block: ;",
|
|
65
67
|
" --tabs-list-item-padding-inline: ;",
|
|
68
|
+
" --tabs-list-item-max-inline-size: ;",
|
|
66
69
|
" --tabs-list-item-colour-transition-duration: ;",
|
|
67
|
-
" --tabs-
|
|
70
|
+
" --tabs-focus-ring-width: ;",
|
|
71
|
+
" --tabs-focus-ring-colour: ;",
|
|
68
72
|
" --tabs-list-item-focus-outline-offset: ;",
|
|
73
|
+
" --tabs-more-popover-surface: ;",
|
|
74
|
+
" --tabs-more-popover-border: ;",
|
|
75
|
+
" --tabs-more-item-surface-hover: ;",
|
|
76
|
+
" --tabs-more-item-colour-selected: ;",
|
|
69
77
|
" --tabs-content-background-colour: ;",
|
|
78
|
+
" --tabs-content-text-colour: ;",
|
|
70
79
|
" --tabs-content-border-width: ;",
|
|
71
80
|
" --tabs-content-border-colour: ;",
|
|
72
81
|
" --tabs-content-border-radius: ;",
|
|
73
82
|
" --tabs-axis-y-gap: ;",
|
|
83
|
+
" --tabs-axis-y-list-max-inline-size: ;",
|
|
74
84
|
" }",
|
|
75
85
|
"}"
|
|
76
86
|
]
|
|
87
|
+
},
|
|
88
|
+
"SRCDEV TabbedContent Scrolling Row": {
|
|
89
|
+
"description": "TabbedContent whose tab row scrolls sideways instead of collapsing into a More menu",
|
|
90
|
+
"scope": "vue,html",
|
|
91
|
+
"body": [
|
|
92
|
+
"<TabbedContent :item-count=\"${1:3}\" overflow-mode=\"scroll\" aria-label=\"${2:Product details}\">",
|
|
93
|
+
" $3",
|
|
94
|
+
"</TabbedContent>"
|
|
95
|
+
]
|
|
77
96
|
}
|
|
78
97
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/* Shared status colours. Each status has a solid colour (dots, markers) plus a tinted
|
|
2
|
+
surface/text pair (badges, pills). Component tokens fall back to these, so overriding
|
|
3
|
+
one here restyles that status across every component. */
|
|
4
|
+
:root {
|
|
5
|
+
--status-success: var(--green-06);
|
|
6
|
+
--status-success-surface: var(--green-01);
|
|
7
|
+
--status-success-text: var(--green-10);
|
|
8
|
+
|
|
9
|
+
--status-warning: var(--orange-05);
|
|
10
|
+
--status-warning-surface: var(--orange-01);
|
|
11
|
+
--status-warning-text: var(--orange-10);
|
|
12
|
+
|
|
13
|
+
--status-danger: var(--red-06);
|
|
14
|
+
--status-danger-surface: var(--red-01);
|
|
15
|
+
--status-danger-text: var(--red-10);
|
|
16
|
+
|
|
17
|
+
--status-info: var(--blue-06);
|
|
18
|
+
--status-info-surface: var(--blue-01);
|
|
19
|
+
--status-info-text: var(--blue-09);
|
|
20
|
+
|
|
21
|
+
--status-neutral: var(--slate-05);
|
|
22
|
+
--status-neutral-surface: var(--slate-01);
|
|
23
|
+
--status-neutral-text: var(--slate-09);
|
|
24
|
+
}
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
# ContainerGlow — Consumer Styling Guide
|
|
2
2
|
|
|
3
|
-
## Public
|
|
3
|
+
## Public token API
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
Static, per-card visual tokens. Set them on an element you own (see Local overrides) or with a
|
|
6
|
+
`style` attribute:
|
|
7
7
|
|
|
8
8
|
| Token | Default | Description |
|
|
9
9
|
|---|---|---|
|
|
10
|
-
| `--container-glow-background` | `
|
|
10
|
+
| `--container-glow-background` | `var(--theme-surface-subtle)` | Card background colour. |
|
|
11
|
+
| `--container-glow-text-colour` | `var(--theme-text)` | Card text colour. |
|
|
11
12
|
| `--container-glow-padding` | `2rem` | Card inner padding. |
|
|
12
|
-
| `--container-glow-aspect-ratio` | `330 / 400` | Card aspect ratio. |
|
|
13
|
+
| `--container-glow-aspect-ratio` | `330 / 400` | Card aspect ratio. Content taller than this grows the card. |
|
|
13
14
|
| `--container-glow-border-radius` | `12px` | Card and glow-effect corner radius (shared by the card and every glow layer). |
|
|
14
|
-
| `--container-glow-min-width` | `280px` | Card minimum width. |
|
|
15
|
+
| `--container-glow-min-width` | `280px` | Card minimum width, capped at the wrapper's width so a narrow viewport never overflows. |
|
|
15
16
|
| `--container-glow-max-width` | `280px` | Card maximum width. |
|
|
16
17
|
| `--container-glow-content-gap` | `0.25rem` | Gap between a card's own slotted children. |
|
|
17
18
|
| `--container-glow-highlight-colour` | `hsl(280 10% 50% / 1)` | Tint of the thin proximity-tracking ring drawn under the coloured glow. |
|
|
@@ -19,9 +20,14 @@ consuming page's own stylesheet:
|
|
|
19
20
|
| `--container-glow-transition-duration` | `1s` | Fade duration when the glow's opacity changes (also disabled under `prefers-reduced-motion: reduce`). |
|
|
20
21
|
| `--container-glow-gradient` | 5-stop conic rainbow gradient | The glow's colour gradient. Override with any valid `background` value (a `conic-gradient(...)`, a flat colour, etc). |
|
|
21
22
|
|
|
23
|
+
> Changed 2026-10-08: `--container-glow-background` defaulted to `white` and the card set no text
|
|
24
|
+
> colour. It now defaults to `var(--theme-surface-subtle)`, with the new
|
|
25
|
+
> `--container-glow-text-colour` defaulting to `var(--theme-text)`. Set
|
|
26
|
+
> `--container-glow-background: white` to keep the old look.
|
|
27
|
+
|
|
22
28
|
```vue
|
|
23
29
|
<ContainerGlow
|
|
24
|
-
style="--container-glow-background: #111; --container-glow-
|
|
30
|
+
style="--container-glow-background: #111; --container-glow-text-colour: #eee; --container-glow-gradient: conic-gradient(from 180deg, #22d3ee, #a855f7, #22d3ee);"
|
|
25
31
|
>
|
|
26
32
|
<template #cta>
|
|
27
33
|
<p>Custom-coloured glow card</p>
|
|
@@ -29,25 +35,40 @@ consuming page's own stylesheet:
|
|
|
29
35
|
</ContainerGlow>
|
|
30
36
|
```
|
|
31
37
|
|
|
32
|
-
|
|
38
|
+
Private tokens, not public API: `--_start`, `--_opacity-active`, `--_gap`, `--_blur`, `--_spread`,
|
|
39
|
+
`--_direction` and `--_gradient`. They're driven by pointer events and the `config` prop; use
|
|
40
|
+
`config` (gap/blur/spread/direction/opacity) or the public tokens above (colour/geometry) instead.
|
|
41
|
+
|
|
42
|
+
## State hooks
|
|
43
|
+
|
|
44
|
+
| Selector | Element |
|
|
45
|
+
|---|---|
|
|
46
|
+
| `.container-glow-wrapper` | Root. A `<ul>` when `tag="li"`, otherwise a `<div>`. Flex row (column with `config.vertical`) that wraps onto new lines. |
|
|
47
|
+
| `.container-glow` | One card per named slot, rendered as the `tag` element. |
|
|
48
|
+
| `.container-glow-glows` | Decorative blurred glow layer inside each card (`aria-hidden`). |
|
|
49
|
+
|
|
50
|
+
> Changed 2026-10-08: the glow layer class was `.glows`; renamed to `.container-glow-glows` so a
|
|
51
|
+
> consumer's own `.glows` class can't collide with it. The wrapper also gained `flex-wrap: wrap`
|
|
52
|
+
> (many cards used to overflow sideways) and renders as a `<ul>` when `tag="li"` (previously an
|
|
53
|
+
> invalid `<li>` inside a `<div>`), with list margin, padding and bullets reset.
|
|
33
54
|
|
|
34
55
|
## The `config` prop (layout and interaction behaviour)
|
|
35
56
|
|
|
36
57
|
Proximity distance, glow spread/blur, wrapper gap, wrapper direction, and the inactive-state
|
|
37
|
-
opacity are **not** CSS tokens
|
|
38
|
-
|
|
39
|
-
|
|
58
|
+
opacity are **not** CSS tokens. They're computed in JavaScript and applied as private custom
|
|
59
|
+
properties, so they're driven entirely by the `config` prop (`ContainerGlowConfig`, importable
|
|
60
|
+
from `srcdev-nuxt-components`):
|
|
40
61
|
|
|
41
62
|
| Field | Type | Default | Description |
|
|
42
63
|
|---|---|---|---|
|
|
43
|
-
| `proximity` | `number` | `40` | Pointer distance (px) at which a card starts glowing. |
|
|
44
|
-
| `spread` | `number` | `80` | Angular spread of the glow (degrees). |
|
|
45
|
-
| `blur` | `number` | `20` | Blur applied to the glow layer (px). |
|
|
46
|
-
| `gap` | `number` | `32` | Gap between cards in the wrapper (px). |
|
|
64
|
+
| `proximity` | `number` | `40` | Pointer distance (px) at which a card starts glowing. Minimum `0`. |
|
|
65
|
+
| `spread` | `number` | `80` | Angular spread of the glow (degrees). Clamped to `0`–`360`. |
|
|
66
|
+
| `blur` | `number` | `20` | Blur applied to the glow layer (px). Minimum `0`. |
|
|
67
|
+
| `gap` | `number` | `32` | Gap between cards in the wrapper (px). Minimum `0`. |
|
|
47
68
|
| `vertical` | `boolean` | `false` | Stack cards vertically instead of horizontally. |
|
|
48
|
-
| `inactiveOpacity` | `number` | `0` | Glow opacity when the pointer isn't nearby. |
|
|
69
|
+
| `inactiveOpacity` | `number` | `0` | Glow opacity when the pointer isn't nearby. Clamped to `0`–`1`. |
|
|
49
70
|
|
|
50
|
-
|
|
71
|
+
Non-finite values (`NaN`, `Infinity`) fall back to the default. All fields are optional:
|
|
51
72
|
|
|
52
73
|
```vue
|
|
53
74
|
<ContainerGlow :config="{ blur: 40, vertical: true }">
|
|
@@ -56,11 +77,9 @@ All fields are optional — pass only the ones you want to change:
|
|
|
56
77
|
</ContainerGlow>
|
|
57
78
|
```
|
|
58
79
|
|
|
59
|
-
---
|
|
60
|
-
|
|
61
80
|
## Named dynamic slots
|
|
62
81
|
|
|
63
|
-
Each named slot renders as one glow card
|
|
82
|
+
Each named slot renders as one glow card. The consumer controls the slot names; there's no
|
|
64
83
|
`itemCount`-style prop:
|
|
65
84
|
|
|
66
85
|
```vue
|
|
@@ -70,23 +89,11 @@ Each named slot renders as one glow card — the consumer controls the slot name
|
|
|
70
89
|
</ContainerGlow>
|
|
71
90
|
```
|
|
72
91
|
|
|
73
|
-
---
|
|
74
|
-
|
|
75
92
|
## Motion
|
|
76
93
|
|
|
77
94
|
The glow's opacity fade (`--container-glow-transition-duration`) is disabled under
|
|
78
95
|
`prefers-reduced-motion: reduce`, so proximity changes are instant rather than animated.
|
|
79
96
|
|
|
80
|
-
---
|
|
81
|
-
|
|
82
|
-
## Internal (non-overridable) custom properties
|
|
83
|
-
|
|
84
|
-
`--_start`, `--_opacity-active`, `--_gap`, `--_blur`, `--_spread`, `--_direction`, and `--_gradient`
|
|
85
|
-
are internal plumbing driven by pointer events and the `config` prop — use `config` (for
|
|
86
|
-
gap/blur/spread/direction/opacity) or the public tokens above (for colour/geometry) instead.
|
|
87
|
-
|
|
88
|
-
---
|
|
89
|
-
|
|
90
97
|
## Local overrides
|
|
91
98
|
|
|
92
99
|
Set the tokens above on an element you own (a page or section class, or a class added with
|
|
@@ -95,8 +102,6 @@ Set the tokens above on an element you own (a page or section class, or a class
|
|
|
95
102
|
`<style scoped>`, tokens set on your element still work, but selectors that reach inside the component need
|
|
96
103
|
`:deep()`. Patterns and examples: `.claude/skills/component-local-style-override.md`.
|
|
97
104
|
|
|
98
|
-
---
|
|
99
|
-
|
|
100
105
|
## Class passthrough
|
|
101
106
|
|
|
102
107
|
Use `styleClassPassthrough` to add classes to the root `.container-glow-wrapper` element.
|
|
@@ -104,4 +109,3 @@ Use `styleClassPassthrough` to add classes to the root `.container-glow-wrapper`
|
|
|
104
109
|
```vue
|
|
105
110
|
<ContainerGlow style-class-passthrough="my-container-glow">...</ContainerGlow>
|
|
106
111
|
```
|
|
107
|
-
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
2
|
+
<component :is="wrapperTag" ref="containerGlowWrapper" class="container-glow-wrapper" :class="elementClasses">
|
|
3
3
|
<component
|
|
4
4
|
:is="tag"
|
|
5
5
|
v-for="(_, name) in $slots"
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
ref="containerGlowItem"
|
|
8
8
|
class="container-glow"
|
|
9
9
|
>
|
|
10
|
-
<div class="glows"></div>
|
|
10
|
+
<div class="container-glow-glows" aria-hidden="true"></div>
|
|
11
11
|
<slot :name="name"></slot>
|
|
12
12
|
</component>
|
|
13
|
-
</
|
|
13
|
+
</component>
|
|
14
14
|
</template>
|
|
15
15
|
|
|
16
16
|
<script setup lang="ts">
|
|
@@ -37,18 +37,22 @@ watch(
|
|
|
37
37
|
}
|
|
38
38
|
);
|
|
39
39
|
|
|
40
|
+
const wrapperTag = computed(() => (props.tag === "li" ? "ul" : "div"));
|
|
41
|
+
|
|
40
42
|
const controller = new AbortController();
|
|
41
43
|
|
|
42
44
|
const containerGlowWrapper = ref<HTMLElement>();
|
|
43
45
|
const containerGlowItem = ref<HTMLElement[]>([]);
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
const clampConfig = (value: number | undefined, fallback: number, min: number, max: number) =>
|
|
48
|
+
typeof value === "number" && Number.isFinite(value) ? Math.min(Math.max(value, min), max) : fallback;
|
|
49
|
+
|
|
46
50
|
const configCache = computed(() => ({
|
|
47
|
-
proximity: props.config.proximity
|
|
48
|
-
inactiveOpacityStr: String(props.config.inactiveOpacity
|
|
49
|
-
gapStr: String(props.config.gap
|
|
50
|
-
blurStr: String(props.config.blur
|
|
51
|
-
spreadStr: String(props.config.spread
|
|
51
|
+
proximity: clampConfig(props.config.proximity, 40, 0, Infinity),
|
|
52
|
+
inactiveOpacityStr: String(clampConfig(props.config.inactiveOpacity, 0, 0, 1)),
|
|
53
|
+
gapStr: String(clampConfig(props.config.gap, 32, 0, Infinity)),
|
|
54
|
+
blurStr: String(clampConfig(props.config.blur, 20, 0, Infinity)),
|
|
55
|
+
spreadStr: String(clampConfig(props.config.spread, 80, 0, 360)),
|
|
52
56
|
direction: props.config.vertical ? "column" : "row",
|
|
53
57
|
}));
|
|
54
58
|
|
|
@@ -164,7 +168,11 @@ onBeforeUnmount(() => {
|
|
|
164
168
|
.container-glow-wrapper {
|
|
165
169
|
display: flex;
|
|
166
170
|
flex-direction: var(--_direction, row);
|
|
171
|
+
flex-wrap: wrap;
|
|
167
172
|
gap: calc(var(--_gap, 32) * 1px);
|
|
173
|
+
margin: 0;
|
|
174
|
+
padding: 0;
|
|
175
|
+
list-style: none;
|
|
168
176
|
|
|
169
177
|
.container-glow {
|
|
170
178
|
& *,
|
|
@@ -185,18 +193,20 @@ onBeforeUnmount(() => {
|
|
|
185
193
|
hsla(0, 0%, 98%, 1) 1turn
|
|
186
194
|
)
|
|
187
195
|
);
|
|
188
|
-
--_opacity-active: v-bind("
|
|
196
|
+
--_opacity-active: v-bind("configCache.inactiveOpacityStr");
|
|
189
197
|
--_start: 0;
|
|
190
198
|
|
|
191
199
|
position: relative;
|
|
192
200
|
|
|
193
201
|
height: 100%;
|
|
194
|
-
background: var(--container-glow-background,
|
|
202
|
+
background: var(--container-glow-background, var(--theme-surface-subtle));
|
|
203
|
+
color: var(--container-glow-text-colour, var(--theme-text));
|
|
195
204
|
padding: var(--container-glow-padding, 2rem);
|
|
196
205
|
aspect-ratio: var(--container-glow-aspect-ratio, 330 / 400);
|
|
197
206
|
border-radius: var(--container-glow-border-radius, 12px);
|
|
198
|
-
min-width: var(--container-glow-min-width, 280px);
|
|
207
|
+
min-width: min(var(--container-glow-min-width, 280px), 100%);
|
|
199
208
|
max-width: var(--container-glow-max-width, 280px);
|
|
209
|
+
overflow-wrap: anywhere;
|
|
200
210
|
display: flex;
|
|
201
211
|
flex-direction: column;
|
|
202
212
|
gap: var(--container-glow-content-gap, 0.25rem);
|
|
@@ -255,7 +265,7 @@ onBeforeUnmount(() => {
|
|
|
255
265
|
mask-composite: intersect;
|
|
256
266
|
}
|
|
257
267
|
|
|
258
|
-
.glows {
|
|
268
|
+
.container-glow-glows {
|
|
259
269
|
pointer-events: none;
|
|
260
270
|
position: absolute;
|
|
261
271
|
inset: 0;
|
|
@@ -288,8 +298,8 @@ onBeforeUnmount(() => {
|
|
|
288
298
|
@media (prefers-reduced-motion: reduce) {
|
|
289
299
|
&::before,
|
|
290
300
|
&::after,
|
|
291
|
-
.glows::before,
|
|
292
|
-
.glows::after {
|
|
301
|
+
.container-glow-glows::before,
|
|
302
|
+
.container-glow-glows::after {
|
|
293
303
|
transition: none;
|
|
294
304
|
}
|
|
295
305
|
}
|