srcdev-nuxt-components 9.6.18 → 9.6.20

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 (187) hide show
  1. package/.claude/rules/consumer/css-relative-colour-calc.md +15 -0
  2. package/.claude/rules/consumer/ids-from-useid.md +3 -2
  3. package/.claude/rules/consumer/vue-dynamic-arg-no-spaces.md +16 -0
  4. package/.claude/skills/component-compliance-checklist.md +11 -1
  5. package/.claude/skills/component-dynamic-slots.md +2 -2
  6. package/.claude/skills/component-naming.md +1 -1
  7. package/.claude/skills/components/accordion-chat.md +99 -0
  8. package/.claude/skills/components/accordion-group.md +98 -0
  9. package/.claude/skills/components/action-menu.md +11 -5
  10. package/.claude/skills/components/auto-grid.md +3 -1
  11. package/.claude/skills/components/capture-qr-code.md +9 -5
  12. package/.claude/skills/components/carousel-flip.md +41 -30
  13. package/.claude/skills/components/column-flow-grid.md +2 -0
  14. package/.claude/skills/components/dashboard-quad-grid.md +7 -2
  15. package/.claude/skills/components/dashboard-stats-grid.md +4 -0
  16. package/.claude/skills/components/decode-qr-code.md +7 -2
  17. package/.claude/skills/components/deep-expanding-menu-classic.md +31 -30
  18. package/.claude/skills/components/deep-expanding-menu.md +22 -21
  19. package/.claude/skills/components/expanding-panel-classic.md +9 -7
  20. package/.claude/skills/components/expanding-panel.md +9 -8
  21. package/.claude/skills/components/grid-stack.md +2 -0
  22. package/.claude/skills/components/layout-grid-by-cols.md +2 -0
  23. package/.claude/skills/components/masonry-grid.md +9 -1
  24. package/.claude/skills/components/page-row.md +3 -0
  25. package/.claude/skills/components/samaritan-prompt.md +24 -8
  26. package/.claude/skills/components/site-header.md +16 -2
  27. package/.claude/skills/components/sparkle-effect.md +79 -0
  28. package/.claude/skills/index.md +16 -14
  29. package/.claude/skills/storybook-add-story.md +33 -0
  30. package/.claude/skills/testing-add-unit-test.md +26 -0
  31. package/.claude/skills/using-component-skills.md +1 -1
  32. package/.vscode/srcdev-component-accordion-chat.code-snippets +42 -0
  33. package/.vscode/srcdev-component-accordion-group.code-snippets +38 -0
  34. package/.vscode/srcdev-component-action-menu.code-snippets +3 -3
  35. package/.vscode/srcdev-component-capture-decode-qr-code.code-snippets +11 -2
  36. package/.vscode/srcdev-component-carousel-flip.code-snippets +22 -4
  37. package/.vscode/srcdev-component-deep-expanding-menu-classic.code-snippets +32 -2
  38. package/.vscode/srcdev-component-deep-expanding-menu.code-snippets +31 -1
  39. package/.vscode/srcdev-component-samaritan-prompt.code-snippets +5 -4
  40. package/.vscode/srcdev-component-site-header.code-snippets +13 -1
  41. package/.vscode/srcdev-component-sparkle-effect.code-snippets +33 -0
  42. package/app/components/01.atoms/animations/sparkle-effect/CONSUMER-STYLING.md +75 -0
  43. package/app/components/01.atoms/animations/sparkle-effect/SparkleEffect.vue +213 -0
  44. package/app/components/01.atoms/animations/sparkle-effect/stories/SparkleEffect.stories.ts +303 -0
  45. package/app/components/01.atoms/animations/sparkle-effect/tests/SparkleEffect.spec.ts +150 -0
  46. package/app/components/01.atoms/content-wrappers/docs-pages/ContentDocs.vue +1 -1
  47. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md +12 -2
  48. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/DashboardQuadGrid.vue +19 -17
  49. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/stories/DashboardQuadGrid.stories.ts +42 -0
  50. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/tests/DashboardQuadGrid.spec.ts +31 -44
  51. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/CONSUMER-STYLING.md +13 -1
  52. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/DashboardStatsGrid.vue +40 -31
  53. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/stories/DashboardStatsGrid.stories.ts +47 -0
  54. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/tests/DashboardStatsGrid.spec.ts +51 -53
  55. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/CONSUMER-STYLING.md +3 -0
  56. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/LayoutGridByCols.vue +4 -0
  57. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/stories/LayoutGridByCols.stories.ts +47 -2
  58. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/tests/LayoutGridByCols.spec.ts +36 -23
  59. package/app/components/01.atoms/grids/auto-grid/AutoGrid.vue +7 -3
  60. package/app/components/01.atoms/grids/auto-grid/CONSUMER-STYLING.md +1 -1
  61. package/app/components/01.atoms/grids/auto-grid/stories/AutoGrid.stories.ts +46 -1
  62. package/app/components/01.atoms/grids/auto-grid/tests/AutoGrid.spec.ts +40 -27
  63. package/app/components/01.atoms/grids/auto-grid/tests/__snapshots__/AutoGrid.spec.ts.snap +1 -1
  64. package/app/components/01.atoms/grids/column-flow-grid/CONSUMER-STYLING.md +9 -0
  65. package/app/components/01.atoms/grids/column-flow-grid/ColumnFlowGrid.vue +3 -2
  66. package/app/components/01.atoms/grids/column-flow-grid/stories/ColumnFlowGrid.stories.ts +46 -0
  67. package/app/components/01.atoms/grids/column-flow-grid/tests/ColumnFlowGrid.spec.ts +24 -11
  68. package/app/components/01.atoms/grids/grid-stack/CONSUMER-STYLING.md +3 -0
  69. package/app/components/01.atoms/grids/grid-stack/GridStack.vue +2 -0
  70. package/app/components/01.atoms/grids/grid-stack/stories/GridStack.stories.ts +41 -0
  71. package/app/components/01.atoms/grids/grid-stack/tests/GridStack.spec.ts +26 -13
  72. package/app/components/01.atoms/grids/masonry-grid/CONSUMER-STYLING.md +15 -0
  73. package/app/components/01.atoms/grids/masonry-grid/MasonryGrid.vue +51 -31
  74. package/app/components/01.atoms/grids/masonry-grid/stories/MasonryGrid.stories.ts +48 -0
  75. package/app/components/01.atoms/grids/masonry-grid/tests/MasonryGrid.spec.ts +84 -29
  76. package/app/components/01.atoms/page-row/CONSUMER-STYLING.md +6 -1
  77. package/app/components/01.atoms/page-row/PageRow.vue +16 -15
  78. package/app/components/01.atoms/page-row/stories/PageRow.stories.ts +23 -0
  79. package/app/components/01.atoms/page-row/tests/PageRow.spec.ts +6 -5
  80. package/app/components/02.molecules/action-menu/ActionMenu.vue +6 -2
  81. package/app/components/02.molecules/action-menu/ActionMenuItem.vue +6 -3
  82. package/app/components/02.molecules/action-menu/CONSUMER-STYLING.md +11 -4
  83. package/app/components/02.molecules/action-menu/stories/ActionMenu.stories.ts +130 -18
  84. package/app/components/02.molecules/expandable/accordion-chat/AccordionChat.vue +227 -0
  85. package/app/components/02.molecules/expandable/accordion-chat/CONSUMER-STYLING.md +150 -0
  86. package/app/components/02.molecules/expandable/accordion-chat/stories/AccordionChat.stories.ts +347 -0
  87. package/app/components/02.molecules/expandable/accordion-chat/tests/AccordionChat.spec.ts +205 -0
  88. package/app/components/02.molecules/expandable/accordion-chat/tests/__snapshots__/AccordionChat.spec.ts.snap +40 -0
  89. package/app/components/02.molecules/expandable/{accordian/AccordianCore.vue → accordion-group/AccordionGroup.vue} +32 -20
  90. package/app/components/02.molecules/expandable/accordion-group/CONSUMER-STYLING.md +66 -0
  91. package/app/components/02.molecules/expandable/accordion-group/stories/AccordionGroup.stories.ts +238 -0
  92. package/app/components/02.molecules/expandable/accordion-group/tests/AccordionGroup.spec.ts +172 -0
  93. package/app/components/02.molecules/expandable/accordion-group/tests/__snapshots__/AccordionGroup.spec.ts.snap +45 -0
  94. package/app/components/02.molecules/expandable/expanding-panel/CONSUMER-STYLING.md +1 -0
  95. package/app/components/02.molecules/expandable/expanding-panel/ExpandingPanel.vue +11 -6
  96. package/app/components/02.molecules/expandable/expanding-panel/stories/ExpandingPanel.stories.ts +82 -1
  97. package/app/components/02.molecules/expandable/expanding-panel/tests/ExpandingPanel.spec.ts +24 -30
  98. package/app/components/02.molecules/expandable/expanding-panel/tests/__snapshots__/ExpandingPanel.spec.ts.snap +8 -8
  99. package/app/components/02.molecules/expandable/expanding-panel-classic/CONSUMER-STYLING.md +1 -0
  100. package/app/components/02.molecules/expandable/expanding-panel-classic/ExpandingPanelClassic.vue +10 -5
  101. package/app/components/02.molecules/expandable/expanding-panel-classic/stories/ExpandingPanelClassic.stories.ts +83 -1
  102. package/app/components/02.molecules/expandable/expanding-panel-classic/tests/ExpandingPanelClassic.spec.ts +24 -30
  103. package/app/components/02.molecules/expandable/expanding-panel-classic/tests/__snapshots__/ExpandingPanelClassic.spec.ts.snap +8 -8
  104. package/app/components/02.molecules/navigation/deep-expanding-menu/CONSUMER-STYLING.md +63 -44
  105. package/app/components/02.molecules/navigation/deep-expanding-menu/DeepExpandingMenu.vue +74 -37
  106. package/app/components/02.molecules/navigation/deep-expanding-menu/stories/DeepExpandingMenu.stories.ts +139 -19
  107. package/app/components/02.molecules/navigation/deep-expanding-menu/tests/DeepExpandingMenu.spec.ts +90 -27
  108. package/app/components/02.molecules/navigation/deep-expanding-menu/tests/__snapshots__/DeepExpandingMenu.spec.ts.snap +8 -8
  109. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/CONSUMER-STYLING.md +58 -26
  110. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/DeepExpandingMenuClassic.vue +113 -57
  111. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/stories/DeepExpandingMenuClassic.stories.ts +139 -19
  112. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/DeepExpandingMenuClassic.spec.ts +140 -22
  113. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/__snapshots__/DeepExpandingMenuClassic.spec.ts.snap +9 -9
  114. package/app/components/02.molecules/qr-code/CONSUMER-STYLING.md +22 -7
  115. package/app/components/02.molecules/qr-code/CaptureQrCode.vue +63 -71
  116. package/app/components/02.molecules/qr-code/DecodeQrCode.vue +47 -10
  117. package/app/components/02.molecules/qr-code/stories/CaptureQrCode.stories.ts +114 -26
  118. package/app/components/02.molecules/qr-code/stories/DecodeQrCode.stories.ts +115 -24
  119. package/app/components/02.molecules/qr-code/tests/CaptureQrCode.spec.ts +124 -51
  120. package/app/components/02.molecules/qr-code/tests/DecodeQrCode.spec.ts +74 -25
  121. package/app/components/02.molecules/samaritan-prompt/CONSUMER-STYLING.md +22 -10
  122. package/app/components/02.molecules/samaritan-prompt/SamaritanPrompt.vue +30 -12
  123. package/app/components/02.molecules/samaritan-prompt/SamaritanPromptMixed.vue +34 -10
  124. package/app/components/02.molecules/samaritan-prompt/stories/SamaritanPrompt.stories.ts +55 -4
  125. package/app/components/02.molecules/samaritan-prompt/stories/SamaritanPromptMixed.stories.ts +67 -7
  126. package/app/components/02.molecules/samaritan-prompt/tests/SamaritanPrompt.spec.ts +113 -29
  127. package/app/components/02.molecules/samaritan-prompt/tests/SamaritanPromptMixed.spec.ts +92 -45
  128. package/app/components/03.organisms/image-galleries/carousel-flip/CONSUMER-STYLING.md +82 -29
  129. package/app/components/03.organisms/image-galleries/carousel-flip/CarouselFlip.vue +176 -113
  130. package/app/components/03.organisms/image-galleries/carousel-flip/stories/CarouselFlip.stories.ts +153 -23
  131. package/app/components/03.organisms/image-galleries/carousel-flip/tests/CarouselFlip.spec.ts +119 -63
  132. package/app/components/03.organisms/responsive-header/tests/__snapshots__/NavigationItems.spec.ts.snap +2 -2
  133. package/app/components/03.organisms/responsive-header/tests/__snapshots__/ResponsiveHeader.spec.ts.snap +2 -2
  134. package/app/components/03.organisms/services/service-detail/tests/ServiceDetail.spec.ts +8 -8
  135. package/app/components/03.organisms/services/service-detail/tests/__snapshots__/ServiceDetail.spec.ts.snap +4 -4
  136. package/app/components/03.organisms/site-header/CONSUMER-STYLING.md +41 -23
  137. package/app/components/03.organisms/site-header/SiteHeader.vue +16 -21
  138. package/app/components/03.organisms/site-header/stories/SiteHeader.stories.ts +138 -0
  139. package/app/components/03.organisms/site-header/tests/SiteHeader.spec.ts +53 -26
  140. package/app/components/03.organisms/site-header/tests/__snapshots__/SiteHeader.spec.ts.snap +2 -2
  141. package/app/types/components/qr-code.d.ts +12 -0
  142. package/app/utils/graphemes.ts +8 -0
  143. package/package.json +1 -1
  144. package/.claude/skills/components/accordian-core.md +0 -180
  145. package/.vscode/srcdev-component-accordian-core.code-snippets +0 -74
  146. package/app/components/01.atoms/animations/entry/.nuxt/app.config.mjs +0 -21
  147. package/app/components/01.atoms/animations/entry/.nuxt/components.d.ts +0 -66
  148. package/app/components/01.atoms/animations/entry/.nuxt/imports.d.ts +0 -30
  149. package/app/components/01.atoms/animations/entry/.nuxt/manifest/meta/test.json +0 -1
  150. package/app/components/01.atoms/animations/entry/.nuxt/types/app.config.d.ts +0 -35
  151. package/app/components/01.atoms/animations/entry/.nuxt/types/build.d.ts +0 -23
  152. package/app/components/01.atoms/animations/entry/.nuxt/types/builder-env.d.ts +0 -1
  153. package/app/components/01.atoms/animations/entry/.nuxt/types/components.d.ts +0 -71
  154. package/app/components/01.atoms/animations/entry/.nuxt/types/imports.d.ts +0 -386
  155. package/app/components/01.atoms/animations/entry/.nuxt/types/middleware.d.ts +0 -11
  156. package/app/components/01.atoms/animations/entry/.nuxt/types/modules.d.ts +0 -79
  157. package/app/components/01.atoms/animations/entry/.nuxt/types/nitro-layouts.d.ts +0 -17
  158. package/app/components/01.atoms/animations/entry/.nuxt/types/nitro-nuxt.d.ts +0 -64
  159. package/app/components/01.atoms/animations/entry/.nuxt/types/plugins.d.ts +0 -29
  160. package/app/components/01.atoms/animations/entry/.nuxt/types/runtime-config.d.ts +0 -32
  161. package/app/components/01.atoms/animations/entry/.nuxt/types/shared-imports.d.ts +0 -10
  162. package/app/components/01.atoms/animations/entry/.nuxt/types/vue-shim.d.ts +0 -0
  163. package/app/components/01.atoms/animations/entry/.nuxtrc +0 -1
  164. package/app/components/01.atoms/animations/entry/node_modules/.cache/nuxt/chrome-workspace.json +0 -1
  165. package/app/components/01.atoms/animations/entry/node_modules/.cache/vite/client/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  166. package/app/components/02.molecules/expandable/accordian/CONSUMER-STYLING.md +0 -100
  167. package/app/components/02.molecules/expandable/accordian/stories/AccordianCore.stories.ts +0 -272
  168. package/app/components/02.molecules/expandable/accordian/tests/AccordianCore.spec.ts +0 -257
  169. package/app/components/02.molecules/expandable/accordian/tests/__snapshots__/AccordianCore.spec.ts.snap +0 -95
  170. package/app/components/03.organisms/responsive-header/tests/.nuxt/app.config.mjs +0 -21
  171. package/app/components/03.organisms/responsive-header/tests/.nuxt/components.d.ts +0 -66
  172. package/app/components/03.organisms/responsive-header/tests/.nuxt/imports.d.ts +0 -30
  173. package/app/components/03.organisms/responsive-header/tests/.nuxt/manifest/meta/test.json +0 -1
  174. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/app.config.d.ts +0 -35
  175. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/build.d.ts +0 -23
  176. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/builder-env.d.ts +0 -1
  177. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/components.d.ts +0 -71
  178. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/imports.d.ts +0 -386
  179. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/middleware.d.ts +0 -11
  180. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/modules.d.ts +0 -79
  181. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-layouts.d.ts +0 -17
  182. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-nuxt.d.ts +0 -64
  183. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/plugins.d.ts +0 -29
  184. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/runtime-config.d.ts +0 -32
  185. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/shared-imports.d.ts +0 -10
  186. package/app/components/03.organisms/responsive-header/tests/.nuxt/types/vue-shim.d.ts +0 -0
  187. package/app/components/03.organisms/responsive-header/tests/node_modules/.cache/nuxt/chrome-workspace.json +0 -1
