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
@@ -7,10 +7,10 @@
7
7
  | `--dashboard-quad-grid-gap` | `2rem` | Gap between the four slot panels |
8
8
  | `--dashboard-quad-grid-padding` | `2rem` | Padding inside each slot panel |
9
9
  | `--dashboard-quad-grid-outline-width` | `0.1rem` | Slot panel outline width |
10
- | `--dashboard-quad-grid-outline-colour` | `black` | Slot panel outline colour |
10
+ | `--dashboard-quad-grid-outline-colour` | `var(--theme-border)` | Slot panel outline colour |
11
11
  | `--dashboard-quad-grid-outline-offset` | `0` | Slot panel outline offset |
12
12
  | `--dashboard-quad-grid-border-width` | `0` | Slot panel border width (off by default — the panel edge is drawn by the outline) |
13
- | `--dashboard-quad-grid-border-colour` | `black` | Slot panel border colour, only visible once `--dashboard-quad-grid-border-width` is set |
13
+ | `--dashboard-quad-grid-border-colour` | `var(--theme-border)` | Slot panel border colour, only visible once `--dashboard-quad-grid-border-width` is set |
14
14
  | `--dashboard-quad-grid-border-radius` | `0.5rem` | Slot panel corner radius |
15
15
  | `--dashboard-quad-grid-rule-width` | `0.1rem` | Width of the grid gap rule line (CSS Gap Decorations — see Notes) |
16
16
  | `--dashboard-quad-grid-rule-colour` | `transparent` | Colour of the grid gap rule line, invisible by default |
@@ -31,6 +31,16 @@ Or scope to a single instance via `styleClassPassthrough`:
31
31
 
32
32
  ---
33
33
 
34
+ ## State hooks
35
+
36
+ No state attributes. Element classes: `.dashboard-quad-grid-wrapper` (root, the container),
37
+ `.dashboard-quad-grid` (the grid), `.dashboard-quad-grid-cell` (each of the four cells) with
38
+ `.dashboard-quad-grid-slot-1` to `-slot-4`. Cells wrap their content and the columns are
39
+ `minmax(0, auto)`, so an unbroken string can't widen the grid.
40
+
41
+ > Changed 2026-10-21: the cells were `.slot1` to `.slot4` (bare classes a consumer's own CSS could
42
+ > match), styled through `& > div`; the outline and border colours defaulted to `black`.
43
+
34
44
  ## Local overrides
35
45
 
