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
@@ -0,0 +1,150 @@
1
+ # AccordionChat — Consumer Styling Guide
2
+
3
+ Every visual property is a token. The defaults take colours from the `--theme-*` slots; the
4
+ Storybook **Default** and **Dark Theme** stories are two complete token sets for the same markup, to
5
+ copy from.
6
+
7
+ ## Public token API
8
+
9
+ ### Layout and shape (resolved on `.accordion-chat`)
10
+
11
+ | Token | Default | Controls |
12
+ |---|---|---|
13
+ | `--accordion-chat-gap` | `1.4rem` | Space between panels |
14
+ | `--accordion-chat-answer-gap` | `1rem` | Space between a question and its answer |
15
+ | `--accordion-chat-bubble-max-width` | `82%` | Maximum width of both bubbles |
16
+ | `--accordion-chat-radius` | `2rem` | Bubble corner radius |
17
+ | `--accordion-chat-tail-radius` | `0.6rem` | The "tail" corner: bottom-end of the question, bottom-start of the answer (logical, so it flips in RTL) |
18
+ | `--accordion-chat-transition-duration` | `200ms` | Question colour change; the icon rotation takes 1.25× this |
19
+ | `--accordion-chat-entrance-duration` | `260ms` | Answer bubble fade-and-settle when it opens |
20
+ | `--accordion-chat-focus-ring-width` | `0.2rem` | Question focus outline width |
21
+ | `--accordion-chat-focus-ring-colour` | `var(--theme-border-focus)` | Question focus outline colour |
22
+
23
+ ### Question bubble (the panel's `summary`)
24
+
25
+ | Token | Default | Controls |
26
+ |---|---|---|
27
+ | `--accordion-chat-question-surface` | `var(--theme-surface-subtle)` | Background, closed |
28
+ | `--accordion-chat-question-colour` | `var(--theme-text)` | Text, closed |
29
+ | `--accordion-chat-question-border` | `transparent` | Border colour, closed |
30
+ | `--accordion-chat-question-surface-open` | `var(--theme-surface-inverted)` | Background, open |
31
+ | `--accordion-chat-question-colour-open` | `var(--theme-text-inverted)` | Text, open |
32
+ | `--accordion-chat-question-border-open` | `var(--accordion-chat-question-border)` | Border colour, open |
33
+ | `--accordion-chat-question-border-width` | `0.1rem` | Border width |
34
+ | `--accordion-chat-question-font-family` | `inherit` | Font family |
35
+ | `--accordion-chat-question-font-size` | `1.6rem` | Font size |
36
+ | `--accordion-chat-question-font-weight` | `600` | Font weight |
37
+ | `--accordion-chat-question-line-height` | `1.3` | Line height |
38
+ | `--accordion-chat-question-padding-block` | `1.3rem` | Vertical padding |
39
+ | `--accordion-chat-question-padding-inline` | `2rem 1.8rem` | Start and end padding |
40
+ | `--accordion-chat-question-icon-gap` | `1.4rem` | Space between the text and the icon |
41
+
42
+ ### Icon (default `+` that turns to `×` when open)
43
+
44
+ | Token | Default | Controls |
45
+ |---|---|---|
46
+ | `--accordion-chat-icon-size` | `2.2rem` | Icon circle size |
47
+ | `--accordion-chat-icon-glyph-size` | `1.4rem` | The `+` glyph size |
48
+ | `--accordion-chat-icon-surface` | `rgb(0 0 0 / 0.07)` | Circle fill, closed |
49
+ | `--accordion-chat-icon-surface-open` | `rgb(255 255 255 / 0.16)` | Circle fill, open |
50
+
51
+ The glyph takes the question's text colour.
52
+
53
+ ### Answer bubble
54
+
55
+ | Token | Default | Controls |
56
+ |---|---|---|
57
+ | `--accordion-chat-answer-surface` | `var(--theme-text-inverted)` | Background (the lightest theme step) |
58
+ | `--accordion-chat-answer-colour` | `var(--theme-text)` | Text |
59
+ | `--accordion-chat-answer-border` | `var(--theme-surface-subtle)` | Border colour |
60
+ | `--accordion-chat-answer-border-width` | `0.1rem` | Border width |
61
+ | `--accordion-chat-answer-font-family` | `inherit` | Font family |
62
+ | `--accordion-chat-answer-font-size` | `1.55rem` | Font size |
63
+ | `--accordion-chat-answer-font-weight` | `400` | Font weight |
64
+ | `--accordion-chat-answer-line-height` | `1.6` | Line height |
65
+ | `--accordion-chat-answer-padding-block` | `1.4rem` | Vertical padding |
66
+ | `--accordion-chat-answer-padding-inline` | `1.8rem` | Horizontal padding |
67
+
68
+ ### Avatar
69
+
70
+ | Token | Default | Controls |
71
+ |---|---|---|
72
+ | `--accordion-chat-avatar-size` | `3rem` | Circle size |
73
+ | `--accordion-chat-avatar-gap` | `1rem` | Space between avatar and bubble |
74
+ | `--accordion-chat-avatar-surface` | `var(--theme-surface-inverted)` | Circle fill |
75
+ | `--accordion-chat-avatar-colour` | `var(--theme-text-inverted)` | Initials colour |
76
+ | `--accordion-chat-avatar-font-family` | `inherit` | Initials font |
77
+ | `--accordion-chat-avatar-font-size` | `1.2rem` | Initials size |
78
+ | `--accordion-chat-avatar-font-weight` | `600` | Initials weight |
79
+
80
+ Private (not public API): `--_accordion-chat-radius`, `--_accordion-chat-tail-radius`,
81
+ `--_accordion-chat-bubble-max-width` and `--_accordion-chat-duration`, each read by several rules.
82
+
83
+ ## State hooks
84
+
85
+ | Selector | Meaning |
86
+ |---|---|
87
+ | `.accordion-chat` | Root |
88
+ | `.accordion-chat-item` | Each panel (an `ExpandingPanelClassic` root) |
89
+ | `.accordion-chat-item .expanding-panel-classic-details[open]` | That panel is open |
90
+ | `.accordion-chat-item .expanding-panel-classic-summary` | Question bubble |
91
+ | `.accordion-chat-icon`, `.accordion-chat-icon-glyph` | Default icon circle and glyph |
92
+ | `.accordion-chat-answer` | Answer row (avatar + bubble), inside the panel's `.inner` |
93
+ | `.accordion-chat-avatar` | Avatar circle (`aria-hidden`) |
94
+ | `.accordion-chat-bubble` | Answer bubble |
95
+
96
+ ## Motion
97
+
98
+ Opening animates the row height (ExpandingPanelClassic, `grid-template-rows`, every browser), then
99
+ the answer bubble fades in and settles from a slight offset. Under `prefers-reduced-motion: reduce`
100
+ the entrance animation and the icon rotation's transition are off; colours still change.
101
+
102
+ ## Local overrides
103
+
104
+ Set the tokens above on an element you own (a page or section class, or a class added with
105
+ `:style-class-passthrough`): they inherit down into the component. Keep the block **unlayered** (no
106
+ `@layer` wrapper) so it beats the library's `@layer components`. If your own file uses
107
+ `<style scoped>`, tokens set on your element still work, but selectors that reach inside the component need
108
+ `:deep()`. Patterns and examples: `.claude/skills/component-local-style-override.md`.
109
+
110
+ **Caveat:** each panel re-declares `--expanding-panel-classic-icon-size` and
111
+ `--expanding-panel-classic-summary-gap` from `--accordion-chat-icon-size` and
112
+ `--accordion-chat-question-icon-gap`, so set those `--accordion-chat-*` tokens; an ancestor's
113
+ `--expanding-panel-classic-*` value for them never lands.
114
+
115
+ ## Recipe: dark, serif theme
116
+
117
+ ```css
118
+ .faq-section {
119
+ --accordion-chat-question-font-family: "Playfair Display", Georgia, serif;
120
+ --accordion-chat-question-font-size: 1.9rem;
121
+ --accordion-chat-question-font-weight: 400;
122
+ --accordion-chat-question-surface: #1a1615;
123
+ --accordion-chat-question-colour: #f2ece2;
124
+ --accordion-chat-question-border: #2b2422;
125
+ --accordion-chat-question-surface-open: #c9a961;
126
+ --accordion-chat-question-colour-open: #14100f;
127
+ --accordion-chat-question-border-open: #c9a961;
128
+ --accordion-chat-icon-surface: rgb(255 255 255 / 0.08);
129
+ --accordion-chat-icon-surface-open: rgb(0 0 0 / 0.12);
130
+ --accordion-chat-answer-surface: #1a1615;
131
+ --accordion-chat-answer-border: #2b2422;
132
+ --accordion-chat-answer-colour: #cbbfb4;
133
+ --accordion-chat-answer-font-weight: 300;
134
+ --accordion-chat-answer-line-height: 1.7;
135
+ --accordion-chat-avatar-surface: #c9a961;
136
+ --accordion-chat-avatar-colour: #14100f;
137
+ --accordion-chat-focus-ring-colour: #c9a961;
138
+ }
139
+ ```
140
+
141
+ ## Class passthrough
142
+
143
+ `style-class-passthrough` adds classes to the root `.accordion-chat` (reactive after mount). Panels
144
+ always get `.accordion-chat-item`.
145
+
146
+ ## Notes
147
+
148
+ - The answer bubble is a wrapper inside the panel's `#content` slot. Never style the panel's
149
+ `.inner` (it must keep `overflow: hidden` and no padding for the row-height animation).
150
+ - Images and videos inside an answer are capped at the bubble's width.
@@ -0,0 +1,347 @@
1
+ import { computed, ref } from "vue";
2
+ import type { Meta, StoryObj } from "@nuxtjs/storybook";
3
+ import AccordionChat from "../AccordionChat.vue";
4
+ import CanvasSwitcher from "../../../../01.atoms/canvas-switcher/CanvasSwitcher.vue";
5
+ import type { MediaCanvas } from "~/types/components";
6
+
7
+ interface StoryArgs {
8
+ name?: string;
9
+ animationDuration?: number;
10
+ avatarLabel?: string;
11
+ showAvatar?: boolean;
12
+ radius?: string;
13
+ bubbleMaxWidth?: string;
14
+ gap?: string;
15
+ styleClassPassthrough?: string | string[];
16
+ }
17
+
18
+ const tokenCategory = "CSS tokens (story only, set in your CSS)";
19
+
20
+ const meta: Meta<StoryArgs> = {
21
+ title: "Molecules/Expandable/AccordionChat",
22
+ component: AccordionChat,
23
+ argTypes: {
24
+ name: {
25
+ control: { type: "text" },
26
+ description: "Shared `name` on every panel's `<details>`: only one answer open at a time. Leave empty for independent panels.",
27
+ },
28
+ animationDuration: {
29
+ control: { type: "number", min: 0, step: 50 },
30
+ description: "Open/close (row height) animation in milliseconds, passed to every panel.",
31
+ },
32
+ avatarLabel: {
33
+ control: { type: "text" },
34
+ description: "Initials in the avatar circle (first two characters). Used when no `#avatar` or `item-{n}-avatar` slot is filled.",
35
+ },
36
+ showAvatar: {
37
+ control: { type: "boolean" },
38
+ description: "`false` removes the avatar column entirely, even when an avatar slot is filled.",
39
+ },
40
+ radius: {
41
+ control: { type: "select" },
42
+ options: ["0.8rem", "1.2rem", "2rem", "3rem"],
43
+ description: "**Story control, not a prop.** Sets `--accordion-chat-radius`. In your CSS: `.faq { --accordion-chat-radius: 1.2rem; }`",
44
+ table: { category: tokenCategory },
45
+ },
46
+ bubbleMaxWidth: {
47
+ control: { type: "select" },
48
+ options: ["60%", "82%", "100%"],
49
+ description: "**Story control, not a prop.** Sets `--accordion-chat-bubble-max-width`. In your CSS: `.faq { --accordion-chat-bubble-max-width: 70%; }`",
50
+ table: { category: tokenCategory },
51
+ },
52
+ gap: {
53
+ control: { type: "select" },
54
+ options: ["0.8rem", "1.4rem", "2.4rem"],
55
+ description: "**Story control, not a prop.** Sets `--accordion-chat-gap`. In your CSS: `.faq { --accordion-chat-gap: 2rem; }`",
56
+ table: { category: tokenCategory },
57
+ },
58
+ styleClassPassthrough: { table: { disable: true } },
59
+ },
60
+ args: {
61
+ name: "",
62
+ animationDuration: 300,
63
+ avatarLabel: "S",
64
+ showAvatar: true,
65
+ radius: "2rem",
66
+ bubbleMaxWidth: "82%",
67
+ gap: "1.4rem",
68
+ },
69
+ parameters: {
70
+ docs: {
71
+ description: {
72
+ component:
73
+ "An accordion drawn as a chat: each `item-{n}-summary` slot is a question bubble on the right, and its `item-{n}-content` slot is the " +
74
+ "answer bubble on the left, which can hold any rich content. The avatar beside each answer comes from `item-{n}-avatar`, then the " +
75
+ "group `#avatar` slot, then `avatarLabel`; with none of them there's no avatar column. `name` makes it exclusive-open. Built on " +
76
+ "[ExpandingPanelClassic](?path=/story/molecules-expandable-expanding-panel-classic--default), so it opens and closes, and animates, in every browser. " +
77
+ "Every colour, size, radius, font and duration is a `--accordion-chat-*` token: the Default and Dark Theme stories are two token sets for the same markup.",
78
+ },
79
+ },
80
+ },
81
+ };
82
+
83
+ export default meta;
84
+ type Story = StoryObj<StoryArgs>;
85
+
86
+ const richItems = [
87
+ {
88
+ q: "Delivery & Returns",
89
+ a: "<p style='margin: 0;'>Free standard delivery on orders over £50. Returns accepted within 30 days.</p>",
90
+ },
91
+ {
92
+ q: "Materials & Care",
93
+ a:
94
+ "<ul style='margin: 0; padding-inline-start: 1.8rem;'><li>100% organic cotton</li><li>Machine wash at 30°C</li><li>Do not tumble dry</li></ul>",
95
+ },
96
+ {
97
+ q: "Sizing Guide",
98
+ a: "<p style='margin: 0;'>Our pieces run true to size. If you're between sizes, we recommend sizing up.</p>",
99
+ },
100
+ ];
101
+
102
+ function useStorySetup(args: StoryArgs) {
103
+ const componentArgs = computed(() => ({
104
+ name: args.name || undefined,
105
+ animationDuration: args.animationDuration,
106
+ avatarLabel: args.avatarLabel,
107
+ showAvatar: args.showAvatar,
108
+ }));
109
+ const tokenStyle = computed(() => ({
110
+ "--accordion-chat-radius": args.radius,
111
+ "--accordion-chat-bubble-max-width": args.bubbleMaxWidth,
112
+ "--accordion-chat-gap": args.gap,
113
+ }));
114
+ return { componentArgs, tokenStyle, items: richItems };
115
+ }
116
+
117
+ const itemsTemplate = `
118
+ <template v-for="(item, i) in items" :key="'s' + i" #[\`item-\${i}-summary\`]>{{ item.q }}</template>
119
+ <template v-for="(item, i) in items" :key="'c' + i" #[\`item-\${i}-content\`]><div v-html="item.a"></div></template>
120
+ `;
121
+
122
+ const stage = "padding: 3.2rem 2.4rem; max-inline-size: 64rem; margin-inline: auto;";
123
+
124
+ const lightTokens = {
125
+ background: "#f9f9f9",
126
+ "--accordion-chat-question-surface": "#ececec",
127
+ "--accordion-chat-question-colour": "#1d1d1f",
128
+ "--accordion-chat-question-surface-open": "#1d1d1f",
129
+ "--accordion-chat-question-colour-open": "#ffffff",
130
+ "--accordion-chat-answer-surface": "#ffffff",
131
+ "--accordion-chat-answer-border": "#e6e4e1",
132
+ "--accordion-chat-answer-colour": "#2a2a2c",
133
+ "--accordion-chat-avatar-surface": "#1d1d1f",
134
+ "--accordion-chat-avatar-colour": "#ffffff",
135
+ };
136
+
137
+ const darkTokens = {
138
+ background: "#0f0e0d",
139
+ "--accordion-chat-question-font-family": "'Playfair Display', Georgia, serif",
140
+ "--accordion-chat-question-font-size": "1.9rem",
141
+ "--accordion-chat-question-font-weight": "400",
142
+ "--accordion-chat-question-surface": "#1a1615",
143
+ "--accordion-chat-question-colour": "#f2ece2",
144
+ "--accordion-chat-question-border": "#2b2422",
145
+ "--accordion-chat-question-surface-open": "#c9a961",
146
+ "--accordion-chat-question-colour-open": "#14100f",
147
+ "--accordion-chat-question-border-open": "#c9a961",
148
+ "--accordion-chat-icon-surface": "rgb(255 255 255 / 0.08)",
149
+ "--accordion-chat-icon-surface-open": "rgb(0 0 0 / 0.12)",
150
+ "--accordion-chat-answer-surface": "#1a1615",
151
+ "--accordion-chat-answer-border": "#2b2422",
152
+ "--accordion-chat-answer-colour": "#cbbfb4",
153
+ "--accordion-chat-answer-font-family": "Poppins, system-ui, sans-serif",
154
+ "--accordion-chat-answer-font-weight": "300",
155
+ "--accordion-chat-answer-font-size": "1.45rem",
156
+ "--accordion-chat-answer-line-height": "1.7",
157
+ "--accordion-chat-avatar-surface": "#c9a961",
158
+ "--accordion-chat-avatar-colour": "#14100f",
159
+ "--accordion-chat-avatar-font-family": "'Playfair Display', Georgia, serif",
160
+ "--accordion-chat-avatar-font-weight": "400",
161
+ "--accordion-chat-avatar-font-size": "1.4rem",
162
+ "--accordion-chat-focus-ring-colour": "#c9a961",
163
+ };
164
+
165
+ const renderWith = (extraStyle: Record<string, string> = {}, slotsTemplate = itemsTemplate) => (args: StoryArgs) => ({
166
+ components: { AccordionChat },
167
+ setup() {
168
+ return { ...useStorySetup(args), extraStyle };
169
+ },
170
+ template: `
171
+ <div style="${stage}" :style="[extraStyle, tokenStyle]">
172
+ <AccordionChat v-bind="componentArgs">${slotsTemplate}</AccordionChat>
173
+ </div>
174
+ `,
175
+ });
176
+
177
+ export const Default: Story = {
178
+ render: renderWith(lightTokens),
179
+ parameters: {
180
+ docs: {
181
+ description: {
182
+ story:
183
+ "The neutral light look: token values set to match the design reference. Without them, the colours come from the `--theme-*` tokens " +
184
+ "(see Theme Defaults). Click a question to open its answer.",
185
+ },
186
+ },
187
+ },
188
+ };
189
+
190
+ export const ThemeDefaults: Story = {
191
+ name: "Theme Defaults (no tokens set)",
192
+ render: renderWith(),
193
+ parameters: {
194
+ docs: { description: { story: "No `--accordion-chat-*` colour tokens set: every colour falls back to the active theme's `--theme-*` slots." } },
195
+ },
196
+ };
197
+
198
+ export const DarkTheme: Story = {
199
+ name: "Dark Theme (tokens only)",
200
+ args: { avatarLabel: "N" },
201
+ render: renderWith(darkTokens),
202
+ parameters: {
203
+ docs: {
204
+ description: {
205
+ story:
206
+ "The same markup with only token overrides: serif questions, gold open state and avatar, light-weight answers on dark bubbles, " +
207
+ "and a question border that turns gold when open. Copy the token set from the story source.",
208
+ },
209
+ },
210
+ },
211
+ };
212
+
213
+ const monogramSvg = `<svg viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg"><rect width="30" height="30" fill="#2f6f5e"></rect><path d="M8 21 L15 8 L22 21 Z" fill="#dce9e2"></path></svg>`;
214
+
215
+ export const LogoAvatar: Story = {
216
+ name: "Logo Avatar (#avatar slot)",
217
+ render: renderWith(lightTokens, `${itemsTemplate}<template #avatar>${monogramSvg}</template>`),
218
+ parameters: {
219
+ docs: { description: { story: "A group-wide `#avatar` slot (here an inline SVG logo; a `NuxtImg` works the same) fills the circle beside every answer, overriding `avatarLabel`." } },
220
+ },
221
+ };
222
+
223
+ export const IconAvatar: Story = {
224
+ name: "Icon Avatar (#avatar slot)",
225
+ render: renderWith(lightTokens, `${itemsTemplate}<template #avatar><Icon name="lucide:sparkles" style="font-size: 1.6rem;" /></template>`),
226
+ };
227
+
228
+ export const NoAvatar: Story = {
229
+ name: "No Avatar",
230
+ args: { avatarLabel: "" },
231
+ render: renderWith(lightTokens),
232
+ parameters: {
233
+ docs: { description: { story: "No avatar slot and an empty `avatarLabel`: no avatar column, and the answer bubble sits flush left. `showAvatar=false` does the same even when a slot is filled." } },
234
+ },
235
+ };
236
+
237
+ export const PerPanelAvatar: Story = {
238
+ name: "Per-Panel Avatar Override",
239
+ render: renderWith(lightTokens, `${itemsTemplate}<template #item-1-avatar><Icon name="lucide:shirt" style="font-size: 1.6rem;" /></template>`),
240
+ parameters: {
241
+ docs: { description: { story: "`item-1-avatar` replaces the avatar for the second answer only; the others keep `avatarLabel`." } },
242
+ },
243
+ };
244
+
245
+ export const ExclusiveOpen: Story = {
246
+ name: "Exclusive Open (shared name)",
247
+ args: { name: "faq" },
248
+ render: renderWith(lightTokens),
249
+ parameters: {
250
+ docs: { description: { story: "With `name` set, opening one question closes the others (native `<details name>` grouping)." } },
251
+ },
252
+ };
253
+
254
+ export const RichContent: Story = {
255
+ name: "Rich Answer Content",
256
+ render: renderWith(
257
+ lightTokens,
258
+ `
259
+ <template #item-0-summary>What does a consultation include?</template>
260
+ <template #item-0-content>
261
+ <p style="margin: 0 0 0.8rem;">A 30-minute chat about your goals, then a written plan. It covers:</p>
262
+ <ol style="margin: 0 0 0.8rem; padding-inline-start: 1.8rem;"><li>Your history and routine</li><li>A patch test</li><li>Pricing for the full course</li></ol>
263
+ <p style="margin: 0;"><a href="#">Book a consultation</a> or call <strong>01234 567890</strong>.</p>
264
+ </template>
265
+ <template #item-1-summary>Where are you?</template>
266
+ <template #item-1-content>
267
+ <p style="margin: 0 0 0.8rem;">12 High Street, Northtown. Here's the studio:</p>
268
+ <img src="/images/rotating-carousel/image-1.webp" alt="The Northtown studio" width="600" height="400" style="border-radius: 1rem; display: block;">
269
+ </template>
270
+ `
271
+ ),
272
+ parameters: {
273
+ docs: { description: { story: "Answers take any markup: paragraphs, lists, links, images (capped to the bubble width)." } },
274
+ },
275
+ };
276
+
277
+ export const StressTest: Story = {
278
+ name: "Stress Test (Worst-Case Data)",
279
+ args: { avatarLabel: "Alexandra" },
280
+ render: (args) => ({
281
+ components: { AccordionChat },
282
+ setup() {
283
+ const longUnbroken = "Donaudampfschifffahrtselektrizitätenhauptbetriebswerkbauunterbeamtengesellschaft";
284
+ const longUrl = "https://example.com/" + "a-very-long-path-segment-without-spaces".repeat(4);
285
+ const longCopy = "Unsere Behandlungen werden individuell auf Ihre Bedürfnisse abgestimmt, damit Sie sich rundum wohlfühlen. ".repeat(6);
286
+ const many = Array.from({ length: 12 }, (_, i) => ({
287
+ question: `Question ${i + 6}`,
288
+ summarySlot: `item-${i + 6}-summary`,
289
+ contentSlot: `item-${i + 6}-content`,
290
+ }));
291
+ return { ...useStorySetup(args), longUnbroken, longUrl, longCopy, many, lightTokens };
292
+ },
293
+ template: `
294
+ <div style="padding: 1.6rem; display: grid; gap: 3.2rem;" :style="[lightTokens, tokenStyle]">
295
+ <AccordionChat v-bind="componentArgs">
296
+ <template #item-0-summary>{{ longUnbroken }}</template>
297
+ <template #item-0-content><p style="margin: 0;">{{ longUrl }}</p></template>
298
+ <template #item-1-summary>{{ longCopy.slice(0, 200) }}</template>
299
+ <template #item-1-content><p style="margin: 0;">{{ longCopy }}</p></template>
300
+ <template #item-2-summary>🎉 Emoji first, then &lt;b&gt;html&lt;/b&gt; as text</template>
301
+ <template #item-2-content><img src="/images/banners/banner-ginger.webp" alt="Wide banner, wider than the bubble" width="1600" height="400"></template>
302
+ <template #item-3-summary>A</template>
303
+ <template #item-4-summary>No content slot: opens to nothing</template>
304
+ <template v-for="item in many" :key="item.summarySlot" #[item.summarySlot]>{{ item.question }}</template>
305
+ <template v-for="item in many" :key="item.contentSlot" #[item.contentSlot]><p style="margin: 0;">Answer to {{ item.question }}.</p></template>
306
+ </AccordionChat>
307
+
308
+ <div dir="rtl">
309
+ <AccordionChat v-bind="componentArgs" avatar-label="👩‍🔬">
310
+ <template #item-0-summary>سؤال طويل من اليمين إلى اليسار للتحقق من الاتجاه</template>
311
+ <template #item-0-content><p style="margin: 0;">إجابة قصيرة. يجب أن تنعكس زوايا الفقاعات.</p></template>
312
+ </AccordionChat>
313
+ </div>
314
+ </div>
315
+ `,
316
+ }),
317
+ decorators: [
318
+ (story, context) => ({
319
+ components: { story, CanvasSwitcher },
320
+ setup() {
321
+ const canvasName = ref<MediaCanvas>(context.parameters.initialCanvas ?? "mobileCanvas");
322
+ return { canvasName };
323
+ },
324
+ template: `
325
+ <div style="padding: 1.2rem 1.6rem; border-block-end: 1px solid currentColor;">
326
+ <CanvasSwitcher v-model:canvas-name="canvasName" />
327
+ </div>
328
+ <div :class="canvasName" style="margin-inline: auto; outline: 1px dashed currentColor;">
329
+ <story />
330
+ </div>
331
+ `,
332
+ }),
333
+ ],
334
+ parameters: {
335
+ initialCanvas: "mobileCanvas",
336
+ docs: {
337
+ story: { inline: false, height: "760px" },
338
+ description: {
339
+ story:
340
+ "Open every question at each canvas width. Long unbroken words and URLs wrap inside both bubbles and the icon stays visible; the " +
341
+ "avatar shows \"Al\" for the label \"Alexandra\"; the wide image shrinks to the bubble; the single-letter and no-content panels still " +
342
+ "look like bubbles; 12 more panels follow item-4 after a gap in the indexes; in the RTL group the question sits on the left, the " +
343
+ "answer on the right, the tail corners flip, and the emoji avatar label stays whole.",
344
+ },
345
+ },
346
+ },
347
+ };