srcdev-nuxt-components 9.6.3 → 9.6.5

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.
Files changed (45) hide show
  1. package/.claude/rules/consumer/vue-no-self-closing.md +3 -2
  2. package/.claude/skills/component-compliance-checklist.md +17 -3
  3. package/.claude/skills/component-inline-action-button.md +33 -2
  4. package/.claude/skills/components/cookie-consent-banner.md +9 -6
  5. package/.claude/skills/components/input-copy.md +47 -100
  6. package/.claude/skills/components/progress-tracker.md +140 -0
  7. package/.claude/skills/components/stepper-list.md +10 -3
  8. package/.claude/skills/components/tabbed-content.md +4 -0
  9. package/.claude/skills/composable-cookie-consent.md +1 -1
  10. package/.claude/skills/index.md +4 -3
  11. package/.claude/skills/storybook-add-story.md +14 -0
  12. package/.claude/skills/testing-add-unit-test.md +19 -0
  13. package/.claude/skills/theming-component-token-pattern.md +1 -1
  14. package/.vscode/srcdev-component-cookie-consent-banner.code-snippets +7 -5
  15. package/.vscode/srcdev-component-input-copy.code-snippets +24 -42
  16. package/.vscode/srcdev-component-progress-tracker.code-snippets +45 -0
  17. package/.vscode/srcdev-component-stepper-list.code-snippets +3 -2
  18. package/.vscode/srcdev-component-tabbed-content.code-snippets +1 -0
  19. package/app/components/01.atoms/tabbed-content/CONSUMER-STYLING.md +5 -1
  20. package/app/components/01.atoms/tabbed-content/TabbedContent.vue +2 -2
  21. package/app/components/01.atoms/tabbed-content/stories/TabbedContent.stories.ts +26 -0
  22. package/app/components/02.molecules/contact-section/tests/__snapshots__/ContactSection.spec.ts.snap +28 -28
  23. package/app/components/02.molecules/cookie-consent-banner/CONSUMER-STYLING.md +13 -4
  24. package/app/components/02.molecules/cookie-consent-banner/CookieConsentBanner.vue +23 -14
  25. package/app/components/02.molecules/cookie-consent-banner/stories/CookieConsentBanner.stories.ts +106 -5
  26. package/app/components/02.molecules/cookie-consent-banner/tests/CookieConsentBanner.spec.ts +33 -11
  27. package/app/components/02.molecules/input-copy/CONSUMER-STYLING.md +82 -124
  28. package/app/components/02.molecules/input-copy/InputCopy.vue +110 -85
  29. package/app/components/02.molecules/input-copy/stories/InputCopy.stories.ts +204 -47
  30. package/app/components/02.molecules/input-copy/tests/InputCopy.spec.ts +147 -171
  31. package/app/components/02.molecules/progress-tracker/CONSUMER-STYLING.md +116 -0
  32. package/app/components/02.molecules/progress-tracker/ProgressTracker.vue +275 -0
  33. package/app/components/02.molecules/progress-tracker/stories/ProgressTracker.stories.ts +424 -0
  34. package/app/components/02.molecules/progress-tracker/tests/ProgressTracker.spec.ts +253 -0
  35. package/app/components/02.molecules/progress-tracker/tests/__snapshots__/ProgressTracker.spec.ts.snap +46 -0
  36. package/app/components/02.molecules/stepper-list/CONSUMER-STYLING.md +42 -7
  37. package/app/components/02.molecules/stepper-list/StepperList.vue +42 -19
  38. package/app/components/02.molecules/stepper-list/stories/StepperList.stories.ts +288 -2
  39. package/app/components/02.molecules/stepper-list/tests/StepperList.spec.ts +161 -34
  40. package/app/components/02.molecules/stepper-list/tests/__snapshots__/StepperList.spec.ts.snap +40 -40
  41. package/app/components/03.organisms/services/service-detail/tests/__snapshots__/ServiceDetail.spec.ts.snap +7 -7
  42. package/app/types/components/cookie-consent-banner.d.ts +2 -0
  43. package/app/types/components/index.ts +1 -0
  44. package/app/types/components/progress-tracker.d.ts +6 -0
  45. package/package.json +1 -1