@@ -313,6 +313,39 @@ prop and try to pass it. Make it unmistakable:
313
313
 
314
314
  `PriceList.stories.ts` and `GoogleReviews.stories.ts` (line-clamp controls) follow this.
315
315
 
316
+ ## Where shared story helpers can live
317
+
318
+ Nowhere shared, so far. Stories ship in the package (`app/` is in `package.json` `files`) but
319
+ `.storybook/` doesn't, so a story importing from `.storybook/` breaks in a consumer's type-check;
320
+ and any `.ts` under `app/components/` is registered as a component (`pathPrefix: false` scan).
321
+ Repeat small fixtures per story instead (the grid `StressTest` stories, 2026-10-21, each carry their
322
+ own oversized-content block).
323
+
324
+ ## Simulating hardware or library events in a stress story
325
+
326
+ When a component's data only arrives from hardware or a third-party child (a camera, a file
327
+ decoder), a story can't feed it worst-case data through props. Add story-only buttons that call the
328
+ component's own handlers through its internal instance, and say so in the story text:
329
+
330
+ ```ts
331
+ const cmpRef = ref<ComponentPublicInstance | null>(null);
332
+ const internals = () =>
333
+ (cmpRef.value?.$ as unknown as { setupState: { onDetect: (codes: { rawValue: string }[]) => void } } | undefined)
334
+ ?.setupState;
335
+ ```
336
+
337
+ `setupState` is internal (not on the public type, hence the cast) but present in every build.
338
+ Don't `defineExpose` handlers just for a story. `CaptureQrCode` and `DecodeQrCode` (2026-10-21)
339
+ follow this.
340
+
341
+ ## Dynamic slot names in template strings
342
+
343
+ A dynamic slot argument can't contain spaces: `` #[`item-${i + 6}-summary`] `` is cut at the first space
344
+ (HTML attribute names end there) and the story fails with "Missing } in template expression". It
345
+ only shows when Storybook renders the story, since the template is a string. Write the expression
346
+ without spaces (`` #[`item-${i}-summary`] ``), or build the names in `setup()` and bind them:
347
+ `#[item.summarySlot]` (AccordionChat's StressTest, 2026-10-20).
348
+
316
349
  ## Docs page
317
350
 
318
351
  `.storybook/preview.ts` sets `tags: ["autodocs"]` globally (2026-10-14), so every story file gets a
@@ -346,6 +346,32 @@ be resolved from the ends and never reaches it. Mount with two items sharing the
346
346
  then `setProps` with the first and last items swapped, and assert the rendered labels in order
347
347
  (`SkipLinks.spec.ts`). With an `href`-only key that test rendered five links instead of four.
348
348
 
349
+ ## Stub third-party children that hold shared state
350
+
351
+ When a component renders a library child whose behaviour the spec doesn't need (the spec drives
352
+ the parent's own handlers), stub the child in the mount helper. Unmounting doesn't isolate a
353
+ library that keeps module-level state: `QrcodeStream` queues every camera start/stop on one shared
354
+ promise chain, so a start left from an earlier test failed mid-test and emitted `error` into the
355
+ next `CaptureQrCode`, but only under full-suite load and only with `SRCDEV_STANDALONE=true`
356
+ (2026-10-21).
357
+
358
+ ```typescript
359
+ const qrcodeStreamStub = { name: "QrcodeStream", emits: ["detect", "error"], template: `<div class="qrcode-stream-stub"></div>` };
360
+
361
+ const mountCapture = async (options: CaptureMountOptions = {}) => {
362
+ const wrapper = await mountSuspended(CaptureQrCode, {
363
+ ...options,
364
+ global: { ...options?.global, stubs: { QrcodeStream: qrcodeStreamStub, ...options?.global?.stubs } },
365
+ });
366
+ mounted.push(wrapper);
367
+ return wrapper;
368
+ };
369
+ ```
370
+
371
+ Assert against the stub's class, not the component's tag name (see
372
+ `.claude/rules/test-no-vacuous-assertions.md`). Run the spec with `npm run test:run` so
373
+ standalone-only modules load.
374
+
349
375
  ## Mocking browser APIs
350
376
 
351
377
  ### Global constructors (ResizeObserver, IntersectionObserver, etc.)
@@ -47,7 +47,7 @@ Component skills land in `.claude/skills/srcdev-nuxt-components/components/`:
47
47
  │ ├── pricing-card.md — SaaS pricing plan card
48
48
  │ ├── input-copy.md — copy-to-clipboard input
49
49
  │ ├── expanding-panel.md — single expand/collapse panel
50
- │ ├── accordian-core.md — native accordion (grouped panels)
50
+ │ ├── accordion-group.md — native accordion (grouped panels)
51
51
  │ ├── display-avatar.md — circular avatar component
52
52
  │ ├── card-core.md — generic card container
53
53
  │ ├── auto-grid.md — responsive auto-fit grid
@@ -0,0 +1,42 @@
1
+ {
2
+ "SRCDEV AccordionChat": {
3
+ "description": "AccordionChat: questions as right-hand bubbles, answers as left-hand bubbles with an avatar",
4
+ "scope": "vue,html",
5
+ "body": [
6
+ "<AccordionChat name=\"${1:faq}\" avatar-label=\"${2:AM}\">",
7
+ " <template #item-0-summary>${3:Question one?}</template>",
8
+ " <template #item-0-content><p>${4:Answer one.}</p></template>",
9
+ "",
10
+ " <template #item-1-summary>${5:Question two?}</template>",
11
+ " <template #item-1-content><p>${6:Answer two.}</p></template>",
12
+ "</AccordionChat>"
13
+ ]
14
+ },
15
+ "SRCDEV AccordionChat Looped": {
16
+ "description": "AccordionChat driven by an array via dynamic item-{n} slot names",
17
+ "scope": "vue,html",
18
+ "body": [
19
+ "<AccordionChat name=\"${1:faq}\" avatar-label=\"${2:AM}\">",
20
+ " <template v-for=\"(item, i) in ${3:items}\" :key=\"`s\\${i}`\" #[`item-\\${i}-summary`]>{{ item.${4:question} }}</template>",
21
+ " <template v-for=\"(item, i) in ${3:items}\" :key=\"`c\\${i}`\" #[`item-\\${i}-content`]><p>{{ item.${5:answer} }}</p></template>",
22
+ "</AccordionChat>"
23
+ ]
24
+ },
25
+ "SRCDEV AccordionChat CSS Override": {
26
+ "description": "AccordionChat colour and type tokens scoped to a page or section class",
27
+ "scope": "css",
28
+ "body": [
29
+ ".${1:faq-section} {",
30
+ " --accordion-chat-question-surface: ${2:#ececec};",
31
+ " --accordion-chat-question-colour: ${3:#1d1d1f};",
32
+ " --accordion-chat-question-surface-open: ${4:#1d1d1f};",
33
+ " --accordion-chat-question-colour-open: ${5:#ffffff};",
34
+ " --accordion-chat-answer-surface: ${6:#ffffff};",
35
+ " --accordion-chat-answer-border: ${7:#e6e4e1};",
36
+ " --accordion-chat-answer-colour: ${8:#2a2a2c};",
37
+ " --accordion-chat-avatar-surface: ${9:#1d1d1f};",
38
+ " --accordion-chat-avatar-colour: ${10:#ffffff};",
39
+ "}"
40
+ ]
41
+ }
42
+ }
@@ -0,0 +1,38 @@
1
+ {
2
+ "SRCDEV AccordionGroup": {
3
+ "description": "AccordionGroup with item-{n}-summary/content slots; a shared name makes it exclusive-open",
4
+ "scope": "vue,html",
5
+ "body": [
6
+ "<AccordionGroup name=\"${1:faq}\">",
7
+ " <template #item-0-summary>${2:Question one?}</template>",
8
+ " <template #item-0-content><p>${3:Answer one.}</p></template>",
9
+ "",
10
+ " <template #item-1-summary>${4:Question two?}</template>",
11
+ " <template #item-1-content><p>${5:Answer two.}</p></template>",
12
+ "</AccordionGroup>"
13
+ ]
14
+ },
15
+ "SRCDEV AccordionGroup Looped": {
16
+ "description": "AccordionGroup driven by an array via dynamic item-{n} slot names",
17
+ "scope": "vue,html",
18
+ "body": [
19
+ "<AccordionGroup name=\"${1:faq}\">",
20
+ " <template v-for=\"(item, i) in ${2:items}\" :key=\"`s\\${i}`\" #[`item-\\${i}-summary`]>{{ item.${3:question} }}</template>",
21
+ " <template v-for=\"(item, i) in ${2:items}\" :key=\"`c\\${i}`\" #[`item-\\${i}-content`]><p>{{ item.${4:answer} }}</p></template>",
22
+ "</AccordionGroup>"
23
+ ]
24
+ },
25
+ "SRCDEV AccordionGroup CSS Override": {
26
+ "description": "AccordionGroup width and panel divider overrides scoped to a page or section class",
27
+ "scope": "css",
28
+ "body": [
29
+ ".${1:faq-section} {",
30
+ " --accordion-group-max-width: ${2:72rem};",
31
+ "",
32
+ " .accordion-group-item:not(:last-child) {",
33
+ " border-block-end: 0.1rem solid ${3:var(--theme-border)};",
34
+ " }",
35
+ "}"
36
+ ]
37
+ }
38
+ }
@@ -20,8 +20,8 @@
20
20
  "scope": "vue,html",
21
21
  "body": [
22
22
  "<ActionMenu>",
23
- " <template v-for=\"($1action, $2i) in $3actions\" :key=\"$1action.label\" #[`item-${$2i}`]>",
24
- " <ActionMenuItem :label=\"$1action.label\" :href=\"$1action.href\" @click=\"$1action.onClick\" />",
23
+ " <template v-for=\"(action, i) in ${1:actions}\" :key=\"`\\${i}-\\${action.label}`\" #[`item-\\${i}`]>",
24
+ " <ActionMenuItem :label=\"action.label\" :href=\"action.href\" @click=\"action.onClick\" />",
25
25
  " </template>",
26
26
  "</ActionMenu>"
27
27
  ]
@@ -30,7 +30,7 @@
30
30
  "description": "CSS override scaffold for ActionMenu tokens",
31
31
  "scope": "css",
32
32
  "body": [
33
- ".$1my-page {",
33
+ ".${1:my-page} {",
34
34
  " --action-menu-trigger-icon-color: $2;",
35
35
  " --action-menu-popover-background: $3;",
36
36
  " --action-menu-popover-border: $4;",
@@ -3,7 +3,11 @@
3
3
  "description": "CaptureQrCode live camera scanner with localisable copy",
4
4
  "scope": "vue,html",
5
5
  "body": [
6
- "<CaptureQrCode camera-stopped-label=\"${1:Camera stopped}\" reset-camera-label=\"${2:Reset camera}\" />"
6
+ "<CaptureQrCode",
7
+ " camera-stopped-label=\"${1:Camera stopped}\"",
8
+ " reset-camera-label=\"${2:Reset camera}\"",
9
+ " :error-messages=\"{ NotAllowedError: '${3:Camera access was blocked.}', default: '${4:The camera could not be started.}' }\"",
10
+ "/>"
7
11
  ]
8
12
  },
9
13
  "SRCDEV CaptureQrCode Custom Error": {
@@ -22,7 +26,12 @@
22
26
  "description": "DecodeQrCode image upload / drop decoder",
23
27
  "scope": "vue,html",
24
28
  "body": [
25
- "<DecodeQrCode upload-label=\"${1:Upload a QR code image}\" drop-label=\"${2:Or drop a QR code image here}\" />"
29
+ "<DecodeQrCode",
30
+ " upload-label=\"${1:Upload a QR code image}\"",
31
+ " drop-label=\"${2:Or drop a QR code image here}\"",
32
+ " no-result-label=\"${3:No QR code was found in that image.}\"",
33
+ " read-error-label=\"${4:That file couldn't be read as an image.}\"",
34
+ "/>"
26
35
  ]
27
36
  }
28
37
  }
@@ -3,7 +3,7 @@
3
3
  "description": "CarouselFlip with looped item slots",
4
4
  "scope": "vue,html",
5
5
  "body": [
6
- "<CarouselFlip :carousel-data-ids=\"$1items.map((item) => item.id)\">",
6
+ "<CarouselFlip :carousel-data-ids=\"$1items.map((item) => item.id)\" aria-label=\"${4:Image carousel}\">",
7
7
  " <template v-for=\"$2item in $1items\" :key=\"$2item.id\" #[$2item.id]>",
8
8
  " $3",
9
9
  " </template>",
@@ -25,15 +25,33 @@
25
25
  "</CarouselFlip>"
26
26
  ]
27
27
  },
