srcdev-nuxt-components 9.1.50 → 9.1.52
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 +8 -27
- 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 +3 -0
- package/.claude/skills/pull-request-description.md +48 -0
- package/.claude/skills/testing-add-unit-test.md +40 -2
- package/README.md +26 -5
- 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/navigation/tab-navigation/TabNavigation.vue +2 -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 +17 -16
package/app/components/02.molecules/action-menu/tests/__snapshots__/ActionMenuItemCore.spec.ts.snap
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`ActionMenuItemCore > Snapshots > as external link 1`] = `
|
|
4
|
+
"<a href="https://example.com" class="action-menu-item-core" role="menuitem">
|
|
5
|
+
<!--v-if--><span class="action-menu-item-label">Edit item</span><span class="action-menu-item-arrow" aria-hidden="true"><span class="iconify i-lucide:arrow-right action-menu-item-arrow-icon" aria-hidden="true"></span></span>
|
|
6
|
+
</a>"
|
|
7
|
+
`;
|
|
8
|
+
|
|
9
|
+
exports[`ActionMenuItemCore > Snapshots > as internal link 1`] = `
|
|
10
|
+
"<a href="/about" class="action-menu-item-core" role="menuitem">
|
|
11
|
+
<!--v-if--><span class="action-menu-item-label">Edit item</span><span class="action-menu-item-arrow" aria-hidden="true"><span class="iconify i-lucide:arrow-right action-menu-item-arrow-icon" aria-hidden="true"></span></span>
|
|
12
|
+
</a>"
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
exports[`ActionMenuItemCore > Snapshots > default button (no href) 1`] = `
|
|
16
|
+
"<button type="button" class="action-menu-item-core" role="menuitem">
|
|
17
|
+
<!--v-if--><span class="action-menu-item-label">Edit item</span><span class="action-menu-item-arrow" aria-hidden="true"><span class="iconify i-lucide:arrow-right action-menu-item-arrow-icon" aria-hidden="true"></span></span>
|
|
18
|
+
</button>"
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
exports[`ActionMenuItemCore > Snapshots > with icon slot 1`] = `"<button type="button" class="action-menu-item-core" role="menuitem"><span class="action-menu-item-icon" aria-hidden="true"><span data-testid="test-icon">⚡</span></span><span class="action-menu-item-label">Edit item</span><span class="action-menu-item-arrow" aria-hidden="true"><span class="iconify i-lucide:arrow-right action-menu-item-arrow-icon" aria-hidden="true"></span></span></button>"`;
|
|
22
|
+
|
|
23
|
+
exports[`ActionMenuItemCore > Snapshots > with styleClassPassthrough 1`] = `
|
|
24
|
+
"<button type="button" class="action-menu-item-core custom-class another-class" role="menuitem">
|
|
25
|
+
<!--v-if--><span class="action-menu-item-label">Edit item</span><span class="action-menu-item-arrow" aria-hidden="true"><span class="iconify i-lucide:arrow-right action-menu-item-arrow-icon" aria-hidden="true"></span></span>
|
|
26
|
+
</button>"
|
|
27
|
+
`;
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from "vitest";
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
2
2
|
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
3
3
|
import DisplayChip from "../DisplayChip.vue";
|
|
4
4
|
|
|
5
5
|
describe("DisplayChip", () => {
|
|
6
|
+
afterEach(() => {
|
|
7
|
+
vi.restoreAllMocks();
|
|
8
|
+
});
|
|
9
|
+
|
|
6
10
|
// ─── Mount ───────────────────────────────────────────────────────────────
|
|
7
11
|
|
|
8
12
|
it("mounts without error", async () => {
|
|
@@ -139,7 +139,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
139
139
|
anchorScrollOffset: undefined,
|
|
140
140
|
});
|
|
141
141
|
|
|
142
|
-
const { isCollapsed, isLoaded, isMenuOpen, isActiveItem, toggleMenu, closeMenu } =
|
|
142
|
+
const { navRef, navListRef, isCollapsed, isLoaded, isMenuOpen, isActiveItem, toggleMenu, closeMenu } =
|
|
143
|
+
useNavCollapse("tab-nav-loaded");
|
|
143
144
|
|
|
144
145
|
const { handleNavClick, activeHash } = useAnchorScroll({ offset: props.anchorScrollOffset });
|
|
145
146
|
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# StepperList — Consumer Styling Guide
|
|
2
|
+
|
|
3
|
+
## Public token API
|
|
4
|
+
|
|
5
|
+
All `--stepper-list-*` tokens are the stable override surface. Set them globally in a theme
|
|
6
|
+
file, scoped to a page wrapper, or per-instance via `styleClassPassthrough`.
|
|
7
|
+
|
|
8
|
+
### Layout & sizing
|
|
9
|
+
|
|
10
|
+
| Token | Default | Controls |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| `--stepper-list-padding-block` | `1.2rem` | Vertical padding for each list item |
|
|
13
|
+
| `--stepper-list-gap` | `2.2rem` | Horizontal gap between indicator and content |
|
|
14
|
+
| `--stepper-list-counter-size` | prop `indicatorSize` (`3rem`) | Width and height of the counter bubble or custom icon |
|
|
15
|
+
| `--stepper-list-counter-font-size` | `1.4rem` | Counter number font size |
|
|
16
|
+
| `--stepper-list-counter-font-weight` | `600` | Counter number font weight |
|
|
17
|
+
| `--stepper-list-connector-width` | `0.2rem` | Connector line thickness |
|
|
18
|
+
|
|
19
|
+
### Colours
|
|
20
|
+
|
|
21
|
+
| Token | Default | Controls |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| `--stepper-list-connector-color` | `currentColor` | Connector line colour |
|
|
24
|
+
| `--stepper-list-icon-color` | `currentColor` | Icon colour for custom `indicator-icon` elements |
|
|
25
|
+
|
|
26
|
+
### Circle indicator
|
|
27
|
+
|
|
28
|
+
| Token | Default | Controls |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| `--stepper-list-counter-circle-background` | `transparent` | Bubble fill |
|
|
31
|
+
| `--stepper-list-counter-circle-text` | `currentColor` | Counter number colour |
|
|
32
|
+
| `--stepper-list-counter-circle-border` | `currentColor` | Bubble border colour |
|
|
33
|
+
| `--stepper-list-counter-circle-border-radius` | `100vw` | Bubble corner rounding |
|
|
34
|
+
|
|
35
|
+
### Disc indicator
|
|
36
|
+
|
|
37
|
+
| Token | Default | Controls |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| `--stepper-list-counter-disc-background` | `transparent` | Bubble fill |
|
|
40
|
+
| `--stepper-list-counter-disc-text` | `currentColor` | Counter number colour |
|
|
41
|
+
| `--stepper-list-counter-disc-border` | `transparent` | Bubble border colour |
|
|
42
|
+
| `--stepper-list-counter-disc-border-radius` | `100vw` | Bubble corner rounding |
|
|
43
|
+
|
|
44
|
+
### Square indicator
|
|
45
|
+
|
|
46
|
+
| Token | Default | Controls |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| `--stepper-list-counter-square-background` | `transparent` | Bubble fill |
|
|
49
|
+
| `--stepper-list-counter-square-text` | `currentColor` | Counter number colour |
|
|
50
|
+
| `--stepper-list-counter-square-border` | `transparent` | Bubble border colour |
|
|
51
|
+
| `--stepper-list-counter-square-border-radius` | `0.25rem` | Bubble corner rounding |
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Global theming — recommended approach
|
|
56
|
+
|
|
57
|
+
Create `assets/styles/setup/07.components/stepper-list.css` in the consuming app and set
|
|
58
|
+
tokens on `:root`. This applies to every `StepperList` across the site.
|
|
59
|
+
|
|
60
|
+
```css
|
|
61
|
+
/* assets/styles/setup/07.components/stepper-list.css */
|
|
62
|
+
:root {
|
|
63
|
+
--stepper-list-counter-disc-background: var(--brand-primary);
|
|
64
|
+
--stepper-list-counter-disc-text: var(--brand-primary-contrast);
|
|
65
|
+
--stepper-list-counter-disc-border: transparent;
|
|
66
|
+
|
|
67
|
+
--stepper-list-counter-circle-text: var(--brand-primary);
|
|
68
|
+
--stepper-list-counter-circle-border: var(--brand-primary);
|
|
69
|
+
|
|
70
|
+
--stepper-list-counter-square-background: var(--brand-primary);
|
|
71
|
+
--stepper-list-counter-square-text: var(--brand-primary-contrast);
|
|
72
|
+
--stepper-list-counter-square-border: transparent;
|
|
73
|
+
|
|
74
|
+
--stepper-list-connector-color: var(--brand-border);
|
|
75
|
+
--stepper-list-icon-color: var(--brand-primary);
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Page-scoped overrides
|
|
82
|
+
|
|
83
|
+
Override tokens for a specific section by scoping them under the page or layout wrapper.
|
|
84
|
+
No `:deep()` is required (component styles are unscoped).
|
|
85
|
+
|
|
86
|
+
```css
|
|
87
|
+
/* In the consuming page's unscoped <style> block */
|
|
88
|
+
.how-it-works {
|
|
89
|
+
.stepper-list {
|
|
90
|
+
--stepper-list-padding-block: 2rem;
|
|
91
|
+
--stepper-list-gap: 3.2rem;
|
|
92
|
+
--stepper-list-counter-size: 4rem;
|
|
93
|
+
--stepper-list-counter-font-size: 1.6rem;
|
|
94
|
+
--stepper-list-connector-color: var(--brand-border-subtle);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Per-instance overrides via styleClassPassthrough
|
|
102
|
+
|
|
103
|
+
Use sparingly — prefer global or page-scoped CSS. When a single instance needs a distinct
|
|
104
|
+
visual style, pass a modifier class:
|
|
105
|
+
|
|
106
|
+
```vue
|
|
107
|
+
<StepperList
|
|
108
|
+
:style-class-passthrough="['completed-steps']"
|
|
109
|
+
indicator-variant="circle"
|
|
110
|
+
:item-count="3"
|
|
111
|
+
>
|
|
112
|
+
...
|
|
113
|
+
</StepperList>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```css
|
|
117
|
+
.stepper-list.completed-steps {
|
|
118
|
+
--stepper-list-counter-circle-background: var(--color-success);
|
|
119
|
+
--stepper-list-counter-circle-text: white;
|
|
120
|
+
--stepper-list-counter-circle-border: transparent;
|
|
121
|
+
--stepper-list-connector-color: var(--color-success);
|
|
122
|
+
--stepper-list-icon-color: var(--color-success);
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Notes
|
|
129
|
+
|
|
130
|
+
- `--stepper-list-counter-size` takes precedence over the `indicatorSize` prop. Use the prop
|
|
131
|
+
for programmatic/template control; use the CSS token for pure-CSS theme overrides.
|
|
132
|
+
- The connector line is positioned by JS (`ResizeObserver`) on each `<li>` as
|
|
133
|
+
`--_connector-top` and `--_connector-height`. These are internal — do not set them.
|
|
134
|
+
- `--stepper-list-connector-color` defaults to `currentColor`, inheriting from the nearest
|
|
135
|
+
ancestor's text colour. Set it explicitly when that behaviour is undesirable.
|
|
136
|
+
- All border tokens (`--stepper-list-counter-*-border`) control the **colour** only — the
|
|
137
|
+
border is always `2px solid`. Override the border shorthand directly on `.stepper-list li
|
|
138
|
+
.stepper-list__indicator-counter::before` if you need a different width.
|
|
@@ -100,9 +100,35 @@ watch(
|
|
|
100
100
|
<style lang="css">
|
|
101
101
|
@layer components {
|
|
102
102
|
.stepper-list {
|
|
103
|
-
|
|
104
|
-
--
|
|
105
|
-
--
|
|
103
|
+
/* Layout */
|
|
104
|
+
--_padding-block: var(--stepper-list-padding-block, 1.2rem);
|
|
105
|
+
--_gap: var(--stepper-list-gap, 2.2rem);
|
|
106
|
+
--_counter-size: var(--stepper-list-counter-size, v-bind(indicatorSize));
|
|
107
|
+
--_counter-font-size: var(--stepper-list-counter-font-size, 1.4rem);
|
|
108
|
+
--_counter-font-weight: var(--stepper-list-counter-font-weight, 600);
|
|
109
|
+
--_connector-width: var(--stepper-list-connector-width, 0.2rem);
|
|
110
|
+
|
|
111
|
+
/* Colours */
|
|
112
|
+
--_connector-color: var(--stepper-list-connector-color, currentColor);
|
|
113
|
+
--_icon-color: var(--stepper-list-icon-color, currentColor);
|
|
114
|
+
|
|
115
|
+
/* Circle indicator */
|
|
116
|
+
--_counter-circle-background: var(--stepper-list-counter-circle-background, transparent);
|
|
117
|
+
--_counter-circle-text: var(--stepper-list-counter-circle-text, currentColor);
|
|
118
|
+
--_counter-circle-border-color: var(--stepper-list-counter-circle-border, currentColor);
|
|
119
|
+
--_counter-circle-border-radius: var(--stepper-list-counter-circle-border-radius, 100vw);
|
|
120
|
+
|
|
121
|
+
/* Disc indicator */
|
|
122
|
+
--_counter-disc-background: var(--stepper-list-counter-disc-background, transparent);
|
|
123
|
+
--_counter-disc-text: var(--stepper-list-counter-disc-text, currentColor);
|
|
124
|
+
--_counter-disc-border-color: var(--stepper-list-counter-disc-border, transparent);
|
|
125
|
+
--_counter-disc-border-radius: var(--stepper-list-counter-disc-border-radius, 100vw);
|
|
126
|
+
|
|
127
|
+
/* Square indicator */
|
|
128
|
+
--_counter-square-background: var(--stepper-list-counter-square-background, transparent);
|
|
129
|
+
--_counter-square-text: var(--stepper-list-counter-square-text, currentColor);
|
|
130
|
+
--_counter-square-border-color: var(--stepper-list-counter-square-border, transparent);
|
|
131
|
+
--_counter-square-border-radius: var(--stepper-list-counter-square-border-radius, 0.25rem);
|
|
106
132
|
|
|
107
133
|
list-style: none;
|
|
108
134
|
counter-reset: stepper-list;
|
|
@@ -111,10 +137,10 @@ watch(
|
|
|
111
137
|
li {
|
|
112
138
|
display: grid;
|
|
113
139
|
grid-template-columns: auto 1fr;
|
|
114
|
-
gap:
|
|
140
|
+
gap: var(--_gap);
|
|
115
141
|
counter-increment: stepper-list;
|
|
116
142
|
padding-inline-start: 0rem;
|
|
117
|
-
padding-block: var(--
|
|
143
|
+
padding-block: var(--_padding-block);
|
|
118
144
|
position: relative;
|
|
119
145
|
|
|
120
146
|
&.indicator-top {
|
|
@@ -132,29 +158,29 @@ watch(
|
|
|
132
158
|
place-content: center;
|
|
133
159
|
width: var(--_counter-size);
|
|
134
160
|
height: var(--_counter-size);
|
|
135
|
-
font-size:
|
|
136
|
-
font-weight:
|
|
161
|
+
font-size: var(--_counter-font-size);
|
|
162
|
+
font-weight: var(--_counter-font-weight);
|
|
137
163
|
}
|
|
138
164
|
|
|
139
165
|
&.indicator-circle .stepper-list__indicator-counter::before {
|
|
140
|
-
background-color: var(--
|
|
141
|
-
color: var(--
|
|
142
|
-
border: 2px solid var(--
|
|
143
|
-
border-radius:
|
|
166
|
+
background-color: var(--_counter-circle-background);
|
|
167
|
+
color: var(--_counter-circle-text);
|
|
168
|
+
border: 2px solid var(--_counter-circle-border-color);
|
|
169
|
+
border-radius: var(--_counter-circle-border-radius);
|
|
144
170
|
}
|
|
145
171
|
|
|
146
172
|
&.indicator-disc .stepper-list__indicator-counter::before {
|
|
147
|
-
background-color: var(--
|
|
148
|
-
color: var(--
|
|
149
|
-
border: 2px solid var(--
|
|
150
|
-
border-radius:
|
|
173
|
+
background-color: var(--_counter-disc-background);
|
|
174
|
+
color: var(--_counter-disc-text);
|
|
175
|
+
border: 2px solid var(--_counter-disc-border-color);
|
|
176
|
+
border-radius: var(--_counter-disc-border-radius);
|
|
151
177
|
}
|
|
152
178
|
|
|
153
179
|
&.indicator-square .stepper-list__indicator-counter::before {
|
|
154
|
-
background-color: var(--
|
|
155
|
-
color: var(--
|
|
156
|
-
border: 2px solid var(--
|
|
157
|
-
border-radius:
|
|
180
|
+
background-color: var(--_counter-square-background);
|
|
181
|
+
color: var(--_counter-square-text);
|
|
182
|
+
border: 2px solid var(--_counter-square-border-color);
|
|
183
|
+
border-radius: var(--_counter-square-border-radius);
|
|
158
184
|
}
|
|
159
185
|
|
|
160
186
|
&.has-indicator::before {
|
|
@@ -172,7 +198,7 @@ watch(
|
|
|
172
198
|
justify-content: center;
|
|
173
199
|
|
|
174
200
|
.indicator-icon {
|
|
175
|
-
color: var(--
|
|
201
|
+
color: var(--_icon-color);
|
|
176
202
|
width: var(--_counter-size) !important;
|
|
177
203
|
height: var(--_counter-size) !important;
|
|
178
204
|
}
|
|
@@ -187,11 +213,11 @@ watch(
|
|
|
187
213
|
transform: translateX(-50%);
|
|
188
214
|
/* JS sets --_connector-top and --_connector-height from measured indicator positions.
|
|
189
215
|
The fallback values handle the indicator-top case before JS runs. */
|
|
190
|
-
top: var(--_connector-top, calc(var(--
|
|
216
|
+
top: var(--_connector-top, calc(var(--_padding-block) + var(--_counter-size)));
|
|
191
217
|
height: var(--_connector-height, auto);
|
|
192
|
-
bottom: calc(-1 * var(--
|
|
193
|
-
width: var(--
|
|
194
|
-
background-color: var(--
|
|
218
|
+
bottom: calc(-1 * var(--_padding-block));
|
|
219
|
+
width: var(--_connector-width);
|
|
220
|
+
background-color: var(--_connector-color);
|
|
195
221
|
}
|
|
196
222
|
}
|
|
197
223
|
</style>
|
package/app/components/03.organisms/image-galleries/slider-gallery/tests/SliderGallery.spec.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
2
|
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
3
3
|
import { nextTick } from "vue";
|
|
4
4
|
import SliderGallery from "../SliderGallery.vue";
|
|
@@ -67,6 +67,10 @@ describe("SliderGallery", () => {
|
|
|
67
67
|
);
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
+
afterEach(() => {
|
|
71
|
+
vi.restoreAllMocks();
|
|
72
|
+
});
|
|
73
|
+
|
|
70
74
|
// ─── Mount ───────────────────────────────────────────────────────────────
|
|
71
75
|
|
|
72
76
|
it("mounts without error", async () => {
|
|
@@ -27,7 +27,6 @@
|
|
|
27
27
|
|
|
28
28
|
<script setup lang="ts">
|
|
29
29
|
import type { InputTypesButton, FormUiTheme, InputButtonVariant } from "~/types/forms/types.forms";
|
|
30
|
-
import { NuxtLink } from "#components";
|
|
31
30
|
interface Props {
|
|
32
31
|
type?: InputTypesButton;
|
|
33
32
|
href?: string;
|
|
@@ -55,6 +54,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
55
54
|
});
|
|
56
55
|
|
|
57
56
|
const slots = useSlots();
|
|
57
|
+
const NuxtLink = resolveComponent("NuxtLink");
|
|
58
58
|
|
|
59
59
|
// If href is undefined tag is button, else it's a link
|
|
60
60
|
const isLink = computed(() => Boolean(props.href));
|