srcdev-nuxt-components 9.0.15 → 9.0.17

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 (112) hide show
  1. package/.claude/settings.json +25 -0
  2. package/.claude/skills/component-aria-landmark.md +68 -0
  3. package/.claude/skills/component-dynamic-slots.md +150 -0
  4. package/.claude/skills/component-export-types.md +61 -0
  5. package/.claude/skills/component-local-style-override.md +126 -0
  6. package/.claude/skills/component-prop-driven-container-layout.md +42 -0
  7. package/.claude/skills/components/accordian-core.md +159 -0
  8. package/.claude/skills/components/contact-section.md +101 -0
  9. package/.claude/skills/components/expanding-panel.md +156 -0
  10. package/.claude/skills/components/eyebrow-text.md +25 -0
  11. package/.claude/skills/components/hero-text.md +25 -0
  12. package/.claude/skills/components/layout-grid-by-cols.md +147 -0
  13. package/.claude/skills/components/layout-row.md +35 -0
  14. package/.claude/skills/components/link-text.md +33 -0
  15. package/.claude/skills/components/page-hero-highlights.md +224 -0
  16. package/.claude/skills/components/services-card.md +28 -0
  17. package/.claude/skills/components/services-section.md +25 -0
  18. package/.claude/skills/components/stepper-list.md +227 -0
  19. package/.claude/skills/css-grid-max-width-gutters.md +67 -0
  20. package/.claude/skills/index.md +15 -3
  21. package/.claude/skills/storybook-add-story.md +60 -0
  22. package/.claude/skills/testing-add-unit-test.md +56 -0
  23. package/app/assets/styles/setup/01.config/index.css +0 -1
  24. package/app/assets/styles/setup/03.theming/default/_dark.css +2 -2
  25. package/app/assets/styles/setup/04.elements/forms/02.typography.css +1 -0
  26. package/app/assets/styles/setup/05.typography/02.utility-classes/_font-classes-page-link.css +14 -14
  27. package/app/assets/styles/setup/index.css +0 -1
  28. package/app/components/01.atoms/card/CardCore.vue +92 -0
  29. package/app/components/01.atoms/card/stories/CardCore.stories.ts +132 -0
  30. package/app/components/01.atoms/card/tests/CardCore.spec.ts +207 -0
  31. package/app/components/01.atoms/card/tests/__snapshots__/CardCore.spec.ts.snap +43 -0
  32. package/app/components/01.atoms/content-wrappers/content-columns-2/ContentColumns2.vue +51 -0
  33. package/app/components/01.atoms/content-wrappers/content-columns-2/stories/ContentColumns2.stories.ts +110 -0
  34. package/app/components/01.atoms/content-wrappers/content-columns-2/tests/ContentColumns2.spec.ts +105 -0
  35. package/app/components/01.atoms/content-wrappers/content-columns-2/tests/__snapshots__/ContentColumns2.spec.ts.snap +14 -0
  36. package/app/components/01.atoms/content-wrappers/content-width/ContentWidth.vue +88 -0
  37. package/app/components/01.atoms/content-wrappers/content-width/stories/ContentWidth.stories.ts +362 -0
  38. package/app/components/01.atoms/content-wrappers/content-width/tests/ContentWidth.spec.ts +132 -0
  39. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/LayoutGridByCols.vue +71 -0
  40. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/stories/LayoutGridByCols.stories.ts +219 -0
  41. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/tests/LayoutGridByCols.spec.ts +174 -0
  42. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/tests/__snapshots__/LayoutGrid.spec.ts.snap +36 -0
  43. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/tests/__snapshots__/LayoutGridByCols.spec.ts.snap +36 -0
  44. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-width/LayoutGridByWidth.vue +70 -0
  45. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-width/stories/LayoutGridByWidth.stories.ts +220 -0
  46. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-width/tests/LayoutGridByWidth.spec.ts +174 -0
  47. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-width/tests/__snapshots__/LayoutGrid.spec.ts.snap +36 -0
  48. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-width/tests/__snapshots__/LayoutGridByCols.spec.ts.snap +36 -0
  49. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-width/tests/__snapshots__/LayoutGridByWidth.spec.ts.snap +36 -0
  50. package/app/components/01.atoms/text-blocks/eyebrow-text/stories/EyebrowText.stories.ts +1 -1
  51. package/app/components/01.atoms/text-blocks/hero-text/stories/HeroText.stories.ts +1 -1
  52. package/app/components/01.atoms/text-blocks/link-text/stories/LinkText.stories.ts +1 -1
  53. package/app/components/02.molecules/contact-section/stories/ContactSection.stories.ts +5 -0
  54. package/app/components/02.molecules/contact-section/tests/ContactSection.spec.ts +15 -0
  55. package/app/components/02.molecules/contact-section/tests/ContactSection.vue +25 -17
  56. package/app/components/{accordian → 02.molecules/expandable/accordian}/stories/AccordianCore.stories.ts +1 -1
  57. package/app/components/02.molecules/expandable/expanding-panel/stories/ExpandingPanel.stories.ts +245 -0
  58. package/app/components/02.molecules/expandable/expanding-panel/tests/ExpandingPanel.spec.ts +351 -0
  59. package/app/components/02.molecules/expandable/expanding-panel/tests/__snapshots__/ExpandingPanel.spec.ts.snap +38 -0
  60. package/app/components/02.molecules/navigation/navigation-horizontal/NavigationHorizontal.vue +162 -0
  61. package/app/components/02.molecules/navigation/navigation-horizontal/stories/NavigationHorizontal.stories.ts +373 -0
  62. package/app/components/02.molecules/navigation/navigation-horizontal/tests/NavigationHorizontal.spec.ts +152 -0
  63. package/app/components/02.molecules/navigation/navigation-horizontal/tests/__snapshots__/NavigationHorizontal.spec.ts.snap +17 -0
  64. package/app/components/02.molecules/profile-section/ProfileSection.vue +2 -3
  65. package/app/components/02.molecules/profile-section/tests/ProfileSection.spec.ts +2 -2
  66. package/app/components/02.molecules/stepper-list/StepperList.vue +131 -92
  67. package/app/components/02.molecules/stepper-list/stories/StepperList.stories.ts +31 -0
  68. package/app/components/02.molecules/stepper-list/tests/StepperList.spec.ts +24 -0
  69. package/app/components/02.molecules/stepper-list/tests/__snapshots__/StepperList.spec.ts.snap +22 -9
  70. package/app/components/03.organisms/image-galleries/slider-gallery/SliderGallery.vue +782 -0
  71. package/app/components/03.organisms/image-galleries/slider-gallery/stories/SliderGallery.stories.ts +233 -0
  72. package/app/components/03.organisms/image-galleries/slider-gallery/tests/SliderGallery.spec.ts +226 -0
  73. package/app/components/03.organisms/image-galleries/slider-gallery/tests/__snapshots__/SliderGallery.spec.ts.snap +69 -0
  74. package/app/components/03.organisms/services/services-grids/ServicesCardGrid.vue +1 -1
  75. package/app/components/03.organisms/services/services-grids/ServicesSectionGrid.vue +1 -1
  76. package/app/components/03.organisms/services/services-section/ServicesSection.vue +2 -3
  77. package/app/components/04.templates/page-hero-highlights/PageHeroHighlights.vue +239 -0
  78. package/app/components/04.templates/page-hero-highlights/stories/PageHeroHighlights.stories.ts +404 -0
  79. package/app/components/04.templates/page-hero-highlights/tests/PageHeroHighlights.spec.ts +198 -0
  80. package/app/components/04.templates/page-hero-highlights/tests/__snapshots__/PageHeroHighlights.spec.ts.snap +19 -0
  81. package/app/components/container-glow/ContainerGlowCore.vue +20 -27
  82. package/app/components/forms/input-button/InputButtonCore.vue +105 -104
  83. package/app/components/glowing-border/stories/GlowingBorder.stories.ts +21 -21
  84. package/app/composables/useAriaLabelledById.ts +13 -0
  85. package/app/layouts/default.vue +8 -3
  86. package/app/pages/forms/examples/buttons/index.vue +6 -6
  87. package/app/pages/forms/examples/material/checkbox-radio-panels.vue +3 -3
  88. package/app/pages/forms/examples/material/text-fields.vue +607 -610
  89. package/app/pages/page-hero-highlights.vue +81 -0
  90. package/app/pages/ui/{display-card.vue → card-core.vue} +15 -15
  91. package/app/pages/ui/contact-section.vue +1 -1
  92. package/app/pages/ui/container-glow.vue +1 -1
  93. package/app/pages/ui/content-width.vue +126 -0
  94. package/app/pages/ui/glowing-border.vue +9 -9
  95. package/app/pages/ui/navigation/navigation-horizontal.vue +484 -0
  96. package/app/pages/ui/services/services-section/[slug].vue +3 -1
  97. package/app/types/components/index.ts +1 -0
  98. package/app/types/components/navigation-horizontal.d.ts +11 -0
  99. package/package.json +2 -2
  100. package/app/assets/styles/setup/01.config/_basic-resets.css +0 -9
  101. package/app/components/content-columns/TwoColumns.vue +0 -59
  102. package/app/components/content-columns/stories/TwoColumns.stories.ts +0 -561
  103. package/app/components/content-containers/ContentContainer.vue +0 -89
  104. package/app/components/content-containers/stories/ContentContainer.stories.ts +0 -465
  105. package/app/components/content-grid/ContentGrid.vue +0 -85
  106. package/app/components/display-card/DisplayCard.vue +0 -122
  107. package/app/components/image-galleries/SliderGallery.vue +0 -786
  108. package/app/pages/ui/content-container.vue +0 -112
  109. /package/app/components/{accordian → 02.molecules/expandable/accordian}/AccordianCore.vue +0 -0
  110. /package/app/components/{accordian → 02.molecules/expandable/accordian}/tests/AccordianCore.spec.ts +0 -0
  111. /package/app/components/{accordian → 02.molecules/expandable/accordian}/tests/__snapshots__/AccordianCore.spec.ts.snap +0 -0
  112. /package/app/components/{expanding-panel → 02.molecules/expandable/expanding-panel}/ExpandingPanel.vue +0 -0
