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,198 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { h } from "vue";
3
+ import { mountSuspended } from "@nuxt/test-utils/runtime";
4
+ import PageHeroHighlights from "../PageHeroHighlights.vue";
5
+
6
+ describe("PageHeroHighlights", () => {
7
+ it("mounts without error", async () => {
8
+ const wrapper = await mountSuspended(PageHeroHighlights);
9
+ expect(wrapper.vm).toBeTruthy();
10
+ });
11
+
12
+ it("renders correct HTML structure", async () => {
13
+ const wrapper = await mountSuspended(PageHeroHighlights, {
14
+ slots: {
15
+ header: "<h1>Page Title</h1>",
16
+ highlights: "<div class='highlight'>Highlight 1</div>",
17
+ content: "<p>Content</p>",
18
+ },
19
+ });
20
+ expect(wrapper.html()).toMatchSnapshot();
21
+ });
22
+
23
+ it("renders as a div by default", async () => {
24
+ const wrapper = await mountSuspended(PageHeroHighlights);
25
+ expect(wrapper.element.tagName.toLowerCase()).toBe("div");
26
+ });
27
+
28
+ it("renders with the correct tag when tag prop is provided", async () => {
29
+ for (const tag of ["section", "main"] as const) {
30
+ const wrapper = await mountSuspended(PageHeroHighlights, {
31
+ props: { tag },
32
+ });
33
+ expect(wrapper.element.tagName.toLowerCase()).toBe(tag);
34
+ }
35
+ });
36
+
37
+ it("renders header slot content", async () => {
38
+ const wrapper = await mountSuspended(PageHeroHighlights, {
39
+ slots: { header: "<h1 class='page-title'>Dashboard</h1>" },
40
+ });
41
+ expect(wrapper.find(".page-title").exists()).toBe(true);
42
+ expect(wrapper.html()).toContain("Dashboard");
43
+ });
44
+
45
+ it("renders highlights slot content", async () => {
46
+ const wrapper = await mountSuspended(PageHeroHighlights, {
47
+ slots: {
48
+ highlights: [
49
+ "<div class='highlight-card'>Card 1</div>",
50
+ "<div class='highlight-card'>Card 2</div>",
51
+ ].join(""),
52
+ },
53
+ });
54
+ expect(wrapper.findAll(".highlight-card").length).toBe(2);
55
+ });
56
+
57
+ it("renders content slot content", async () => {
58
+ const wrapper = await mountSuspended(PageHeroHighlights, {
59
+ slots: { content: "<p class='body-text'>Page body</p>" },
60
+ });
61
+ expect(wrapper.find(".body-text").exists()).toBe(true);
62
+ expect(wrapper.html()).toContain("Page body");
63
+ });
64
+
65
+ it("content slot is rendered inside .content-slot", async () => {
66
+ const wrapper = await mountSuspended(PageHeroHighlights, {
67
+ slots: { content: "<p class='body-text'>Page body</p>" },
68
+ });
69
+ expect(wrapper.find(".content-slot .body-text").exists()).toBe(true);
70
+ });
71
+
72
+ it("header slot is rendered inside .header-slot", async () => {
73
+ const wrapper = await mountSuspended(PageHeroHighlights, {
74
+ slots: { header: "<h1 class='page-title'>Dashboard</h1>" },
75
+ });
76
+ expect(wrapper.find(".header-slot .page-title").exists()).toBe(true);
77
+ });
78
+
79
+ it("adds aria-labelledby when tag is section", async () => {
80
+ const wrapper = await mountSuspended(PageHeroHighlights, {
81
+ props: { tag: "section" },
82
+ });
83
+ expect(wrapper.find(".page-hero-highlights").attributes("aria-labelledby")).toBeTruthy();
84
+ });
85
+
86
+ it("adds aria-labelledby when tag is main", async () => {
87
+ const wrapper = await mountSuspended(PageHeroHighlights, {
88
+ props: { tag: "main" },
89
+ });
90
+ expect(wrapper.find(".page-hero-highlights").attributes("aria-labelledby")).toBeTruthy();
91
+ });
92
+
93
+ it("does not add aria-labelledby when tag is div", async () => {
94
+ const wrapper = await mountSuspended(PageHeroHighlights, {
95
+ props: { tag: "div" },
96
+ });
97
+ expect(wrapper.find(".page-hero-highlights").attributes("aria-labelledby")).toBeUndefined();
98
+ });
99
+
100
+ it("exposes headingId via header scoped slot and aria-labelledby matches", async () => {
101
+ const wrapper = await mountSuspended(PageHeroHighlights, {
102
+ props: { tag: "section" },
103
+ slots: {
104
+ header: (props: Record<string, unknown>) =>
105
+ h("h1", { id: props.headingId, class: "page-title" }, "Dashboard"),
106
+ },
107
+ });
108
+ const labelledBy = wrapper.find(".page-hero-highlights").attributes("aria-labelledby");
109
+ expect(labelledBy).toBeTruthy();
110
+ expect(wrapper.find(".page-title").attributes("id")).toBe(labelledBy);
111
+ });
112
+
113
+ it("applies flexible-widths class by default", async () => {
114
+ const wrapper = await mountSuspended(PageHeroHighlights);
115
+ expect(wrapper.find(".highlights-row").classes()).toContain("flexible-widths");
116
+ expect(wrapper.find(".highlights-row").classes()).not.toContain("equal-widths");
117
+ });
118
+
119
+ it("applies equal-widths class when highlightsEqualWidths is true", async () => {
120
+ const wrapper = await mountSuspended(PageHeroHighlights, {
121
+ props: { highlightsEqualWidths: true },
122
+ });
123
+ expect(wrapper.find(".highlights-row").classes()).toContain("equal-widths");
124
+ expect(wrapper.find(".highlights-row").classes()).not.toContain("flexible-widths");
125
+ });
126
+
127
+ it("applies justify-start class by default", async () => {
128
+ const wrapper = await mountSuspended(PageHeroHighlights);
129
+ expect(wrapper.find(".highlights-row").classes()).toContain("justify-start");
130
+ });
131
+
132
+ it("applies the correct justify class for each highlightsJustify value", async () => {
133
+ const values = ["start", "center", "end", "space-between", "space-around"] as const;
134
+ for (const value of values) {
135
+ const wrapper = await mountSuspended(PageHeroHighlights, {
136
+ props: { highlightsJustify: value },
137
+ });
138
+ expect(wrapper.find(".highlights-row").classes()).toContain(`justify-${value}`);
139
+ }
140
+ });
141
+
142
+ it("does not apply highlight-title-baseline class by default", async () => {
143
+ const wrapper = await mountSuspended(PageHeroHighlights);
144
+ expect(wrapper.find(".page-hero-highlights").classes()).not.toContain("highlight-title-baseline");
145
+ });
146
+
147
+ it("applies highlight-title-baseline class when highlightTitleBaseline is true", async () => {
148
+ const wrapper = await mountSuspended(PageHeroHighlights, {
149
+ props: { highlightTitleBaseline: true },
150
+ });
151
+ expect(wrapper.find(".page-hero-highlights").classes()).toContain("highlight-title-baseline");
152
+ });
153
+
154
+ it("applies styleClassPassthrough classes", async () => {
155
+ const wrapper = await mountSuspended(PageHeroHighlights, {
156
+ props: { styleClassPassthrough: ["extra-class", "another-class"] },
157
+ });
158
+ const el = wrapper.find(".page-hero-highlights");
159
+ expect(el.classes()).toContain("extra-class");
160
+ expect(el.classes()).toContain("another-class");
161
+ });
162
+
163
+ describe("gridColumns", () => {
164
+ interface ComponentInstance {
165
+ gridColumns: string;
166
+ }
167
+
168
+ it("defaults to fixed 16px gutters with no maxWidth", async () => {
169
+ const wrapper = await mountSuspended(PageHeroHighlights);
170
+ const vm = wrapper.vm as unknown as ComponentInstance;
171
+ expect(vm.gridColumns).toBe("16px 1fr 16px");
172
+ });
173
+
174
+ it("returns centered max-width columns when maxWidth is set and contentAlign is center", async () => {
175
+ const wrapper = await mountSuspended(PageHeroHighlights, {
176
+ props: { maxWidth: "1064px", contentAlign: "center" },
177
+ });
178
+ const vm = wrapper.vm as unknown as ComponentInstance;
179
+ expect(vm.gridColumns).toBe("max(16px, (100% - 1064px) / 2) 1fr max(16px, (100% - 1064px) / 2)");
180
+ });
181
+
182
+ it("returns start-aligned columns when maxWidth is set and contentAlign is start", async () => {
183
+ const wrapper = await mountSuspended(PageHeroHighlights, {
184
+ props: { maxWidth: "1064px", contentAlign: "start" },
185
+ });
186
+ const vm = wrapper.vm as unknown as ComponentInstance;
187
+ expect(vm.gridColumns).toBe("16px minmax(0, 1064px) 1fr");
188
+ });
189
+
190
+ it("ignores contentAlign when maxWidth is not set", async () => {
191
+ const wrapper = await mountSuspended(PageHeroHighlights, {
192
+ props: { contentAlign: "start" },
193
+ });
194
+ const vm = wrapper.vm as unknown as ComponentInstance;
195
+ expect(vm.gridColumns).toBe("16px 1fr 16px");
196
+ });
197
+ });
198
+ });
@@ -0,0 +1,19 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`PageHeroHighlights > renders correct HTML structure 1`] = `
4
+ "<div class="page-hero-highlights">
5
+ <div class="header-row">
6
+ <div class="header-slot">
7
+ <h1>Page Title</h1>
8
+ </div>
9
+ </div>
10
+ <div class="highlights-row flexible-widths justify-start">
11
+ <div class="highlight">Highlight 1</div>
12
+ </div>
13
+ <div class="content-row">
14
+ <div class="content-slot">
15
+ <p>Content</p>
16
+ </div>
17
+ </div>
18
+ </div>"
19
+ `;
@@ -2,13 +2,13 @@
2
2
  <div ref="containerGlowWrapper" class="container-glow-wrapper" :class="elementClasses">
