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.
- package/.claude/commands/migrate-component.md +20 -6
- package/.claude/component-ledger/audit.json +1 -1
- package/.claude/component-ledger/build.mjs +26 -0
- package/.claude/component-ledger/output.html +8 -3
- package/.claude/component-ledger/template.html +7 -2
- package/.claude/skills/component-dynamic-slots.md +1 -1
- package/.claude/skills/components/alert-mask-core.md +84 -0
- package/.claude/skills/components/canvas-switcher.md +1 -1
- package/.claude/skills/components/carousel-flip.md +112 -168
- package/.claude/skills/components/container-glow.md +123 -0
- package/.claude/skills/components/dashboard-quad-grid.md +98 -0
- package/.claude/skills/components/dashboard-stats-grid.md +103 -0
- package/.claude/skills/components/display-theme-switch.md +60 -0
- package/.claude/skills/components/glowing-border.md +66 -0
- package/.claude/skills/components/input-range-core.md +142 -0
- package/.claude/skills/components/section-parallax.md +32 -10
- package/.claude/skills/components/ui-block-decorated.md +56 -0
- package/.claude/skills/components/wipe-away-vertical.md +98 -0
- package/.claude/skills/icon-sets.md +1 -1
- package/.claude/skills/index.md +10 -1
- package/.vscode/css-custom-data.json +38 -0
- package/.vscode/settings.json +3 -0
- package/.vscode/srcdev-component-alert-mask-core.code-snippets +31 -0
- package/.vscode/srcdev-component-carousel-flip.code-snippets +41 -0
- package/.vscode/srcdev-component-container-glow.code-snippets +33 -0
- package/.vscode/srcdev-component-dashboard-quad-grid.code-snippets +36 -0
- package/.vscode/srcdev-component-dashboard-stats-grid.code-snippets +35 -0
- package/.vscode/srcdev-component-display-theme-switch.code-snippets +27 -0
- package/.vscode/srcdev-component-glowing-border.code-snippets +23 -0
- package/.vscode/srcdev-component-input-range.code-snippets +85 -0
- package/.vscode/srcdev-component-section-parallax.code-snippets +27 -0
- package/.vscode/srcdev-component-ui-block-decorated.code-snippets +24 -0
- package/.vscode/srcdev-component-wipe-away-vertical.code-snippets +25 -0
- package/README.md +18 -37
- package/app/components/{alert-mask → 01.atoms/alert-mask}/AlertMaskCore.vue +16 -17
- package/app/components/01.atoms/alert-mask/CONSUMER-STYLING.md +46 -0
- package/app/components/01.atoms/alert-mask/stories/AlertMaskCore.stories.ts +79 -0
- package/app/components/{alert-mask → 01.atoms/alert-mask}/tests/AlertMaskCore.spec.ts +10 -10
- package/app/components/01.atoms/animations/container-glow/CONSUMER-STYLING.md +86 -0
- package/app/components/01.atoms/animations/container-glow/ContainerGlow.vue +299 -0
- package/app/components/01.atoms/animations/container-glow/stories/ContainerGlow.stories.ts +175 -0
- package/app/components/01.atoms/animations/container-glow/tests/ContainerGlow.spec.ts +131 -0
- package/app/components/01.atoms/animations/container-glow/tests/__snapshots__/ContainerGlow.spec.ts.snap +14 -0
- package/app/components/01.atoms/animations/glowing-border/CONSUMER-STYLING.md +61 -0
- package/app/components/01.atoms/animations/glowing-border/GlowingBorder.vue +132 -0
- package/app/components/01.atoms/animations/glowing-border/stories/GlowingBorder.stories.ts +165 -0
- package/app/components/01.atoms/animations/glowing-border/tests/GlowingBorder.spec.ts +94 -0
- package/app/components/01.atoms/animations/glowing-border/tests/__snapshots__/GlowingBorder.spec.ts.snap +3 -0
- package/app/components/01.atoms/animations/section-parallax/CONSUMER-STYLING.md +44 -0
- package/app/components/{parallax → 01.atoms/animations/section-parallax}/SectionParallax.vue +11 -10
- package/app/components/01.atoms/animations/section-parallax/stories/SectionParallax.stories.ts +98 -0
- package/app/components/01.atoms/animations/section-parallax/tests/SectionParallax.spec.ts +102 -0
- package/app/components/01.atoms/animations/section-parallax/tests/__snapshots__/SectionParallax.spec.ts.snap +9 -0
- package/app/components/01.atoms/animations/view-timeline/CONSUMER-STYLING.md +70 -0
- package/app/components/01.atoms/animations/view-timeline/WipeAwayVertical.vue +181 -0
- package/app/components/01.atoms/animations/view-timeline/stories/WipeAwayVertical.stories.ts +269 -0
- package/app/components/01.atoms/animations/view-timeline/tests/WipeAwayVertical.spec.ts +127 -0
- package/app/components/01.atoms/canvas-switcher/CONSUMER-STYLING.md +1 -1
- package/app/components/01.atoms/canvas-switcher/stories/CanvasSwitcher.stories.ts +1 -1
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md +42 -0
- package/app/components/{layout-grids/LayoutGridA.vue → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/DashboardQuadGrid.vue} +23 -13
- package/app/components/{layout-grids/stories/LayoutGridA.stories.ts → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/stories/DashboardQuadGrid.stories.ts} +151 -23
- package/app/components/{layout-grids/tests/LayoutGridA.spec.ts → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/tests/DashboardQuadGrid.spec.ts} +25 -25
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/CONSUMER-STYLING.md +41 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/DashboardStatsGrid.vue +133 -0
- package/app/components/{layout-grids/stories/LayoutGridB.stories.ts → 01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/stories/DashboardStatsGrid.stories.ts} +24 -24
- package/app/components/{layout-grids/tests/LayoutGridB.spec.ts → 01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/tests/DashboardStatsGrid.spec.ts} +23 -23
- package/app/components/01.atoms/navigation/skip-links/SkipLinks.vue +7 -1
- package/app/components/01.atoms/ui-block-decorated/CONSUMER-STYLING.md +39 -0
- package/app/components/01.atoms/ui-block-decorated/UiBlockDecorated.vue +141 -0
- package/app/components/01.atoms/ui-block-decorated/stories/UiBlockDecorated.stories.ts +148 -0
- package/app/components/01.atoms/ui-block-decorated/tests/UiBlockDecorated.spec.ts +80 -0
- package/app/components/01.atoms/ui-block-decorated/tests/__snapshots__/UiBlockDecorated.spec.ts.snap +3 -0
- package/app/components/02.molecules/alert-masked-content/tests/AlertMaskedContent.spec.ts +6 -6
- package/app/components/02.molecules/display-theme-switch/CONSUMER-STYLING.md +34 -0
- package/app/components/02.molecules/display-theme-switch/DisplayThemeSwitch.vue +102 -0
- package/app/components/02.molecules/display-theme-switch/stories/DisplayThemeSwitch.stories.ts +115 -0
- package/app/components/02.molecules/display-theme-switch/tests/DisplayThemeSwitch.spec.ts +86 -0
- package/app/components/03.organisms/image-galleries/carousel-flip/CONSUMER-STYLING.md +46 -0
- package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/CarouselFlip.vue +63 -42
- package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/stories/CarouselFlip.stories.ts +96 -11
- package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/tests/CarouselFlip.spec.ts +80 -0
- package/app/components/05.forms/form-field/FormField.vue +1 -1
- package/app/components/05.forms/form-wrapper/FormWrapper.vue +1 -1
- package/app/components/05.forms/input-range/CONSUMER-STYLING.md +45 -0
- package/app/components/05.forms/input-range/InputRangeCore.vue +79 -143
- package/app/components/05.forms/input-range/stories/InputRangeCore.stories.ts +126 -0
- package/app/components/05.forms/input-range/tests/InputRangeCore.spec.ts +124 -0
- package/app/components/05.forms/input-range/variants/InputRangeDefault.vue +9 -32
- package/app/components/05.forms/input-range/variants/stories/InputRangeDefault.stories.ts +108 -0
- package/app/components/05.forms/input-range/variants/tests/InputRangeDefault.spec.ts +140 -0
- package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +135 -0
- package/app/components/05.forms/triple-toggle-switch/CONSUMER-STYLING.md +16 -2
- package/app/components/05.forms/triple-toggle-switch/TripleToggleSwitchCore.vue +6 -4
- package/app/types/components/container-glow.d.ts +8 -0
- package/app/types/components/index.ts +1 -1
- package/package.json +3 -2
- package/app/components/05.forms/input-range-fancy/InputRangeFancyCore.vue +0 -426
- package/app/components/05.forms/input-range-fancy/InputRangeFancyWithLabel.vue +0 -94
- package/app/components/carousels/CarouselBasic.vue +0 -317
- package/app/components/carousels/CarouselInfinite.vue +0 -358
- package/app/components/container-glow/ContainerGlowCore.vue +0 -296
- package/app/components/container-glow/stories/ContainerGlowCore.stories.ts +0 -337
- package/app/components/display-theme-switch/DisplayThemeSwitch.vue +0 -189
- package/app/components/display-theme-switch/stories/DisplayThemeSwitch.stories.ts +0 -154
- package/app/components/glowing-border/GlowingBorder.vue +0 -142
- package/app/components/glowing-border/stories/GlowingBorder.stories.ts +0 -141
- package/app/components/layout-grids/LayoutGridB.vue +0 -134
- package/app/components/test-storybook/TestStorybook.vue +0 -49
- package/app/components/test-storybook/stories/TestStorybook.stories.ts +0 -28
- package/app/components/ui-block-decorated/UiBlockDecorated.vue +0 -167
- package/app/components/view-timeline/WipeAwayVertical.vue +0 -180
- 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 `
|
|
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
|
|
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
|
},
|
package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md
ADDED
|
@@ -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="
|
|
3
|
-
<div class="
|
|
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
|
-
|
|
22
|
-
styleClassPassthrough
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
.
|
|
41
|
+
.dashboard-quad-grid-wrapper {
|
|
41
42
|
container-type: inline-size;
|
|
42
43
|
|
|
43
|
-
.
|
|
44
|
+
.dashboard-quad-grid {
|
|
44
45
|
display: grid;
|
|
45
|
-
gap:
|
|
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:
|
|
49
|
-
outline:
|
|
50
|
-
|
|
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
|
|
3
|
-
import type { MediaCanvas } from "
|
|
2
|
+
import DashboardQuadGridComponent from "../DashboardQuadGrid.vue";
|
|
3
|
+
import type { MediaCanvas } from "~/types/components";
|
|
4
4
|
|
|
5
5
|
// Define the args interface
|
|
6
|
-
interface
|
|
6
|
+
interface DashboardQuadGridArgs {
|
|
7
7
|
mediaCanvas: MediaCanvas;
|
|
8
8
|
styleClassPassthrough: string[];
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export default {
|
|
12
|
-
title: "
|
|
13
|
-
component:
|
|
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<
|
|
43
|
+
} as Meta<DashboardQuadGridArgs>;
|
|
44
44
|
|
|
45
|
-
const Template: StoryFn<
|
|
46
|
-
components: {
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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<
|
|
159
|
-
components: {
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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
|
|
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<
|
|
278
|
-
components: {
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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: {
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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
|
|
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
|
};
|