@@ -0,0 +1,46 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`ProgressTracker > renders correct HTML structure 1`] = `
4
+ "<div class="progress-tracker">
5
+ <div class="progress-tracker-header">
6
+ <p class="progress-tracker-label" aria-live="polite" tabindex="-1">Step 2 of 3</p>
7
+ <div class="progress-tracker-bar" role="progressbar" aria-valuemin="0" aria-valuemax="3" aria-valuenow="1" aria-valuetext="Step 2 of 3" style="--_progress-tracker-progress: 0.3333333333333333;"><span class="progress-tracker-bar-fill"></span></div>
8
+ </div>
9
+ <ol role="list" class="stepper-list has-connectors progress-tracker-list">
10
+ <li class="stepper-list-item has-indicator indicator-top indicator-disc" style="--_connector-left: 0px; --_connector-top: 0px; --_connector-height: 0px;">
11
+ <div class="stepper-list__indicator-custom"><span class="progress-tracker-indicator is-done" aria-hidden="true"><span class="iconify i-lucide:check progress-tracker-indicator-icon" aria-hidden="true"></span></span></div>
12
+ <div class="stepper-list-content">
13
+ <div class="progress-tracker-step is-done">
14
+ <h3 id="v-0-0-step-0" class="progress-tracker-title" tabindex="-1">Create your account<span class="sr-only">, Completed</span></h3>
15
+ <p class="progress-tracker-body">Enter your email and choose a password.</p>
16
+ <!--v-if-->
17
+ </div>
18
+ </div>
19
+ </li>
20
+ <li class="stepper-list-item has-indicator indicator-top indicator-disc" style="--_connector-left: 0px; --_connector-top: 0px; --_connector-height: 0px;">
21
+ <div class="stepper-list__indicator-custom"><span class="progress-tracker-indicator is-current" aria-hidden="true">2</span></div>
22
+ <div class="stepper-list-content">
23
+ <div class="progress-tracker-step is-current">
24
+ <h3 id="v-0-0-step-1" class="progress-tracker-title" tabindex="-1" aria-current="step">Verify your email
25
+ <!--v-if-->
26
+ </h3>
27
+ <p class="progress-tracker-body">Click the link we sent to your inbox.</p>
28
+ <!--v-if-->
29
+ </div>
30
+ </div>
31
+ </li>
32
+ <li class="stepper-list-item has-indicator indicator-top indicator-disc">
33
+ <div class="stepper-list__indicator-custom"><span class="progress-tracker-indicator is-upcoming" aria-hidden="true">3</span></div>
34
+ <div class="stepper-list-content">
35
+ <div class="progress-tracker-step is-upcoming">
36
+ <h3 id="v-0-0-step-2" class="progress-tracker-title" tabindex="-1">Choose a plan
37
+ <!--v-if-->
38
+ </h3>
39
+ <!--v-if-->
40
+ <!--v-if-->
41
+ </div>
42
+ </div>
43
+ </li>
44
+ </ol>
45
+ </div>"
46
+ `;
@@ -15,12 +15,14 @@ file, scoped to a page wrapper, or per-instance via `styleClassPassthrough`.
15
15
  | `--stepper-list-counter-font-size` | `1.4rem` | Counter number font size |
16
16
  | `--stepper-list-counter-font-weight` | `600` | Counter number font weight |
17
17
  | `--stepper-list-connector-width` | `0.2rem` | Connector line thickness |
18
+ | `--stepper-list-connector-gap` | `0` | Space between each end of the connector line and the indicators it joins. A gap larger than half the space between two indicators hides that line |
18
19
 
19
20
  ### Colours
20
21
 
21
22
  | Token | Default | Controls |
22
23
  |---|---|---|
23
24
  | `--stepper-list-connector-color` | `currentColor` | Connector line colour |