@@ -174,6 +174,66 @@ template: `
174
174
  This keeps the ID wiring self-contained inside the component — the parent story just
175
175
  consumes what the slot exposes, rather than generating its own ID.
176
176
 
177
+ ### Extra controls that are not component props
178
+
179
+ Use when you want a Storybook control that sets something other than a component prop — e.g. a CSS custom property toggle.
180
+
181
+ `Meta<typeof Component>` is strict: its `argTypes`/`args` keys must match the component's actual props. Adding extras causes a TypeScript error. The fix is a `StoryArgs` type that covers both:
182
+
183
+ ```ts
184
+ import { computed } from "vue"; // ← must be explicit in .ts files (not auto-imported)
185
+ import type { Meta, StoryObj } from "@nuxtjs/storybook";
186
+ import ComponentName from "../ComponentName.vue";
187
+
188
+ type StoryArgs = {
189
+ // mirror the component props you want controls for
190
+ tag?: "div" | "section";
191
+ // plus any extras
192
+ headerBackground?: string;
193
+ };
194
+
195
+ const meta: Meta<StoryArgs> = { // ← StoryArgs, not typeof ComponentName
196
+ title: "...",
197
+ component: ComponentName,
198
+ argTypes: {
199
+ headerBackground: { control: "color", description: "Sets --my-header-bg" },
200
+ },
201
+ args: { headerBackground: "" },
202
+ };
203
+
204
+ export default meta;
205
+ type Story = StoryObj<typeof ComponentName>; // ← still strict for individual stories
206
+ ```
207
+
208
+ Strip extra args before `v-bind` using a `useStorySetup` helper in `setup()`:
209
+
210
+ ```ts
211
+ function useStorySetup(args: StoryArgs) {
212
+ const bgStyles = computed(() => ({
213
+ ...(args.headerBackground ? { "--my-header-bg": args.headerBackground } : {}),
214
+ }));
215
+ const componentArgs = computed(() => {
216
+ const { headerBackground: _h, ...rest } = args;
217
+ return rest;
218
+ });
219
+ return { bgStyles, componentArgs };
220
+ }
221
+
222
+ export const Default: Story = {
223
+ render: (args: StoryArgs) => ({
224
+ components: { ComponentName },
225
+ setup() { return useStorySetup(args); },
226
+ template: `<ComponentName v-bind="componentArgs" :style="bgStyles" />`,
227
+ }),
228
+ };
229
+ ```
230
+
231
+ Key points:
232
+
233
+ - `computed` is **not** auto-imported in `.ts` story files — import it explicitly from `"vue"`.
234
+ - Extra args must be stripped before `v-bind` — spreading unknown keys onto a component makes them unknown HTML attributes.
235
+ - CSS custom properties set via `:style` on the component root are picked up by `var()` in the component's scoped CSS.
236
+
177
237
  ## Notes
178
238
 
179
239
  - Use `table: { category: "..." }` in `argTypes` when a component has many props — it groups
@@ -165,6 +165,34 @@ it("exposes headingId via scoped slot", async () => {
165
165
  - Always `afterEach(() => wrapper?.unmount())` to prevent test leaks.
166
166
  - Use a `createWrapper` helper to keep individual tests short.
167
167
  - Include at least one snapshot test per meaningful visual state.
168
+ - `nextTick` is **not** auto-imported in test files — always import it explicitly: `import { nextTick } from "vue"`.
169
+
170
+ ## Fake timers
171
+
172
+ `vitest.setup.ts` calls `vi.useFakeTimers()` globally and resets in `afterEach`. **Never** call `vi.useFakeTimers()`, `vi.useRealTimers()`, or `vi.runAllTimers()` inside a test file — it conflicts with the global setup and can cause infinite loops or bleed between tests.
173
+
174
+ Use `vi.advanceTimersByTime(ms)` to move time forward by a specific amount. Avoid `vi.runAllTimers()` — it fires all queued timers including any that re-queue themselves, which loops infinitely.
175
+
176
+ ```ts
177
+ // ✅
178
+ vi.advanceTimersByTime(500);
179
+ await nextTick();
180
+
181
+ // ❌ — can loop infinitely if a timer re-queues itself
182
+ vi.runAllTimers();
183
+ ```
184
+
185
+ ## Hyphenated prop attributes in tests
186
+
187
+ When a component uses a hyphenated Vue prop like `:tab-index` or `:aria-label`, Vue renders it as the literal hyphenated DOM attribute. Assert with the hyphenated form — not the camelCase equivalent:
188
+
189
+ ```ts
190
+ // ✅ — prop :tab-index renders as the DOM attribute "tab-index"
191
+ expect(wrapper.find(".el").attributes("tab-index")).toBe("2");
192
+
193
+ // ❌ — "tabindex" won't match
194
+ expect(wrapper.find(".el").attributes("tabindex")).toBe("2");
195
+ ```
168
196
 
169
197
  ## Snapshot testing
170
198
 
@@ -195,6 +223,34 @@ const style = (el.element as HTMLElement).style;
195
223
  expect(style.getPropertyValue("--custom-prop")).toBe("expected-value");
196
224
  ```
