srcdev-nuxt-components 9.1.51 → 9.1.53
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/settings.json +29 -26
- package/.claude/settings.local.json +24 -52
- package/.claude/skills/components/action-menu.md +141 -0
- package/.claude/skills/components/stepper-list.md +52 -18
- package/.claude/skills/css-nesting-conventions.md +121 -0
- package/.claude/skills/index.md +4 -0
- package/.claude/skills/pull-request-description.md +48 -0
- package/.claude/skills/testing-add-unit-test.md +40 -2
- package/.claude/skills/vercel-node-version.md +48 -0
- package/README.md +26 -5
- package/app/components/01.atoms/page-row/PageRow.vue +5 -1
- package/app/components/02.molecules/action-menu/ActionMenu.vue +218 -0
- package/app/components/02.molecules/action-menu/ActionMenuItemCore.vue +123 -0
- package/app/components/02.molecules/action-menu/CONSUMER-STYLING.md +125 -0
- package/app/components/02.molecules/action-menu/stories/ActionMenu.stories.ts +326 -0
- package/app/components/02.molecules/action-menu/tests/ActionMenu.spec.ts +458 -0
- package/app/components/02.molecules/action-menu/tests/ActionMenuItemCore.spec.ts +199 -0
- package/app/components/02.molecules/action-menu/tests/__snapshots__/ActionMenu.spec.ts.snap +28 -0
- package/app/components/02.molecules/action-menu/tests/__snapshots__/ActionMenuItemCore.spec.ts.snap +27 -0
- package/app/components/02.molecules/display-chip/tests/DisplayChip.spec.ts +5 -1
- package/app/components/02.molecules/stepper-list/CONSUMER-STYLING.md +138 -0
- package/app/components/02.molecules/stepper-list/StepperList.vue +50 -24
- package/app/components/03.organisms/image-galleries/slider-gallery/tests/SliderGallery.spec.ts +5 -1
- package/app/components/05.forms/input-button/InputButtonCore.vue +1 -1
- package/app/components/carousels/tests/CarouselFlip.spec.ts +1 -0
- package/app/components/responsive-header/NavigationItems.vue +302 -81
- package/app/components/responsive-header/ResponsiveHeader.vue +360 -56
- package/app/layouts/default.vue +43 -352
- package/app/layouts/site-navigation-demo.vue +36 -34
- package/app/pages/page-hero-highlights.vue +3 -3
- package/app/pages/ui/scroll-reveal-image.vue +3 -3
- package/app/pages/ui/simple-grid.vue +9 -20
- package/modules/colour-scheme.ts +1 -1
- package/nuxt.config.ts +11 -2
- package/package.json +18 -21
package/.claude/settings.json
CHANGED
|
@@ -1,33 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"permissions": {
|
|
3
3
|
"allow": [
|
|
4
|
-
"Bash(
|
|
5
|
-
"Bash(
|
|
6
|
-
"Bash(
|
|
7
|
-
"Bash(
|
|
8
|
-
"Bash(
|
|
9
|
-
"Bash(
|
|
10
|
-
"Bash(
|
|
11
|
-
"Bash(npx vitest run app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-width/tests/LayoutGridByWidth.spec.ts)",
|
|
12
|
-
"Bash(npx vitest run app/components/01.atoms/card/tests/CardCore.spec.ts)",
|
|
13
|
-
"Bash(npx vitest run app/components/image-galleries/slider-gallery/tests/SliderGallery.spec.ts)",
|
|
14
|
-
"Bash(npx vitest run)",
|
|
15
|
-
"Bash(git mv:*)",
|
|
16
|
-
"Bash(npx tsc:*)",
|
|
17
|
-
"Bash(npx vitest:*)",
|
|
18
|
-
"Bash(git show:*)",
|
|
4
|
+
"Bash(grep -v \"^+\\\\s*$\\\\|^-\\\\s*$\")",
|
|
5
|
+
"Bash(python3 -c \"import json,sys; p=json.load\\(sys.stdin\\); print\\('main:', p.get\\('main'\\)\\); exp=p.get\\('exports',{}\\).get\\('.',{}\\); print\\('exports .:', exp\\)\")",
|
|
6
|
+
"Bash(node -e \"const p = require\\('/Users/simoncornforth/websites/nuxt-components/.output/server/node_modules/vue/package.json'\\); console.log\\(JSON.stringify\\(p.exports, null, 2\\)\\)\")",
|
|
7
|
+
"Bash(node .output/server/index.mjs)",
|
|
8
|
+
"Bash(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:3000/)",
|
|
9
|
+
"Bash(kill %1)",
|
|
10
|
+
"Bash(xargs kill -9)",
|
|
19
11
|
"Edit(/.claude/skills/components/**)",
|
|
20
|
-
"Bash(
|
|
21
|
-
"Bash(
|
|
22
|
-
"Bash(
|
|
23
|
-
"Bash(npm
|
|
24
|
-
"Bash(
|
|
25
|
-
"Bash(
|
|
26
|
-
"Bash(node -e
|
|
27
|
-
"Bash(
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
12
|
+
"Bash(command -v nvm)",
|
|
13
|
+
"Bash(command -v fnm)",
|
|
14
|
+
"Bash(command -v volta)",
|
|
15
|
+
"Bash(npm --version)",
|
|
16
|
+
"Bash(python3 -c ' *)",
|
|
17
|
+
"Bash(node -e ' *)",
|
|
18
|
+
"Bash(/usr/local/bin/node -e \"...\")",
|
|
19
|
+
"Bash(which node *)",
|
|
20
|
+
"Read(//usr/local/bin/**)",
|
|
21
|
+
"Read(//Users/simoncornforth/.nvm/versions/**)",
|
|
22
|
+
"Read(//opt/homebrew/**)",
|
|
23
|
+
"Read(//usr/**)",
|
|
24
|
+
"Bash(/usr/local/Cellar/node@24/24.16.0/bin/node -e ' *)",
|
|
25
|
+
"Bash(/usr/local/Cellar/node@24/24.16.0/bin/node --version)",
|
|
26
|
+
"Bash(/usr/local/Cellar/node@24/24.16.0/bin/npm --version)",
|
|
27
|
+
"Bash(git -C /Users/simoncornforth/websites/instepreflexology log --oneline -3)",
|
|
28
|
+
"Bash(git -C /Users/simoncornforth/websites/instepreflexology add package-lock.json .nvmrc)",
|
|
29
|
+
"Bash(git -C /Users/simoncornforth/websites/instepreflexology commit -m ' *)",
|
|
30
|
+
"Bash(git -C /Users/simoncornforth/websites/instepreflexology push)"
|
|
31
|
+
],
|
|
32
|
+
"additionalDirectories": [
|
|
33
|
+
"/Users/simoncornforth/websites/instepreflexology"
|
|
31
34
|
]
|
|
32
35
|
}
|
|
33
36
|
}
|
|
@@ -1,66 +1,38 @@
|
|
|
1
1
|
{
|
|
2
|
-
"hooks": {
|
|
3
|
-
"PostToolUse": [
|
|
4
|
-
{
|
|
5
|
-
"matcher": "Bash",
|
|
6
|
-
"hooks": [
|
|
7
|
-
{
|
|
8
|
-
"type": "command",
|
|
9
|
-
"command": "grep -q '/Users/' .claude/settings.json 2>/dev/null && { echo 'settings.json contains absolute paths — move them to settings.local.json' >&2; exit 1; } || exit 0"
|
|
10
|
-
}
|
|
11
|
-
]
|
|
12
|
-
}
|
|
13
|
-
]
|
|
14
|
-
},
|
|
15
2
|
"permissions": {
|
|
16
3
|
"allow": [
|
|
17
4
|
"WebSearch",
|
|
18
|
-
"mcp__ide__getDiagnostics",
|
|
19
5
|
"WebFetch(domain:drafts.csswg.org)",
|
|
20
|
-
"
|
|
21
|
-
"Bash(
|
|
22
|
-
"Bash(
|
|
6
|
+
"mcp__ide__getDiagnostics",
|
|
7
|
+
"Bash(npm run:*)",
|
|
8
|
+
"Bash(npm install:*)",
|
|
9
|
+
"Bash(npm audit *)",
|
|
10
|
+
"Bash(npm ls *)",
|
|
11
|
+
"Bash(npx vitest:*)",
|
|
12
|
+
"Bash(npx tsc:*)",
|
|
13
|
+
"Bash(npx vue-tsc:*)",
|
|
14
|
+
"Bash(npx nuxi:*)",
|
|
15
|
+
"Bash(git add:*)",
|
|
16
|
+
"Bash(git mv:*)",
|
|
17
|
+
"Bash(git show:*)",
|
|
18
|
+
"Bash(git status *)",
|
|
19
|
+
"Bash(git log:*)",
|
|
20
|
+
"Bash(git diff:*)",
|
|
21
|
+
"Bash(git ls-tree *)",
|
|
22
|
+
"Bash(git commit:*)",
|
|
23
|
+
"Bash(gh release:*)",
|
|
24
|
+
"Edit(/.claude/skills/components/**)",
|
|
25
|
+
"Bash(git -C /Users/simoncornforth/websites/nuxt-components *)",
|
|
23
26
|
"Bash(ls -la /Users/simoncornforth/websites/luxury-locs-by-natasha-nuxt3/.env*)",
|
|
24
|
-
"Bash(
|
|
25
|
-
"Bash(git -C /Users/simoncornforth/websites/nuxt-components log --oneline -5)",
|
|
26
|
-
"Bash(git -C /Users/simoncornforth/websites/nuxt-components show 16ac4ef --stat)",
|
|
27
|
-
"Bash(git -C /Users/simoncornforth/websites/nuxt-components log --oneline -8)",
|
|
28
|
-
"Bash(git -C /Users/simoncornforth/websites/nuxt-components show 5597a15 --stat)",
|
|
29
|
-
"Bash(git -C /Users/simoncornforth/websites/nuxt-components show 5597a15 -- \"*.vue\" \"*.css\")",
|
|
30
|
-
"Bash(ls /Users/simoncornforth/websites/nuxt-components/*.md)",
|
|
31
|
-
"Bash(git -C /Users/simoncornforth/websites/nuxt-components status)",
|
|
32
|
-
"Bash(git -C /Users/simoncornforth/websites/nuxt-components add .claude/skills/components/services-section.md .claude/skills/components/services-section-grid.md .claude/skills/index.md app/components/03.organisms/services/services-grids/ServicesSectionGrid.vue app/components/03.organisms/services/services-section/ServicesSection.vue app/components/03.organisms/services/services-card/stories/ app/components/03.organisms/services/services-card/tests/ app/components/03.organisms/services/services-grids/stories/ app/components/03.organisms/services/services-grids/tests/ app/components/03.organisms/services/services-section/stories/ app/components/03.organisms/services/services-section/tests/)",
|
|
33
|
-
"Bash(git -C /Users/simoncornforth/websites/nuxt-components commit -m ':*)",
|
|
34
|
-
"Bash(git -C /Users/simoncornforth/websites/nuxt-components log --oneline v9.1.22..HEAD)",
|
|
35
|
-
"Bash(git -C /Users/simoncornforth/websites/nuxt-components log --oneline c401cea..HEAD)",
|
|
36
|
-
"Bash(git -C /Users/simoncornforth/websites/nuxt-components show 3058d63 --stat)",
|
|
37
|
-
"Bash(git -C /Users/simoncornforth/websites/nuxt-components show 0f99d7a --stat)",
|
|
38
|
-
"Bash(git -C /Users/simoncornforth/websites/nuxt-components diff app/components/parallax/SectionParallax.vue)",
|
|
39
|
-
"Bash(git -C /Users/simoncornforth/websites/nuxt-components diff HEAD app/components/parallax/SectionParallax.vue)",
|
|
40
|
-
"Bash(git -C /Users/simoncornforth/websites/nuxt-components diff --cached)",
|
|
41
|
-
"Bash(SRCDEV_STANDALONE=true npx vitest --run app/components/02.molecules/samaritan-prompt)",
|
|
42
|
-
"Bash(SRCDEV_STANDALONE=true npx vitest --run app/composables/tests/useCancellableTimer app/components/02.molecules/samaritan-prompt/tests/SamaritanPromptMixed)",
|
|
43
|
-
"Bash(SRCDEV_STANDALONE=true npx vitest --run app/components/02.molecules/samaritan-prompt/tests/SamaritanPromptMixed)",
|
|
44
|
-
"Bash(SRCDEV_STANDALONE=true npx vitest --run app/composables/tests/useCancellableTimer)",
|
|
45
|
-
"Bash(SRCDEV_STANDALONE=true npm run test:run -- --reporter=verbose)",
|
|
46
|
-
"Bash(cp /Users/simoncornforth/websites/nuxt-components/.claude/skills/components/data-grid.md /Users/simoncornforth/websites/nuxt-components/.claude/skills/components/auto-grid.md)",
|
|
47
|
-
"Bash(rm /Users/simoncornforth/websites/nuxt-components/.claude/skills/components/data-grid.md)",
|
|
48
|
-
"Bash(git -C /Users/simoncornforth/websites/nuxt-components log --oneline -10)",
|
|
49
|
-
"Bash(git -C /Users/simoncornforth/websites/nuxt-components log 5a80426..HEAD --oneline)",
|
|
50
|
-
"Bash(git -C /Users/simoncornforth/websites/nuxt-components show 71c276e --stat)",
|
|
51
|
-
"Bash(git -C /Users/simoncornforth/websites/nuxt-components show a1e1641 --stat)"
|
|
27
|
+
"Bash(ls /Users/simoncornforth/websites/nuxt-components/*.md)"
|
|
52
28
|
],
|
|
53
29
|
"additionalDirectories": [
|
|
54
|
-
"/Users/simoncornforth/websites/nuxt-components/app/components/01.atoms/content-wrappers/content-width",
|
|
55
30
|
"/Users/simoncornforth/websites/nuxt-components/.claude/skills",
|
|
56
31
|
"/Users/simoncornforth/websites/nuxt-components/.claude/skills/components",
|
|
57
|
-
"/Users/simoncornforth/websites/nuxt-components/app/components/02.molecules/navigation/site-navigation/tests",
|
|
58
|
-
"/Users/simoncornforth/websites/luxury-locs-by-natasha-nuxt3/app/pages",
|
|
59
|
-
"/Users/simoncornforth/websites/nuxt-components/app/assets/styles/extends-layer",
|
|
60
32
|
"/Users/simoncornforth/websites/nuxt-components/app/components/01.atoms",
|
|
61
|
-
"/
|
|
62
|
-
"/Users/simoncornforth/websites/
|
|
63
|
-
"/
|
|
33
|
+
"/Users/simoncornforth/websites/nuxt-components/app/assets/styles/extends-layer",
|
|
34
|
+
"/Users/simoncornforth/websites/luxury-locs-by-natasha-nuxt3/app/pages",
|
|
35
|
+
"/private/tmp"
|
|
64
36
|
]
|
|
65
37
|
}
|
|
66
38
|
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# ActionMenu
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`ActionMenu` is a trigger-and-popover component that shows a compact ellipsis button (`lucide:ellipsis`).
|
|
6
|
+
Clicking it opens an anchored menu list populated via indexed dynamic slots (`item-{n}`). Each slot
|
|
7
|
+
should contain a single `ActionMenuItemCore` — either a `<button>` (for actions) or a link (for
|
|
8
|
+
navigation). The popover API and CSS anchor positioning handle positioning and dismiss behaviour
|
|
9
|
+
natively; no JavaScript click-outside logic is needed.
|
|
10
|
+
|
|
11
|
+
**Location**: `app/components/02.molecules/action-menu/`
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Components
|
|
16
|
+
|
|
17
|
+
### ActionMenu
|
|
18
|
+
|
|
19
|
+
| Prop | Type | Default | Notes |
|
|
20
|
+
|---|---|---|---|
|
|
21
|
+
| `itemCount` | `number` | `0` | Number of `item-{n}` slots to render. |
|
|
22
|
+
| `label` | `string` | `"Open actions menu"` | Used as `aria-label` on the trigger and `aria-label` on the menu list. |
|
|
23
|
+
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes on the root `<div>`. |
|
|
24
|
+
|
|
25
|
+
**Slots**
|
|
26
|
+
|
|
27
|
+
| Slot | When used |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `item-{n}` | One per item, where `n` is 0-indexed up to `itemCount - 1`. Should contain one `ActionMenuItemCore`. |
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
### ActionMenuItemCore
|
|
34
|
+
|
|
35
|
+
| Prop | Type | Default | Notes |
|
|
36
|
+
|---|---|---|---|
|
|
37
|
+
| `label` | `string` | — | **Required.** Visible text for the row. |
|
|
38
|
+
| `href` | `string` | `undefined` | If set, renders as `<a>` (external) or `NuxtLink` (internal `/…` path). Omit for a `<button>`. |
|
|
39
|
+
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes on the root element. |
|
|
40
|
+
|
|
41
|
+
**Slots**
|
|
42
|
+
|
|
43
|
+
| Slot | Content |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `#icon` | Optional left icon (e.g. `<Icon name="lucide:pencil" />`). Wrapped in `aria-hidden` span. |
|
|
46
|
+
|
|
47
|
+
**Emits**
|
|
48
|
+
|
|
49
|
+
| Event | Payload | Notes |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| `click` | `MouseEvent` | Fired on every click regardless of whether the item is a button or link. |
|
|
52
|
+
|
|
53
|
+
**Notes on routing**
|
|
54
|
+
- Internal paths (`/…`) resolve to `<NuxtLink>` via `resolveComponent`.
|
|
55
|
+
- External URLs or relative paths without a leading `/` render as plain `<a>`.
|
|
56
|
+
- `type="button"` is set automatically on `<button>` elements to prevent accidental form submission.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Basic usage
|
|
61
|
+
|
|
62
|
+
```vue
|
|
63
|
+
<ActionMenu :item-count="3" label="Row actions">
|
|
64
|
+
<template #item-0>
|
|
65
|
+
<ActionMenuItemCore label="Edit" @click="handleEdit">
|
|
66
|
+
<template #icon><Icon name="lucide:pencil" /></template>
|
|
67
|
+
</ActionMenuItemCore>
|
|
68
|
+
</template>
|
|
69
|
+
<template #item-1>
|
|
70
|
+
<ActionMenuItemCore label="View detail" href="/records/123">
|
|
71
|
+
<template #icon><Icon name="lucide:eye" /></template>
|
|
72
|
+
</ActionMenuItemCore>
|
|
73
|
+
</template>
|
|
74
|
+
<template #item-2>
|
|
75
|
+
<ActionMenuItemCore label="Delete" @click="handleDelete">
|
|
76
|
+
<template #icon><Icon name="lucide:trash-2" /></template>
|
|
77
|
+
</ActionMenuItemCore>
|
|
78
|
+
</template>
|
|
79
|
+
</ActionMenu>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Link vs button items
|
|
85
|
+
|
|
86
|
+
| Scenario | Use |
|
|
87
|
+
|---|---|
|
|
88
|
+
| Triggers a JS handler (delete, share, copy…) | Omit `href` — renders as `<button>` |
|
|
89
|
+
| Navigates to an internal Nuxt route | `href="/path"` — renders as `<NuxtLink>` |
|
|
90
|
+
| Navigates to an external URL | `href="https://…"` — renders as `<a>` |
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## CSS token API
|
|
95
|
+
|
|
96
|
+
See `CONSUMER-STYLING.md` in the component folder for the full token reference and override
|
|
97
|
+
examples. Prefer global CSS for action menus — they appear site-wide in tables, cards, and lists.
|
|
98
|
+
|
|
99
|
+
Quick reference:
|
|
100
|
+
|
|
101
|
+
```css
|
|
102
|
+
/* assets/styles/setup/07.components/action-menu.css */
|
|
103
|
+
:root {
|
|
104
|
+
--action-menu-block-distance: 0.6rem;
|
|
105
|
+
--action-menu-trigger-border-radius: 0.4rem;
|
|
106
|
+
--action-menu-trigger-surface-hover: var(--brand-surface-subtle);
|
|
107
|
+
--action-menu-trigger-icon-color: var(--brand-text-muted);
|
|
108
|
+
|
|
109
|
+
--action-menu-popover-background: var(--brand-surface);
|
|
110
|
+
--action-menu-popover-border: 0.1rem solid var(--brand-border);
|
|
111
|
+
--action-menu-popover-border-radius: 0.6rem;
|
|
112
|
+
|
|
113
|
+
--action-menu-item-surface-hover: var(--brand-surface-subtle);
|
|
114
|
+
--action-menu-item-text-color: var(--brand-text);
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Notes
|
|
121
|
+
|
|
122
|
+
- **Popover API + CSS anchor positioning** — the menu uses `popover` attribute and `position-anchor`.
|
|
123
|
+
Both are broadly supported (Chrome 114+, Firefox 125+, Safari 17+). No polyfill is included.
|
|
124
|
+
- **Auto-close** — clicking any `<li>` row fires `hidePopover()` on the menu. The `ActionMenuItemCore`
|
|
125
|
+
emitting `click` triggers normally before the menu closes.
|
|
126
|
+
- **Focus management** — on open the `toggle` event fires `handleToggle`, which moves focus to the
|
|
127
|
+
first `[role="menuitem"]` inside the popover.
|
|
128
|
+
- **Keyboard navigation `currentIndex === -1` guard** — `handleKeydown` computes the current
|
|
129
|
+
position via `items.indexOf(document.activeElement)`. When focus is outside the menu this returns
|
|
130
|
+
`-1`. Always guard explicitly before applying wrap-around math: `ArrowDown` should focus
|
|
131
|
+
`items[0]`; `ArrowUp` should focus `items[items.length - 1]`. Without the guard, the modulo
|
|
132
|
+
formula gives `items[n-2]` for `ArrowUp` — the second-to-last item instead of the last.
|
|
133
|
+
- **Right-aligned by default** — the menu's right edge aligns with the trigger's right edge
|
|
134
|
+
(`right: anchor(right)`). Flips above the trigger near the bottom of the viewport
|
|
135
|
+
(`position-try-fallbacks: flip-block`).
|
|
136
|
+
- **`anchorName` format** — internally generated as `--action-menu-anchor-{id}` (a valid CSS
|
|
137
|
+
`<dashed-ident>`). Set via a CSS custom property on the root element so both the trigger's
|
|
138
|
+
`anchor-name` and the popover's `position-anchor` can reference the same value.
|
|
139
|
+
- **Dynamic slots stability** — `item-{n}` slots enforce that only `ActionMenuItemCore` content
|
|
140
|
+
enters the list; arbitrary HTML inside the popover is not supported and will break the ARIA
|
|
141
|
+
`menu` / `menuitem` pattern.
|
|
@@ -94,7 +94,7 @@ Counter bubbles are rendered automatically via CSS — no `#indicator-*` slots n
|
|
|
94
94
|
</StepperList>
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
Custom indicator SVGs should use the `indicator-icon` class — it applies `color: var(--stepper-list-icon)` and sizes the icon to match `indicatorSize`.
|
|
97
|
+
Custom indicator SVGs should use the `indicator-icon` class — it applies `color: var(--stepper-list-icon-color)` and sizes the icon to match `indicatorSize`.
|
|
98
98
|
|
|
99
99
|
### Mixed — custom icons for completed steps, counters for pending
|
|
100
100
|
|
|
@@ -146,21 +146,44 @@ Custom indicator SVGs should use the `indicator-icon` class — it applies `colo
|
|
|
146
146
|
|
|
147
147
|
## CSS custom properties
|
|
148
148
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
|
156
|
-
|
|
157
|
-
| `--stepper-list-
|
|
158
|
-
| `--stepper-list-
|
|
159
|
-
| `--stepper-list-counter-
|
|
160
|
-
| `--stepper-list-counter-
|
|
161
|
-
| `--stepper-list-counter-
|
|
162
|
-
| `--stepper-list-connector-
|
|
163
|
-
|
|
149
|
+
All `--stepper-list-*` tokens are the stable consumer override surface. See
|
|
150
|
+
[CONSUMER-STYLING.md](../../../app/components/02.molecules/stepper-list/CONSUMER-STYLING.md)
|
|
151
|
+
for full examples (global theming, page-scoped, and per-instance overrides).
|
|
152
|
+
|
|
153
|
+
### Layout & sizing
|
|
154
|
+
|
|
155
|
+
| Token | Default | Controls |
|
|
156
|
+
|---|---|---|
|
|
157
|
+
| `--stepper-list-padding-block` | `1.2rem` | Vertical padding for each list item |
|
|
158
|
+
| `--stepper-list-gap` | `2.2rem` | Horizontal gap between indicator and content |
|
|
159
|
+
| `--stepper-list-counter-size` | prop `indicatorSize` (`3rem`) | Counter bubble / icon width and height |
|
|
160
|
+
| `--stepper-list-counter-font-size` | `1.4rem` | Counter number font size |
|
|
161
|
+
| `--stepper-list-counter-font-weight` | `600` | Counter number font weight |
|
|
162
|
+
| `--stepper-list-connector-width` | `0.2rem` | Connector line thickness |
|
|
163
|
+
|
|
164
|
+
### Colours
|
|
165
|
+
|
|
166
|
+
| Token | Default | Controls |
|
|
167
|
+
|---|---|---|
|
|
168
|
+
| `--stepper-list-connector-color` | `currentColor` | Connector line colour |
|
|
169
|
+
| `--stepper-list-icon-color` | `currentColor` | Icon colour for custom `indicator-icon` elements |
|
|
170
|
+
|
|
171
|
+
### Per-variant tokens
|
|
172
|
+
|
|
173
|
+
| Token | Default | Controls |
|
|
174
|
+
|---|---|---|
|
|
175
|
+
| `--stepper-list-counter-circle-background` | `transparent` | Circle bubble fill |
|
|
176
|
+
| `--stepper-list-counter-circle-text` | `currentColor` | Circle counter number colour |
|
|
177
|
+
| `--stepper-list-counter-circle-border` | `currentColor` | Circle bubble border colour |
|
|
178
|
+
| `--stepper-list-counter-circle-border-radius` | `100vw` | Circle bubble corner rounding |
|
|
179
|
+
| `--stepper-list-counter-disc-background` | `transparent` | Disc bubble fill |
|
|
180
|
+
| `--stepper-list-counter-disc-text` | `currentColor` | Disc counter number colour |
|
|
181
|
+
| `--stepper-list-counter-disc-border` | `transparent` | Disc bubble border colour |
|
|
182
|
+
| `--stepper-list-counter-disc-border-radius` | `100vw` | Disc bubble corner rounding |
|
|
183
|
+
| `--stepper-list-counter-square-background` | `transparent` | Square bubble fill |
|
|
184
|
+
| `--stepper-list-counter-square-text` | `currentColor` | Square counter number colour |
|
|
185
|
+
| `--stepper-list-counter-square-border` | `transparent` | Square bubble border colour |
|
|
186
|
+
| `--stepper-list-counter-square-border-radius` | `0.25rem` | Square bubble corner rounding |
|
|
164
187
|
|
|
165
188
|
---
|
|
166
189
|
|
|
@@ -192,23 +215,34 @@ See [component-local-style-override.md](../component-local-style-override.md) fo
|
|
|
192
215
|
─────────────────────────────────────────────────────────────────── */
|
|
193
216
|
.stepper-list {
|
|
194
217
|
&.my-stepper {
|
|
218
|
+
/* Layout & sizing */
|
|
219
|
+
/* --stepper-list-padding-block: 1.2rem; */
|
|
220
|
+
/* --stepper-list-gap: 2.2rem; */
|
|
221
|
+
/* --stepper-list-counter-size: 3rem; */
|
|
222
|
+
/* --stepper-list-counter-font-size: 1.4rem; */
|
|
223
|
+
/* --stepper-list-counter-font-weight: 600; */
|
|
224
|
+
/* --stepper-list-connector-width: 0.2rem; */
|
|
225
|
+
|
|
195
226
|
/* Counter bubble — disc variant */
|
|
196
227
|
/* --stepper-list-counter-disc-background: var(--brand-primary); */
|
|
197
228
|
/* --stepper-list-counter-disc-text: white; */
|
|
198
229
|
/* --stepper-list-counter-disc-border: transparent; */
|
|
230
|
+
/* --stepper-list-counter-disc-border-radius: 100vw; */
|
|
199
231
|
|
|
200
232
|
/* Counter bubble — circle variant */
|
|
201
233
|
/* --stepper-list-counter-circle-background: transparent; */
|
|
202
234
|
/* --stepper-list-counter-circle-text: var(--brand-primary); */
|
|
203
235
|
/* --stepper-list-counter-circle-border: var(--brand-primary); */
|
|
236
|
+
/* --stepper-list-counter-circle-border-radius: 100vw; */
|
|
204
237
|
|
|
205
238
|
/* Counter bubble — square variant */
|
|
206
239
|
/* --stepper-list-counter-square-background: var(--brand-primary); */
|
|
207
240
|
/* --stepper-list-counter-square-text: white; */
|
|
208
241
|
/* --stepper-list-counter-square-border: transparent; */
|
|
242
|
+
/* --stepper-list-counter-square-border-radius: 0.25rem; */
|
|
209
243
|
|
|
210
244
|
/* Custom indicator icon colour */
|
|
211
|
-
/* --stepper-list-icon: var(--brand-primary); */
|
|
245
|
+
/* --stepper-list-icon-color: var(--brand-primary); */
|
|
212
246
|
|
|
213
247
|
/* Connector line */
|
|
214
248
|
/* --stepper-list-connector-color: var(--brand-primary); */
|
|
@@ -223,5 +257,5 @@ See [component-local-style-override.md](../component-local-style-override.md) fo
|
|
|
223
257
|
|
|
224
258
|
- Always keep `itemCount` in sync with the number of `#item-*` slots you provide — mismatches will render empty `<li>` rows.
|
|
225
259
|
- Slot names are **zero-indexed** — `#item-0` not `#item-1`.
|
|
226
|
-
- The `indicator-icon` class is defined inside the component CSS and sizes the SVG to `var(--_counter-size)` (driven by `indicatorSize`). Always add it to custom SVGs.
|
|
260
|
+
- The `indicator-icon` class is defined inside the component CSS and sizes the SVG to `var(--_counter-size)` (driven by `indicatorSize` or `--stepper-list-counter-size`). Always add it to custom SVGs.
|
|
227
261
|
- Auto-imported in Nuxt — no manual import needed.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# CSS Nesting Conventions
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This project uses **native CSS nesting** (W3C CSS Nesting spec), not Sass/SCSS. They look similar but behave differently in one important way: native CSS does not support BEM-style `&` concatenation for child element selectors.
|
|
6
|
+
|
|
7
|
+
## The key rule
|
|
8
|
+
|
|
9
|
+
> `&` must be followed by a combinator (space, `>`, `~`, `+`) or a selector starting with `.`, `#`, `:`, `[`, or `*`. It **cannot** be followed by a bare identifier or `__` prefix.
|
|
10
|
+
|
|
11
|
+
## ❌ What NOT to do — Sass-style BEM concatenation
|
|
12
|
+
|
|
13
|
+
```css
|
|
14
|
+
/* Sass/SCSS — does NOT work in native CSS */
|
|
15
|
+
.demo-controls {
|
|
16
|
+
padding: 1.6rem;
|
|
17
|
+
|
|
18
|
+
&__heading { /* ← Invalid native CSS — esbuild converts to :is(__heading) */
|
|
19
|
+
font-size: 1.1rem;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
&__fields { /* ← Invalid native CSS */
|
|
23
|
+
display: flex;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Why it silently fails**: esbuild converts `&__heading` to `:is(__heading)`, which tries to match an HTML element named `__heading`. No such element exists, so the styles are never applied. There's no error — just missing styles.
|
|
29
|
+
|
|
30
|
+
The build will show this warning:
|
|
31
|
+
```
|
|
32
|
+
▲ [WARNING] Cannot use type selector "__heading" directly after nesting selector "&" [css-syntax-error]
|
|
33
|
+
CSS nesting syntax does not allow the "&" selector to come before a type selector.
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## ✅ Correct patterns
|
|
37
|
+
|
|
38
|
+
### Nested descendant (preferred for BEM child elements)
|
|
39
|
+
|
|
40
|
+
```css
|
|
41
|
+
.demo-controls {
|
|
42
|
+
padding: 1.6rem;
|
|
43
|
+
|
|
44
|
+
& .demo-controls__heading { /* space + full class name */
|
|
45
|
+
font-size: 1.1rem;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
& .demo-controls__fields {
|
|
49
|
+
display: flex;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Flat top-level rules (also valid — avoids repetition for deeply nested structures)
|
|
55
|
+
|
|
56
|
+
```css
|
|
57
|
+
.demo-controls {
|
|
58
|
+
padding: 1.6rem;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.demo-controls__heading {
|
|
62
|
+
font-size: 1.1rem;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.demo-controls__fields {
|
|
66
|
+
display: flex;
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Same-element modifier (this IS valid)
|
|
71
|
+
|
|
72
|
+
```css
|
|
73
|
+
/* & followed by a class — matches the same element that also has this class */
|
|
74
|
+
.button {
|
|
75
|
+
background: blue;
|
|
76
|
+
|
|
77
|
+
&.button--large { /* ← valid: & immediately followed by . */
|
|
78
|
+
padding: 2rem;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
&:hover { /* ← valid: & immediately followed by : */
|
|
82
|
+
background: darkblue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
&[disabled] { /* ← valid: & immediately followed by [ */
|
|
86
|
+
opacity: 0.5;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Pseudo-elements and pseudo-classes
|
|
92
|
+
|
|
93
|
+
```css
|
|
94
|
+
.component {
|
|
95
|
+
&::before { content: ""; } /* ✅ valid */
|
|
96
|
+
&::after { content: ""; } /* ✅ valid */
|
|
97
|
+
&:focus { outline: auto; } /* ✅ valid */
|
|
98
|
+
&:not(.active) { opacity: 0.5; } /* ✅ valid */
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Media / container queries inside a rule
|
|
103
|
+
|
|
104
|
+
```css
|
|
105
|
+
.component {
|
|
106
|
+
grid-template-columns: 1fr;
|
|
107
|
+
|
|
108
|
+
@media (width >= 768px) {
|
|
109
|
+
grid-template-columns: 1fr 2fr; /* ✅ valid — query wraps the property */
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Spot-check during review
|
|
115
|
+
|
|
116
|
+
If you see `&__` or `&-` in a `.vue` `<style lang="css">` block, it's Sass syntax and will not work. Convert it to `& .full-class-name` or lift it to a top-level rule.
|
|
117
|
+
|
|
118
|
+
## Related
|
|
119
|
+
|
|
120
|
+
- `CLAUDE.md` → Styling Methodology section
|
|
121
|
+
- `css-grid-max-width-gutters.md` — example of correct native nesting in a grid utility
|
package/.claude/skills/index.md
CHANGED
|
@@ -33,6 +33,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
33
33
|
├── component-dynamic-slots.md — named dynamic slots ($slots iteration) vs indexed dynamic slots (itemCount pattern)
|
|
34
34
|
├── component-local-style-override.md — styleClassPassthrough + scoped style block for per-usage visual customisation
|
|
35
35
|
├── component-prop-driven-container-layout.md — vary CSS grid layout inside @container queries using data-* attribute selectors
|
|
36
|
+
├── css-nesting-conventions.md — native CSS nesting rules: why &__child Sass BEM concatenation silently breaks, correct patterns
|
|
36
37
|
├── css-grid-max-width-gutters.md — cap a centre grid column width by growing gutters, with start/center alignment variants
|
|
37
38
|
├── css-animation-utilities.md — scroll-driven animation utility classes: scroller-x (carousel), entry-zoom-reveal, entry-slide-in, entry-exit-blur, auto-rotate
|
|
38
39
|
├── component-aria-landmark.md — useAriaLabelledById composable: aria-labelledby for section/main/article/aside tags
|
|
@@ -40,10 +41,12 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
40
41
|
├── component-inline-action-button.md — InputButtonCore variant="inline" pattern for buttons embedded in custom input wrappers
|
|
41
42
|
├── vue-video-autoplay.md — autoplay on client-side navigation: use <source> child (not :src on <video>), :key, and explicit v.load()
|
|
42
43
|
├── icon-sets.md — icon set packages required by layer components, FOUC prevention, component→package map
|
|
44
|
+
├── vercel-node-version.md — .nvmrc pinned to Node 24 is required; without it Vercel uses npm 10 which crashes on versionless optional stubs
|
|
43
45
|
├── robots-env-aware.md — @nuxtjs/robots: allow crawling on prod domain only, block on preview/staging via env var
|
|
44
46
|
├── new-app-scaffold.md — scaffold a new Nuxt consumer app extending this layer (package.json, nuxt.config, app structure, CLAUDE.md)
|
|
45
47
|
├── qa-panel.md — collapsible dev-only panel for toggling component props live on a page (demo pages and consuming apps)
|
|
46
48
|
├── release-notes.md — produce release notes as a fenced markdown block from git log
|
|
49
|
+
├── pull-request-description.md — produce a PR description as a fenced markdown block from git diff vs main
|
|
47
50
|
├── composable-canonical-url.md — useCanonicalUrl: set <link rel="canonical"> from runtimeConfig.public.canonicalHost; layout setup, node types
|
|
48
51
|
├── composable-whatsapp.md — useWhatsApp: open pre-filled wa.me link from form payload; runtime config, security, usage
|
|
49
52
|
├── composable-zod-validation.md — useZodValidation: schema-driven form validation, error binding, submit flow, API error push
|
|
@@ -82,6 +85,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
82
85
|
├── auto-grid.md — AutoGrid: auto-fit responsive grid, $slots iteration, --auto-grid-min-col-size/gap tokens, semantic tag + aria
|
|
83
86
|
├── display-avatar.md — DisplayAvatar: circular avatar with image/initials fallback, size variants, chip badge, icon slot, styleClassPassthrough
|
|
84
87
|
├── card-core.md — CardCore: generic card container, dynamic named slots as rows, 4 variants, blurred backdrop layer, full CSS token API
|
|
88
|
+
├── action-menu.md — ActionMenu + ActionMenuItemCore: ellipsis trigger + anchored popover menu, indexed item-{n} slots, link/button items, full CSS token API
|
|
85
89
|
├── display-dialog.md — DisplayDialog: native <dialog> overlay, 5 variants (dialog/modal/confirm/alert/fullscreen), useDialogControls integration, CSS token API
|
|
86
90
|
├── display-chip.md — DisplayChip: status indicator chip overlay, CSS trig positioning, circle/square shapes, status colours, icon/label content
|
|
87
91
|
├── display-pill.md — DisplayPill: pill/badge label with icon slot, 6 variants, 3 sizes, reversible order, full CSS token API for border/outline/colour
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Pull Request Description
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
When asked to create a pull request description, produce a fenced markdown code block (` ```markdown `) so the content can be copied and pasted directly into GitHub's PR body field without formatting being stripped.
|
|
6
|
+
|
|
7
|
+
## Steps
|
|
8
|
+
|
|
9
|
+
### 1. Identify the base branch
|
|
10
|
+
|
|
11
|
+
Run `git log --oneline main..HEAD` to list all commits on the current branch since it diverged from `main`.
|
|
12
|
+
|
|
13
|
+
### 2. Review what changed
|
|
14
|
+
|
|
15
|
+
Run `git diff main...HEAD --stat` for a file-level summary, then `git diff main...HEAD` for the full diff. Focus on intent, not just mechanics.
|
|
16
|
+
|
|
17
|
+
### 3. Produce a fenced markdown block
|
|
18
|
+
|
|
19
|
+
Always wrap the output in a ` ```markdown ` code fence — never render it as plain markdown.
|
|
20
|
+
|
|
21
|
+
## Format
|
|
22
|
+
|
|
23
|
+
```markdown
|
|
24
|
+
## Summary
|
|
25
|
+
|
|
26
|
+
One or two sentences explaining what this PR does and why. No bullet points here — write it as prose.
|
|
27
|
+
|
|
28
|
+
## Changes
|
|
29
|
+
|
|
30
|
+
- **`FileOrComponentName`** — what changed and why
|
|
31
|
+
- Keep each bullet to one line where possible
|
|
32
|
+
|
|
33
|
+
## Testing
|
|
34
|
+
|
|
35
|
+
- How the change was verified (unit tests, manual check, build passing, etc.)
|
|
36
|
+
- Note any areas that couldn't be covered automatically
|
|
37
|
+
|
|
38
|
+
## Notes
|
|
39
|
+
|
|
40
|
+
Any caveats, follow-up tickets, or decisions worth flagging for reviewers (optional — omit if nothing to say).
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Notes
|
|
44
|
+
|
|
45
|
+
- Only include sections that have content — omit empty headings (especially `Notes` if there's nothing to flag)
|
|
46
|
+
- Lead with the user-facing or functional change; CSS/test/doc tidy-ups can be secondary bullets
|
|
47
|
+
- Keep the tone factual — describe what changed, not the effort involved
|
|
48
|
+
- Do not include "Co-Authored-By" or other git trailer lines — those belong in the commit message, not the PR body
|