36
46
  Set the tokens above on an element you own (a page or section class, or a class added with
@@ -1,16 +1,16 @@
1
1
  <template>
2
2
  <div class="dashboard-quad-grid-wrapper" :class="[elementClasses]">
3
3
  <div class="dashboard-quad-grid">
4
- <div class="slot1">
4
+ <div class="dashboard-quad-grid-cell dashboard-quad-grid-slot-1">
5
5
  <slot name="slot1"></slot>
6
6
  </div>
7
- <div class="slot2">
7
+ <div class="dashboard-quad-grid-cell dashboard-quad-grid-slot-2">
8
8
  <slot name="slot2"></slot>
9
9
  </div>
10
- <div class="slot3">
10
+ <div class="dashboard-quad-grid-cell dashboard-quad-grid-slot-3">
11
11
  <slot name="slot3"></slot>
12
12
  </div>
13
- <div class="slot4">
13
+ <div class="dashboard-quad-grid-cell dashboard-quad-grid-slot-4">
14
14
  <slot name="slot4"></slot>
15
15
  </div>
16
16
  </div>
@@ -50,61 +50,63 @@ watch(
50
50
  rule-break: intersection;
51
51
  rule-inset: var(--dashboard-quad-grid-rule-inset, 0);
52
52
 
53
- & > div {
53
+ .dashboard-quad-grid-cell {
54
+ min-inline-size: 0;
55
+ overflow-wrap: anywhere;
54
56
  padding: var(--dashboard-quad-grid-padding, 2rem);
55
57
  outline: var(--dashboard-quad-grid-outline-width, 0.1rem) solid
56
- var(--dashboard-quad-grid-outline-colour, black);
58
+ var(--dashboard-quad-grid-outline-colour, var(--theme-border));
57
59
  outline-offset: var(--dashboard-quad-grid-outline-offset, 0);
58
60
  border: var(--dashboard-quad-grid-border-width, 0) solid
59
- var(--dashboard-quad-grid-border-colour, black);
61
+ var(--dashboard-quad-grid-border-colour, var(--theme-border));
60
62
  border-radius: var(--dashboard-quad-grid-border-radius, 0.5rem);
61
63
  }
62
64
 
63
65
  @container (min-width: 768px) {
64
- grid-template-columns: repeat(2, auto);
66
+ grid-template-columns: repeat(2, minmax(0, auto));
65
67
  grid-template-rows: repeat(3, auto);
66
68
 
67
- .slot1 {
69
+ .dashboard-quad-grid-slot-1 {
68
70
  grid-column: 1 / span 2;
69
71
  grid-row: 1;
70
72
  }
71
73
 
72
- .slot2 {
74
+ .dashboard-quad-grid-slot-2 {
73
75
  grid-column: 1 / span 2;
74
76
  grid-row: 2;
75
77
  }
76
78
 
77
- .slot3 {
79
+ .dashboard-quad-grid-slot-3 {
78
80
  grid-column: 1;
79
81
  grid-row: 3;
80
82
  }
81
83
 
82
- .slot4 {
84
+ .dashboard-quad-grid-slot-4 {
83
85
  grid-column: 2;
84
86
  grid-row: 3;
85
87
  }
86
88
  }
87
89
 
88
90
  @container (min-width: 1060px) {
89
- grid-template-columns: repeat(3, auto);
91
+ grid-template-columns: repeat(3, minmax(0, auto));
90
92
  grid-template-rows: repeat(2, auto);
91
93
 
92
- .slot1 {
94
+ .dashboard-quad-grid-slot-1 {
93
95
  grid-column: 1;
94
96
  grid-row: 1 / span 2;
95
97
  }
96
98
 
97
- .slot2 {
99
+ .dashboard-quad-grid-slot-2 {
98
100
  grid-column: 2 / span 2;
99
101
  grid-row: 1;
100
102
  }
101
103
 
102
- .slot3 {
104
+ .dashboard-quad-grid-slot-3 {
103
105
  grid-column: 2;
104
106
  grid-row: 2;
105
107
  }
106
108
 
107
- .slot4 {
109
+ .dashboard-quad-grid-slot-4 {
108
110
  grid-column: 3;
109
111
  grid-row: 2;
110
112
  }
@@ -1,4 +1,6 @@
1
+ import { ref } from "vue";
1
2
  import type { Meta, StoryFn } from "@nuxtjs/storybook";
3
+ import CanvasSwitcher from "../../../../canvas-switcher/CanvasSwitcher.vue";
2
4
  import DashboardQuadGridComponent from "../DashboardQuadGrid.vue";
3
5
  import type { MediaCanvas } from "~/types/components";
4
6
 
@@ -530,3 +532,43 @@ ResponsiveComparison.parameters = {
530
532
  },
531
533
  },
532
534
  };
535
+
536
+ export const StressTest: StoryFn = () => ({
537
+ components: { DashboardQuadGridComponent },
538
+ template: `
539
+ <DashboardQuadGridComponent>
540
+ <template #slot1><div style="padding: 1.2rem; background: var(--theme-surface-subtle); outline: 1px dashed currentColor;"><h3 style="margin: 0;">Supercalifragilisticexpialidociousandthensomemoretextwithoutanyspaces</h3><p style="margin: 0;">https://example.com/a/very/long/unbroken/path/that/never/wraps/on/its/own</p></div></template>
541
+ <template #slot2><div style="padding: 1.2rem; background: var(--theme-surface-subtle); outline: 1px dashed currentColor;"><p style="margin: 0;">Ein sehr langer Beschreibungstext aus Northtown, der auf schmalen Bildschirmen mehrfach umbrechen muss, ohne das Raster zu sprengen oder die Nachbarspalten zusammenzudrücken.</p></div></template>
542
+ <template #slot3><div style="padding: 1.2rem; background: var(--theme-surface-subtle); outline: 1px dashed currentColor;"><img src="/images/banners/banner-1.webp" width="1600" height="400" alt="" style="display: block; max-inline-size: 100%; block-size: auto;"></div></template>
543
+ <template #slot4><div style="padding: 1.2rem; background: var(--theme-surface-subtle); outline: 1px dashed currentColor;"><p style="margin: 0;">🙂 👍🏽 👨‍👩‍👧 &lt;b&gt;Not bold&lt;/b&gt;</p></div></template>
544
+ </DashboardQuadGridComponent>
545
+ `,
546
+ });
547
+ StressTest.storyName = "Stress Test (Worst-Case Data)";
548
+ StressTest.decorators = [
549
+ (story, context) => ({
550
+ components: { story, CanvasSwitcher },
551
+ setup() {
552
+ const canvasName = ref<MediaCanvas>(context.parameters.initialCanvas ?? "mobileCanvas");
553
+ return { canvasName };
554
+ },
555
+ template: `
556
+ <div style="padding: 1.2rem 1.6rem; border-block-end: 1px solid currentColor;">
557
+ <CanvasSwitcher v-model:canvas-name="canvasName" />
558
+ </div>
559
+ <div :class="canvasName" style="margin-inline: auto; padding: 2rem; outline: 1px dashed currentColor;">
560
+ <story />
561
+ </div>
562
+ `,
563
+ }),
564
+ ];
565
+ StressTest.parameters = {
566
+ layout: "fullscreen",
567
+ docs: {
568
+ story: { inline: false, height: "720px" },
569
+ description: {
570
+ story:
571
+ "Oversized and hostile slot content at every `CanvasSwitcher` width. Check that: the unbroken heading and URL wrap inside their cell instead of widening it; the German paragraph wraps; the 1600px image scales down to its cell; emoji, `<b>` and right-to-left text render as text; the empty and one-character cells keep their place; no neighbouring cell is squeezed to nothing and nothing scrolls sideways. At tablet and wider, check the two- and three-column arrangements too.",
572
+ },
573
+ },
574
+ };
@@ -3,19 +3,6 @@ import { mountSuspended } from "@nuxt/test-utils/runtime";
3
3
  import type { DOMWrapper } from "@vue/test-utils";
4
4
  import DashboardQuadGrid from "../DashboardQuadGrid.vue";
5
5
 
6
- // Mock useStyleClassPassthrough composable
7
- const mockElementClasses = { value: "" };
8
- const mockResetElementClasses = vi.fn();
9
-
10
- const mockUseStyleClassPassthrough = vi.fn(() => ({
11
- elementClasses: mockElementClasses,
12
- resetElementClasses: mockResetElementClasses,
13
- }));
14
-
15
- vi.mock("#imports", () => ({
16
- useStyleClassPassthrough: mockUseStyleClassPassthrough,
17
- }));
18
-
19
6
  describe("DashboardQuadGrid", () => {
20
7
  let wrapper: ReturnType<typeof mountSuspended>;
21
8
 
@@ -35,19 +22,19 @@ describe("DashboardQuadGrid", () => {
35
22
 
36
23
  beforeEach(() => {
37
24
  vi.clearAllMocks();
38
- mockElementClasses.value = "";
39
25
  });
40
26
 
41
27
  afterEach(() => {
42
28
  wrapper?.unmount();
29
+ vi.restoreAllMocks();
43
30
  });
44
31
 
45
32
  describe("Component Rendering", () => {
46
33
  it("mounts without error", async () => {
47
34
  await createWrapper();
48
35
 
49
- expect(wrapper.find(".dashboard-quad-grid-wrapper")).toBeTruthy();
50
- expect(wrapper.find(".dashboard-quad-grid")).toBeTruthy();
36
+ expect(wrapper.find(".dashboard-quad-grid-wrapper").exists()).toBe(true);
37
+ expect(wrapper.find(".dashboard-quad-grid").exists()).toBe(true);
51
38
  });
52
39
 
53
40
  it("renders all four slot containers", async () => {
@@ -56,10 +43,10 @@ describe("DashboardQuadGrid", () => {
56
43
  const slots = wrapper.findAll(".dashboard-quad-grid > div");
57
44
  expect(slots).toHaveLength(4);
58
45
 
59
- expect(wrapper.find(".slot1")).toBeTruthy();
60
- expect(wrapper.find(".slot2")).toBeTruthy();
61
- expect(wrapper.find(".slot3")).toBeTruthy();
62
- expect(wrapper.find(".slot4")).toBeTruthy();
46
+ expect(wrapper.find(".dashboard-quad-grid-slot-1").exists()).toBe(true);
47
+ expect(wrapper.find(".dashboard-quad-grid-slot-2").exists()).toBe(true);
48
+ expect(wrapper.find(".dashboard-quad-grid-slot-3").exists()).toBe(true);
49
+ expect(wrapper.find(".dashboard-quad-grid-slot-4").exists()).toBe(true);
63
50
  });
64
51
 
65
52
  it("has correct grid container structure", async () => {
@@ -83,7 +70,7 @@ describe("DashboardQuadGrid", () => {
83
70
  }
84
71
  );
85
72
 
86
- const slot1 = wrapper.find(".slot1");
73
+ const slot1 = wrapper.find(".dashboard-quad-grid-slot-1");
87
74
  expect(slot1.exists()).toBe(true);
88
75
  expect(slot1.html()).toContain("Slot 1 Content");
89
76
  expect(slot1.html()).toContain("This is slot 1");
@@ -97,7 +84,7 @@ describe("DashboardQuadGrid", () => {
97
84
  }
98
85
  );
99
86
 
100
- const slot2 = wrapper.find(".slot2");
87
+ const slot2 = wrapper.find(".dashboard-quad-grid-slot-2");
101
88
  expect(slot2.exists()).toBe(true);
102
89
  expect(slot2.html()).toContain("Slot 2 Content");
103
90
  });
@@ -110,7 +97,7 @@ describe("DashboardQuadGrid", () => {
110
97
  }
111
98
  );
112
99
 
113
- const slot3 = wrapper.find(".slot3");
100
+ const slot3 = wrapper.find(".dashboard-quad-grid-slot-3");
114
101
  expect(slot3.exists()).toBe(true);
115
102
  expect(slot3.html()).toContain("Slot 3 Content");
116
103
  });
@@ -123,7 +110,7 @@ describe("DashboardQuadGrid", () => {
123
110
  }
124
111
  );
125
112
 
126
- const slot4 = wrapper.find(".slot4");
113
+ const slot4 = wrapper.find(".dashboard-quad-grid-slot-4");
127
114
  expect(slot4.exists()).toBe(true);
128
115
  expect(slot4.html()).toContain("Slot 4 Content");
129
116
  });
@@ -139,10 +126,10 @@ describe("DashboardQuadGrid", () => {
139
126
  }
140
127
  );
141
128
 
142
- expect(wrapper.find(".slot1").html()).toContain("Header");
143
- expect(wrapper.find(".slot2").html()).toContain("Paragraph");
144
- expect(wrapper.find(".slot3").html()).toContain("Division");
145
- expect(wrapper.find(".slot4").html()).toContain("Span");
129
+ expect(wrapper.find(".dashboard-quad-grid-slot-1").html()).toContain("Header");
130
+ expect(wrapper.find(".dashboard-quad-grid-slot-2").html()).toContain("Paragraph");
131
+ expect(wrapper.find(".dashboard-quad-grid-slot-3").html()).toContain("Division");
132
+ expect(wrapper.find(".dashboard-quad-grid-slot-4").html()).toContain("Span");
146
133
  });
147
134
 
148
135
  it("handles empty slots gracefully", async () => {
@@ -171,7 +158,7 @@ describe("DashboardQuadGrid", () => {
171
158
  await createWrapper({ styleClassPassthrough: className });
172
159
 
173
160
  // Component should mount without error with string class
174
- expect(wrapper.find(".dashboard-quad-grid")).toBeTruthy();
161
+ expect(wrapper.find(".dashboard-quad-grid").exists()).toBe(true);
175
162
  });
176
163
 
177
164
  it("accepts array styleClassPassthrough", async () => {
@@ -179,14 +166,14 @@ describe("DashboardQuadGrid", () => {
179
166
  await createWrapper({ styleClassPassthrough: classNames });
180
167
 
181
168
  // Component should mount without error with array of classes
182
- expect(wrapper.find(".dashboard-quad-grid")).toBeTruthy();
169
+ expect(wrapper.find(".dashboard-quad-grid").exists()).toBe(true);
183
170
  });
184
171
 
185
172
  it("uses default empty array for styleClassPassthrough", async () => {
186
173
  await createWrapper();
187
174
 
188
175
  // Component should mount without error with default props
189
- expect(wrapper.find(".dashboard-quad-grid")).toBeTruthy();
176
+ expect(wrapper.find(".dashboard-quad-grid").exists()).toBe(true);
190
177
  });
191
178
 
192
179
  it("renders correctly with various props", async () => {
@@ -203,8 +190,8 @@ describe("DashboardQuadGrid", () => {
203
190
  await createWrapper({ styleClassPassthrough: ["initial"] });
204
191
 
205
192
  // Component should maintain its structure
206
- expect(wrapper.find(".dashboard-quad-grid-wrapper")).toBeTruthy();
207
- expect(wrapper.find(".dashboard-quad-grid")).toBeTruthy();
193
+ expect(wrapper.find(".dashboard-quad-grid-wrapper").exists()).toBe(true);
194
+ expect(wrapper.find(".dashboard-quad-grid").exists()).toBe(true);
208
195
  expect(wrapper.findAll(".dashboard-quad-grid > div")).toHaveLength(4);
209
196
  });
210
197
  });
@@ -215,17 +202,17 @@ describe("DashboardQuadGrid", () => {
215
202
 
216
203
  const wrapper_element = wrapper.find(".dashboard-quad-grid-wrapper");
217
204
  const grid = wrapper.find(".dashboard-quad-grid");
218
- const slot1 = wrapper.find(".slot1");
219
- const slot2 = wrapper.find(".slot2");
220
- const slot3 = wrapper.find(".slot3");
221
- const slot4 = wrapper.find(".slot4");
205
+ const slot1 = wrapper.find(".dashboard-quad-grid-slot-1");
206
+ const slot2 = wrapper.find(".dashboard-quad-grid-slot-2");
207
+ const slot3 = wrapper.find(".dashboard-quad-grid-slot-3");
208
+ const slot4 = wrapper.find(".dashboard-quad-grid-slot-4");
222
209
 
223
210
  expect(wrapper_element.classes()).toContain("dashboard-quad-grid-wrapper");
224
211
  expect(grid.classes()).toContain("dashboard-quad-grid");
225
- expect(slot1.classes()).toContain("slot1");
226
- expect(slot2.classes()).toContain("slot2");
227
- expect(slot3.classes()).toContain("slot3");
228
- expect(slot4.classes()).toContain("slot4");
212
+ expect(slot1.classes()).toContain("dashboard-quad-grid-slot-1");
213
+ expect(slot2.classes()).toContain("dashboard-quad-grid-slot-2");
214
+ expect(slot3.classes()).toContain("dashboard-quad-grid-slot-3");
215
+ expect(slot4.classes()).toContain("dashboard-quad-grid-slot-4");
229
216
  });
230
217
 
231
218
  it("maintains correct DOM hierarchy", async () => {
@@ -241,7 +228,7 @@ describe("DashboardQuadGrid", () => {
241
228
 
242
229
  // Verify each slot is a direct child of the grid
243
230
  slots.forEach((slot: DOMWrapper<Element>, index: number) => {
244
- const expectedClass = `slot${index + 1}`;
231
+ const expectedClass = `dashboard-quad-grid-slot-${index + 1}`;
245
232
  expect(slot.classes()).toContain(expectedClass);
246
233
  });
247
234
  });
@@ -305,7 +292,7 @@ describe("DashboardQuadGrid", () => {
305
292
  }
306
293
  );
307
294
 
308
- const slot1 = wrapper.find(".slot1");
295
+ const slot1 = wrapper.find(".dashboard-quad-grid-slot-1");
309
296
  expect(slot1.find("header").exists()).toBe(true);
310
297
  expect(slot1.find("h1").text()).toBe("Complex Header");
311
298
  expect(slot1.find("nav").exists()).toBe(true);
@@ -320,7 +307,7 @@ describe("DashboardQuadGrid", () => {
320
307
  }
321
308
  );
322
309
 
323
- const slot1 = wrapper.find(".slot1 p");
310
+ const slot1 = wrapper.find(".dashboard-quad-grid-slot-1 p");
324
311
  expect(slot1.exists()).toBe(true);
325
312
  // The HTML should be properly parsed
326
313
  expect(slot1.html()).toContain("&lt;Special&gt;");
@@ -7,7 +7,7 @@
7
7
  | `--dashboard-stats-grid-gap` | `1.2rem` | Gap between panels, in every row |
8
8
  | `--dashboard-stats-grid-panel-padding` | `1.2rem` | Padding inside each panel |
9
9
  | `--dashboard-stats-grid-border-width` | `0.1rem` | Panel border width |
10
- | `--dashboard-stats-grid-border-colour` | `hsl(0, 29%, 3%)` | Panel border colour |
10
+ | `--dashboard-stats-grid-border-colour` | `var(--theme-border)` | Panel border colour |
11
11
  | `--dashboard-stats-grid-border-radius` | `1.2rem` | Panel corner radius |
12
12
  | `--dashboard-stats-grid-rule-width` | `0.1rem` | Width of the grid gap rule line (CSS Gap Decorations — see Notes) |
13
13
  | `--dashboard-stats-grid-rule-colour` | `transparent` | Colour of the grid gap rule line, invisible by default |
@@ -28,6 +28,18 @@ Or scope to a single instance via `styleClassPassthrough`:
28
28
 
29
29
  ---
30
30
 
31
+ ## State hooks
32
+
33
+ No state attributes. Element classes: `.dashboard-stats-grid` (root),
34
+ `.dashboard-stats-grid-top-row`, `.dashboard-stats-grid-top-row-slot-1` (with its
35
+ `-slot-1-inner` panel grid), `-top-row-slot-2`, `-top-row-slot-3`, `.dashboard-stats-grid-bottom-row`,
36
+ and `.dashboard-stats-grid-panel` on every panel.
37
+
38
+ > Changed 2026-10-21: these were `.top-row`, `.top-row-slot-1`, `.top-row-slot-1-inner`,
39
+ > `.top-row-slot-2`, `.top-row-slot-3`, `.bottom-row` and `.panel`; the border colour defaulted to
40
+ > `hsl(0, 29%, 3%)`. Tracks are `minmax(0, 1fr)` and panels wrap, so an unbroken string can't
41
+ > widen the grid.
42
+
31
43
  ## Local overrides
32
44
 
33
45
  Set the tokens above on an element you own (a page or section class, or a class added with
@@ -1,28 +1,28 @@
1
1
  <template>
2
2
  <div class="dashboard-stats-grid" :class="[elementClasses]">
3
- <section class="top-row">
4
- <div class="top-row-slot-1">
5
- <div class="top-row-slot-1-inner">
6
- <div v-for="key in topRowSlot1ItemCount" :key="key" class="panel">
3
+ <section class="dashboard-stats-grid-top-row">
4
+ <div class="dashboard-stats-grid-top-row-slot-1">
5
+ <div class="dashboard-stats-grid-top-row-slot-1-inner">
6
+ <div v-for="key in topRowSlot1Count" :key="key" class="dashboard-stats-grid-panel">
7
7
  <slot :name="`top-row-slot1-${key}-content`"></slot>
8
8
  </div>
9
9
  </div>
10
10
  </div>
11
11
 
12
- <div class="top-row-slot-2">
13
- <div class="panel">
12
+ <div class="dashboard-stats-grid-top-row-slot-2">
13
+ <div class="dashboard-stats-grid-panel">
14
14
  <slot name="top-row-slot-2"></slot>
15
15
  </div>
16
16
  </div>
17
- <div class="top-row-slot-3">
18
- <div class="panel">
17
+ <div class="dashboard-stats-grid-top-row-slot-3">
18
+ <div class="dashboard-stats-grid-panel">
19
19
  <slot name="top-row-slot-3"></slot>
20
20
  </div>
21
21
  </div>
22
22
  </section>
23
23
 
24
- <section class="bottom-row">
25
- <div v-for="key in bottomRowItemCount" :key="key" class="panel">
24
+ <section class="dashboard-stats-grid-bottom-row">
25
+ <div v-for="key in bottomRowCount" :key="key" class="dashboard-stats-grid-panel">
26
26
  <slot :name="`bottom-row-${key}-content`"></slot>
27
27
  </div>
28
28
  </section>
@@ -42,6 +42,10 @@ const props = withDefaults(defineProps<Props>(), {
42
42
  styleClassPassthrough: () => [],
43
43
  });
44
44
 
45
+ const toItemCount = (value: number) => (Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0);
46
+ const topRowSlot1Count = computed(() => toItemCount(props.topRowSlot1ItemCount));
47
+ const bottomRowCount = computed(() => toItemCount(props.bottomRowItemCount));
48
+
45
49
  const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
46
50
 
47
51
  watch(
@@ -55,17 +59,9 @@ watch(
55
59
  <style lang="css">
56
60
  @layer components {
57
61
  .dashboard-stats-grid {
58
- .panel {
59
- border: var(--dashboard-stats-grid-border-width, 0.1rem) solid
60
- var(--dashboard-stats-grid-border-colour, hsl(0, 29%, 3%));
61
- border-radius: var(--dashboard-stats-grid-border-radius, 1.2rem);
62
- padding: var(--dashboard-stats-grid-panel-padding, 1.2rem);
63
- height: auto;
64
- }
65
-
66
62
  container-type: inline-size;
67
63
  display: grid;
68
- grid-template-columns: 1fr;
64
+ grid-template-columns: minmax(0, 1fr);
69
65
  gap: var(--dashboard-stats-grid-gap, 1.2rem);
70
66
  width: 100%;
71
67
  margin-inline: auto;
@@ -75,57 +71,70 @@ watch(
75
71
  rule-break: intersection;
76
72
  rule-inset: var(--dashboard-stats-grid-rule-inset, 0);
77
73
 
78
- .top-row {
74
+ .dashboard-stats-grid-panel {
75
+ border: var(--dashboard-stats-grid-border-width, 0.1rem) solid
76
+ var(--dashboard-stats-grid-border-colour, var(--theme-border));
77
+ border-radius: var(--dashboard-stats-grid-border-radius, 1.2rem);
78
+ padding: var(--dashboard-stats-grid-panel-padding, 1.2rem);
79
+ height: auto;
80
+ min-inline-size: 0;
81
+ overflow-wrap: anywhere;
82
+ }
83
+
84
+ .dashboard-stats-grid-top-row {
79
85
  display: grid;
80
86
  gap: var(--dashboard-stats-grid-gap, 1.2rem);
81
87
  width: 100%;
82
88
 
83
- grid-template-columns: 1fr;
89
+ grid-template-columns: minmax(0, 1fr);
84
90
  grid-template-areas:
85
91
  "slot1"
86
92
  "slot2"
87
93
  "slot3";
88
94
 
89
95
  @container (min-width: 1024px) {
90
- grid-template-columns: 1fr minmax(46rem, 33%);
96
+ grid-template-columns: minmax(0, 1fr) minmax(46rem, 33%);
91
97
  grid-template-areas:
92
98
  "slot1 slot2"
93
99
  "slot3 slot2";
94
100
  }
95
101
  }
96
102
 
97
- .top-row-slot-1 {
103
+ .dashboard-stats-grid-top-row-slot-1 {
98
104
  grid-area: slot1;
99
105
  container-type: inline-size;
100
106
 
101
- .top-row-slot-1-inner {
107
+ .dashboard-stats-grid-top-row-slot-1-inner {
102
108
  display: grid;
103
- grid-template-columns: repeat(2, 1fr);
109
+ grid-template-columns: repeat(2, minmax(0, 1fr));
104
110
 
105
111
  gap: var(--dashboard-stats-grid-gap, 1.2rem);
106
112
 
107
113
  @container (min-width: 680px) {
108
- grid-template-columns: repeat(3, 1fr);
114
+ grid-template-columns: repeat(3, minmax(0, 1fr));
109
115
  }
110
116
 
111
- .panel {
117
+ .dashboard-stats-grid-panel {
112
118
  display: grid;
113
119
  }
114
120
  }
115
121
  }
116
122
 
117
- .top-row-slot-2 {
123
+ .dashboard-stats-grid-top-row-slot-2 {
118
124
  grid-area: slot2;
119
125
  display: grid;
126
+ min-inline-size: 0;
120
127
  }
121
- .top-row-slot-3 {
128
+
129
+ .dashboard-stats-grid-top-row-slot-3 {
122
130
  grid-area: slot3;
123
131
  display: grid;
132
+ min-inline-size: 0;
124
133
  }
125
134
 
126
- .bottom-row {
135
+ .dashboard-stats-grid-bottom-row {
127
136
  display: grid;
128
- grid-template-columns: repeat(2, 1fr);
137
+ grid-template-columns: repeat(2, minmax(0, 1fr));
129
138
  gap: var(--dashboard-stats-grid-gap, 1.2rem);
130
139
  }
131
140
  }
@@ -1,4 +1,6 @@
1
+ import { ref } from "vue";
1
2
  import type { Meta, StoryFn } from "@nuxtjs/storybook";
3
+ import CanvasSwitcher from "../../../../canvas-switcher/CanvasSwitcher.vue";
2
4
  import DashboardStatsGridComponent from "../DashboardStatsGrid.vue";
3
5
  import type { MediaCanvas } from "~/types/components";
4
6
 
@@ -420,3 +422,48 @@ ResponsiveComparison.parameters = {
420
422
  },
421
423
  },
422
424
  };
425
+
426
+ export const StressTest: StoryFn = () => ({
427
+ components: { DashboardStatsGridComponent },
428
+ template: `
429
+ <DashboardStatsGridComponent :top-row-slot1-item-count="4.7" :bottom-row-item-count="3">
430
+ <template #top-row-slot1-1-content><div style="padding: 1.2rem; background: var(--theme-surface-subtle); outline: 1px dashed currentColor;"><h3 style="margin: 0;">Supercalifragilisticexpialidociousandthensomemoretextwithoutanyspaces</h3><p style="margin: 0;">https://example.com/a/very/long/unbroken/path/that/never/wraps/on/its/own</p></div></template>
431
+ <template #top-row-slot1-2-content><div style="padding: 1.2rem; background: var(--theme-surface-subtle); outline: 1px dashed currentColor;"><p style="margin: 0;">Ein sehr langer Beschreibungstext aus Northtown, der auf schmalen Bildschirmen mehrfach umbrechen muss, ohne das Raster zu sprengen oder die Nachbarspalten zusammenzudrücken.</p></div></template>
432
+ <template #top-row-slot1-3-content><div style="padding: 1.2rem; background: var(--theme-surface-subtle); outline: 1px dashed currentColor;"><img src="/images/banners/banner-1.webp" width="1600" height="400" alt="" style="display: block; max-inline-size: 100%; block-size: auto;"></div></template>
433
+ <template #top-row-slot1-4-content><div style="padding: 1.2rem; background: var(--theme-surface-subtle); outline: 1px dashed currentColor;"><p style="margin: 0;">🙂 👍🏽 👨‍👩‍👧 &lt;b&gt;Not bold&lt;/b&gt;</p></div></template>
434
+ <template #top-row-slot-2><div style="padding: 1.2rem; background: var(--theme-surface-subtle); outline: 1px dashed currentColor;"><p style="margin: 0;" dir="rtl">مرحبا بالعالم، نص طويل من اليمين إلى اليسار</p></div></template>
435
+ <template #top-row-slot-3><div style="padding: 1.2rem; background: var(--theme-surface-subtle); outline: 1px dashed currentColor;"></div></template>
436
+ <template #bottom-row-1-content><div style="padding: 1.2rem; background: var(--theme-surface-subtle); outline: 1px dashed currentColor;"><p style="margin: 0;">x</p></div></template>
437
+ <template #bottom-row-2-content><div style="padding: 1.2rem; background: var(--theme-surface-subtle); outline: 1px dashed currentColor;"><h3 style="margin: 0;">Supercalifragilisticexpialidociousandthensomemoretextwithoutanyspaces</h3><p style="margin: 0;">https://example.com/a/very/long/unbroken/path/that/never/wraps/on/its/own</p></div></template>
438
+ <template #bottom-row-3-content><div style="padding: 1.2rem; background: var(--theme-surface-subtle); outline: 1px dashed currentColor;"><p style="margin: 0;">Ein sehr langer Beschreibungstext aus Northtown, der auf schmalen Bildschirmen mehrfach umbrechen muss, ohne das Raster zu sprengen oder die Nachbarspalten zusammenzudrücken.</p></div></template>
439
+ </DashboardStatsGridComponent>
440
+ `,
441
+ });
442
+ StressTest.storyName = "Stress Test (Worst-Case Data)";
443
+ StressTest.decorators = [
444
+ (story, context) => ({
445
+ components: { story, CanvasSwitcher },
446
+ setup() {
447
+ const canvasName = ref<MediaCanvas>(context.parameters.initialCanvas ?? "mobileCanvas");
448
+ return { canvasName };
449
+ },
450
+ template: `
451
+ <div style="padding: 1.2rem 1.6rem; border-block-end: 1px solid currentColor;">
452
+ <CanvasSwitcher v-model:canvas-name="canvasName" />
453
+ </div>
454
+ <div :class="canvasName" style="margin-inline: auto; padding: 2rem; outline: 1px dashed currentColor;">
455
+ <story />
456
+ </div>
457
+ `,
458
+ }),
459
+ ];
460
+ StressTest.parameters = {
461
+ layout: "fullscreen",
462
+ docs: {
463
+ story: { inline: false, height: "720px" },
464
+ description: {
465
+ story:
466
+ "Oversized and hostile slot content at every `CanvasSwitcher` width. Check that: the unbroken heading and URL wrap inside their cell instead of widening it; the German paragraph wraps; the 1600px image scales down to its cell; emoji, `<b>` and right-to-left text render as text; the empty and one-character cells keep their place; no neighbouring cell is squeezed to nothing and nothing scrolls sideways. `topRowSlot1ItemCount` is `4.7` here and renders 4 panels. Counts are floored, and negative or non-finite counts render none; until 2026-10-21 a fractional count rendered no panels at all.",
467
+ },
468
+ },
469
+ };