197
225
 
226
+ **`v-bind()` in CSS — root component only.** Vue's `v-bind(propName)` in a component's `<style>` block sets `--v-bind-propName` on that component's root element in JSDOM. This only works when the component under test **is** the root wrapper. When the component is rendered as a **child** inside a parent, JSDOM does not apply those inline styles — `getPropertyValue` returns `""`.
227
+
228
+ ```ts
229
+ // ❌ Won't work — StepperList is a child; JSDOM doesn't apply its v-bind() styles
230
+ const list = wrapper.find(".stepper-list");
231
+ expect((list.element as HTMLElement).style.getPropertyValue("--v-bind-indicatorSize")).toBe("4rem");
232
+
233
+ // ✅ Use findComponent + props() to test prop pass-through to a child component
234
+ expect(wrapper.findComponent(StepperList).props("indicatorSize")).toBe("4rem");
235
+ ```
236
+
237
+ ## Testing prop pass-through to child components
238
+
239
+ When a parent component forwards a prop to a child, use `findComponent` + `.props()` rather than inspecting the DOM:
240
+
241
+ ```ts
242
+ import ChildComponent from "../../child/ChildComponent.vue";
243
+
244
+ it("passes myProp to ChildComponent", async () => {
245
+ const wrapper = await mountSuspended(ParentComponent, {
246
+ props: { myProp: "value" },
247
+ });
248
+ expect(wrapper.findComponent(ChildComponent).props("myProp")).toBe("value");
249
+ });
250
+ ```
251
+
252
+ Import the child component directly in the test file — it is not auto-imported there.
253
+
198
254
  ## Mocking browser APIs