28
+ "SRCDEV CarouselFlip Track Edges": {
29
+ "description": "CarouselFlip with prev/next overlaid on the ends of the visible track",
30
+ "scope": "vue,html",
31
+ "body": [
32
+ "<CarouselFlip",
33
+ " :carousel-data-ids=\"$1items.map((item) => item.id)\"",
34
+ " button-layout=\"track-edges\"",
35
+ " :allow-carousel-overflow=\"true\"",
36
+ ">",
37
+ " <template v-for=\"$2item in $1items\" :key=\"$2item.id\" #[$2item.id]>",
38
+ " $3",
39
+ " </template>",
40
+ "</CarouselFlip>"
41
+ ]
42
+ },
28
43
  "SRCDEV CarouselFlip CSS Override": {
29
44
  "description": "CSS override scaffold for CarouselFlip tokens",
30
45
  "scope": "css",
31
46
  "body": [
32
47
  ".$1my-page {",
33
48
  " .carousel-flip {",
34
- " --carousel-flip-gap: $2;",
35
- " --carousel-flip-marker-active-colour: $3;",
36
- " --carousel-flip-button-background-colour: $4;",
49
+ " --carousel-flip-gap: ${2:1rem};",
50
+ " --carousel-flip-marker-active-colour: ${3:var(--theme-surface)};",
51
+ " --carousel-flip-button-background-colour: ${4:var(--theme-surface-subtle)};",
52
+ " --carousel-flip-button-icon-colour: ${5:var(--theme-text)};",
53
+ " --carousel-flip-button-align: ${6:center};",
54
+ " --carousel-flip-button-inline-offset: ${7:0px};",
37
55
  " }",
38
56
  "}"
39
57
  ]
@@ -1,9 +1,39 @@
1
1
  {
2
2
  "SRCDEV DeepExpandingMenuClassic Basic": {
3
- "description": "DeepExpandingMenuClassic — <details>-based fallback nav",
3
+ "description": "DeepExpandingMenuClassic — <details>-based nav",
4
4
  "scope": "vue,html",
5
+ "prefix": "srcdev-deep-expanding-menu-classic",
5
6
  "body": [
6
- "<DeepExpandingMenuClassic :nav-links=\"$1navLinks\" />"
7
+ "<DeepExpandingMenuClassic :nav-links=\"${1:navLinks}\" aria-label=\"${2:Main}\" />"
8
+ ]
9
+ },
10
+ "SRCDEV DeepExpandingMenuClassic Nav Links": {
11
+ "description": "DeepExpandingMenuClassic — navLinks data (ResponsiveHeaderNavItem[])",
12
+ "scope": "typescript,vue",
13
+ "prefix": "srcdev-deep-expanding-menu-classic-links",
14
+ "body": [
15
+ "const ${1:navLinks}: ResponsiveHeaderNavItem[] = [",
16
+ " { name: \"${2:Home}\", path: \"${3:/}\" },",
17
+ " {",
18
+ " name: \"${4:Services}\",",
19
+ " childLinksTitle: \"${5:Our services}\",",
20
+ " childLinks: [",
21
+ " { name: \"${6:Haircuts}\", path: \"${7:/services/haircuts}\" },",
22
+ " ],",
23
+ " },",
24
+ "];"
25
+ ]
26
+ },
27
+ "SRCDEV DeepExpandingMenuClassic Style Override": {
28
+ "description": "DeepExpandingMenuClassic — unlayered token override scaffold",
29
+ "scope": "css,vue",
30
+ "prefix": "srcdev-deep-expanding-menu-classic-css",
31
+ "body": [
32
+ ".${1:site-nav} {",
33
+ " --deep-expanding-menu-classic-item-gap: ${2:2.4rem};",
34
+ " --deep-expanding-menu-classic-panel-background-colour: ${3:var(--theme-surface-subtle)};",
35
+ " --deep-expanding-menu-classic-panel-heading-line-clamp: ${4:none};",
36
+ "}"
7
37
  ]
8
38
  }
9
39
  }
@@ -2,8 +2,38 @@
2
2
  "SRCDEV DeepExpandingMenu Basic": {
3
3
  "description": "DeepExpandingMenu — anchor-positioned popover nav panels",
4
4
  "scope": "vue,html",
5
+ "prefix": "srcdev-deep-expanding-menu",
5
6
  "body": [
6
- "<DeepExpandingMenu :nav-links=\"$1navLinks\" />"
7
+ "<DeepExpandingMenu :nav-links=\"${1:navLinks}\" aria-label=\"${2:Main}\" />"
8
+ ]
9
+ },
10
+ "SRCDEV DeepExpandingMenu Nav Links": {
11
+ "description": "DeepExpandingMenu — navLinks data (ResponsiveHeaderNavItem[])",
12
+ "scope": "typescript,vue",
13
+ "prefix": "srcdev-deep-expanding-menu-links",
14
+ "body": [
15
+ "const ${1:navLinks}: ResponsiveHeaderNavItem[] = [",
16
+ " { name: \"${2:Home}\", path: \"${3:/}\" },",
17
+ " {",
18
+ " name: \"${4:Services}\",",
19
+ " childLinksTitle: \"${5:Our services}\",",
20
+ " childLinks: [",
21
+ " { name: \"${6:Haircuts}\", path: \"${7:/services/haircuts}\" },",
22
+ " ],",
23
+ " },",
24
+ "];"
25
+ ]
26
+ },
27
+ "SRCDEV DeepExpandingMenu Style Override": {
28
+ "description": "DeepExpandingMenu — unlayered token override scaffold",
29
+ "scope": "css,vue",
30
+ "prefix": "srcdev-deep-expanding-menu-css",
31
+ "body": [
32
+ ".${1:site-nav} {",
33
+ " --deep-expanding-menu-gap: ${2:2.4rem};",
34
+ " --deep-expanding-menu-panel-background-colour: ${3:var(--theme-surface-subtle)};",
35
+ " --deep-expanding-menu-panel-heading-line-clamp: ${4:none};",
36
+ "}"
7
37
  ]
8
38
  }
9
39
  }
