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.
- package/.claude/rules/consumer/css-relative-colour-calc.md +15 -0
- package/.claude/rules/consumer/ids-from-useid.md +3 -2
- package/.claude/rules/consumer/vue-dynamic-arg-no-spaces.md +16 -0
- package/.claude/skills/component-compliance-checklist.md +11 -1
- package/.claude/skills/component-dynamic-slots.md +2 -2
- package/.claude/skills/component-naming.md +1 -1
- package/.claude/skills/components/accordion-chat.md +99 -0
- package/.claude/skills/components/accordion-group.md +98 -0
- package/.claude/skills/components/action-menu.md +11 -5
- package/.claude/skills/components/auto-grid.md +3 -1
- package/.claude/skills/components/capture-qr-code.md +9 -5
- package/.claude/skills/components/carousel-flip.md +41 -30
- package/.claude/skills/components/column-flow-grid.md +2 -0
- package/.claude/skills/components/dashboard-quad-grid.md +7 -2
- package/.claude/skills/components/dashboard-stats-grid.md +4 -0
- package/.claude/skills/components/decode-qr-code.md +7 -2
- package/.claude/skills/components/deep-expanding-menu-classic.md +31 -30
- package/.claude/skills/components/deep-expanding-menu.md +22 -21
- package/.claude/skills/components/expanding-panel-classic.md +9 -7
- package/.claude/skills/components/expanding-panel.md +9 -8
- package/.claude/skills/components/grid-stack.md +2 -0
- package/.claude/skills/components/layout-grid-by-cols.md +2 -0
- package/.claude/skills/components/masonry-grid.md +9 -1
- package/.claude/skills/components/page-row.md +3 -0
- package/.claude/skills/components/samaritan-prompt.md +24 -8
- package/.claude/skills/components/site-header.md +16 -2
- package/.claude/skills/components/sparkle-effect.md +79 -0
- package/.claude/skills/index.md +16 -14
- package/.claude/skills/storybook-add-story.md +33 -0
- package/.claude/skills/testing-add-unit-test.md +26 -0
- package/.claude/skills/using-component-skills.md +1 -1
- package/.vscode/srcdev-component-accordion-chat.code-snippets +42 -0
- package/.vscode/srcdev-component-accordion-group.code-snippets +38 -0
- package/.vscode/srcdev-component-action-menu.code-snippets +3 -3
- package/.vscode/srcdev-component-capture-decode-qr-code.code-snippets +11 -2
- package/.vscode/srcdev-component-carousel-flip.code-snippets +22 -4
- package/.vscode/srcdev-component-deep-expanding-menu-classic.code-snippets +32 -2
- package/.vscode/srcdev-component-deep-expanding-menu.code-snippets +31 -1
- package/.vscode/srcdev-component-samaritan-prompt.code-snippets +5 -4
- package/.vscode/srcdev-component-site-header.code-snippets +13 -1
- package/.vscode/srcdev-component-sparkle-effect.code-snippets +33 -0
- package/app/components/01.atoms/animations/sparkle-effect/CONSUMER-STYLING.md +75 -0
- package/app/components/01.atoms/animations/sparkle-effect/SparkleEffect.vue +213 -0
- package/app/components/01.atoms/animations/sparkle-effect/stories/SparkleEffect.stories.ts +303 -0
- package/app/components/01.atoms/animations/sparkle-effect/tests/SparkleEffect.spec.ts +150 -0
- package/app/components/01.atoms/content-wrappers/docs-pages/ContentDocs.vue +1 -1
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md +12 -2
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/DashboardQuadGrid.vue +19 -17
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/stories/DashboardQuadGrid.stories.ts +42 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/tests/DashboardQuadGrid.spec.ts +31 -44
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/CONSUMER-STYLING.md +13 -1
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/DashboardStatsGrid.vue +40 -31
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/stories/DashboardStatsGrid.stories.ts +47 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/tests/DashboardStatsGrid.spec.ts +51 -53
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/CONSUMER-STYLING.md +3 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/LayoutGridByCols.vue +4 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/stories/LayoutGridByCols.stories.ts +47 -2
- package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/tests/LayoutGridByCols.spec.ts +36 -23
- package/app/components/01.atoms/grids/auto-grid/AutoGrid.vue +7 -3
- package/app/components/01.atoms/grids/auto-grid/CONSUMER-STYLING.md +1 -1
- package/app/components/01.atoms/grids/auto-grid/stories/AutoGrid.stories.ts +46 -1
- package/app/components/01.atoms/grids/auto-grid/tests/AutoGrid.spec.ts +40 -27
- package/app/components/01.atoms/grids/auto-grid/tests/__snapshots__/AutoGrid.spec.ts.snap +1 -1
- package/app/components/01.atoms/grids/column-flow-grid/CONSUMER-STYLING.md +9 -0
- package/app/components/01.atoms/grids/column-flow-grid/ColumnFlowGrid.vue +3 -2
- package/app/components/01.atoms/grids/column-flow-grid/stories/ColumnFlowGrid.stories.ts +46 -0
- package/app/components/01.atoms/grids/column-flow-grid/tests/ColumnFlowGrid.spec.ts +24 -11
- package/app/components/01.atoms/grids/grid-stack/CONSUMER-STYLING.md +3 -0
- package/app/components/01.atoms/grids/grid-stack/GridStack.vue +2 -0
- package/app/components/01.atoms/grids/grid-stack/stories/GridStack.stories.ts +41 -0
- package/app/components/01.atoms/grids/grid-stack/tests/GridStack.spec.ts +26 -13
- package/app/components/01.atoms/grids/masonry-grid/CONSUMER-STYLING.md +15 -0
- package/app/components/01.atoms/grids/masonry-grid/MasonryGrid.vue +51 -31
- package/app/components/01.atoms/grids/masonry-grid/stories/MasonryGrid.stories.ts +48 -0
- package/app/components/01.atoms/grids/masonry-grid/tests/MasonryGrid.spec.ts +84 -29
- package/app/components/01.atoms/page-row/CONSUMER-STYLING.md +6 -1
- package/app/components/01.atoms/page-row/PageRow.vue +16 -15
- package/app/components/01.atoms/page-row/stories/PageRow.stories.ts +23 -0
- package/app/components/01.atoms/page-row/tests/PageRow.spec.ts +6 -5
- package/app/components/02.molecules/action-menu/ActionMenu.vue +6 -2
- package/app/components/02.molecules/action-menu/ActionMenuItem.vue +6 -3
- package/app/components/02.molecules/action-menu/CONSUMER-STYLING.md +11 -4
- package/app/components/02.molecules/action-menu/stories/ActionMenu.stories.ts +130 -18
- package/app/components/02.molecules/expandable/accordion-chat/AccordionChat.vue +227 -0
- package/app/components/02.molecules/expandable/accordion-chat/CONSUMER-STYLING.md +150 -0
- package/app/components/02.molecules/expandable/accordion-chat/stories/AccordionChat.stories.ts +347 -0
- package/app/components/02.molecules/expandable/accordion-chat/tests/AccordionChat.spec.ts +205 -0
- package/app/components/02.molecules/expandable/accordion-chat/tests/__snapshots__/AccordionChat.spec.ts.snap +40 -0
- package/app/components/02.molecules/expandable/{accordian/AccordianCore.vue → accordion-group/AccordionGroup.vue} +32 -20
- package/app/components/02.molecules/expandable/accordion-group/CONSUMER-STYLING.md +66 -0
- package/app/components/02.molecules/expandable/accordion-group/stories/AccordionGroup.stories.ts +238 -0
- package/app/components/02.molecules/expandable/accordion-group/tests/AccordionGroup.spec.ts +172 -0
- package/app/components/02.molecules/expandable/accordion-group/tests/__snapshots__/AccordionGroup.spec.ts.snap +45 -0
- package/app/components/02.molecules/expandable/expanding-panel/CONSUMER-STYLING.md +1 -0
- package/app/components/02.molecules/expandable/expanding-panel/ExpandingPanel.vue +11 -6
- package/app/components/02.molecules/expandable/expanding-panel/stories/ExpandingPanel.stories.ts +82 -1
- package/app/components/02.molecules/expandable/expanding-panel/tests/ExpandingPanel.spec.ts +24 -30
- package/app/components/02.molecules/expandable/expanding-panel/tests/__snapshots__/ExpandingPanel.spec.ts.snap +8 -8
- package/app/components/02.molecules/expandable/expanding-panel-classic/CONSUMER-STYLING.md +1 -0
- package/app/components/02.molecules/expandable/expanding-panel-classic/ExpandingPanelClassic.vue +10 -5
- package/app/components/02.molecules/expandable/expanding-panel-classic/stories/ExpandingPanelClassic.stories.ts +83 -1
- package/app/components/02.molecules/expandable/expanding-panel-classic/tests/ExpandingPanelClassic.spec.ts +24 -30
- package/app/components/02.molecules/expandable/expanding-panel-classic/tests/__snapshots__/ExpandingPanelClassic.spec.ts.snap +8 -8
- package/app/components/02.molecules/navigation/deep-expanding-menu/CONSUMER-STYLING.md +63 -44
- package/app/components/02.molecules/navigation/deep-expanding-menu/DeepExpandingMenu.vue +74 -37
- package/app/components/02.molecules/navigation/deep-expanding-menu/stories/DeepExpandingMenu.stories.ts +139 -19
- package/app/components/02.molecules/navigation/deep-expanding-menu/tests/DeepExpandingMenu.spec.ts +90 -27
- package/app/components/02.molecules/navigation/deep-expanding-menu/tests/__snapshots__/DeepExpandingMenu.spec.ts.snap +8 -8
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/CONSUMER-STYLING.md +58 -26
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/DeepExpandingMenuClassic.vue +113 -57
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/stories/DeepExpandingMenuClassic.stories.ts +139 -19
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/DeepExpandingMenuClassic.spec.ts +140 -22
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/tests/__snapshots__/DeepExpandingMenuClassic.spec.ts.snap +9 -9
- package/app/components/02.molecules/qr-code/CONSUMER-STYLING.md +22 -7
- package/app/components/02.molecules/qr-code/CaptureQrCode.vue +63 -71
- package/app/components/02.molecules/qr-code/DecodeQrCode.vue +47 -10
- package/app/components/02.molecules/qr-code/stories/CaptureQrCode.stories.ts +114 -26
- package/app/components/02.molecules/qr-code/stories/DecodeQrCode.stories.ts +115 -24
- package/app/components/02.molecules/qr-code/tests/CaptureQrCode.spec.ts +124 -51
- package/app/components/02.molecules/qr-code/tests/DecodeQrCode.spec.ts +74 -25
- package/app/components/02.molecules/samaritan-prompt/CONSUMER-STYLING.md +22 -10
- package/app/components/02.molecules/samaritan-prompt/SamaritanPrompt.vue +30 -12
- package/app/components/02.molecules/samaritan-prompt/SamaritanPromptMixed.vue +34 -10
- package/app/components/02.molecules/samaritan-prompt/stories/SamaritanPrompt.stories.ts +55 -4
- package/app/components/02.molecules/samaritan-prompt/stories/SamaritanPromptMixed.stories.ts +67 -7
- package/app/components/02.molecules/samaritan-prompt/tests/SamaritanPrompt.spec.ts +113 -29
- package/app/components/02.molecules/samaritan-prompt/tests/SamaritanPromptMixed.spec.ts +92 -45
- package/app/components/03.organisms/image-galleries/carousel-flip/CONSUMER-STYLING.md +82 -29
- package/app/components/03.organisms/image-galleries/carousel-flip/CarouselFlip.vue +176 -113
- package/app/components/03.organisms/image-galleries/carousel-flip/stories/CarouselFlip.stories.ts +153 -23
- package/app/components/03.organisms/image-galleries/carousel-flip/tests/CarouselFlip.spec.ts +119 -63
- package/app/components/03.organisms/responsive-header/tests/__snapshots__/NavigationItems.spec.ts.snap +2 -2
- package/app/components/03.organisms/responsive-header/tests/__snapshots__/ResponsiveHeader.spec.ts.snap +2 -2
- package/app/components/03.organisms/services/service-detail/tests/ServiceDetail.spec.ts +8 -8
- package/app/components/03.organisms/services/service-detail/tests/__snapshots__/ServiceDetail.spec.ts.snap +4 -4
- package/app/components/03.organisms/site-header/CONSUMER-STYLING.md +41 -23
- package/app/components/03.organisms/site-header/SiteHeader.vue +16 -21
- package/app/components/03.organisms/site-header/stories/SiteHeader.stories.ts +138 -0
- package/app/components/03.organisms/site-header/tests/SiteHeader.spec.ts +53 -26
- package/app/components/03.organisms/site-header/tests/__snapshots__/SiteHeader.spec.ts.snap +2 -2
- package/app/types/components/qr-code.d.ts +12 -0
- package/app/utils/graphemes.ts +8 -0
- package/package.json +1 -1
- package/.claude/skills/components/accordian-core.md +0 -180
- package/.vscode/srcdev-component-accordian-core.code-snippets +0 -74
- package/app/components/01.atoms/animations/entry/.nuxt/app.config.mjs +0 -21
- package/app/components/01.atoms/animations/entry/.nuxt/components.d.ts +0 -66
- package/app/components/01.atoms/animations/entry/.nuxt/imports.d.ts +0 -30
- package/app/components/01.atoms/animations/entry/.nuxt/manifest/meta/test.json +0 -1
- package/app/components/01.atoms/animations/entry/.nuxt/types/app.config.d.ts +0 -35
- package/app/components/01.atoms/animations/entry/.nuxt/types/build.d.ts +0 -23
- package/app/components/01.atoms/animations/entry/.nuxt/types/builder-env.d.ts +0 -1
- package/app/components/01.atoms/animations/entry/.nuxt/types/components.d.ts +0 -71
- package/app/components/01.atoms/animations/entry/.nuxt/types/imports.d.ts +0 -386
- package/app/components/01.atoms/animations/entry/.nuxt/types/middleware.d.ts +0 -11
- package/app/components/01.atoms/animations/entry/.nuxt/types/modules.d.ts +0 -79
- package/app/components/01.atoms/animations/entry/.nuxt/types/nitro-layouts.d.ts +0 -17
- package/app/components/01.atoms/animations/entry/.nuxt/types/nitro-nuxt.d.ts +0 -64
- package/app/components/01.atoms/animations/entry/.nuxt/types/plugins.d.ts +0 -29
- package/app/components/01.atoms/animations/entry/.nuxt/types/runtime-config.d.ts +0 -32
- package/app/components/01.atoms/animations/entry/.nuxt/types/shared-imports.d.ts +0 -10
- package/app/components/01.atoms/animations/entry/.nuxt/types/vue-shim.d.ts +0 -0
- package/app/components/01.atoms/animations/entry/.nuxtrc +0 -1
- package/app/components/01.atoms/animations/entry/node_modules/.cache/nuxt/chrome-workspace.json +0 -1
- package/app/components/01.atoms/animations/entry/node_modules/.cache/vite/client/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
- package/app/components/02.molecules/expandable/accordian/CONSUMER-STYLING.md +0 -100
- package/app/components/02.molecules/expandable/accordian/stories/AccordianCore.stories.ts +0 -272
- package/app/components/02.molecules/expandable/accordian/tests/AccordianCore.spec.ts +0 -257
- package/app/components/02.molecules/expandable/accordian/tests/__snapshots__/AccordianCore.spec.ts.snap +0 -95
- package/app/components/03.organisms/responsive-header/tests/.nuxt/app.config.mjs +0 -21
- package/app/components/03.organisms/responsive-header/tests/.nuxt/components.d.ts +0 -66
- package/app/components/03.organisms/responsive-header/tests/.nuxt/imports.d.ts +0 -30
- package/app/components/03.organisms/responsive-header/tests/.nuxt/manifest/meta/test.json +0 -1
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/app.config.d.ts +0 -35
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/build.d.ts +0 -23
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/builder-env.d.ts +0 -1
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/components.d.ts +0 -71
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/imports.d.ts +0 -386
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/middleware.d.ts +0 -11
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/modules.d.ts +0 -79
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-layouts.d.ts +0 -17
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/nitro-nuxt.d.ts +0 -64
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/plugins.d.ts +0 -29
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/runtime-config.d.ts +0 -32
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/shared-imports.d.ts +0 -10
- package/app/components/03.organisms/responsive-header/tests/.nuxt/types/vue-shim.d.ts +0 -0
- package/app/components/03.organisms/responsive-header/tests/node_modules/.cache/nuxt/chrome-workspace.json +0 -1
package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md
CHANGED
|
@@ -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` | `
|
|
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` | `
|
|
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="
|
|
4
|
+
<div class="dashboard-quad-grid-cell dashboard-quad-grid-slot-1">
|
|
5
5
|
<slot name="slot1"></slot>
|
|
6
6
|
</div>
|
|
7
|
-
<div class="
|
|
7
|
+
<div class="dashboard-quad-grid-cell dashboard-quad-grid-slot-2">
|
|
8
8
|
<slot name="slot2"></slot>
|
|
9
9
|
</div>
|
|
10
|
-
<div class="
|
|
10
|
+
<div class="dashboard-quad-grid-cell dashboard-quad-grid-slot-3">
|
|
11
11
|
<slot name="slot3"></slot>
|
|
12
12
|
</div>
|
|
13
|
-
<div class="
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
-
.
|
|
69
|
+
.dashboard-quad-grid-slot-1 {
|
|
68
70
|
grid-column: 1 / span 2;
|
|
69
71
|
grid-row: 1;
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
.
|
|
74
|
+
.dashboard-quad-grid-slot-2 {
|
|
73
75
|
grid-column: 1 / span 2;
|
|
74
76
|
grid-row: 2;
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
.
|
|
79
|
+
.dashboard-quad-grid-slot-3 {
|
|
78
80
|
grid-column: 1;
|
|
79
81
|
grid-row: 3;
|
|
80
82
|
}
|
|
81
83
|
|
|
82
|
-
.
|
|
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
|
-
.
|
|
94
|
+
.dashboard-quad-grid-slot-1 {
|
|
93
95
|
grid-column: 1;
|
|
94
96
|
grid-row: 1 / span 2;
|
|
95
97
|
}
|
|
96
98
|
|
|
97
|
-
.
|
|
99
|
+
.dashboard-quad-grid-slot-2 {
|
|
98
100
|
grid-column: 2 / span 2;
|
|
99
101
|
grid-row: 1;
|
|
100
102
|
}
|
|
101
103
|
|
|
102
|
-
.
|
|
104
|
+
.dashboard-quad-grid-slot-3 {
|
|
103
105
|
grid-column: 2;
|
|
104
106
|
grid-row: 2;
|
|
105
107
|
}
|
|
106
108
|
|
|
107
|
-
.
|
|
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;">🙂 👍🏽 👨👩👧 <b>Not bold</b></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")).
|
|
50
|
-
expect(wrapper.find(".dashboard-quad-grid")).
|
|
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(".
|
|
60
|
-
expect(wrapper.find(".
|
|
61
|
-
expect(wrapper.find(".
|
|
62
|
-
expect(wrapper.find(".
|
|
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(".
|
|
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(".
|
|
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(".
|
|
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(".
|
|
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(".
|
|
143
|
-
expect(wrapper.find(".
|
|
144
|
-
expect(wrapper.find(".
|
|
145
|
-
expect(wrapper.find(".
|
|
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")).
|
|
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")).
|
|
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")).
|
|
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")).
|
|
207
|
-
expect(wrapper.find(".dashboard-quad-grid")).
|
|
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(".
|
|
219
|
-
const slot2 = wrapper.find(".
|
|
220
|
-
const slot3 = wrapper.find(".
|
|
221
|
-
const slot4 = wrapper.find(".
|
|
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("
|
|
226
|
-
expect(slot2.classes()).toContain("
|
|
227
|
-
expect(slot3.classes()).toContain("
|
|
228
|
-
expect(slot4.classes()).toContain("
|
|
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
|
|
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(".
|
|
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(".
|
|
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("<Special>");
|
|
@@ -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` | `
|
|
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
|
|
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
|
|
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
|
-
.
|
|
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
|
-
|
|
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;">🙂 👍🏽 👨👩👧 <b>Not bold</b></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
|
+
};
|