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,38 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`ExpandingPanel > renders correct HTML structure with all props set 1`] = `
4
+ "<div class="expanding-panel extra-class">
5
+ <details class="expanding-panel-details" name="snap-all" open="">
6
+ <summary id="id-snap-all-trigger" class="expanding-panel-summary" aria-controls="id-snap-all-content" aria-expanded="true"><span class="label-wrapper"></span>
7
+ <!--v-if-->
8
+ </summary>
9
+ </details>
10
+ <div id="id-snap-all-content" class="expanding-panel-content" aria-labelledby="id-snap-all-trigger" role="region">
11
+ <div class="inner"></div>
12
+ </div>
13
+ </div>"
14
+ `;
15
+
16
+ exports[`ExpandingPanel > renders correct HTML structure with default props 1`] = `
17
+ "<div class="expanding-panel">
18
+ <details class="expanding-panel-details" name="snap-default">
19
+ <summary id="id-snap-default-trigger" class="expanding-panel-summary" aria-controls="id-snap-default-content" aria-expanded="false"><span class="label-wrapper"></span><span class="icon-wrapper"><span class="iconify i-bi:caret-down-fill icon mi-12" aria-hidden="true"></span></span></summary>
20
+ </details>
21
+ <div id="id-snap-default-content" class="expanding-panel-content" aria-labelledby="id-snap-default-trigger" role="region">
22
+ <div class="inner"></div>
23
+ </div>
24
+ </div>"
25
+ `;
26
+
27
+ exports[`ExpandingPanel > renders correct HTML structure with populated slots 1`] = `
28
+ "<div class="expanding-panel">
29
+ <details class="expanding-panel-details" name="snap-slots">
30
+ <summary id="id-snap-slots-trigger" class="expanding-panel-summary" aria-controls="id-snap-slots-content" aria-expanded="false"><span class="label-wrapper"><span>Summary text</span></span><span class="icon-wrapper"><span>▸</span></span></summary>
31
+ </details>
32
+ <div id="id-snap-slots-content" class="expanding-panel-content" aria-labelledby="id-snap-slots-trigger" role="region">
33
+ <div class="inner">
34
+ <p>Content text</p>
35
+ </div>
36
+ </div>
37
+ </div>"
38
+ `;
@@ -0,0 +1,139 @@
1
+ <template>
2
+ <nav class="navigation-horizontal" :class="[elementClasses]">
3
+ <component :is="tag" class="navigation-horizontal-list">
4
+ <li v-for="(item, index) in navItemData.main" :key="index" :class="item.cssName">
5
+ <NuxtLink :href="item.href" :external="item.isExternal ? true : undefined">
6
+ <Icon v-if="item.iconName" :name="`icon-${item.iconName}`" />
7
+ {{ item.text }}
8
+ </NuxtLink>
9
+ </li>
10
+ </component>
11
+ </nav>
12
+ </template>
13
+
14
+ <script setup lang="ts">
15
+ export interface NavItem {
16
+ text: string;
17
+ href?: string;
18
+ isExternal?: boolean;
19
+ iconName?: string;
20
+ cssName?: string;
21
+ }
22
+
23
+ export interface NavItemData {
24
+ [key: string]: NavItem[];
25
+ }
26
+
27
+ interface Props {
28
+ tag?: "ol" | "ul" | "div";
29
+ navItemData: NavItemData;
30
+ styleClassPassthrough?: string | string[];
31
+ }
32
+ const props = withDefaults(defineProps<Props>(), {
33
+ tag: "ul",
34
+ styleClassPassthrough: () => [],
35
+ });
36
+
37
+ const { elementClasses, updateElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
38
+
39
+ watch(
40
+ () => props.styleClassPassthrough,
41
+ () => {
42
+ updateElementClasses(props.styleClassPassthrough);
43
+ }
44
+ );
45
+ </script>
46
+
47
+ <style lang="css">
48
+ @layer components {
49
+ .navigation-horizontal {
50
+ --_border-block-start-size: 0;
51
+ --_border-block-end-size: 3px;
52
+
53
+ --_active-link-colour: lime;
54
+
55
+ anchor-name: --active-nav;
56
+
57
+ background-color: var(--page-bg);
58
+
59
+ &::after {
60
+ content: "";
61
+ border-block-start: var(--_border-block-start-size) solid transparent;
62
+ border-block-end: var(--_border-block-end-size) solid transparent;
63
+
64
+ background:
65
+ radial-gradient(var(--page-bg)) padding-box,
66
+ radial-gradient(var(--_active-link-colour), transparent) border-box;
67
+
68
+ /* background:
69
+ radial-gradient(ellipse at 50% 100%, transparent 10%, var(--page-bg) 75%) padding-box,
70
+ radial-gradient(ellipse at 50% 100%, var(--_active-link-colour) 10%, transparent 75%) border-box; */
71
+
72
+ position: absolute;
73
+ position-anchor: --active-nav;
74
+
75
+ left: calc(anchor(left) - 40px);
76
+ right: calc(anchor(right) - 40px);
77
+ top: anchor(top --nav-ul);
78
+ bottom: anchor(bottom --nav-ul);
79
+
80
+ pointer-events: none;
81
+ z-index: -1;
82
+
83
+ opacity: 0;
84
+ transition:
85
+ inset 300ms,
86
+ opacity 700ms;
87
+ transition-delay: 700ms, 0ms;
88
+ }
89
+
90
+ .navigation-horizontal-list {
91
+ anchor-name: --nav-ul;
92
+
93
+ border-block-start: var(--_border-block-start-size) solid hsl(0 0% 100% / 0.2);
94
+ border-block-end: var(--_border-block-end-size) solid hsl(0 0% 100% / 0.2);
95
+
96
+ a:is(:hover, :focus) {
97
+ anchor-name: --active-nav;
98
+ }
99
+ }
100
+
101
+ &:has(a:hover, a:focus)::after {
102
+ opacity: 1;
103
+ transition-delay: 0ms, 0ms;
104
+ }
105
+ }
106
+
107
+ @layer general-styling {
108
+ .navigation-horizontal {
109
+ .navigation-horizontal-list {
110
+ list-style: none;
111
+ margin: 0rem;
112
+ padding: 2rem;
113
+ gap: 3rem;
114
+
115
+ display: flex;
116
+ justify-content: center;
117
+ }
118
+
119
+ a {
120
+ color: white;
121
+ text-decoration: none;
122
+ padding: 0.5rem 1rem;
123
+
124
+ border-radius: 0.2rem;
125
+ /* border: 2px solid hsl(0 0 100% / 0.25); */
126
+ border-bottom: 2px solid transparent;
127
+ background: hsl(0 0 20%);
128
+ transition: background-color 300ms;
129
+ }
130
+
131
+ a:is(:hover, :focus) {
132
+ /* background: var(--_active-link-colour); */
133
+ border-color: var(--_active-link-colour);
134
+ box-shadow: 0 0 32px oklch(from var(--_active-link-colour) l c h / 0.5);
135
+ }
136
+ }
137
+ }
138
+ }
139
+ </style>
@@ -0,0 +1,172 @@
1
+ <template>
2
+ <nav class="navigation-horizontal-advanced" :class="[elementClasses]">
3
+ <component :is="tag" class="navigation-horizontal-list">
4
+ <li v-for="(item, index) in navItemData.main" :key="index" :class="item.cssName">
5
+ <NuxtLink :href="item.href" :external="item.isExternal ? true : undefined">
6
+ <Icon v-if="item.iconName" :name="`icon-${item.iconName}`" />
7
+ {{ item.text }}
8
+ </NuxtLink>
9
+ </li>
10
+ </component>
11
+ </nav>
12
+ </template>
13
+
14
+ <script setup lang="ts">
15
+ export interface NavItem {
16
+ text: string;
17
+ href?: string;
18
+ isExternal?: boolean;
19
+ iconName?: string;
20
+ cssName?: string;
21
+ }
22
+
23
+ export interface NavItemData {
24
+ [key: string]: NavItem[];
25
+ }
26
+
27
+ interface Props {
28
+ tag?: "ol" | "ul" | "div";
29
+ navItemData: NavItemData;
30
+ styleClassPassthrough?: string | string[];
31
+ }
32
+ const props = withDefaults(defineProps<Props>(), {
33
+ tag: "ul",
34
+ styleClassPassthrough: () => [],
35
+ });
36
+
37
+ const { elementClasses, updateElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
38
+
39
+ watch(
40
+ () => props.styleClassPassthrough,
41
+ () => {
42
+ updateElementClasses(props.styleClassPassthrough);
43
+ }
44
+ );
45
+ </script>
46
+
47
+ <style lang="css">
48
+ @layer components {
49
+ .navigation-horizontal-advanced {
50
+ /* ─── Public token API ─────────────────────────────────────────── */
51
+
52
+ /* Colours */
53
+ --_nav-canvas-colour: var(--page-bg);
54
+ --_active-link-colour: var(--nav-active-colour, lime);
55
+ --_link-colour: var(--nav-link-colour, light-dark(hsl(0 0% 10%), hsl(0 0% 100%)));
56
+ --_link-bg: var(--nav-link-bg, light-dark(hsl(0 0% 88%), hsl(0 0% 20%)));
57
+ --_border-colour: var(--nav-border-colour, light-dark(hsl(0 0% 0% / 0.15), hsl(0 0% 100% / 0.2)));
58
+
59
+ /* Borders */
60
+ --_border-block-start-size: var(--nav-border-start, 0);
61
+ --_border-block-end-size: var(--nav-border-end, 3px);
62
+
63
+ /* Layout */
64
+ --_list-padding: var(--nav-list-padding, 2rem);
65
+ --_list-gap: var(--nav-list-gap, 1rem);
66
+ --_link-padding-block: var(--nav-link-padding-block, 0.5rem);
67
+ --_link-padding-inline: var(--nav-link-padding-inline, 1rem);
68
+ --_link-border-radius: var(--nav-link-border-radius, 0.2rem);
69
+
70
+ /* Glow effect */
71
+ --_glow-pos-x: var(--nav-glow-pos-x, 50%);
72
+ --_glow-pos-y: var(--nav-glow-pos-y, 100%);
73
+ --_glow-inner-stop: var(--nav-glow-inner-stop, 10%);
74
+ --_glow-outer-stop: var(--nav-glow-outer-stop, 75%);
75
+ --_glow-size: var(--nav-glow-size, 32px);
76
+ --_glow-opacity: var(--nav-glow-opacity, 0.5);
77
+ --_anchor-offset: var(--nav-anchor-offset, 40px);
78
+
79
+ /* Animation */
80
+ --_transition-duration: var(--nav-transition-duration, 300ms);
81
+
82
+ /* ─────────────────────────────────────────────────────────────── */
83
+
84
+ anchor-name: --active-nav;
85
+
86
+ background-color: var(--_nav-canvas-colour);
87
+
88
+ &::after {
89
+ content: "";
90
+ border-block-start: var(--_border-block-start-size) solid transparent;
91
+ border-block-end: var(--_border-block-end-size) solid transparent;
92
+
93
+ background:
94
+ radial-gradient(
95
+ ellipse at var(--_glow-pos-x) var(--_glow-pos-y),
96
+ transparent var(--_glow-inner-stop),
97
+ var(--page-bg) var(--_glow-outer-stop)
98
+ )
99
+ padding-box,
100
+ radial-gradient(
101
+ ellipse at var(--_glow-pos-x) var(--_glow-pos-y),
102
+ var(--_active-link-colour) var(--_glow-inner-stop),
103
+ transparent var(--_glow-outer-stop)
104
+ )
105
+ border-box;
106
+
107
+ position: absolute;
108
+ position-anchor: --active-nav;
109
+
110
+ left: calc(anchor(left) - var(--_anchor-offset));
111
+ right: calc(anchor(right) - var(--_anchor-offset));
112
+ top: anchor(top --nav-ul);
113
+ bottom: anchor(bottom --nav-ul);
114
+
115
+ pointer-events: none;
116
+ z-index: -1;
117
+
118
+ opacity: 0;
119
+ transition:
120
+ inset var(--_transition-duration),
121
+ opacity 700ms;
122
+ transition-delay: 700ms, 0ms;
123
+ }
124
+
125
+ .navigation-horizontal-list {
126
+ anchor-name: --nav-ul;
127
+
128
+ border-block-start: var(--_border-block-start-size) solid var(--_border-colour);
129
+ border-block-end: var(--_border-block-end-size) solid var(--_border-colour);
130
+
131
+ a:is(:hover, :focus) {
132
+ anchor-name: --active-nav;
133
+ }
134
+ }
135
+
136
+ &:has(a:hover, a:focus)::after {
137
+ opacity: 1;
138
+ transition-delay: 0ms, 0ms;
139
+ }
140
+ }
141
+
142
+ @layer general-styling {
143
+ .navigation-horizontal-advanced {
144
+ .navigation-horizontal-list {
145
+ list-style: none;
146
+ margin: 0rem;
147
+ padding: var(--_list-padding);
148
+ gap: var(--_list-gap);
149
+
150
+ display: flex;
151
+ justify-content: center;
152
+ }
153
+
154
+ a {
155
+ color: var(--_link-colour);
156
+ text-decoration: none;
157
+ padding: var(--_link-padding-block) var(--_link-padding-inline);
158
+
159
+ border-radius: var(--_link-border-radius);
160
+ border-bottom: 2px solid transparent;
161
+ background: var(--_link-bg);
162
+ transition: background-color var(--_transition-duration);
163
+ }
164
+
165
+ a:is(:hover, :focus) {
166
+ border-color: var(--_active-link-colour);
167
+ box-shadow: 0 0 var(--_glow-size) oklch(from var(--_active-link-colour) l c h / var(--_glow-opacity));
168
+ }
169
+ }
170
+ }
171
+ }
172
+ </style>
@@ -3,7 +3,7 @@
3
3
  :is="tag"
4
4
  class="profile-section"
5
5
  :class="[elementClasses]"
6
- :aria-labelledby="needsLabel ? headingId : undefined"
6
+ :aria-labelledby="ariaLabelledby"
7
7
  >
8
8
  <header class="profile-section-header">
9
9
  <slot v-if="hasEyebrowTextSlot" name="eyebrowText"></slot>
@@ -51,8 +51,7 @@ const props = withDefaults(defineProps<Props>(), {
51
51
  styleClassPassthrough: () => [],
52
52
  });
53
53
 
54
- const headingId = useId();
55
- const needsLabel = computed(() => props.tag === "section" || props.tag === "article");
54
+ const { headingId, ariaLabelledby } = useAriaLabelledById(() => props.tag);
56
55
 
57
56
  const slots = useSlots();
58
57
  const hasEyebrowTextSlot = computed(() => Boolean(slots.eyebrowText));
@@ -152,11 +152,11 @@ describe("ProfileSection", () => {
152
152
  expect(wrapper.find(".profile-section").attributes("aria-labelledby")).toBeUndefined();
153
153
  });
154
154
 
155
- it("does not add aria-labelledby when tag is main", async () => {
155
+ it("adds aria-labelledby when tag is main", async () => {
156
156
  const wrapper = await mountSuspended(ProfileSection, {
157
157
  props: { ...defaultProps, tag: "main" },
158
158
  });
159
- expect(wrapper.find(".profile-section").attributes("aria-labelledby")).toBeUndefined();
159
+ expect(wrapper.find(".profile-section").attributes("aria-labelledby")).toBeTruthy();
160
160
  });
161
161
 
162
162
  it("does not render profile-links section when slot is not provided", async () => {
@@ -1,5 +1,10 @@
1
1
  <template>
2
- <component :is="tag" class="stepper-list" :class="[elementClasses, { 'has-connectors': props.connected }]">
2
+ <component
3
+ :is="tag"
4
+ ref="listEl"
5
+ class="stepper-list"
6
+ :class="[elementClasses, { 'has-connectors': props.connected }]"
7
+ >
3
8
  <li
4
9
  v-for="index in itemCount"
5
10
  :key="index"
@@ -10,9 +15,7 @@
10
15
  ]"
11
16
  >
12
17
  <div
13
- :class="
14
- $slots[`indicator-${index - 1}`] ? 'stepper-list__indicator-custom' : 'stepper-list__indicator-counter'
15
- "
18
+ :class="$slots[`indicator-${index - 1}`] ? 'stepper-list__indicator-custom' : 'stepper-list__indicator-counter'"
16
19
  >
17
20
  <slot :name="`indicator-${index - 1}`"></slot>
18
21
  </div>
@@ -28,6 +31,7 @@ interface Props {
28
31
  tag?: "ul" | "ol";
29
32
  indicatorAlignment?: "top" | "center";
30
33
  indicatorVariant?: "disc" | "square" | "circle";
34
+ indicatorSize?: string;
31
35
  connected?: boolean;
32
36
  itemCount: number;
33
37
  styleClassPassthrough?: string | string[];
@@ -37,122 +41,157 @@ const props = withDefaults(defineProps<Props>(), {
37
41
  tag: "ul",
38
42
  indicatorAlignment: "top",
39
43
  indicatorVariant: "disc",
44
+ indicatorSize: "3rem",
40
45
  connected: true,
41
46
  styleClassPassthrough: () => [],
42
47
  });
43
48
 
44
49
  const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
50
+
51
+ const listEl = ref<HTMLElement | null>(null);
52
+ let resizeObserver: ResizeObserver | null = null;
53
+
54
+ function updateConnectors() {
55
+ if (!listEl.value || !props.connected) return;
56
+ const lis = Array.from(listEl.value.querySelectorAll<HTMLLIElement>(":scope > li"));
57
+
58
+ lis.forEach((li, i) => {
59
+ if (i >= lis.length - 1) return;
60
+
61
+ const indicator = li.querySelector<HTMLElement>(
62
+ ".stepper-list__indicator-counter, .stepper-list__indicator-custom"
63
+ );
64
+ const nextIndicator = lis[i + 1]?.querySelector<HTMLElement>(
65
+ ".stepper-list__indicator-counter, .stepper-list__indicator-custom"
66
+ );
67
+
68
+ if (!indicator || !nextIndicator) return;
69
+
70
+ const liRect = li.getBoundingClientRect();
71
+ const indicatorRect = indicator.getBoundingClientRect();
72
+ const nextIndicatorRect = nextIndicator.getBoundingClientRect();
73
+
74
+ li.style.setProperty("--_connector-top", `${indicatorRect.bottom - liRect.top}px`);
75
+ li.style.setProperty("--_connector-height", `${Math.max(0, nextIndicatorRect.top - indicatorRect.bottom)}px`);
76
+ });
77
+ }
78
+
79
+ onMounted(() => {
80
+ updateConnectors();
81
+ if (typeof ResizeObserver !== "undefined" && listEl.value) {
82
+ resizeObserver = new ResizeObserver(updateConnectors);
83
+ resizeObserver.observe(listEl.value);
84
+ }
85
+ });
86
+
87
+ onBeforeUnmount(() => {
88
+ resizeObserver?.disconnect();
89
+ });
90
+
91
+ watch(
92
+ () => [props.itemCount, props.connected, props.indicatorAlignment],
93
+ async () => {
94
+ await nextTick();
95
+ updateConnectors();
96
+ }
97
+ );
45
98
  </script>
46
99
 
47
100
  <style lang="css">
48
101
  @layer components {
49
- .stepper-list {
50
- --_list-padding-block: 1.2rem;
51
- --_counter-size: 3rem;
52
- --_stepper-list-connector-width: 0.2rem;
53
-
54
- list-style: none;
55
- counter-reset: stepper-list;
56
- padding: 0;
57
-
58
- li {
59
- display: grid;
60
- grid-template-columns: auto 1fr;
61
- gap: 2.2rem;
62
- counter-increment: stepper-list;
63
- padding-inline-start: 0rem;
64
- padding-block: var(--_list-padding-block);
65
- position: relative;
66
-
67
- &.indicator-top {
68
- align-items: start;
69
- }
102
+ .stepper-list {
103
+ --_list-padding-block: 1.2rem;
104
+ --_counter-size: v-bind(indicatorSize);
105
+ --_stepper-list-connector-width: 0.2rem;
70
106
 
71
- &.indicator-center {
72
- align-items: center;
73
- }
74
-
75
- .stepper-list__indicator-counter::before {
76
- content: counter(stepper-list);
107
+ list-style: none;
108
+ counter-reset: stepper-list;
109
+ padding: 0;
77
110
 
111
+ li {
78
112
  display: grid;
79
- place-content: center;
80
- width: var(--_counter-size);
81
- height: var(--_counter-size);
82
- font-size: 1.4rem;
83
- font-weight: 600;
84
- }
113
+ grid-template-columns: auto 1fr;
114
+ gap: 2.2rem;
115
+ counter-increment: stepper-list;
116
+ padding-inline-start: 0rem;
117
+ padding-block: var(--_list-padding-block);
118
+ position: relative;
119
+
120
+ &.indicator-top {
121
+ align-items: start;
122
+ }
85
123
 
86
- &.indicator-circle .stepper-list__indicator-counter::before {
87
- background-color: var(--stepper-list-counter-circle-background);
88
- color: var(--stepper-list-counter-circle-text);
89
- border: 2px solid var(--stepper-list-counter-circle-border);
90
- border-radius: 100vw;
91
- }
124
+ &.indicator-center {
125
+ align-items: center;
126
+ }
92
127
 
93
- &.indicator-disc .stepper-list__indicator-counter::before {
94
- background-color: var(--stepper-list-counter-disc-background);
95
- color: var(--stepper-list-counter-disc-text);
96
- border: 2px solid var(--stepper-list-counter-disc-border);
97
- border-radius: 100vw;
98
- }
128
+ .stepper-list__indicator-counter::before {
129
+ content: counter(stepper-list);
99
130
 
100
- &.indicator-square .stepper-list__indicator-counter::before {
101
- background-color: var(--stepper-list-counter-square-background);
102
- color: var(--stepper-list-counter-square-text);
103
- border: 2px solid var(--stepper-list-counter-square-border);
104
- border-radius: 0.25rem;
105
- }
131
+ display: grid;
132
+ place-content: center;
133
+ width: var(--_counter-size);
134
+ height: var(--_counter-size);
135
+ font-size: 1.4rem;
136
+ font-weight: 600;
137
+ }
106
138
 
107
- &.has-indicator::before {
108
- display: none;
109
- }
139
+ &.indicator-circle .stepper-list__indicator-counter::before {
140
+ background-color: var(--stepper-list-counter-circle-background);
141
+ color: var(--stepper-list-counter-circle-text);
142
+ border: 2px solid var(--stepper-list-counter-circle-border);
143
+ border-radius: 100vw;
144
+ }
110
145
 
111
- .stepper-list__indicator-custom,
112
- .stepper-list__indicator-counter {
113
- anchor-name: --indicator-bubble;
114
- }
146
+ &.indicator-disc .stepper-list__indicator-counter::before {
147
+ background-color: var(--stepper-list-counter-disc-background);
148
+ color: var(--stepper-list-counter-disc-text);
149
+ border: 2px solid var(--stepper-list-counter-disc-border);
150
+ border-radius: 100vw;
151
+ }
115
152
 
116
- .stepper-list__indicator-custom {
117
- display: inline-flex;
118
- align-items: center;
119
- justify-content: center;
153
+ &.indicator-square .stepper-list__indicator-counter::before {
154
+ background-color: var(--stepper-list-counter-square-background);
155
+ color: var(--stepper-list-counter-square-text);
156
+ border: 2px solid var(--stepper-list-counter-square-border);
157
+ border-radius: 0.25rem;
158
+ }
120
159
 
121
- .indicator-icon {
122
- color: var(--stepper-list-icon);
123
- width: var(--_counter-size);
124
- height: var(--_counter-size);
160
+ &.has-indicator::before {
161
+ display: none;
162
+ }
163
+
164
+ .stepper-list__indicator-custom,
165
+ .stepper-list__indicator-counter {
166
+ anchor-name: --indicator-bubble;
167
+ }
168
+
169
+ .stepper-list__indicator-custom {
170
+ display: inline-flex;
171
+ align-items: center;
172
+ justify-content: center;
173
+
174
+ .indicator-icon {
175
+ color: var(--stepper-list-icon);
176
+ width: var(--_counter-size) !important;
177
+ height: var(--_counter-size) !important;
178
+ }
125
179
  }
126
180
  }
127
181
  }
128
- }
129
-
130
- /* Fallback: per-li connectors for browsers without anchor positioning */
131
- /* @supports not (anchor-name: --x) { */
132
- .stepper-list.has-connectors li:not(:last-child)::after {
133
- content: "";
134
- position: absolute;
135
- left: calc(var(--_counter-size) / 2);
136
- transform: translateX(-50%);
137
- top: calc(1.2rem + 3rem);
138
- bottom: calc(-1 * var(--_list-padding-block));
139
- width: var(--_stepper-list-connector-width);
140
- background-color: var(--stepper-list-connector-color, currentColor);
141
- }
142
- /* } */
143
182
 
144
- /* Enhanced: anchor-based connector positioning */
145
- /* @supports (anchor-name: --x) {
146
183
  .stepper-list.has-connectors li:not(:last-child)::after {
147
184
  content: "";
148
185
  position: absolute;
149
- left: anchor(--indicator-bubble, center);
186
+ left: calc(var(--_counter-size) / 2);
150
187
  transform: translateX(-50%);
151
- top: anchor(--indicator-bubble, bottom);
152
- bottom: calc(-1 * var(--stepper-list-item-gap, 2.4rem));
153
- width: var(--stepper-list-connector-width, 2px);
188
+ /* JS sets --_connector-top and --_connector-height from measured indicator positions.
189
+ The fallback values handle the indicator-top case before JS runs. */
190
+ top: var(--_connector-top, calc(var(--_list-padding-block) + var(--_counter-size)));
191
+ height: var(--_connector-height, auto);
192
+ bottom: calc(-1 * var(--_list-padding-block));
193
+ width: var(--_stepper-list-connector-width);
154
194
  background-color: var(--stepper-list-connector-color, currentColor);
155
195
  }
156
- } */
157
196
  }
158
197
  </style>