@@ -48,13 +48,14 @@
48
48
  ]
49
49
  },
50
50
  "SRCDEV SamaritanPrompt Tokens": {
51
- "description": "SamaritanPrompt / SamaritanPromptMixed CSS token overrides",
51
+ "description": "SamaritanPrompt / SamaritanPromptMixed CSS token overrides (classic white-on-black look)",
52
52
  "scope": "css,vue",
53
53
  "body": [
54
54
  "--samaritan-prompt-font-size: ${1:2rem};",
55
- "--samaritan-prompt-text-colour: ${2:var(--theme-text)};",
56
- "--samaritan-prompt-underline-colour: ${3:var(--theme-text)};",
57
- "--samaritan-prompt-cursor-colour: ${4:#cc0000};"
55
+ "--samaritan-prompt-text-colour: ${2:#fff};",
56
+ "--samaritan-prompt-underline-colour: ${3:#fff};",
57
+ "--samaritan-prompt-cursor-colour: ${4:#cc0000};",
58
+ "--samaritan-prompt-text-line-clamp: ${5:none};"
58
59
  ]
59
60
  }
60
61
  }
@@ -14,6 +14,18 @@
14
14
  "</SiteHeader>"
15
15
  ]
16
16
  },
17
+ "SRCDEV SiteHeader Sticky Tokens": {
18
+ "description": "SiteHeader sticky + layout token overrides (set on the styleClassPassthrough class)",
19
+ "scope": "css",
20
+ "body": [
21
+ ".${1:header} {",
22
+ " --site-header-position: ${2:sticky};",
23
+ " --site-header-sticky-offset: ${3:0px};",
24
+ " --site-header-bg: ${4:var(--theme-surface-subtle)};",
25
+ " --site-header-branding-max-width: ${5:50%};",
26
+ "}"
27
+ ]
28
+ },
17
29
  "SRCDEV SiteHeader With Secondary Navigation": {
18
30
  "description": "SiteHeader with the secondaryNavigation slot (e.g. a settings icon link)",
19
31
  "scope": "vue,html",
@@ -36,7 +48,7 @@
36
48
  "body": [
37
49
  "<SiteHeader",
38
50
  " :responsive-nav-links=\"$1responsiveNavLinks\"",
39
- " home-nav-aria-label=\"${2:Home Navigation}\"",
51
+ " home-nav-aria-label=\"${2:Home navigation}\"",
40
52
  " main-nav-aria-label=\"${3:Main navigation}\"",
41
53
  " secondary-nav-aria-label=\"${4:Secondary navigation}\"",
42
54
  " overflow-menu-aria-label=\"${5:Overflow navigation menu}\"",
@@ -0,0 +1,33 @@
1
+ {
2
+ "SRCDEV SparkleEffect Button": {
3
+ "description": "SparkleEffect around a pill button (sparkles along its border on hover/focus)",
4
+ "scope": "vue,html",
5
+ "body": [
6
+ "<SparkleEffect :count=\"${1:5}\"${4| continuous,|}>",
7
+ " <InputButton button-text=\"${2:Button text}\" variant=\"${3|primary,secondary,tertiary|}\" is-pill />",
8
+ "</SparkleEffect>"
9
+ ]
10
+ },
11
+ "SRCDEV SparkleEffect Card": {
12
+ "description": "SparkleEffect around block content with its own corner radius",
13
+ "scope": "vue,html",
14
+ "body": [
15
+ "<SparkleEffect tag=\"div\" radius=\"${1:1.2rem}\" :count=\"${2:7}\">",
16
+ " $0",
17
+ "</SparkleEffect>"
18
+ ]
19
+ },
20
+ "SRCDEV SparkleEffect CSS Override": {
21
+ "description": "SparkleEffect token overrides scoped to a page or section class",
22
+ "scope": "css,vue",
23
+ "body": [
24
+ ".${1:page-name} {",
25
+ " --sparkle-effect-colour: ${2:hsl(260 80% 70%)};",
26
+ " --sparkle-effect-size: ${3:0.7rem};",
27
+ " --sparkle-effect-blur: ${6:1px};",
28
+ " --sparkle-effect-duration: ${4:1.5s};",
29
+ " --sparkle-effect-glow-opacity: ${5:0.5};",
30
+ "}"
31
+ ]
32
+ }
33
+ }
@@ -0,0 +1,75 @@
1
+ # SparkleEffect — Consumer Styling Guide
2
+
3
+ ## Public token API
4
+
5
+ | Token | Default | Description |
6
+ |---|---|---|
7
+ | `--sparkle-effect-colour` | `var(--theme-border)` | Glow colour, and the base hue the sparkles shift from. With relative colour support each sparkle is a lightened, hue-shifted version of it; without, they use it as-is. |
8
+ | `--sparkle-effect-size` | `0.7rem` | Width of one sparkle arm (each arm is 1:3, and each sparkle scales this by its preset). |
9
+ | `--sparkle-effect-blur` | `1px` | Blur on each sparkle. `0` gives crisp sparkles; the original pen used `3px`. |
10
+ | `--sparkle-effect-duration` | `1.5s` | Base travel time; each sparkle multiplies it by its own speed. |
11
+ | `--sparkle-effect-radius` | `100vw` | Corner shape of the path the sparkles follow and of the glow. Match the slotted element's radius. Also set by the `radius` prop. |
12
+ | `--sparkle-effect-glow-blur` | `1.2rem` | Blur of the glow behind the slot. |
13
+ | `--sparkle-effect-glow-opacity` | `0.5` | Glow opacity while active. |
14
+
15
+ Private tokens, not public API: `--_sparkle-effect-colour` (the resolved colour) and the per-sparkle
16
+ `--_sparkle-effect-spark-*` values (colour, scale, translate, offset, speed, timing), which
17
+ the component writes inline on each sparkle from its preset table.
18
+
19
+ ## State hooks
20
+
21
+ | Selector | Meaning |
22
+ |---|---|
23
+ | `.sparkle-effect` | Root element (`span` or `div`, per `tag`). |
24
+ | `.sparkle-effect::before` | The glow, behind the slot. |
25
+ | `.sparkle-effect-sparks` | `aria-hidden` layer holding the sparkles, over the slot. |
26
+ | `.sparkle-effect-spark` | One sparkle. |
27
+ | `.sparkle-effect[data-continuous]` | Set by the `continuous` prop: sparkles loop while active. |
28
+
29
+ The effect runs on `.sparkle-effect:hover` and when anything inside has `:focus-visible`. It's
30
+ suppressed while the slotted element (direct child) is `:disabled` or `[aria-disabled="true"]`.
31
+
32
+ ## Motion and browser support
33
+
34
+ - Sparkles travel along the wrapper's border box with `offset-path: border-box`. They render only
35
+ where that's supported (Chrome 116+, Safari 17.2+, Firefox 122+). Elsewhere, including Safari
36
+ 16, the glow still works.
37
+ - Under `prefers-reduced-motion: reduce` the sparkles are hidden and only the glow fades in.
38
+ - Sparkles start at the top-left of the path and snap back when the pointer leaves, so each hover
39
+ replays the effect. With `continuous`, each sparkle loops instead (`sparkle-effect-travel` keyframes:
40
+ travel, drift and fade in one), at its own speed, until the hover or focus ends.
41
+
42
+ ## Shape
43
+
44
+ The wrapper shrinks to its slot (`inline-grid` for `span`, `grid` for `div`) and can't read the
45
+ slotted element's own `border-radius`, so set `--sparkle-effect-radius` (or `radius`) to match it:
46
+ the default `100vw` suits pill buttons, a card might need `1.2rem`, a round icon button `50%`.
47
+
48
+ ## Local overrides
49
+
50
+ Set the tokens above on an element you own (a page or section class, or a class added with
51
+ `:style-class-passthrough`): they inherit down into the component. Keep the block **unlayered** (no
52
+ `@layer` wrapper) so it beats the library's `@layer components`. If your own file uses
53
+ `<style scoped>`, tokens set on your element still work, but selectors that reach inside the component need
54
+ `:deep()`. Patterns and examples: `.claude/skills/component-local-style-override.md`.
55
+
56
+ **Caveat:** the `radius` prop writes `--sparkle-effect-radius` inline on the root, so when it's set
57
+ an ancestor's value for that token never lands.
58
+
59
+ ## Class passthrough
60
+
61
+ `styleClassPassthrough` adds classes to the root, for sizing or placing the wrapper (e.g.
62
+ `inline-size: 100%` around a full-width card) or for setting the tokens above on one instance.
63
+
64
+ ```vue
65
+ <SparkleEffect style-class-passthrough="promo-sparkle">
66
+ <InputButton button-text="Join the waitlist" is-pill />
67
+ </SparkleEffect>
68
+ ```
69
+
70
+ ```css
71
+ .promo-sparkle {
72
+ --sparkle-effect-colour: hsl(260 80% 70%);
73
+ --sparkle-effect-duration: 2s;
74
+ }
75
+ ```