3
3
  <component
4
4
  :is="tag"
5
- v-for="(item, key) in itemCount"
6
- :key="key"
5
+ v-for="(_, name) in $slots"
6
+ :key="name"
7
7
  ref="containerGlowItem"
8
8
  class="container-glow-core"
9
9
  >
10
10
  <div class="glows"></div>
11
- <slot :name="`container-glow-${key}`"></slot>
11
+ <slot :name="name"></slot>
12
12
  </component>
13
13
  </div>
14
14
  </template>
@@ -23,30 +23,23 @@ interface Config {
23
23
  inactiveOpacity: number;
24
24
  }
25
25
 
26
- const props = defineProps({
27
- itemCount: {
28
- type: Number,
29
- required: true,
30
- },
31
- tag: {
32
- type: String as PropType<string>,
33
- default: "div",
34
- },
35
- styleClassPassthrough: {
36
- type: [String, Array] as PropType<string | string[]>,
37
- default: () => [],
38
- },
39
- config: {
40
- type: Object as PropType<Config>,
41
- default: () => ({
42
- proximity: 40,
43
- spread: 80,
44
- blur: 20,
45
- gap: 32,
46
- vertical: false,
47
- inactiveOpacity: 0,
48
- }),
49
- },
26
+ interface Props {
27
+ tag?: string;
28
+ config?: Config;
29
+ styleClassPassthrough?: string | string[];
30
+ }
31
+
32
+ const props = withDefaults(defineProps<Props>(), {
33
+ tag: "div",
34
+ config: () => ({
35
+ proximity: 40,
36
+ spread: 80,
37
+ blur: 20,
38
+ gap: 32,
39
+ vertical: false,
40
+ inactiveOpacity: 0,
41
+ }),
42
+ styleClassPassthrough: () => [],
50
43
  });
51
44
 
52
45
  const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
@@ -86,134 +86,135 @@ const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough)
86
86
 
87
87
  <style lang="css">
88
88
  @layer components {
89
- .input-button-core {
90
- display: grid;
91
- grid-auto-flow: column;
92
- gap: var(--button-icon-gap);
93
- justify-content: center;
94
- align-items: center;
95
- box-sizing: content-box;
96
- border-radius: var(--button-border-radius);
97
- font-family: var(--font-family);
98
- padding-inline: var(--button-padding-inline);
99
- padding-block: var(--button-padding-block);
100
- touch-action: manipulation;
101
- overflow: hidden;
102
- transition: all var(--control-transition-duration) var(--control-transition-ease);
103
-
104
- &.is-link {
105
- display: inline-grid;
106
- }
89
+ .input-button-core {
90
+ display: grid;
91
+ grid-auto-flow: column;
92
+ gap: var(--button-icon-gap);
93
+ justify-content: center;
94
+ align-items: center;
95
+ box-sizing: content-box;
96
+ border-radius: var(--button-border-radius);
97
+ font-family: var(--font-family);
98
+ padding-inline: var(--button-padding-inline);
99
+ padding-block: var(--button-padding-block);
100
+ touch-action: manipulation;
101
+ overflow: hidden;
102
+ transition: all var(--control-transition-duration) var(--control-transition-ease);
103
+
104
+ &.is-link {
105
+ display: inline-grid;
106
+ }
107
107
 
108
- /*
108
+ /*
109
109
  * Variants
110
110
  **/
111
- &.primary {
112
- background-color: var(--theme-button-primary-surface);
113
- color: var(--theme-button-primary-text);
114
- border: var(--button-border-width) solid var(--theme-button-primary-border);
115
- outline: var(--button-outline-width) solid var(--theme-button-primary-outline);
116
-
117
- &:hover,
118
- &:focus-visible {
119
- background-color: var(--theme-button-secondary-surface);
120
- color: var(--theme-button-secondary-text);
121
- border-color: var(--theme-button-primary-border-active);
122
- outline-color: var(--theme-button-primary-outline-active);
123
- }
124
-
125
- &.is-pending {
126
- background-color: color-mix(in oklab, var(--theme-button-primary-surface) 50%, transparent);
127
- }
128
- }
129
-
130
- &.secondary {
131
- background-color: var(--theme-button-secondary-surface);
132
- border: var(--button-border-width) solid var(--theme-button-secondary-border);
133
- color: var(--theme-button-secondary-text);
134
- outline: var(--button-outline-width) solid var(--theme-button-secondary-outline);
135
-
136
- &:hover,
137
- &:focus-visible {
111
+ &.primary {
138
112
  background-color: var(--theme-button-primary-surface);
139
113
  color: var(--theme-button-primary-text);
140
- border-color: var(--theme-button-secondary-border-active);
141
- outline-color: var(--theme-button-secondary-outline-active);
114
+ border: var(--button-border-width) solid var(--theme-button-primary-border);
115
+ outline: var(--button-outline-width) solid var(--theme-button-primary-outline);
116
+
117
+ &:hover,
118
+ &:focus-visible {
119
+ background-color: var(--theme-button-secondary-surface);
120
+ color: var(--theme-button-secondary-text);
121
+ border-color: var(--theme-button-primary-border-active);
122
+ outline-color: var(--theme-button-primary-outline-active);
123
+ }
124
+
125
+ &.is-pending {
126
+ background-color: color-mix(in oklab, var(--theme-button-primary-surface) 50%, transparent);
127
+ }
142
128
  }
143
- }
144
129
 
145
- &.tertiary {
146
- background-color: var(--theme-button-tertiary-surface);
147
- border: var(--button-border-width) solid transparent;
148
- color: var(--theme-button-tertiary-text);
149
- text-decoration: underline;
150
- outline: var(--button-outline-width) solid transparent;
130
+ &.secondary {
131
+ background-color: var(--theme-button-secondary-surface);
132
+ border: var(--button-border-width) solid var(--theme-button-secondary-border);
133
+ color: var(--theme-button-secondary-text);
134
+ outline: var(--button-outline-width) solid var(--theme-button-secondary-outline);
135
+
136
+ &:hover,
137
+ &:focus-visible {
138
+ background-color: var(--theme-button-primary-surface);
139
+ color: var(--theme-button-primary-text);
140
+ border-color: var(--theme-button-secondary-border-active);
141
+ outline-color: var(--theme-button-secondary-outline-active);
142
+ }
143
+ }
151
144
 
152
- &:hover,
153
- &:focus-visible {
154
- border-color: var(--theme-button-tertiary-border-active);
155
- outline-color: var(--theme-button-tertiary-outline-active);
145
+ &.tertiary {
146
+ background-color: var(--theme-button-tertiary-surface);
147
+ border: var(--button-border-width) solid transparent;
148
+ color: var(--theme-button-tertiary-text);
149
+ text-decoration: underline;
150
+ outline: var(--button-outline-width) solid transparent;
151
+
152
+ &:hover,
153
+ &:focus-visible {
154
+ border-color: var(--theme-button-tertiary-border-active);
155
+ outline-color: var(--theme-button-tertiary-outline-active);
156
+ }
156
157
  }
157
- }
158
158
 
159
- &.primary,
160
- &.secondary,
161
- &.tertiary {
162
- &.pill {
163
- border-radius: 100vw;
159
+ &.primary,
160
+ &.secondary,
161
+ &.tertiary {
162
+ &.pill {
163
+ border-radius: 100vw;
164
+ }
164
165
  }
165
- }
166
166
 
167
- /*
167
+ /*
168
168
  * Shared States
169
169
  **/
170
- &:hover {
171
- cursor: pointer;
172
- }
173
-
174
- &:focus-visible {
175
- outline-width: var(--button-focus-ring-width);
176
- outline-offset: var(--button-focus-ring-offset);
177
- }
170
+ &:hover {
171
+ cursor: pointer;
172
+ }
178
173
 
179
- &[readonly] {
180
- opacity: 0.5;
181
- &:hover,
182
174
  &:focus-visible {
183
- cursor: not-allowed;
184
- pointer-events: none;
175
+ outline-width: var(--button-focus-ring-width);
176
+ outline-offset: var(--button-focus-ring-offset);
185
177
  }
186
- }
187
-
188
- .button-text {
189
- display: inline-block;
190
- white-space: nowrap;
191
- font-size: var(--button-font-size);
192
- line-height: var(--button-line-height);
193
- font-weight: var(--button-font-weight);
194
- }
195
178
 
196
- .btn-icon {
197
- display: flex;
179
+ &[readonly] {
180
+ opacity: 0.5;
181
+ &:hover,
182
+ &:focus-visible {
183
+ cursor: not-allowed;
184
+ pointer-events: none;
185
+ }
186
+ }
198
187
 
199
- .icon {
200
- aspect-ratio: 1;
188
+ .button-text {
201
189
  display: inline-block;
202
- height: var(--input-icon-size);
203
- width: var(--input-icon-size);
190
+ white-space: nowrap;
191
+ font-size: var(--button-font-size);
192
+ line-height: var(--button-line-height);
193
+ font-weight: var(--button-font-weight);
194
+ text-transform: var(--button-text-transform);
204
195
  }
205
- }
206
-
207
- &.icon-only {
208
- aspect-ratio: 1;
209
- border-radius: var(--button-border-radius-icon-only);
210
- margin: 0;
211
- padding: 0;
212
196
 
213
197
  .btn-icon {
214
- margin: 1.2rem;
198
+ display: flex;
199
+
200
+ .icon {
201
+ aspect-ratio: 1;
202
+ display: inline-block;
203
+ height: var(--input-icon-size);
204
+ width: var(--input-icon-size);
205
+ }
206
+ }
207
+
208
+ &.icon-only {
209
+ aspect-ratio: 1;
210
+ border-radius: var(--button-border-radius-icon-only);
211
+ margin: 0;
212
+ padding: 0;
213
+
214
+ .btn-icon {
215
+ margin: 1.2rem;
216
+ }
215
217
  }
216
218
  }
217
219
  }
218
- }
219
220
  </style>
@@ -5,8 +5,8 @@ import StorybookComponent from "../GlowingBorder.vue";
5
5
  interface GlowingBorderStoryArgs {
6
6
  variant: "vivid" | "subtle" | "silver" | "steel";
7
7
  content: string;
8
- showDisplayCard: boolean;
9
- displayCardVariant: "solid" | "subtle" | "soft" | "outline";
8
+ showCardCore: boolean;
9
+ cardCoreVariant: "solid" | "subtle" | "soft" | "outline";
10
10
  }
11
11
 
12
12
  export default {
@@ -28,17 +28,17 @@ export default {
28
28
  category: "Content",
29
29
  },
30
30
  },
31
- showDisplayCard: {
31
+ showCardCore: {
32
32
  control: { type: "boolean" },
33
- description: "Show content wrapped in a DisplayCard component",
33
+ description: "Show content wrapped in a CardCore component",
34
34
  table: {
35
35
  category: "Content",
36
36
  },
37
37
  },
38
- displayCardVariant: {
38
+ cardCoreVariant: {
39
39
  control: { type: "select" },
40
40
  options: ["solid", "subtle", "soft", "outline"],
41
- description: "DisplayCard variant when showDisplayCard is true",
41
+ description: "CardCore variant when showCardCore is true",
42
42
  table: {
43
43
  category: "Content",
44
44
  },
@@ -54,8 +54,8 @@ export default {
54
54
  variant: "vivid",
55
55
  content:
56
56
  "This is default slot content for the GlowingBorder component. As it's a slot, any HTML content can be placed here.",
57
- showDisplayCard: false,
58
- displayCardVariant: "solid",
57
+ showCardCore: false,
58
+ cardCoreVariant: "solid",
59
59
  },
60
60
  } as Meta<GlowingBorderStoryArgs>;
61
61
 
@@ -70,32 +70,32 @@ const Template: StoryFn<GlowingBorderStoryArgs> = (args) => ({
70
70
  :variant="args.variant"
71
71
  :style-class-passthrough="['storybook-demo']"
72
72
  >
73
- <div v-if="!args.showDisplayCard" style="padding: 20px; color: var(--slate-02);">
73
+ <div v-if="!args.showCardCore" style="padding: 20px; color: var(--slate-02);">
74
74
  <h3 style="margin: 0 0 12px 0; font-size: 1.5rem; font-weight: 600;">GlowingBorder Header</h3>
75
75
  <h4 style="margin: 0 0 16px 0; font-size: 1.25rem; font-weight: 500;">GlowingBorder Content</h4>
76
76
  <p style="margin: 0 0 12px 0; line-height: 1.6;">{{ args.content }}</p>
77
77
  <p style="margin: 0; font-size: 0.875rem; color: var(--slate-09);">GlowingBorder Footer</p>
78
78
  </div>
79
79
 
80
- <!-- DisplayCard version -->
81
- <DisplayCard
80
+ <!-- CardCore version -->
81
+ <CardCore
82
82
  v-else
83
- :variant="args.displayCardVariant"
83
+ :variant="args.cardCoreVariant"
84
84
  :has-dividers="true"
85
85
  :no-outline="false"
86
86
  >
87
87
  <template #header>
88
- <h2 style="margin: 0; font-size: 1.5rem; font-weight: 600;">DisplayCard Header</h2>
88
+ <h2 style="margin: 0; font-size: 1.5rem; font-weight: 600;">CardCore Header</h2>
89
89
  </template>
90
90
  <template #default>
91
- <h3 style="margin: 0 0 12px 0; font-size: 1.25rem; font-weight: 500; color: var(--slate-02);">DisplayCard Content</h3>
91
+ <h3 style="margin: 0 0 12px 0; font-size: 1.25rem; font-weight: 500; color: var(--slate-02);">CardCore Content</h3>
92
92
  <p style="margin: 0 0 12px 0; line-height: 1.6; color: var(--slate-02);">{{ args.content }}</p>
93
93
  <p style="margin: 0; line-height: 1.6; color: var(--slate-02);">This demonstrates how GlowingBorder can wrap other components.</p>
94
94
  </template>
95
95
  <template #footer>
96
- <p style="margin: 0; font-size: 0.875rem; color: var(--slate-02);">DisplayCard Footer</p>
96
+ <p style="margin: 0; font-size: 0.875rem; color: var(--slate-02);">CardCore Footer</p>
97
97
  </template>
98
- </DisplayCard>
98
+ </CardCore>
99
99
  </StorybookComponent>
100
100
  </div>
101
101
  `,
@@ -131,11 +131,11 @@ Steel.args = {
131
131
  "Steel variant provides a cool, industrial glow effect that's perfect for technical or utilitarian design themes.",
132
132
  };
133
133
 
134
- export const WithDisplayCard = Template.bind({});
135
- WithDisplayCard.args = {
134
+ export const WithCardCore = Template.bind({});
135
+ WithCardCore.args = {
136
136
  variant: "vivid",
137
137
  content:
138
- "This example shows how GlowingBorder can enhance other components like DisplayCard, creating layered visual effects.",
139
- showDisplayCard: true,
140
- displayCardVariant: "solid",
138
+ "This example shows how GlowingBorder can enhance other components like CardCore, creating layered visual effects.",
139
+ showCardCore: true,
140
+ cardCoreVariant: "solid",
141
141
  };
@@ -0,0 +1,13 @@
1
+ import type { MaybeRefOrGetter } from "vue";
2
+
3
+ const LABELLED_TAGS = new Set(["section", "main", "article", "aside"]);
4
+
5
+ export function useAriaLabelledById(tag: MaybeRefOrGetter<string>) {
6
+ const headingId = useId();
7
+
8
+ const ariaLabelledby = computed(() => {
9
+ return LABELLED_TAGS.has(toValue(tag)) ? headingId : undefined;
10
+ });
11
+
12
+ return { headingId, ariaLabelledby };
13
+ }