srcdev-nuxt-components 9.4.8 → 9.4.10

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 (113) hide show
  1. package/.claude/commands/migrate-component.md +20 -6
  2. package/.claude/component-ledger/audit.json +1 -1
  3. package/.claude/component-ledger/build.mjs +26 -0
  4. package/.claude/component-ledger/output.html +8 -3
  5. package/.claude/component-ledger/template.html +7 -2
  6. package/.claude/skills/component-dynamic-slots.md +1 -1
  7. package/.claude/skills/components/alert-mask-core.md +84 -0
  8. package/.claude/skills/components/canvas-switcher.md +1 -1
  9. package/.claude/skills/components/carousel-flip.md +112 -168
  10. package/.claude/skills/components/container-glow.md +123 -0
  11. package/.claude/skills/components/dashboard-quad-grid.md +98 -0
  12. package/.claude/skills/components/dashboard-stats-grid.md +103 -0
  13. package/.claude/skills/components/display-theme-switch.md +60 -0
  14. package/.claude/skills/components/glowing-border.md +66 -0
  15. package/.claude/skills/components/input-range-core.md +142 -0
  16. package/.claude/skills/components/section-parallax.md +32 -10
  17. package/.claude/skills/components/ui-block-decorated.md +56 -0
  18. package/.claude/skills/components/wipe-away-vertical.md +98 -0
  19. package/.claude/skills/icon-sets.md +1 -1
  20. package/.claude/skills/index.md +10 -1
  21. package/.vscode/css-custom-data.json +38 -0
  22. package/.vscode/settings.json +3 -0
  23. package/.vscode/srcdev-component-alert-mask-core.code-snippets +31 -0
  24. package/.vscode/srcdev-component-carousel-flip.code-snippets +41 -0
  25. package/.vscode/srcdev-component-container-glow.code-snippets +33 -0
  26. package/.vscode/srcdev-component-dashboard-quad-grid.code-snippets +36 -0
  27. package/.vscode/srcdev-component-dashboard-stats-grid.code-snippets +35 -0
  28. package/.vscode/srcdev-component-display-theme-switch.code-snippets +27 -0
  29. package/.vscode/srcdev-component-glowing-border.code-snippets +23 -0
  30. package/.vscode/srcdev-component-input-range.code-snippets +85 -0
  31. package/.vscode/srcdev-component-section-parallax.code-snippets +27 -0
  32. package/.vscode/srcdev-component-ui-block-decorated.code-snippets +24 -0
  33. package/.vscode/srcdev-component-wipe-away-vertical.code-snippets +25 -0
  34. package/README.md +18 -37
  35. package/app/components/{alert-mask → 01.atoms/alert-mask}/AlertMaskCore.vue +16 -17
  36. package/app/components/01.atoms/alert-mask/CONSUMER-STYLING.md +46 -0
  37. package/app/components/01.atoms/alert-mask/stories/AlertMaskCore.stories.ts +79 -0
  38. package/app/components/{alert-mask → 01.atoms/alert-mask}/tests/AlertMaskCore.spec.ts +10 -10
  39. package/app/components/01.atoms/animations/container-glow/CONSUMER-STYLING.md +86 -0
  40. package/app/components/01.atoms/animations/container-glow/ContainerGlow.vue +299 -0
  41. package/app/components/01.atoms/animations/container-glow/stories/ContainerGlow.stories.ts +175 -0
  42. package/app/components/01.atoms/animations/container-glow/tests/ContainerGlow.spec.ts +131 -0
  43. package/app/components/01.atoms/animations/container-glow/tests/__snapshots__/ContainerGlow.spec.ts.snap +14 -0
  44. package/app/components/01.atoms/animations/glowing-border/CONSUMER-STYLING.md +61 -0
  45. package/app/components/01.atoms/animations/glowing-border/GlowingBorder.vue +132 -0
  46. package/app/components/01.atoms/animations/glowing-border/stories/GlowingBorder.stories.ts +165 -0
  47. package/app/components/01.atoms/animations/glowing-border/tests/GlowingBorder.spec.ts +94 -0
  48. package/app/components/01.atoms/animations/glowing-border/tests/__snapshots__/GlowingBorder.spec.ts.snap +3 -0
  49. package/app/components/01.atoms/animations/section-parallax/CONSUMER-STYLING.md +44 -0
  50. package/app/components/{parallax → 01.atoms/animations/section-parallax}/SectionParallax.vue +11 -10
  51. package/app/components/01.atoms/animations/section-parallax/stories/SectionParallax.stories.ts +98 -0
  52. package/app/components/01.atoms/animations/section-parallax/tests/SectionParallax.spec.ts +102 -0
  53. package/app/components/01.atoms/animations/section-parallax/tests/__snapshots__/SectionParallax.spec.ts.snap +9 -0
  54. package/app/components/01.atoms/animations/view-timeline/CONSUMER-STYLING.md +70 -0
  55. package/app/components/01.atoms/animations/view-timeline/WipeAwayVertical.vue +181 -0
  56. package/app/components/01.atoms/animations/view-timeline/stories/WipeAwayVertical.stories.ts +269 -0
  57. package/app/components/01.atoms/animations/view-timeline/tests/WipeAwayVertical.spec.ts +127 -0
  58. package/app/components/01.atoms/canvas-switcher/CONSUMER-STYLING.md +1 -1
  59. package/app/components/01.atoms/canvas-switcher/stories/CanvasSwitcher.stories.ts +1 -1
  60. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md +42 -0
  61. package/app/components/{layout-grids/LayoutGridA.vue → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/DashboardQuadGrid.vue} +23 -13
  62. package/app/components/{layout-grids/stories/LayoutGridA.stories.ts → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/stories/DashboardQuadGrid.stories.ts} +151 -23
  63. package/app/components/{layout-grids/tests/LayoutGridA.spec.ts → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/tests/DashboardQuadGrid.spec.ts} +25 -25
  64. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/CONSUMER-STYLING.md +41 -0
  65. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/DashboardStatsGrid.vue +133 -0
  66. package/app/components/{layout-grids/stories/LayoutGridB.stories.ts → 01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/stories/DashboardStatsGrid.stories.ts} +24 -24
  67. package/app/components/{layout-grids/tests/LayoutGridB.spec.ts → 01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/tests/DashboardStatsGrid.spec.ts} +23 -23
  68. package/app/components/01.atoms/navigation/skip-links/SkipLinks.vue +7 -1
  69. package/app/components/01.atoms/ui-block-decorated/CONSUMER-STYLING.md +39 -0
  70. package/app/components/01.atoms/ui-block-decorated/UiBlockDecorated.vue +141 -0
  71. package/app/components/01.atoms/ui-block-decorated/stories/UiBlockDecorated.stories.ts +148 -0
  72. package/app/components/01.atoms/ui-block-decorated/tests/UiBlockDecorated.spec.ts +80 -0
  73. package/app/components/01.atoms/ui-block-decorated/tests/__snapshots__/UiBlockDecorated.spec.ts.snap +3 -0
  74. package/app/components/02.molecules/alert-masked-content/tests/AlertMaskedContent.spec.ts +6 -6
  75. package/app/components/02.molecules/display-theme-switch/CONSUMER-STYLING.md +34 -0
  76. package/app/components/02.molecules/display-theme-switch/DisplayThemeSwitch.vue +102 -0
  77. package/app/components/02.molecules/display-theme-switch/stories/DisplayThemeSwitch.stories.ts +115 -0
  78. package/app/components/02.molecules/display-theme-switch/tests/DisplayThemeSwitch.spec.ts +86 -0
  79. package/app/components/03.organisms/image-galleries/carousel-flip/CONSUMER-STYLING.md +46 -0
  80. package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/CarouselFlip.vue +63 -42
  81. package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/stories/CarouselFlip.stories.ts +96 -11
  82. package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/tests/CarouselFlip.spec.ts +80 -0
  83. package/app/components/05.forms/form-field/FormField.vue +1 -1
  84. package/app/components/05.forms/form-wrapper/FormWrapper.vue +1 -1
  85. package/app/components/05.forms/input-range/CONSUMER-STYLING.md +45 -0
  86. package/app/components/05.forms/input-range/InputRangeCore.vue +79 -143
  87. package/app/components/05.forms/input-range/stories/InputRangeCore.stories.ts +126 -0
  88. package/app/components/05.forms/input-range/tests/InputRangeCore.spec.ts +124 -0
  89. package/app/components/05.forms/input-range/variants/InputRangeDefault.vue +9 -32
  90. package/app/components/05.forms/input-range/variants/stories/InputRangeDefault.stories.ts +108 -0
  91. package/app/components/05.forms/input-range/variants/tests/InputRangeDefault.spec.ts +140 -0
  92. package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +135 -0
  93. package/app/components/05.forms/triple-toggle-switch/CONSUMER-STYLING.md +16 -2
  94. package/app/components/05.forms/triple-toggle-switch/TripleToggleSwitchCore.vue +6 -4
  95. package/app/types/components/container-glow.d.ts +8 -0
  96. package/app/types/components/index.ts +1 -1
  97. package/package.json +3 -2
  98. package/app/components/05.forms/input-range-fancy/InputRangeFancyCore.vue +0 -426
  99. package/app/components/05.forms/input-range-fancy/InputRangeFancyWithLabel.vue +0 -94
  100. package/app/components/carousels/CarouselBasic.vue +0 -317
  101. package/app/components/carousels/CarouselInfinite.vue +0 -358
  102. package/app/components/container-glow/ContainerGlowCore.vue +0 -296
  103. package/app/components/container-glow/stories/ContainerGlowCore.stories.ts +0 -337
  104. package/app/components/display-theme-switch/DisplayThemeSwitch.vue +0 -189
  105. package/app/components/display-theme-switch/stories/DisplayThemeSwitch.stories.ts +0 -154
  106. package/app/components/glowing-border/GlowingBorder.vue +0 -142
  107. package/app/components/glowing-border/stories/GlowingBorder.stories.ts +0 -141
  108. package/app/components/layout-grids/LayoutGridB.vue +0 -134
  109. package/app/components/test-storybook/TestStorybook.vue +0 -49
  110. package/app/components/test-storybook/stories/TestStorybook.stories.ts +0 -28
  111. package/app/components/ui-block-decorated/UiBlockDecorated.vue +0 -167
  112. package/app/components/view-timeline/WipeAwayVertical.vue +0 -180
  113. package/app/types/components/carousel-basic.d.ts +0 -19