199
255
 
200
256
  Mock before the `describe` block if the component uses ResizeObserver, IntersectionObserver, etc.:
@@ -1,3 +1,2 @@
1
1
  @import "./_normalise";
2
- @import "./_basic-resets";
3
2
  @import "./_head";
@@ -78,8 +78,8 @@
78
78
  /* ===========================================
79
79
  ANCHOR LINK VARIABLES
80
80
  =========================================== */
81
- --colour-link-default: var(--blue-10);
82
- --colour-link-hover: var(--blue-09);
81
+ --colour-link-default: var(--blue-02);
82
+ --colour-link-hover: var(--blue-03);
83
83
 
84
84
  /* ===========================================
85
85
  FORM INPUT VARIABLES
@@ -13,6 +13,7 @@
13
13
  --button-font-size: 1.6rem; /* 16px */
14
14
  --button-line-height: 1.4;
15
15
  --button-font-weight: 600;
16
+ --button-text-transform: none;
16
17
 
17
18
  /* Description */
18
19
  --field-description-font-size: 1.3rem;
@@ -1,5 +1,5 @@
1
1
  .page-link-large {
2
- color: var(--theme-link-default);
2
+ color: var(--colour-link-default);
3
3
  font-size: var(--step-6);
4
4
  font-weight: 400;
5
5
  font-variation-settings: "wght" 400;
@@ -8,7 +8,7 @@
8
8
  }
