srcdev-nuxt-components 9.0.15 → 9.0.16

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 (107) 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-local-style-override.md +126 -0
  5. package/.claude/skills/component-prop-driven-container-layout.md +42 -0
  6. package/.claude/skills/components/accordian-core.md +159 -0
  7. package/.claude/skills/components/contact-section.md +101 -0
  8. package/.claude/skills/components/expanding-panel.md +156 -0
  9. package/.claude/skills/components/eyebrow-text.md +25 -0
  10. package/.claude/skills/components/hero-text.md +25 -0
  11. package/.claude/skills/components/layout-grid-by-cols.md +147 -0
  12. package/.claude/skills/components/layout-row.md +35 -0
  13. package/.claude/skills/components/link-text.md +33 -0
  14. package/.claude/skills/components/page-hero-highlights.md +224 -0
  15. package/.claude/skills/components/services-card.md +28 -0
  16. package/.claude/skills/components/services-section.md +25 -0
  17. package/.claude/skills/components/stepper-list.md +227 -0
  18. package/.claude/skills/css-grid-max-width-gutters.md +67 -0
  19. package/.claude/skills/index.md +14 -3
  20. package/.claude/skills/storybook-add-story.md +60 -0
  21. package/.claude/skills/testing-add-unit-test.md +56 -0
  22. package/app/assets/styles/setup/01.config/index.css +0 -1
  23. package/app/assets/styles/setup/03.theming/default/_dark.css +2 -2
  24. package/app/assets/styles/setup/04.elements/forms/02.typography.css +1 -0
  25. package/app/assets/styles/setup/05.typography/02.utility-classes/_font-classes-page-link.css +14 -14
  26. package/app/assets/styles/setup/index.css +0 -1
  27. package/app/components/01.atoms/card/CardCore.vue +92 -0
  28. package/app/components/01.atoms/card/stories/CardCore.stories.ts +132 -0
  29. package/app/components/01.atoms/card/tests/CardCore.spec.ts +207 -0
  30. package/app/components/01.atoms/card/tests/__snapshots__/CardCore.spec.ts.snap +43 -0
  31. package/app/components/01.atoms/content-wrappers/content-columns-2/ContentColumns2.vue +51 -0
  32. package/app/components/01.atoms/content-wrappers/content-columns-2/stories/ContentColumns2.stories.ts +110 -0
  33. package/app/components/01.atoms/content-wrappers/content-columns-2/tests/ContentColumns2.spec.ts +105 -0
  34. package/app/components/01.atoms/content-wrappers/content-columns-2/tests/__snapshots__/ContentColumns2.spec.ts.snap +14 -0
  35. package/app/components/01.atoms/content-wrappers/content-width/ContentWidth.vue +88 -0
  36. package/app/components/01.atoms/content-wrappers/content-width/stories/ContentWidth.stories.ts +362 -0
  37. package/app/components/01.atoms/content-wrappers/content-width/tests/ContentWidth.spec.ts +132 -0
  38. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/LayoutGridByCols.vue +71 -0
  39. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/stories/LayoutGridByCols.stories.ts +219 -0
  40. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/tests/LayoutGridByCols.spec.ts +174 -0
  41. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/tests/__snapshots__/LayoutGrid.spec.ts.snap +36 -0
  42. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-cols/tests/__snapshots__/LayoutGridByCols.spec.ts.snap +36 -0
  43. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-width/LayoutGridByWidth.vue +70 -0
  44. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-width/stories/LayoutGridByWidth.stories.ts +220 -0
  45. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-width/tests/LayoutGridByWidth.spec.ts +174 -0
  46. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-width/tests/__snapshots__/LayoutGrid.spec.ts.snap +36 -0
  47. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-width/tests/__snapshots__/LayoutGridByCols.spec.ts.snap +36 -0
  48. package/app/components/01.atoms/content-wrappers/layout-grid/layout-grid-by-width/tests/__snapshots__/LayoutGridByWidth.spec.ts.snap +36 -0
  49. package/app/components/01.atoms/text-blocks/eyebrow-text/stories/EyebrowText.stories.ts +1 -1
  50. package/app/components/01.atoms/text-blocks/hero-text/stories/HeroText.stories.ts +1 -1
  51. package/app/components/01.atoms/text-blocks/link-text/stories/LinkText.stories.ts +1 -1
  52. package/app/components/02.molecules/contact-section/stories/ContactSection.stories.ts +5 -0
  53. package/app/components/02.molecules/contact-section/tests/ContactSection.spec.ts +15 -0
  54. package/app/components/02.molecules/contact-section/tests/ContactSection.vue +25 -17
  55. package/app/components/{accordian → 02.molecules/expandable/accordian}/stories/AccordianCore.stories.ts +1 -1
  56. package/app/components/02.molecules/expandable/expanding-panel/stories/ExpandingPanel.stories.ts +245 -0
  57. package/app/components/02.molecules/expandable/expanding-panel/tests/ExpandingPanel.spec.ts +351 -0
  58. package/app/components/02.molecules/expandable/expanding-panel/tests/__snapshots__/ExpandingPanel.spec.ts.snap +38 -0
  59. package/app/components/02.molecules/navigation/navigation-horizontal/NavigationHorizontal.vue +139 -0
  60. package/app/components/02.molecules/navigation/navigation-horizontal/NavigationHorizontalAdvanced.vue +172 -0
  61. package/app/components/02.molecules/profile-section/ProfileSection.vue +2 -3
  62. package/app/components/02.molecules/profile-section/tests/ProfileSection.spec.ts +2 -2
  63. package/app/components/02.molecules/stepper-list/StepperList.vue +131 -92
  64. package/app/components/02.molecules/stepper-list/stories/StepperList.stories.ts +31 -0
  65. package/app/components/02.molecules/stepper-list/tests/StepperList.spec.ts +24 -0
  66. package/app/components/02.molecules/stepper-list/tests/__snapshots__/StepperList.spec.ts.snap +22 -9
  67. package/app/components/03.organisms/image-galleries/slider-gallery/SliderGallery.vue +782 -0
  68. package/app/components/03.organisms/image-galleries/slider-gallery/stories/SliderGallery.stories.ts +233 -0
  69. package/app/components/03.organisms/image-galleries/slider-gallery/tests/SliderGallery.spec.ts +226 -0
  70. package/app/components/03.organisms/image-galleries/slider-gallery/tests/__snapshots__/SliderGallery.spec.ts.snap +69 -0
  71. package/app/components/03.organisms/services/services-grids/ServicesCardGrid.vue +1 -1
  72. package/app/components/03.organisms/services/services-grids/ServicesSectionGrid.vue +1 -1
  73. package/app/components/03.organisms/services/services-section/ServicesSection.vue +2 -3
  74. package/app/components/04.templates/page-hero-highlights/PageHeroHighlights.vue +239 -0
  75. package/app/components/04.templates/page-hero-highlights/stories/PageHeroHighlights.stories.ts +404 -0
  76. package/app/components/04.templates/page-hero-highlights/tests/PageHeroHighlights.spec.ts +198 -0
  77. package/app/components/04.templates/page-hero-highlights/tests/__snapshots__/PageHeroHighlights.spec.ts.snap +19 -0
  78. package/app/components/container-glow/ContainerGlowCore.vue +20 -27
  79. package/app/components/forms/input-button/InputButtonCore.vue +105 -104
  80. package/app/components/glowing-border/stories/GlowingBorder.stories.ts +21 -21
  81. package/app/composables/useAriaLabelledById.ts +13 -0
  82. package/app/layouts/default.vue +8 -3
  83. package/app/pages/forms/examples/buttons/index.vue +6 -6
  84. package/app/pages/forms/examples/material/checkbox-radio-panels.vue +3 -3
  85. package/app/pages/forms/examples/material/text-fields.vue +607 -610
  86. package/app/pages/page-hero-highlights.vue +81 -0
  87. package/app/pages/ui/{display-card.vue → card-core.vue} +15 -15
  88. package/app/pages/ui/contact-section.vue +1 -1
  89. package/app/pages/ui/container-glow.vue +1 -1
  90. package/app/pages/ui/content-width.vue +126 -0
  91. package/app/pages/ui/glowing-border.vue +9 -9
  92. package/app/pages/ui/navigation/navigation-horizontal.vue +493 -0
  93. package/app/pages/ui/services/services-section/[slug].vue +3 -1
  94. package/package.json +2 -2
  95. package/app/assets/styles/setup/01.config/_basic-resets.css +0 -9
  96. package/app/components/content-columns/TwoColumns.vue +0 -59
  97. package/app/components/content-columns/stories/TwoColumns.stories.ts +0 -561
  98. package/app/components/content-containers/ContentContainer.vue +0 -89
  99. package/app/components/content-containers/stories/ContentContainer.stories.ts +0 -465
  100. package/app/components/content-grid/ContentGrid.vue +0 -85
  101. package/app/components/display-card/DisplayCard.vue +0 -122
  102. package/app/components/image-galleries/SliderGallery.vue +0 -786
  103. package/app/pages/ui/content-container.vue +0 -112
  104. /package/app/components/{accordian → 02.molecules/expandable/accordian}/AccordianCore.vue +0 -0
  105. /package/app/components/{accordian → 02.molecules/expandable/accordian}/tests/AccordianCore.spec.ts +0 -0
  106. /package/app/components/{accordian → 02.molecules/expandable/accordian}/tests/__snapshots__/AccordianCore.spec.ts.snap +0 -0
  107. /package/app/components/{expanding-panel → 02.molecules/expandable/expanding-panel}/ExpandingPanel.vue +0 -0