@@ -0,0 +1,127 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { mountSuspended } from "@nuxt/test-utils/runtime";
3
+ import WipeAwayVertical from "../WipeAwayVertical.vue";
4
+
5
+ describe("WipeAwayVertical", () => {
6
+ // ─── Mount ───────────────────────────────────────────────────────────────
7
+
8
+ it("mounts without error", async () => {
9
+ const wrapper = await mountSuspended(WipeAwayVertical, { props: { itemCount: 3 } });
10
+ expect(wrapper.vm).toBeTruthy();
11
+ });
12
+
13
+ // ─── Root element ────────────────────────────────────────────────────────
14
+
15
+ it("renders a <div> as the root element by default", async () => {
16
+ const wrapper = await mountSuspended(WipeAwayVertical, { props: { itemCount: 2 } });
17
+ expect(wrapper.element.tagName).toBe("DIV");
18
+ });
19
+
20
+ it("renders the tag prop as the root element", async () => {
21
+ const wrapper = await mountSuspended(WipeAwayVertical, {
22
+ props: { itemCount: 2, tag: "section" },
23
+ });
24
+ expect(wrapper.element.tagName).toBe("SECTION");
25
+ });
26
+
27
+ it("always has the wipe-away-vertical class", async () => {
28
+ const wrapper = await mountSuspended(WipeAwayVertical, { props: { itemCount: 2 } });
29
+ expect(wrapper.classes()).toContain("wipe-away-vertical");
30
+ });
31
+
32
+ // ─── Slots ───────────────────────────────────────────────────────────────
33
+
34
+ it("renders one sticky-item per itemCount but one fewer scrolling-section (the last item has no wipe of its own)", async () => {
35
+ const wrapper = await mountSuspended(WipeAwayVertical, { props: { itemCount: 3 } });
36
+ expect(wrapper.findAll(".sticky-item")).toHaveLength(3);
37
+ expect(wrapper.findAll(".scrolling-section")).toHaveLength(2);
38
+ });
39
+
40
+ it("assigns each scrolling-section its own grid-row after the leading buffer", async () => {
41
+ const wrapper = await mountSuspended(WipeAwayVertical, { props: { itemCount: 3 } });
42
+ const sections = wrapper.findAll(".scrolling-section");
43
+ expect((sections[0]!.element as HTMLElement).style.gridRow).toBe("2");
44
+ expect((sections[1]!.element as HTMLElement).style.gridRow).toBe("3");
45
+ });
46
+
47
+ it("renders a leading buffer spacer before the first scrolling section", async () => {
48
+ const wrapper = await mountSuspended(WipeAwayVertical, { props: { itemCount: 3 } });
49
+ expect(wrapper.find(".leading-buffer").exists()).toBe(true);
50
+ expect(wrapper.find(".leading-buffer").attributes("aria-hidden")).toBe("true");
51
+ });
52
+
53
+ it("renders a trailing buffer spacer after the last scrolling section", async () => {
54
+ const wrapper = await mountSuspended(WipeAwayVertical, { props: { itemCount: 3 } });
55
+ expect(wrapper.find(".trailing-buffer").exists()).toBe(true);
56
+ expect(wrapper.find(".trailing-buffer").attributes("aria-hidden")).toBe("true");
57
+ expect((wrapper.find(".trailing-buffer").element as HTMLElement).style.gridRow).toBe("4");
58
+ });
59
+
60
+ it("renders named stickyItem-{key} slot content", async () => {
61
+ const wrapper = await mountSuspended(WipeAwayVertical, {
62
+ props: { itemCount: 2 },
63
+ slots: {
64
+ "stickyItem-0": "<p class='sticky-0'>First</p>",
65
+ "stickyItem-1": "<p class='sticky-1'>Second</p>",
66
+ },
67
+ });
68
+ expect(wrapper.find(".sticky-0").exists()).toBe(true);
69
+ expect(wrapper.find(".sticky-1").exists()).toBe(true);
70
+ });
71
+
72
+ it("renders named scrollingItem-{key} slot content for all but the last item", async () => {
73
+ const wrapper = await mountSuspended(WipeAwayVertical, {
74
+ props: { itemCount: 3 },
75
+ slots: {
76
+ "scrollingItem-0": "<p class='scroll-0'>First</p>",
77
+ "scrollingItem-1": "<p class='scroll-1'>Second</p>",
78
+ "scrollingItem-2": "<p class='scroll-2'>Third</p>",
79
+ },
80
+ });
81
+ expect(wrapper.find(".scroll-0").exists()).toBe(true);
82
+ expect(wrapper.find(".scroll-1").exists()).toBe(true);
83
+ expect(wrapper.find(".scroll-2").exists()).toBe(false);
84
+ });
85
+
86
+ // ─── Per-item styles ─────────────────────────────────────────────────────
87
+
88
+ it("assigns descending z-index across sticky items", async () => {
89
+ const wrapper = await mountSuspended(WipeAwayVertical, { props: { itemCount: 3 } });
90
+ const items = wrapper.findAll(".sticky-item");
91
+ expect((items[0]!.element as HTMLElement).style.zIndex).toBe("3");
92
+ expect((items[1]!.element as HTMLElement).style.zIndex).toBe("2");
93
+ expect((items[2]!.element as HTMLElement).style.zIndex).toBe("1");
94
+ });
95
+
96
+ // Note: `animation-timeline` and `timeline-scope` are experimental CSS
97
+ // properties that jsdom's CSSOM silently drops when set via style bindings
98
+ // (they never appear in the serialized style attribute), so they can't be
99
+ // asserted on directly in this test environment.
100
+
101
+ // ─── styleClassPassthrough ───────────────────────────────────────────────
102
+
103
+ it("applies a single styleClassPassthrough string to the root", async () => {
104
+ const wrapper = await mountSuspended(WipeAwayVertical, {
105
+ props: { itemCount: 2, styleClassPassthrough: "custom-timeline" },
106
+ });
107
+ expect(wrapper.classes()).toContain("custom-timeline");
108
+ });
109
+
110
+ it("applies multiple styleClassPassthrough classes from an array", async () => {
111
+ const wrapper = await mountSuspended(WipeAwayVertical, {
112
+ props: { itemCount: 2, styleClassPassthrough: ["a-class", "b-class"] },
113
+ });
114
+ expect(wrapper.classes()).toContain("a-class");
115
+ expect(wrapper.classes()).toContain("b-class");
116
+ });
117
+
118
+ it("updates classes when styleClassPassthrough prop changes", async () => {
119
+ const wrapper = await mountSuspended(WipeAwayVertical, {
120
+ props: { itemCount: 2, styleClassPassthrough: ["original"] },
121
+ });
122
+ expect(wrapper.classes()).toContain("original");
123
+ await wrapper.setProps({ styleClassPassthrough: ["updated"] });
124
+ expect(wrapper.classes()).not.toContain("original");
125
+ expect(wrapper.classes()).toContain("updated");
126
+ });
127
+ });
@@ -22,6 +22,6 @@ CanvasSwitcher itself exposes the following public tokens:
22
22
  `.mobileCanvas`, `.tabletCanvas`, `.laptopCanvas`, `.desktopCanvas`, `.fullWidthCanvas` are also