9
9
 
10
10
  .page-link-large-semibold {
11
- color: var(--theme-link-default);
11
+ color: var(--colour-link-default);
12
12
  font-size: var(--step-6);
13
13
  font-weight: 600;
14
14
  font-variation-settings: "wght" 600;
@@ -17,7 +17,7 @@
17
17
  }
18
18
 
19
19
  .page-link-medium {
20
- color: var(--theme-link-default);
20
+ color: var(--colour-link-default);
21
21
  font-size: var(--step-5);
22
22
  font-weight: 400;
23
23
  font-variation-settings: "wght" 400;
@@ -26,7 +26,7 @@
26
26
  }
27
27
 
28
28
  .page-link-medium-semibold {
29
- color: var(--theme-link-default);
29
+ color: var(--colour-link-default);
30
30
  font-size: var(--step-5);
31
31
  font-weight: 600;
32
32
  font-variation-settings: "wght" 600;
@@ -35,7 +35,7 @@
35
35
  }
36
36
 
37
37
  .page-link-normal {
38
- color: var(--theme-link-default);
38
+ color: var(--colour-link-default);
39
39
  font-size: var(--step-4);
40
40
  font-weight: 400;
41
41
  font-variation-settings: "wght" 400;
@@ -44,7 +44,7 @@
44
44
  }