25
+ | `--stepper-list-connector-image` | `none` | Image painted over the connector colour, e.g. a `repeating-linear-gradient()` for a dashed or dotted line (set the colour to `transparent` underneath) |
24
26
  | `--stepper-list-icon-color` | `currentColor` | Icon colour for custom `indicator-icon` elements |
25
27
 
26
28
  ### Circle indicator
@@ -58,8 +60,8 @@ The defaults above are the component's own fallbacks. The library's default them
58
60
  > reads, so custom indicator icons fell back to `currentColor`. It now sets
59
61
  > `--stepper-list-icon-color`.
60
62
 
61
- Private (not public API): `--_gap`, `--_counter-size` and `--_padding-block`, plus `--_connector-top` and
62
- `--_connector-height`, which script measures and sets inline on each `<li>`.
63
+ Private (not public API): `--_counter-size` and `--_padding-block`, plus `--_connector-left`,
64
+ `--_connector-top` and `--_connector-height`, which script measures and sets inline on each `<li>`.
63
65
 
64
66
  ---
65
67
 
@@ -72,8 +74,14 @@ Private (not public API): `--_gap`, `--_counter-size` and `--_padding-block`, pl
72
74
  | `.indicator-top`, `.indicator-center` | `<li>` | `indicator-alignment` |
73
75
  | `.indicator-disc`, `.indicator-square`, `.indicator-circle` | `<li>` | `indicator-variant` |
74
76
 
75
- Inner classes: `.stepper-list__indicator-counter` (auto-numbered bubble) or
76
- `.stepper-list__indicator-custom` (wraps a custom `indicator-{n}` slot).
77
+ Inner classes: `.stepper-list-item` (each `<li>`), `.stepper-list__indicator-counter`
78
+ (auto-numbered bubble) or `.stepper-list__indicator-custom` (wraps a custom `indicator-{n}` slot),
79
+ and `.stepper-list-content` (wraps the `item-{n}` slot).
80
+
81
+ > Changed 2026-10-15: `<li>` gained `.stepper-list-item` and the content wrapper gained
82
+ > `.stepper-list-content` (it had no class). The content column is now `minmax(0, 1fr)` with
83
+ > `overflow-wrap: anywhere`, so an unbroken string or long URL wraps instead of widening the list.
84
+ > The root carries `role="list"`, since `list-style: none` drops list semantics in Safari.
77
85
 
78
86
  ---
79
87
 
@@ -156,6 +164,29 @@ visual style, pass a modifier class:
156
164
 
157
165
  ---
158
166
 
167
+ ## Recipe: per-step states and dashed connectors
168
+
169
+ The component has no "done / current / upcoming" prop. Put a state class on the content you pass
170
+ to each `#indicator-{n}` slot, then set tokens on the matching `.stepper-list-item` with `:has()`.
171
+ Each connector belongs to the item above it, so it takes that item's state.
172
+
173
+ ```css
174
+ .onboarding-steps {
175
+ --stepper-list-connector-color: transparent;
176
+ --stepper-list-connector-image: repeating-linear-gradient(to bottom, #3a3d45 0 0.2rem, transparent 0.2rem 0.6rem);
177
+
178
+ .stepper-list-item:has(.step-marker.is-done) {
179
+ --stepper-list-connector-image: repeating-linear-gradient(to bottom, #00c8d7 0 0.4rem, transparent 0.4rem 0.7rem);
180
+ }
181
+ }
182
+ ```
183
+
184
+ Shapes such as diamonds go in the indicator slot (a rotated square). The connector is centred on
185
+ whatever the slot renders, so it needs no adjustment. The "Timeline (styled demo)" story shows the
186
+ full pattern.
187
+
188
+ ---
189
+
159
190
  ## Class passthrough
160
191
 
161
192
  `style-class-passthrough` adds classes to the root `<ul>`/`<ol>`. Tokens set on a passthrough class
@@ -167,11 +198,15 @@ land for the whole list. Reactive after mount.
167
198
 