23
23
  declared in this component's stylesheet (not scoped to `.canvas-switcher`) so a consumer can apply
24
24
  the same `MediaCanvas` value returned by `v-model:canvas-name` directly as a class on a preview
25
- wrapper to constrain its width — see `LayoutGridA`/`LayoutGridB` stories for the pattern. These are
25
+ wrapper to constrain its width — see `DashboardQuadGrid`/`DashboardStatsGrid` stories for the pattern. These are
26
26
  plain global utility classes, not overridable via a CSS custom property; import `CanvasSwitcher`
27
27
  (or otherwise ensure its styles are bundled) wherever you rely on them.
@@ -31,7 +31,7 @@ export default {
31
31
  docs: {
32
32
  description: {
33
33
  component:
34
- "An icon button group for switching between MediaCanvas preview widths (mobile/tablet/laptop/desktop/full width). Also declares the `.mobileCanvas`/`.tabletCanvas`/etc. utility classes a consumer can apply to a preview wrapper to constrain it to the matching width — see LayoutGridA/LayoutGridB stories.",
34
+ "An icon button group for switching between MediaCanvas preview widths (mobile/tablet/laptop/desktop/full width). Also declares the `.mobileCanvas`/`.tabletCanvas`/etc. utility classes a consumer can apply to a preview wrapper to constrain it to the matching width — see DashboardQuadGrid/DashboardStatsGrid stories.",
35
35
  },
36
36
  },
37
37
  },