@@ -0,0 +1,105 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { mountSuspended } from "@nuxt/test-utils/runtime";
3
+ import ContentColumns2 from "../ContentColumns2.vue";
4
+
5
+ describe("ContentColumns2", () => {
6
+ it("mounts without error", async () => {
7
+ const wrapper = await mountSuspended(ContentColumns2);
8
+ expect(wrapper.vm).toBeTruthy();
9
+ });
10
+
11
+ it("renders correct HTML structure", async () => {
12
+ const wrapper = await mountSuspended(ContentColumns2, {
13
+ slots: {
14
+ slot1: "<p>Column 1</p>",
15
+ slot2: "<p>Column 2</p>",
16
+ },
17
+ });
18
+ expect(wrapper.html()).toMatchSnapshot();
19
+ });
20
+
21
+ it("applies the default data-testid", async () => {
22
+ const wrapper = await mountSuspended(ContentColumns2);
23
+ expect(wrapper.find("[data-testid='content-columns-2']").exists()).toBe(true);
24
+ });
25
+
26
+ it("applies a custom data-testid", async () => {
27
+ const wrapper = await mountSuspended(ContentColumns2, {
28
+ props: { dataTestid: "my-layout" },
29
+ });
30
+ expect(wrapper.find("[data-testid='my-layout']").exists()).toBe(true);
31
+ });
32
+
33
+ it("applies styleClassPassthrough classes", async () => {
34
+ const wrapper = await mountSuspended(ContentColumns2, {
35
+ props: { styleClassPassthrough: ["custom-class", "another-class"] },
36
+ });
37
+ const el = wrapper.find(".content-columns-2");
38
+ expect(el.classes()).toContain("custom-class");
39
+ expect(el.classes()).toContain("another-class");
40
+ });
41
+
42
+ it("applies styleClassPassthrough as a string", async () => {
43
+ const wrapper = await mountSuspended(ContentColumns2, {
44
+ props: { styleClassPassthrough: "string-class" },
45
+ });
46
+ expect(wrapper.find(".content-columns-2").classes()).toContain("string-class");
47
+ });
48
+
49
+ it("renders slot1 content when slot1 is provided", async () => {
50
+ const wrapper = await mountSuspended(ContentColumns2, {
51
+ slots: { slot1: "<p>Left content</p>" },
52
+ });
53
+ expect(wrapper.find(".col-1").exists()).toBe(true);
54
+ expect(wrapper.find(".col-1").text()).toBe("Left content");
55
+ });
56
+
57
+ it("renders slot2 content when slot2 is provided", async () => {
58
+ const wrapper = await mountSuspended(ContentColumns2, {
59
+ slots: { slot2: "<p>Right content</p>" },
60
+ });
61
+ expect(wrapper.find(".col-2").exists()).toBe(true);
62
+ expect(wrapper.find(".col-2").text()).toBe("Right content");
63
+ });
64
+
65
+ it("does not render col-1 when slot1 is not provided", async () => {
66
+ const wrapper = await mountSuspended(ContentColumns2, {
67
+ slots: { slot2: "<p>Right only</p>" },
68
+ });
69
+ expect(wrapper.find(".col-1").exists()).toBe(false);
70
+ });
71
+
72
+ it("does not render col-2 when slot2 is not provided", async () => {
73
+ const wrapper = await mountSuspended(ContentColumns2, {
74
+ slots: { slot1: "<p>Left only</p>" },
75
+ });
76
+ expect(wrapper.find(".col-2").exists()).toBe(false);
77
+ });
78
+
79
+ it("renders as a div by default", async () => {
80
+ const wrapper = await mountSuspended(ContentColumns2);
81
+ expect(wrapper.element.tagName).toBe("DIV");
82
+ });
83
+
84
+ it.each(["section", "article", "main"] as const)("renders as <%s> when tag prop is set", async (tag) => {
85
+ const wrapper = await mountSuspended(ContentColumns2, { props: { tag } });
86
+ expect(wrapper.element.tagName).toBe(tag.toUpperCase());
87
+ });
88
+
89
+ it("renders neither column when no slots are provided", async () => {
90
+ const wrapper = await mountSuspended(ContentColumns2);
91
+ expect(wrapper.find(".col-1").exists()).toBe(false);
92
+ expect(wrapper.find(".col-2").exists()).toBe(false);
93
+ });
94
+
95
+ it("renders both columns when both slots are provided", async () => {
96
+ const wrapper = await mountSuspended(ContentColumns2, {
97
+ slots: {
98
+ slot1: "<p>Left</p>",
99
+ slot2: "<p>Right</p>",
100
+ },
101
+ });
102
+ expect(wrapper.find(".col-1").exists()).toBe(true);
103
+ expect(wrapper.find(".col-2").exists()).toBe(true);
104
+ });
105
+ });
@@ -0,0 +1,14 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`ContentColumns2 > renders correct HTML structure 1`] = `
4
+ "<div class="content-columns-2" data-testid="content-columns-2">
5
+ <div class="inner">
6
+ <div class="col-1">
7
+ <p>Column 1</p>
8
+ </div>
9
+ <div class="col-2">
10
+ <p>Column 2</p>
11
+ </div>
12
+ </div>
13
+ </div>"
14
+ `;
@@ -0,0 +1,88 @@
1
+ <template>
2
+ <component
3
+ :is="tag"
4
+ :id="id"
5
+ class="content-width-wrapper"
6
+ :class="elementClasses"
7
+ :tab-index="isLandmark ? 0 : null"
8
+ :aria-label="isLandmark ? 'Content Width Landmark' : undefined"
9
+ >
10
+ <div class="content-width" :class="justifyContent">
11
+ <div class="content-width-inner">
12
+ <slot name="default"></slot>
13
+ </div>
14
+ </div>
15
+ </component>
16
+ </template>
17
+
18
+ <script setup lang="ts">
19
+ interface Props {
20
+ tag?: "div" | "section" | "article" | "aside" | "header" | "footer" | "main" | "nav";
21
+ label?: string;
22
+ isLandmark?: boolean;
23
+ justifyContent?: "start" | "center" | "end";
24
+ styleClassPassthrough?: string | string[];
25
+ }
26
+
27
+ const props = withDefaults(defineProps<Props>(), {
28
+ tag: "div",
29
+ label: "",
30
+ isLandmark: false,
31
+ justifyContent: "center",
32
+ styleClassPassthrough: () => [],
33
+ });
34
+
35
+ const id = useId();
36
+
37
+ const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
38
+ </script>
39
+
40
+ <style lang="css">
41
+ @layer components {
42
+ .content-width-wrapper {
43
+ container-type: inline-size;
44
+ container-name: content-width;
45
+
46
+ .content-width {
47
+ --gutter: 16px;
48
+ --content-max-width: auto;
49
+ --justify-content: initial;
50
+
51
+ display: grid;
52
+
53
+ grid-template-columns:
54
+ [gutter-start]
55
+ var(--gutter)
56
+ [content-start]
57
+ var(--content-max-width)
58
+ [content-end]
59
+ var(--gutter)
60
+ [gutter-end];
61
+
62
+ justify-content: var(--justify-content);
63
+ box-sizing: border-box;
64
+
65
+ @container content-width (width >= 1092px) {
66
+ --gutter: 0;
67
+ --content-max-width: 1064px;
68
+ --justify-content: center;
69
+
70
+ &.start {
71
+ --gutter: 16px;
72
+ --justify-content: start;
73
+ }
74
+
75
+ &.end {
76
+ --gutter: 16px;
77
+ --justify-content: end;
78
+ }
79
+ }
80
+
81
+ .content-width-inner {
82
+ grid-column: content;
83
+ background-color: var(--page-bg);
84
+ }
85
+ }
86
+ }
87
+ }
88
+ </style>
@@ -0,0 +1,362 @@
1
+ import type { Meta, StoryObj } from "@nuxtjs/storybook";
2
+ import { computed } from "vue";
3
+ import ContentWidthComponent from "../ContentWidth.vue";
4
+
5
+ interface ContentWidthArgs {
6
+ dataTestid: string;
7
+ tag: string;
8
+ id?: string;
9
+ styleClassPassthrough: string[];
10
+ isLandmark: boolean;
11
+ justifyContent: "start" | "center" | "end";
12
+ showBackground: boolean;
13
+ }
14
+
15
+ type Story = StoryObj<ContentWidthArgs>;
16
+
17
+ export default {
18
+ title: "Atoms/Content Wrappers/Content Width",
19
+ component: ContentWidthComponent,
20
+ argTypes: {
21
+ tag: {
22
+ control: { type: "select" },
23
+ options: ["div", "section", "article", "aside", "header", "footer", "main", "nav", "ul", "ol"],
24
+ description: "HTML tag to render as",
25
+ table: { category: "Semantic" },
26
+ },
27
+ isLandmark: {
28
+ control: { type: "boolean" },
29
+ description: "Whether this element should be a landmark (adds tabindex and aria-label)",
30
+ table: { category: "Accessibility" },
31
+ },
32
+ justifyContent: {
33
+ control: { type: "select" },
34
+ options: ["start", "center", "end"],
35
+ description: "Horizontal alignment of the content track at ≥1092px",
36
+ table: { category: "Layout" },
37
+ },
38
+ showBackground: {
39
+ control: { type: "boolean" },
40
+ description: "Highlight the wrapper background to compare wrapper vs content track width",
41
+ table: { category: "Debug" },
42
+ },
43
+ dataTestid: {
44
+ control: { type: "text" },
45
+ description: "Test ID for the inner content width element",
46
+ table: { category: "Testing" },
47
+ },
48
+ id: {
49
+ control: { type: "text" },
50
+ description: "ID attribute for the component",
51
+ table: { category: "HTML" },
52
+ },
53
+ styleClassPassthrough: {
54
+ table: { disable: true },
55
+ },
56
+ },
57
+ args: {
58
+ tag: "div",
59
+ isLandmark: false,
60
+ justifyContent: "center",
61
+ showBackground: false,
62
+ dataTestid: "content-width",
63
+ id: "",
64
+ styleClassPassthrough: [],
65
+ },
66
+ parameters: {
67
+ docs: {
68
+ description: {
69
+ component:
70
+ "A responsive content width wrapper that uses CSS Container Queries to provide optimal content width. Automatically adjusts from fluid width on small screens to a maximum 1064px width on larger screens (≥1092px) with proper gutters.",
71
+ },
72
+ },
73
+ },
74
+ } as Meta<ContentWidthArgs>;
75
+
76
+ // ===== BASIC STORIES =====
77
+
78
+ export const Default: Story = {
79
+ args: {},
80
+ render: (args) => ({
81
+ components: { ContentWidthComponent },
82
+ setup() {
83
+ const longText =
84
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium.";
85
+ const wrapperStyle = computed(() => (args.showBackground ? { background: "rgba(99, 102, 241, 0.15)" } : {}));
86
+ return { args, longText, wrapperStyle };
87
+ },
88
+ template: `
89
+ <div style="background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%); min-height: 100vh; padding: 20px 0;">
90
+ <ContentWidthComponent
91
+ :data-testid="args.dataTestid"
92
+ :tag="args.tag"
93
+ :style-class-passthrough="args.styleClassPassthrough"
94
+ :is-landmark="args.isLandmark"
95
+ :justify-content="args.justifyContent"
96
+ :style="wrapperStyle"
97
+ >
98
+ <div style="padding: 20px; background: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);">
99
+ <h2 style="margin: 0 0 16px 0; color: #374151; font-size: 24px;">Content Width Example</h2>
100
+ <p style="margin: 0 0 16px 0; color: #6b7280; line-height: 1.6;">{{ longText }}</p>
101
+ <div style="padding: 16px; background: #f3f4f6; border-radius: 8px; margin: 16px 0;">
102
+ <h3 style="margin: 0 0 8px 0; color: #374151; font-size: 16px;">Container Behavior</h3>
103
+ <ul style="margin: 0; color: #6b7280; line-height: 1.5; padding-left: 20px;">
104
+ <li><strong>Small screens:</strong> Fluid width with 16px gutters</li>
105
+ <li><strong>≥1092px screens:</strong> Fixed 1064px max-width, centered</li>
106
+ <li><strong>Container queries:</strong> Responsive layout without media queries</li>
107
+ </ul>
108
+ </div>
109
+ <p style="margin: 0; color: #6b7280; font-size: 14px; font-family: monospace; background: #e5e7eb; padding: 8px; border-radius: 4px;">
110
+ Tag: {{ args.tag }} | Landmark: {{ args.isLandmark ? 'Yes' : 'No' }}
111
+ </p>
112
+ </div>
113
+ </ContentWidthComponent>
114
+ </div>
115
+ `,
116
+ }),
117
+ };
118
+
119
+ // ===== SEMANTIC TAG STORIES =====
120
+
121
+ const semanticRender = (args: ContentWidthArgs) => ({
122
+ components: { ContentWidthComponent },
123
+ setup() {
124
+ const tagInfo: Record<string, { description: string; use: string }> = {
125
+ div: { description: "Generic container", use: "Default wrapper element" },
126
+ section: { description: "Thematic grouping", use: "Groups related content together" },
127
+ article: { description: "Self-contained content", use: "Blog posts, news articles, user comments" },
128
+ aside: { description: "Tangentially related", use: "Sidebars, pull quotes, advertising" },
129
+ header: { description: "Introductory content", use: "Page headers, section headers" },
130
+ footer: { description: "Footer information", use: "Page footers, section footers" },
131
+ main: { description: "Main content", use: "Primary content of the document" },
132
+ nav: { description: "Navigation links", use: "Navigation menus, breadcrumbs" },
133
+ };
134
+ const info = computed(() => tagInfo[args.tag] ?? { description: "Generic container", use: "Default usage" });
135
+ const wrapperStyle = computed(() => (args.showBackground ? { background: "rgba(99, 102, 241, 0.15)" } : {}));
136
+ return { args, info, wrapperStyle };
137
+ },
138
+ template: `
139
+ <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 60vh; padding: 40px 0;">
140
+ <ContentWidthComponent
141
+ :data-testid="args.dataTestid"
142
+ :tag="args.tag"
143
+ :style-class-passthrough="args.styleClassPassthrough"
144
+ :is-landmark="args.isLandmark"
145
+ :justify-content="args.justifyContent"
146
+ :style="wrapperStyle"
147
+ >
148
+ <div style="padding: 30px; background: white; border-radius: 12px; text-align: center;">
149
+ <div style="display: inline-block; padding: 8px 16px; background: #667eea; color: white; border-radius: 6px; font-family: monospace; font-size: 16px; margin-bottom: 20px;">
150
+ &lt;{{ args.tag }}&gt;
151
+ </div>
152
+ <h2 style="margin: 0 0 12px 0; color: #374151; font-size: 22px;">{{ info.description }}</h2>
153
+ <p style="margin: 0 0 20px 0; color: #6b7280; line-height: 1.5; max-width: 400px; margin-left: auto; margin-right: auto;">{{ info.use }}</p>
154
+ <div style="padding: 16px; background: #f8fafc; border-radius: 8px; border-left: 4px solid #667eea;">
155
+ <p style="margin: 0; color: #374151; font-size: 14px;">
156
+ This semantic element helps structure your content meaningfully for both users and assistive technologies.
157
+ </p>
158
+ </div>
159
+ </div>
160
+ </ContentWidthComponent>
161
+ </div>
162
+ `,
163
+ });
164
+
165
+ export const SemanticSection: Story = { args: { tag: "section" }, render: semanticRender };
166
+ export const SemanticArticle: Story = { args: { tag: "article" }, render: semanticRender };
167
+ export const SemanticAside: Story = { args: { tag: "aside" }, render: semanticRender };
168
+ export const SemanticHeader: Story = { args: { tag: "header" }, render: semanticRender };
169
+ export const SemanticMain: Story = { args: { tag: "main", isLandmark: true }, render: semanticRender };
170
+ export const SemanticNav: Story = { args: { tag: "nav", isLandmark: true }, render: semanticRender };
171
+
172
+ // ===== CONTENT EXAMPLES =====
173
+
174
+ export const BlogArticle: Story = {
175
+ args: { tag: "article" },
176
+ parameters: {
177
+ docs: {
178
+ description: {
179
+ story:
180
+ "Example of a blog article layout showing how ContentWidth provides optimal reading width and responsive behavior.",
181
+ },
182
+ },
183
+ },
184
+ render: (args) => ({
185
+ components: { ContentWidthComponent },
186
+ setup() {
187
+ const wrapperStyle = computed(() => (args.showBackground ? { background: "rgba(99, 102, 241, 0.15)" } : {}));
188
+ return { args, wrapperStyle };
189
+ },
190
+ template: `
191
+ <div style="background: #f9fafb;">
192
+ <ContentWidthComponent
193
+ :data-testid="args.dataTestid"
194
+ :tag="args.tag"
195
+ :style-class-passthrough="args.styleClassPassthrough"
196
+ :is-landmark="args.isLandmark"
197
+ :justify-content="args.justifyContent"
198
+ :style="wrapperStyle"
199
+ >
200
+ <article style="padding: 40px; background: white; border-radius: 0;">
201
+ <header style="margin-bottom: 30px; text-align: center;">
202
+ <h1 style="margin: 0 0 8px 0; color: #1f2937; font-size: 32px; font-weight: 700;">The Art of Responsive Design</h1>
203
+ <p style="margin: 0; color: #6b7280; font-size: 16px;">How container queries are revolutionizing web layouts</p>
204
+ </header>
205
+ <div style="line-height: 1.7; color: #374151;">
206
+ <p style="margin: 0 0 24px 0; font-size: 18px; color: #4b5563;">
207
+ Container queries represent a paradigm shift in responsive design, moving beyond viewport-based breakpoints
208
+ to component-centric layouts that adapt to their container's size rather than the entire screen.
209
+ </p>
210
+ <pre style="background: #1f2937; color: #e5e7eb; padding: 20px; border-radius: 8px; overflow-x: auto; margin: 24px 0; font-size: 14px; line-height: 1.4;"><code>container-type: inline-size;
211
+ container-name: content-width;
212
+
213
+ @container content-width (width >= 1092px) {
214
+ --content-max-width: 1064px;
215
+ --gutter: 0;
216
+ }</code></pre>
217
+ <p style="margin: 0;">
218
+ This approach creates more maintainable, flexible layouts that work consistently regardless of
219
+ where the component is placed in your application.
220
+ </p>
221
+ </div>
222
+ </article>
223
+ </ContentWidthComponent>
224
+ </div>
225
+ `,
226
+ }),
227
+ };
228
+
229
+ // ===== ACCESSIBILITY STORIES =====
230
+
231
+ const accessibilityRender = (args: ContentWidthArgs) => ({
232
+ components: { ContentWidthComponent },
233
+ setup() {
234
+ const wrapperStyle = computed(() => (args.showBackground ? { background: "rgba(99, 102, 241, 0.15)" } : {}));
235
+ return { args, wrapperStyle };
236
+ },
237
+ template: `
238
+ <div style="background: #f3f4f6; padding: 20px;">
239
+ <div style="background: white; border-radius: 12px; padding: 20px; margin-bottom: 20px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);">
240
+ <h3 style="margin: 0 0 16px 0; color: #374151;">Accessibility Features</h3>
241
+ <div style="padding: 12px; background: #f3f4f6; border-radius: 6px; border-left: 3px solid #6366f1;">
242
+ <strong style="color: #4338ca;">Current state:</strong>
243
+ {{ args.isLandmark ? 'Landmark enabled (focusable, labeled)' : 'Standard wrapper (no landmark features)' }}
244
+ </div>
245
+ </div>
246
+ <ContentWidthComponent
247
+ :data-testid="args.dataTestid"
248
+ :tag="args.tag"
249
+ :style-class-passthrough="args.styleClassPassthrough"
250
+ :is-landmark="args.isLandmark"
251
+ :justify-content="args.justifyContent"
252
+ :style="wrapperStyle"
253
+ >
254
+ <div style="padding: 24px; background: white; border-radius: 8px; border: 2px solid #d1d5db;">
255
+ <div :style="{
256
+ padding: '16px',
257
+ background: args.isLandmark ? '#dbeafe' : '#f9fafb',
258
+ borderRadius: '8px',
259
+ border: args.isLandmark ? '2px solid #3b82f6' : '2px solid #e5e7eb'
260
+ }">
261
+ <h3 style="margin: 0 0 12px 0; color: #374151;">
262
+ {{ args.isLandmark ? '🏷️ Landmark Wrapper' : '📦 Standard Wrapper' }}
263
+ </h3>
264
+ <div style="font-family: monospace; font-size: 12px; background: rgba(0, 0, 0, 0.05); padding: 8px; border-radius: 4px;">
265
+ <div>tabIndex: {{ args.isLandmark ? '0' : 'null' }}</div>
266
+ <div>aria-label: {{ args.isLandmark ? '"Content Width Landmark"' : 'undefined' }}</div>
267
+ </div>
268
+ </div>
269
+ </div>
270
+ </ContentWidthComponent>
271
+ </div>
272
+ `,
273
+ });
274
+
275
+ export const WithLandmark: Story = {
276
+ args: { tag: "section", isLandmark: true },
277
+ parameters: {
278
+ docs: {
279
+ description: {
280
+ story:
281
+ "ContentWidth with landmark accessibility features enabled - makes the wrapper focusable and properly labeled for assistive technologies.",
282
+ },
283
+ },
284
+ },
285
+ render: accessibilityRender,
286
+ };
287
+
288
+ export const WithoutLandmark: Story = {
289
+ args: { tag: "div", isLandmark: false },
290
+ parameters: {
291
+ docs: {
292
+ description: {
293
+ story: "Standard ContentWidth without landmark features - regular content width wrapper behavior.",
294
+ },
295
+ },
296
+ },
297
+ render: accessibilityRender,
298
+ };
299
+
300
+ // ===== RESPONSIVE DEMONSTRATION =====
301
+
302
+ export const ResponsiveBehavior: Story = {
303
+ parameters: {
304
+ docs: {
305
+ description: {
306
+ story:
307
+ "Interactive demonstration of how ContentWidth responds to different container widths using CSS Container Queries instead of media queries.",
308
+ },
309
+ },
310
+ },
311
+ render: () => ({
312
+ components: { ContentWidthComponent },
313
+ template: `
314
+ <div style="background: #f8fafc; padding: 20px;">
315
+ <div style="margin-bottom: 40px;">
316
+ <h2 style="text-align: center; margin: 0 0 16px 0; color: #374151; font-size: 28px;">Responsive Behavior Demonstration</h2>
317
+ <p style="text-align: center; margin: 0 0 32px 0; color: #6b7280; max-width: 600px; margin-left: auto; margin-right: auto; line-height: 1.6;">
318
+ ContentWidth adapts its layout based on its own width using container queries.
319
+ Resize your browser to see the responsive behavior in action.
320
+ </p>
321
+ </div>
322
+ <div style="margin-bottom: 40px;">
323
+ <h3 style="margin: 0 0 16px 0; color: #374151;">Narrow Container (&lt; 1092px)</h3>
324
+ <div style="max-width: 800px; border: 2px dashed #cbd5e1; padding: 20px; margin-bottom: 20px;">
325
+ <ContentWidthComponent tag="div">
326
+ <div style="padding: 20px; background: #fef3c7; border-radius: 8px; text-align: center;">
327
+ <h4 style="margin: 0 0 8px 0; color: #92400e;">Fluid Width Mode</h4>
328
+ <p style="margin: 0; color: #b45309; font-size: 14px;">Content uses available width with 16px gutters on each side</p>
329
+ </div>
330
+ </ContentWidthComponent>
331
+ </div>
332
+ </div>
333
+ <div style="margin-bottom: 40px;">
334
+ <h3 style="margin: 0 0 16px 0; color: #374151;">Wide Container (≥ 1092px)</h3>
335
+ <div style="border: 2px dashed #cbd5e1; padding: 20px;">
336
+ <ContentWidthComponent tag="div">
337
+ <div style="padding: 20px; background: #dcfce7; border-radius: 8px; text-align: center;">
338
+ <h4 style="margin: 0 0 8px 0; color: #166534;">Fixed Width Mode</h4>
339
+ <p style="margin: 0; color: #15803d; font-size: 14px;">Content is constrained to 1064px max-width and centered within the container</p>
340
+ </div>
341
+ </ContentWidthComponent>
342
+ </div>
343
+ </div>
344
+ <ContentWidthComponent tag="section">
345
+ <div style="padding: 24px; background: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);">
346
+ <h3 style="margin: 0 0 16px 0; color: #374151;">How It Works</h3>
347
+ <div style="display: grid; gap: 16px;">
348
+ <div style="padding: 16px; background: #f1f5f9; border-radius: 8px; border-left: 4px solid #3b82f6;">
349
+ <h4 style="margin: 0 0 8px 0; color: #1e40af; font-size: 14px; font-weight: 600;">Container Queries</h4>
350
+ <p style="margin: 0; color: #475569; font-size: 14px; line-height: 1.5;">Uses <code>@container</code> rules to respond to the container's own width, not the viewport width.</p>
351
+ </div>
352
+ <div style="padding: 16px; background: #f0fdf4; border-radius: 8px; border-left: 4px solid #22c55e;">
353
+ <h4 style="margin: 0 0 8px 0; color: #15803d; font-size: 14px; font-weight: 600;">CSS Grid Layout</h4>
354
+ <p style="margin: 0; color: #166534; font-size: 14px; line-height: 1.5;">Named grid lines create flexible layouts that adapt to content needs automatically.</p>
355
+ </div>
356
+ </div>
357
+ </div>
358
+ </ContentWidthComponent>
359
+ </div>
360
+ `,
361
+ }),
362
+ };