168
199
  - `--stepper-list-counter-size` takes precedence over the `indicatorSize` prop. Use the prop
169
200
  for programmatic/template control; use the CSS token for pure-CSS theme overrides.
170
- - The connector line is positioned by JS (`ResizeObserver`) on each `<li>` as
171
- `--_connector-top` and `--_connector-height`. These are internal — do not set them.
201
+ - The connector line is positioned by JS on each `<li>` as `--_connector-left`,
202
+ `--_connector-top` and `--_connector-height`, centred on the measured indicator, so it lines up
203
+ under a custom indicator wider than `--stepper-list-counter-size`. These are internal — do not
204
+ set them. They are re-measured when the list or any item resizes, and when a `class` or the
205
+ content changes inside the list, so per-step state styling (the recipe above) keeps the lines
206
+ in place. A change made only through an inline `style` attribute isn't seen; toggle a class
207
+ instead.
172
208
  - `--stepper-list-connector-color` defaults to `currentColor`, inheriting from the nearest
173
209
  ancestor's text colour. Set it explicitly when that behaviour is undesirable.
174
210
  - All border tokens (`--stepper-list-counter-*-border`) control the **colour** only — the
175
211
  border is always `2px solid`. Override the border shorthand directly on `.stepper-list li
176
212
  .stepper-list__indicator-counter::before` if you need a different width.
177
-
@@ -2,13 +2,15 @@
2
2
  <component
3
3
  :is="tag"
4
4
  ref="listEl"
5
+ role="list"
5
6
  class="stepper-list"
6
7
  :class="[elementClasses, { 'has-connectors': props.connected }]"
7
8
  >
8
9
  <li
9
- v-for="index in itemCount"
10
+ v-for="index in itemTotal"
10
11
  :key="index"
11
12
  :class="[
13
+ 'stepper-list-item',
12
14
  { 'has-indicator': $slots[`indicator-${index - 1}`] },
13
15
  `indicator-${props.indicatorAlignment}`,
14
16
  `indicator-${props.indicatorVariant}`,
@@ -19,7 +21,7 @@
19
21
  >
20
22
  <slot :name="`indicator-${index - 1}`"></slot>
21
23
  </div>
22
- <div>
24
+ <div class="stepper-list-content">
23
25
  <slot :name="`item-${index - 1}`"></slot>
24
26
  </div>
25
27
  </li>
@@ -53,6 +55,8 @@ watch(
53
55
  () => resetElementClasses(props.styleClassPassthrough)
54
56
  );
55
57
 
58
+ const itemTotal = computed(() => Math.max(0, Math.floor(Number(props.itemCount)) || 0));
59
+
56
60
  const listEl = ref<HTMLElement | null>(null);
57
61
  let resizeObserver: ResizeObserver | null = null;
58
62
 
@@ -76,27 +80,50 @@ function updateConnectors() {
76
80
  const indicatorRect = indicator.getBoundingClientRect();
77
81
  const nextIndicatorRect = nextIndicator.getBoundingClientRect();
78
82
 
79
- li.style.setProperty("--_connector-top", `${indicatorRect.bottom - liRect.top}px`);
83
+ li.style.setProperty("--_connector-left", `${indicatorRect.left - liRect.left + indicatorRect.width / 2}px`);
84
+ li.style.setProperty("--_connector-top",`${indicatorRect.bottom - liRect.top}px`);
80
85
  li.style.setProperty("--_connector-height", `${Math.max(0, nextIndicatorRect.top - indicatorRect.bottom)}px`);
81
86
  });
82
87
  }
83
88
 