@@ -0,0 +1,42 @@
1
+ # DashboardQuadGrid — Consumer Styling Guide
2
+
3
+ ## Public token API
4
+
5
+ | Token | Default | Controls |
6
+ |---|---|---|
7
+ | `--dashboard-quad-grid-gap` | `2rem` | Gap between the four slot panels |
8
+ | `--dashboard-quad-grid-padding` | `2rem` | Padding inside each slot panel |
9
+ | `--dashboard-quad-grid-outline-width` | `0.1rem` | Slot panel outline width |
10
+ | `--dashboard-quad-grid-outline-colour` | `light-dark(black, white)` | Slot panel outline colour |
11
+ | `--dashboard-quad-grid-outline-offset` | `0` | Slot panel outline offset |
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` | `light-dark(black, white)` | Slot panel border colour, only visible once `--dashboard-quad-grid-border-width` is set |
14
+ | `--dashboard-quad-grid-border-radius` | `0.5rem` | Slot panel corner radius |
15
+ | `--dashboard-quad-grid-rule-width` | `0.1rem` | Width of the grid gap rule line (CSS Gap Decorations — see Notes) |
16
+ | `--dashboard-quad-grid-rule-colour` | `transparent` | Colour of the grid gap rule line, invisible by default |
17
+ | `--dashboard-quad-grid-rule-inset` | `0` | Inset of the grid gap rule line from the panel edges |
18
+
19
+ ```css
20
+ .my-page {
21
+ --dashboard-quad-grid-gap: 1.6rem;
22
+ --dashboard-quad-grid-outline-colour: var(--theme-border);
23
+ }
24
+ ```
25
+
26
+ Or scope to a single instance via `styleClassPassthrough`:
27
+
28
+ ```vue
29
+ <DashboardQuadGrid style-class-passthrough="promo-quad-grid">...</DashboardQuadGrid>
30
+ ```
31
+
32
+ ## Notes
33
+
34
+ - The four-panel layout (mobile stack → tablet 2×3 → desktop 3×2 with slot1 spanning vertically)
35
+ is structural, driven by fixed container-query breakpoints (`768px`, `1060px`) rather than
36
+ tokens/props — this is a fixed dashboard shape, not a general-purpose grid. Use
37
+ `LayoutGridByCols`/`LayoutGridByWidth` instead if you need an arbitrary column count.
38
+ - Responds to its own container width (`container-type: inline-size`), not the viewport.
39
+ - Also declares a `rule`/`rule-break`/`rule-inset` gap-decoration line between grid cells (the
40
+ CSS Gap Decorations spec) via `--dashboard-quad-grid-rule-*` tokens. Browser support is limited
41
+ as of 2026, so it's transparent by default and safe to ignore — set
42
+ `--dashboard-quad-grid-rule-colour` to opt in where supported.
@@ -1,6 +1,6 @@
1
1
  <template>
2
- <div class="layout-grid-a-wrapper" :class="[elementClasses]">
3
- <div class="layout-grid-a">
2
+ <div class="dashboard-quad-grid-wrapper" :class="[elementClasses]">
3
+ <div class="dashboard-quad-grid">
4
4
  <div class="slot1">
5
5
  <slot name="slot1"></slot>
6
6
  </div>
@@ -18,11 +18,12 @@
18
18
  </template>
19
19
 
20
20
  <script setup lang="ts">
21
- const props = defineProps({
22
- styleClassPassthrough: {
23
- type: [String, Array] as PropType<string | string[]>,
24
- default: () => [],
25
- },
21
+ interface Props {
22
+ styleClassPassthrough?: string | string[];
23
+ }
24
+
25
+ const props = withDefaults(defineProps<Props>(), {
26
+ styleClassPassthrough: () => [],
26
27
  });
27
28
 
28
29
  const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
@@ -37,17 +38,26 @@ watch(
37
38
 
38
39
  <style lang="css">
39
40
  @layer components {
40
- .layout-grid-a-wrapper {
41
+ .dashboard-quad-grid-wrapper {
41
42
  container-type: inline-size;
42
43
 
43
- .layout-grid-a {
44
+ .dashboard-quad-grid {
44
45
  display: grid;
45
- gap: 20px;
46
+ gap: var(--dashboard-quad-grid-gap, 2rem);
47
+
48
+ /* CSS Gap Decorations (rule/rule-break/rule-inset) — limited browser support as of 2026, transparent by default */
49
+ rule: var(--dashboard-quad-grid-rule-width, 0.1rem) solid var(--dashboard-quad-grid-rule-colour, transparent);
50
+ rule-break: intersection;
51
+ rule-inset: var(--dashboard-quad-grid-rule-inset, 0);
46
52
 
47
53
  & > div {
48
- padding: 20px;
49
- outline: 1px solid light-dark(black, white);
50
- border-radius: 5px;
54
+ padding: var(--dashboard-quad-grid-padding, 2rem);
55
+ outline: var(--dashboard-quad-grid-outline-width, 0.1rem) solid
56
+ var(--dashboard-quad-grid-outline-colour, light-dark(black, white));
57
+ outline-offset: var(--dashboard-quad-grid-outline-offset, 0);
58
+ border: var(--dashboard-quad-grid-border-width, 0) solid
59
+ var(--dashboard-quad-grid-border-colour, light-dark(black, white));
60
+ border-radius: var(--dashboard-quad-grid-border-radius, 0.5rem);
51
61
  }
52
62
 
53
63
  @container (min-width: 768px) {
@@ -1,16 +1,16 @@
1
1
  import type { Meta, StoryFn } from "@nuxtjs/storybook";
2
- import LayoutGridAComponent from "../LayoutGridA.vue";
3
- import type { MediaCanvas } from "../../../types/components";
2
+ import DashboardQuadGridComponent from "../DashboardQuadGrid.vue";
3
+ import type { MediaCanvas } from "~/types/components";
4
4
 
5
5
  // Define the args interface
6
- interface LayoutGridAArgs {
6
+ interface DashboardQuadGridArgs {
7
7
  mediaCanvas: MediaCanvas;
8
8
  styleClassPassthrough: string[];
9
9
  }
10
10
 
11
11
  export default {
12
- title: "Components/Layouts/LayoutGridA",
13
- component: LayoutGridAComponent,
12
+ title: "Atoms/Content Wrappers/Dashboard Quad Grid",
13
+ component: DashboardQuadGridComponent,
14
14
  argTypes: {
15
15
  // Canvas/Media Query Testing
16
16
  mediaCanvas: {
@@ -40,10 +40,10 @@ export default {
40
40
  },
41
41
  },
42
42
  },
43
- } as Meta<LayoutGridAArgs>;
43
+ } as Meta<DashboardQuadGridArgs>;
44
44
 
45
- const Template: StoryFn<LayoutGridAArgs> = (args) => ({
46
- components: { LayoutGridAComponent },
45
+ const Template: StoryFn<DashboardQuadGridArgs> = (args) => ({
46
+ components: { DashboardQuadGridComponent },
47
47
  setup() {
48
48
  const sampleContent = {
49
49
  shortText: "Brief sample content for this slot.",
@@ -57,7 +57,7 @@ const Template: StoryFn<LayoutGridAArgs> = (args) => ({
57
57
  },
58
58
  template: `