45
45
 
46
46
  .page-link-normal-semibold {
47
- color: var(--theme-link-default);
47
+ color: var(--colour-link-default);
48
48
  font-size: var(--step-4);
49
49
  font-weight: 600;
50
50
  font-variation-settings: "wght" 600;
@@ -53,7 +53,7 @@
53
53
  }
54
54
 
55
55
  .page-link-small {
56
- color: var(--theme-link-default);
56
+ color: var(--colour-link-default);
57
57
  font-size: var(--step-3);
58
58
  font-weight: 400;
59
59
  font-variation-settings: "wght" 400;
@@ -62,7 +62,7 @@
62
62
  }
63
63
 
64
64
  .page-link-small-semibold {
65
- color: var(--theme-link-default);
65
+ color: var(--colour-link-default);
66
66
  font-size: var(--step-3);
67
67
  font-weight: 600;
68
68
  font-variation-settings: "wght" 600;
@@ -71,7 +71,7 @@
71
71
  }
72
72
 
73
73
  .page-link-xsmall {
74
- color: var(--theme-link-default);
74
+ color: var(--colour-link-default);
75
75
  font-size: var(--step-2);
76
76
  font-weight: 400;
77
77
  font-variation-settings: "wght" 400;
@@ -80,7 +80,7 @@
80
80
  }
81
81
 