89
+ let mutationObserver: MutationObserver | null = null;
90
+
91
+ // Items can resize or restyle without the list changing size (e.g. a card moving from one step to the next).
92
+ function observeItems() {
93
+ if (!resizeObserver || !listEl.value) return;
94
+ resizeObserver.disconnect();
95
+ resizeObserver.observe(listEl.value);
96
+ listEl.value.querySelectorAll(":scope > li").forEach((li) => resizeObserver?.observe(li));
97
+ }
98
+
84
99
  onMounted(() => {
85
100
  updateConnectors();
86
- if (typeof ResizeObserver !== "undefined" && listEl.value) {
101
+ if (!listEl.value) return;
102
+ if (typeof ResizeObserver !== "undefined") {
87
103
  resizeObserver = new ResizeObserver(updateConnectors);
88
- resizeObserver.observe(listEl.value);
104
+ observeItems();
105
+ }
106
+ if (typeof MutationObserver !== "undefined") {
107
+ mutationObserver = new MutationObserver(updateConnectors);
108
+ mutationObserver.observe(listEl.value, {
109
+ subtree: true,
110
+ childList: true,
111
+ attributes: true,
112
+ attributeFilter: ["class"],
113
+ });
89
114
  }
90
115
  });
91
116
 
92
117
  onBeforeUnmount(() => {
93
118
  resizeObserver?.disconnect();
119
+ mutationObserver?.disconnect();
94
120
  });
95
121
 
96
122
  watch(
97
- () => [props.itemCount, props.connected, props.indicatorAlignment],
123
+ () => [itemTotal.value, props.connected, props.indicatorAlignment],
98
124
  async () => {
99
125
  await nextTick();
126
+ observeItems();
100
127
  updateConnectors();
101
128
  }
102
129
  );
@@ -107,7 +134,6 @@ watch(
107
134
  .stepper-list {
108
135
  /* Layout */
109
136
  --_padding-block: var(--stepper-list-padding-block, 1.2rem);
110
- --_gap: var(--stepper-list-gap, 2.2rem);
111
137
  --_counter-size: var(--stepper-list-counter-size, v-bind(indicatorSize));
112
138
 
113
139
  list-style: none;
@@ -116,8 +142,8 @@ watch(
116
142
 
117
143
  li {
118
144
  display: grid;
119
- grid-template-columns: auto 1fr;
120
- gap: var(--_gap);
145
+ grid-template-columns: auto minmax(0, 1fr);
146
+ gap: var(--stepper-list-gap, 2.2rem);
121
147
  counter-increment: stepper-list;
122
148
  padding-inline-start: 0rem;
123
149
  padding-block: var(--_padding-block);
@@ -163,13 +189,8 @@ watch(
163
189
  border-radius: var(--stepper-list-counter-square-border-radius, 0.25rem);
164
190
  }
165
191
 
166
- &.has-indicator::before {
167
- display: none;
168
- }
169
-
170
- .stepper-list__indicator-custom,
171
- .stepper-list__indicator-counter {
172
- anchor-name: --indicator-bubble;
192
+ .stepper-list-content {
193
+ overflow-wrap: anywhere;
173
194
  }
174
195
 
175
196
  .stepper-list__indicator-custom {
@@ -189,15 +210,17 @@ watch(
189
210
  .stepper-list.has-connectors li:not(:last-child)::after {
190
211
  content: "";
191
212
  position: absolute;
192
- left: calc(var(--_counter-size) / 2);
213
+ left: var(--_connector-left, calc(var(--_counter-size) / 2));
193
214
  transform: translateX(-50%);
194
- /* JS sets --_connector-top and --_connector-height from measured indicator positions.
195
- The fallback values handle the indicator-top case before JS runs. */
215
+ /* JS sets --_connector-left, --_connector-top and --_connector-height from measured indicator
216
+ positions. The fallback values handle the indicator-top case before JS runs. */
196
217
  top: var(--_connector-top, calc(var(--_padding-block) + var(--_counter-size)));
197
218
  height: var(--_connector-height, auto);
198
219
  bottom: calc(-1 * var(--_padding-block));
199
220
  width: var(--stepper-list-connector-width, 0.2rem);
221
+ clip-path: inset(var(--stepper-list-connector-gap, 0) 0);
200
222
  background-color: var(--stepper-list-connector-color, currentColor);
223
+ background-image: var(--stepper-list-connector-image, none);
201
224
  }
202
225
  }
203
226
  </style>