59
59
  <div style="padding: 20px;" :class="args.mediaCanvas">
60
- <LayoutGridAComponent :style-class-passthrough="[args.mediaCanvas, ...args.styleClassPassthrough]">
60
+ <DashboardQuadGridComponent :style-class-passthrough="[args.mediaCanvas, ...args.styleClassPassthrough]">
61
61
  <template #slot1>
62
62
  <h3 style="margin: 0 0 16px 0; color: #374151; font-size: 18px;">Primary Content</h3>
63
63
  <p style="margin: 0 0 12px 0; color: #6b7280; line-height: 1.5;">{{ sampleContent.longText }}</p>
@@ -78,7 +78,7 @@ const Template: StoryFn<LayoutGridAArgs> = (args) => ({
78
78
  <h3 style="margin: 0 0 16px 0; color: #dc2626; font-size: 18px;">Additional Details</h3>
79
79
  <p style="margin: 0; color: #ef4444; line-height: 1.5;">{{ sampleContent.mediumText }}</p>
80
80
  </template>
81
- </LayoutGridAComponent>
81
+ </DashboardQuadGridComponent>
82
82
  </div>
83
83
  `,
84
84
  });
@@ -155,14 +155,14 @@ FullWidthView.parameters = {
155
155
 
156
156
  // ===== CONTENT VARIATION STORIES =====
157
157
 
158
- const ContentVariationsTemplate: StoryFn<LayoutGridAArgs> = (args) => ({
159
- components: { LayoutGridAComponent },
158
+ const ContentVariationsTemplate: StoryFn<DashboardQuadGridArgs> = (args) => ({
159
+ components: { DashboardQuadGridComponent },
160
160
  setup() {
161
161
  return { args };
162
162
  },
163
163
  template: `
164
164
  <div style="padding: 20px;" :class="args.mediaCanvas">
165
- <LayoutGridAComponent :style-class-passthrough="[args.mediaCanvas]">
165
+ <DashboardQuadGridComponent :style-class-passthrough="[args.mediaCanvas]">
166
166
  <!-- Slot 1 - Main article content -->
167
167
  <template #slot1>
168
168
  <article style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 24px; border-radius: 12px; margin: -20px; height: calc(100% + 40px); display: flex; flex-direction: column;">
@@ -254,7 +254,7 @@ const ContentVariationsTemplate: StoryFn<LayoutGridAArgs> = (args) => ({
254
254
  </div>
255
255
  </div>
256
256
  </template>
257
- </LayoutGridAComponent>
257
+ </DashboardQuadGridComponent>
258
258
  </div>
259
259
  `,
260
260
  });
@@ -267,21 +267,149 @@ DashboardExample.parameters = {
267
267
  docs: {
268
268
  description: {
269
269
  story:
270
- "Complete dashboard example showcasing LayoutGridA with a featured article, performance metrics, quick actions, and activity feed. Demonstrates real-world usage with rich content.",
270
+ "Complete dashboard example showcasing DashboardQuadGrid with a featured article, performance metrics, quick actions, and activity feed. Demonstrates real-world usage with rich content.",
271
+ },
272
+ },
273
+ };
274
+
275
+ // ===== RULE DECORATIONS (CSS GAP DECORATIONS) =====
276
+
277
+ const RuleDecorationsTemplate: StoryFn<DashboardQuadGridArgs> = (args) => ({
278
+ components: { DashboardQuadGridComponent },
279
+ setup() {
280
+ const ruleTokenStyle = [
281
+ "--dashboard-quad-grid-outline-width: 0",
282
+ "--dashboard-quad-grid-border-width: 0",
283
+ "--dashboard-quad-grid-border-radius: 0",
284
+ "--dashboard-quad-grid-rule-width: 0.2rem",
285
+ "--dashboard-quad-grid-rule-colour: light-dark(#cbd5e1, #475569)",
286
+ "--dashboard-quad-grid-rule-inset: 0",
287
+ ].join("; ");
288
+
289
+ return { args, ruleTokenStyle };
290
+ },
291
+ template: `
292
+ <div style="padding: 20px;" :class="args.mediaCanvas">
293
+ <DashboardQuadGridComponent :style-class-passthrough="[args.mediaCanvas]" :style="ruleTokenStyle">
294
+ <!-- Slot 1 - Main article content -->
295
+ <template #slot1>
296
+ <article style="color: inherit; padding: 24px; height: 100%; display: flex; flex-direction: column;">
297
+ <h2 style="margin: 0 0 16px 0; font-size: 24px;">Featured Article</h2>
298
+ <div style="flex: 1;">
299
+ <p style="margin: 0 0 16px 0; opacity: 0.85; line-height: 1.6;">
300
+ Discover the latest insights and trends in modern web development. This comprehensive guide covers
301
+ everything you need to know about building scalable, responsive applications.
302
+ </p>
303
+ <p style="margin: 0 0 20px 0; opacity: 0.75; line-height: 1.6;">
304
+ From CSS Grid and Container Queries to Vue 3 composition patterns, learn how to create stunning
305
+ user interfaces that work seamlessly across all devices and screen sizes.
306
+ </p>
307
+ </div>
308
+ <div style="display: flex; justify-content: space-between; align-items: center;">
309
+ <span style="opacity: 0.6; font-size: 14px;">Published: Feb 5, 2026</span>
310
+ <button style="padding: 10px 20px; background: transparent; color: inherit; border: 1px solid currentColor; border-radius: 6px; cursor: pointer;">
311
+ Read More
312
+ </button>
313
+ </div>
314
+ </article>
315
+ </template>
316
+
317
+ <!-- Slot 2 - Statistics dashboard -->
318
+ <template #slot2>
319
+ <div style="padding: 24px; height: 100%;">
320
+ <h3 style="margin: 0 0 20px 0; font-size: 18px;">Performance Overview</h3>
321
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 16px;">
322
+ <div style="text-align: center; padding: 16px;">
323
+ <div style="font-size: 28px; font-weight: bold; color: #059669; margin-bottom: 4px;">98.5%</div>
324
+ <div style="font-size: 12px; opacity: 0.7;">Uptime</div>
325
+ </div>
326
+ <div style="text-align: center; padding: 16px;">
327
+ <div style="font-size: 28px; font-weight: bold; color: #3b82f6; margin-bottom: 4px;">1,247</div>
328
+ <div style="font-size: 12px; opacity: 0.7;">Active Users</div>
329
+ </div>
330
+ <div style="text-align: center; padding: 16px;">
331
+ <div style="font-size: 28px; font-weight: bold; color: #f59e0b; margin-bottom: 4px;">342ms</div>
332
+ <div style="font-size: 12px; opacity: 0.7;">Avg Response</div>
333
+ </div>
334
+ </div>
335
+ </div>
336
+ </template>
337
+
338
+ <!-- Slot 3 - Quick actions -->
339
+ <template #slot3>
340
+ <div style="padding: 20px; height: 100%;">
341
+ <h3 style="margin: 0 0 16px 0; font-size: 16px;">Quick Actions</h3>
342
+ <div style="display: flex; flex-direction: column; gap: 12px;">
343
+ <button style="width: 100%; padding: 12px; background: transparent; color: inherit; border: 1px solid currentColor; border-radius: 6px; cursor: pointer; text-align: left;">
344
+ 📊 View Analytics
345
+ </button>
346
+ <button style="width: 100%; padding: 12px; background: transparent; color: inherit; border: 1px solid currentColor; border-radius: 6px; cursor: pointer; text-align: left;">
347
+ ⚙️ Settings
348
+ </button>
349
+ <button style="width: 100%; padding: 12px; background: transparent; color: inherit; border: 1px solid currentColor; border-radius: 6px; cursor: pointer; text-align: left;">
350
+ 👥 Manage Users
351
+ </button>
352
+ </div>
353
+ </div>
354
+ </template>
355
+
356
+ <!-- Slot 4 - Recent activity -->
357
+ <template #slot4>
358
+ <div style="padding: 20px; height: 100%;">
359
+ <h3 style="margin: 0 0 16px 0; font-size: 16px;">Recent Activity</h3>
360
+ <div style="display: flex; flex-direction: column; gap: 4px;">
361
+ <div style="display: flex; align-items: center; padding: 8px 0;">
362
+ <div style="width: 8px; height: 8px; background: #059669; border-radius: 50%; margin-right: 12px;"></div>
363
+ <div style="flex: 1;">
364
+ <div style="font-size: 13px;">New user registered</div>
365
+ <div style="font-size: 11px; opacity: 0.6;">2 minutes ago</div>
366
+ </div>
367
+ </div>
368
+ <div style="display: flex; align-items: center; padding: 8px 0;">
369
+ <div style="width: 8px; height: 8px; background: #3b82f6; border-radius: 50%; margin-right: 12px;"></div>
370
+ <div style="flex: 1;">
371
+ <div style="font-size: 13px;">System backup completed</div>
372
+ <div style="font-size: 11px; opacity: 0.6;">15 minutes ago</div>
373
+ </div>
374
+ </div>
375
+ <div style="display: flex; align-items: center; padding: 8px 0;">
376
+ <div style="width: 8px; height: 8px; background: #f59e0b; border-radius: 50%; margin-right: 12px;"></div>
377
+ <div style="flex: 1;">
378
+ <div style="font-size: 13px;">Update available</div>
379
+ <div style="font-size: 11px; opacity: 0.6;">1 hour ago</div>
380
+ </div>
381
+ </div>
382
+ </div>
383
+ </div>
384
+ </template>
385
+ </DashboardQuadGridComponent>
386
+ </div>
387
+ `,
388
+ });
389
+
390
+ export const RuleDecorations = RuleDecorationsTemplate.bind({});
391
+ RuleDecorations.args = {
392
+ mediaCanvas: "desktopCanvas",
393
+ };
394
+ RuleDecorations.parameters = {
395
+ docs: {
396
+ description: {
397
+ story:
398
+ "Same dashboard content as DashboardExample, but with --dashboard-quad-grid-outline-width, -border-width and -border-radius set to 0 and the CSS Gap Decorations rule tokens (--dashboard-quad-grid-rule-width/-colour/-inset) driving the panel dividers instead. Requires a browser that supports the CSS Gap Decorations spec (rule/rule-break/rule-inset) — limited support as of 2026, so the divider lines may not render everywhere.",
271
399
  },
272
400
  },
273
401
  };
274
402
 
275
403
  // ===== CONTENT LENGTH VARIATIONS =====
276
404
 
277
- const ContentLengthTemplate: StoryFn<LayoutGridAArgs> = (args) => ({
278
- components: { LayoutGridAComponent },
405
+ const ContentLengthTemplate: StoryFn<DashboardQuadGridArgs> = (args) => ({
406
+ components: { DashboardQuadGridComponent },
279
407
  setup() {
280
408
  return { args };
281
409
  },
282
410
  template: `
283
411
  <div style="padding: 20px;" :class="args.mediaCanvas">
284
- <LayoutGridAComponent :style-class-passthrough="[args.mediaCanvas]">
412
+ <DashboardQuadGridComponent :style-class-passthrough="[args.mediaCanvas]">
285
413
  <!-- Slot 1 - Short content -->
286
414
  <template #slot1>
287
415
  <h3 style="margin: 0 0 16px 0; color: #374151;">Short Content</h3>
@@ -317,7 +445,7 @@ const ContentLengthTemplate: StoryFn<LayoutGridAArgs> = (args) => ({
317
445
  <li>List item three</li>
318
446
  </ul>
319
447
  </template>
320
- </LayoutGridAComponent>
448
+ </DashboardQuadGridComponent>
321
449
  </div>
322
450
  `,
323
451
  });
@@ -338,7 +466,7 @@ ContentLengthVariations.parameters = {
338
466
  // ===== COMPARISON STORY =====
339
467
 
340
468
  const ComparisonTemplate: StoryFn = () => ({
341
- components: { LayoutGridAComponent },
469
+ components: { DashboardQuadGridComponent },
342
470
  setup() {
343
471
  const canvases: MediaCanvas[] = ["mobileCanvas", "tabletCanvas", "laptopCanvas", "desktopCanvas"];
344
472
  return { canvases };
@@ -353,7 +481,7 @@ const ComparisonTemplate: StoryFn = () => ({
353
481
  {{ canvas === 'mobileCanvas' ? '(< 768px)' : canvas === 'tabletCanvas' ? '(768px - 1059px)' : '(≥ 1060px)' }}
354
482
  </h3>
355
483
  <div style="border: 2px solid #e5e7eb; border-radius: 12px; padding: 20px; background: #fafafa;">
356
- <LayoutGridAComponent :style-class-passthrough="[canvas]">
484
+ <DashboardQuadGridComponent :style-class-passthrough="[canvas]">
357
485
  <template #slot1>
358
486
  <div style="background: #dbeafe; padding: 16px; border-radius: 8px; text-align: center; font-weight: bold; color: #1e40af; margin: -20px; height: calc(100% + 40px); display: flex; align-items: center; justify-content: center;">
359
487
  Slot 1
@@ -383,7 +511,7 @@ const ComparisonTemplate: StoryFn = () => ({
383
511
  Slot 4
384
512
  </div>
385
513
  </template>
386
- </LayoutGridAComponent>
514
+ </DashboardQuadGridComponent>
387
515
  </div>
388
516
  </div>
389
517
  </div>
@@ -396,7 +524,7 @@ ResponsiveComparison.parameters = {
396
524
  docs: {
397
525
  description: {
398
526
  story:
399
- "Side-by-side comparison showing how LayoutGridA transforms across different responsive breakpoints. Notice how the layout changes from stacked (mobile) to 2×3 grid (tablet) to 3×2 grid with spanning elements (desktop).",
527
+ "Side-by-side comparison showing how DashboardQuadGrid transforms across different responsive breakpoints. Notice how the layout changes from stacked (mobile) to 2×3 grid (tablet) to 3×2 grid with spanning elements (desktop).",
400
528
  },
401
529
  },
402
530
  };