82
82
  .page-link-xsmall-semibold {
83
- color: var(--theme-link-default);
83
+ color: var(--colour-link-default);
84
84
  font-size: var(--step-2);
85
85
  font-weight: 600;
86
86
  font-variation-settings: "wght" 600;
@@ -100,16 +100,16 @@
100
100
  .page-link-xsmall-semibold {
101
101
  margin: 0;
102
102
  &:visited {
103
- color: var(--theme-link-default);
103
+ color: var(--colour-link-default);
104
104
  }
105
105
 
106
106
  &:hover {
107
- color: var(--theme-link-hover);
107
+ color: var(--colour-link-hover);
108
108
  }
109
109
 
110
110
  &:focus-visible {
111
- color: var(--theme-link-hover);
112
- outline: 2px solid var(--theme-link-default);
111
+ color: var(--colour-link-hover);
112
+ outline: 2px solid var(--colour-link-default);
113
113
  outline-offset: 3px;
114
114
  border-radius: 4px;
115
115
  }
@@ -1,4 +1,3 @@
1
- /* @layer reset, colours, theming, form-tokens, typography, a11y, utilities, components; */
2
1
  @layer reset, colours, theming, form-tokens, typography, a11y, components, utilities;
3
2
 
4
3
  @import "./01.config/" layer(reset);
@@ -0,0 +1,92 @@
1
+ <template>
2
+ <component
3
+ :is="tag"
4
+ class="card-core"
5
+ :class="[variant, elementClasses, { 'has-dividers': hasDividers }, { 'no-outline': noOutline }]"
6
+ >
7
+ <template v-for="(_, name) in $slots" :key="name">
8
+ <div class="card-row" :class="`card-row-${name}`">
9
+ <slot :name="name"></slot>
10
+ </div>
11
+ </template>
12
+ </component>
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ interface Props {
17
+ tag?: "div" | "section" | "article" | "aside" | "main" | "nav";
18
+ hasDividers?: boolean;
19
+ noOutline?: boolean;
20
+ variant?: "solid" | "subtle" | "soft" | "outline";
21
+ styleClassPassthrough?: string | string[];
22
+ }
23
+
24
+ const props = withDefaults(defineProps<Props>(), {
25
+ tag: "div",
26
+ hasDividers: false,
27
+ noOutline: false,
28
+ variant: "solid",
29
+ styleClassPassthrough: () => [],
30
+ });
31
+ const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
32
+
33
+ watch(
34
+ () => props.styleClassPassthrough,
35
+ () => {
36
+ resetElementClasses(props.styleClassPassthrough);
37
+ }
38
+ );
39
+ </script>
40
+
41
+ <style lang="css">
42
+ @layer components {
43
+ .card-core {
44
+ --_inner-padding: 1rem;
45
+ --_background-color: white;
46
+ --_border-color: green;
47
+ --_border-width: 0.2rem;
48
+ --_box-shadow-color: transparent;
49
+
50
+ display: grid;
51
+ grid-auto-flow: row;
52
+ /* gap: 1rem; */
53
+ border-radius: 0.5rem;
54
+ overflow: hidden;
55
+
56
+ background-color: var(--_background-color, transparent);
57
+ border: var(--_border-width) solid var(--_border-color, transparent);
58
+ box-shadow: 0 0 0.4rem var(--_border-width) var(--_box-shadow-color, transparent);
59
+
60
+ &.no-outline {
61
+ --_border-width: 0;
62
+ }
63
+
64
+ &.solid {
65
+ --_background-color: light-dark(var(--slate-00), var(--slate-10));
66
+ --_border-color: red;
67
+ }
68
+
69
+ &.subtle {
70
+ --_background-color: color-mix(in oklab, light-dark(var(--slate-01), var(--slate-08)) 50%, transparent);
71
+ --_border-color: red;
72
+ }
73
+
74
+ &.soft {
75
+ --_background-color: color-mix(in oklab, light-dark(var(--slate-01), var(--slate-08)) 20%, transparent);
76
+ --_box-shadow-color: color-mix(in oklab, light-dark(var(--slate-02), var(--slate-08)) 80%, transparent);
77
+ }
78
+
79
+ &.outline {
80
+ --_background-color: transparent;
81
+ --_border-color: green;
82
+ }
83
+
84
+ &.has-dividers {
85
+ .card-row + .card-row {
86
+ /* border-top: 0.2rem solid var(--_border-color); */
87
+ border-top: 0.2rem solid green;
88
+ }
89
+ }
90
+ }
91
+ }
92
+ </style>
@@ -0,0 +1,132 @@
1
+ import type { Meta, StoryFn } from "@nuxtjs/storybook";
2
+ import StorybookComponent from "../CardCore.vue";
3
+
4
+ interface CardCoreArgs {
5
+ tag: "div" | "section" | "article" | "aside" | "main" | "nav";
6
+ variant: "solid" | "subtle" | "soft" | "outline";
7
+ hasDividers: boolean;
8
+ noOutline: boolean;
9
+ styleClassPassthrough: string[];
10
+ }
11
+
12
+ export default {
13
+ title: "Atoms/Card/Card Core Dynamic",
14
+ component: StorybookComponent,
15
+ argTypes: {
16
+ tag: {
17
+ control: { type: "select" },
18
+ options: ["div", "section", "article", "aside", "main", "nav"],
19
+ description: "HTML tag to render",
20
+ table: { category: "Semantic" },
21
+ },
22
+ variant: {
23
+ control: { type: "select" },
24
+ options: ["solid", "subtle", "soft", "outline"],
25
+ description: "Visual style variant",
26
+ table: { category: "Appearance" },
27
+ },
28
+ hasDividers: {
29
+ control: { type: "boolean" },
30
+ description: "Add dividers between slot sections",
31
+ table: { category: "Appearance" },
32
+ },
33
+ noOutline: {
34
+ control: { type: "boolean" },
35
+ description: "Remove border outline",
36
+ table: { category: "Appearance" },
37
+ },
38
+ styleClassPassthrough: {
39
+ table: { disable: true },
40
+ },
41
+ },
42
+ args: {
43
+ tag: "div",
44
+ variant: "solid",
45
+ hasDividers: false,
46
+ noOutline: false,
47
+ styleClassPassthrough: [],
48
+ },
49
+ parameters: {
50
+ docs: {
51
+ description: {
52
+ component:
53
+ "A dynamic display card that renders whatever named slots are provided. Any number of slots can be passed and each is wrapped in its own div.",
54
+ },
55
+ },
56
+ },
57
+ } as Meta<CardCoreArgs>;
58
+
59
+ const Template: StoryFn<CardCoreArgs> = (args) => ({
60
+ components: { StorybookComponent },
61
+ setup() {
62
+ return { args };
63
+ },
64
+ template: `
65
+ <div style="padding: 40px; max-width: 480px;">
66
+ <StorybookComponent
67
+ :tag="args.tag"
68
+ :variant="args.variant"
69
+ :has-dividers="args.hasDividers"
70
+ :no-outline="args.noOutline"
71
+ :style-class-passthrough="args.styleClassPassthrough"
72
+ >
73
+ <template #header>
74
+ <div style="padding: 1.6rem; border-bottom: 1px solid transparent;">
75
+ <p style="margin: 0; font-size: 1.2rem; text-transform: uppercase; letter-spacing: 0.08em; opacity: 0.6;">Category</p>
76
+ <h2 style="margin: 0.4rem 0 0; font-size: 2rem; font-weight: 600;">Card Title</h2>
77
+ </div>
78
+ </template>
79
+ <template #media>
80
+ <div style="aspect-ratio: 16/9; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center; color: white; font-size: 1.4rem;">
81
+ Media Slot
82
+ </div>
83
+ </template>
84
+ <template #body>
85
+ <div style="padding: 1.6rem;">
86
+ <p style="margin: 0; line-height: 1.6; opacity: 0.8;">
87
+ This is the body content. It can contain any markup — paragraphs, lists, or other components.
88
+ </p>
89
+ </div>
90
+ </template>
91
+ <template #footer>
92
+ <div style="padding: 1.2rem 1.6rem; display: flex; gap: 0.8rem; justify-content: flex-end;">
93
+ <button style="padding: 0.8rem 1.6rem; border-radius: 0.4rem; border: 1px solid currentColor; background: transparent; cursor: pointer; opacity: 0.7;">Cancel</button>
94
+ <button style="padding: 0.8rem 1.6rem; border-radius: 0.4rem; border: none; background: #667eea; color: white; cursor: pointer;">Confirm</button>
95
+ </div>
96
+ </template>
97
+ </StorybookComponent>
98
+ </div>
99
+ `,
100
+ });
101
+
102
+ export const Default = Template.bind({});
103
+ Default.args = {};
104
+
105
+ export const Subtle = Template.bind({});
106
+ Subtle.args = { variant: "subtle" };
107
+
108
+ export const Soft = Template.bind({});
109
+ Soft.args = { variant: "soft" };
110
+
111
+ export const Outline = Template.bind({});
112
+ Outline.args = { variant: "outline" };
113
+
114
+ export const WithDividers = Template.bind({});
115
+ WithDividers.args = { hasDividers: true };
116
+ WithDividers.parameters = {
117
+ docs: {
118
+ description: {
119
+ story: "Adds visual dividers between each slot section.",
120
+ },
121
+ },
122
+ };
123
+
124
+ export const NoOutline = Template.bind({});
125
+ NoOutline.args = { noOutline: true };
126
+ NoOutline.parameters = {
127
+ docs: {
128
+ description: {
129
+ story: "Card without a border or box shadow.",
130
+ },
131
+ },
132
+ };