srcdev-nuxt-components 9.4.9 → 9.4.11

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 (178) hide show
  1. package/.claude/commands/migrate-component.md +38 -6
  2. package/.claude/component-ledger/audit.json +1 -1
  3. package/.claude/component-ledger/build.mjs +26 -0
  4. package/.claude/component-ledger/output.html +8 -3
  5. package/.claude/component-ledger/template.html +7 -2
  6. package/.claude/skills/component-dynamic-slots.md +1 -1
  7. package/.claude/skills/components/alert-mask-core.md +84 -0
  8. package/.claude/skills/components/canvas-switcher.md +1 -1
  9. package/.claude/skills/components/carousel-flip.md +112 -168
  10. package/.claude/skills/components/container-glow.md +123 -0
  11. package/.claude/skills/components/dashboard-quad-grid.md +98 -0
  12. package/.claude/skills/components/dashboard-stats-grid.md +103 -0
  13. package/.claude/skills/components/display-theme-switch.md +60 -0
  14. package/.claude/skills/components/glowing-border.md +66 -0
  15. package/.claude/skills/components/input-number-core.md +214 -0
  16. package/.claude/skills/components/input-range-core.md +142 -0
  17. package/.claude/skills/components/input-select-core.md +151 -0
  18. package/.claude/skills/components/input-text-core.md +16 -0
  19. package/.claude/skills/components/input-textarea-core.md +154 -0
  20. package/.claude/skills/components/section-parallax.md +32 -10
  21. package/.claude/skills/components/select-menu.md +195 -0
  22. package/.claude/skills/components/toggle-switch-core.md +150 -0
  23. package/.claude/skills/components/ui-block-decorated.md +56 -0
  24. package/.claude/skills/components/wipe-away-vertical.md +98 -0
  25. package/.claude/skills/icon-sets.md +1 -1
  26. package/.claude/skills/index.md +15 -1
  27. package/.vscode/css-custom-data.json +38 -0
  28. package/.vscode/settings.json +3 -0
  29. package/.vscode/srcdev-component-alert-mask-core.code-snippets +31 -0
  30. package/.vscode/srcdev-component-carousel-flip.code-snippets +41 -0
  31. package/.vscode/srcdev-component-container-glow.code-snippets +33 -0
  32. package/.vscode/srcdev-component-dashboard-quad-grid.code-snippets +36 -0
  33. package/.vscode/srcdev-component-dashboard-stats-grid.code-snippets +35 -0
  34. package/.vscode/srcdev-component-display-theme-switch.code-snippets +27 -0
  35. package/.vscode/srcdev-component-glowing-border.code-snippets +23 -0
  36. package/.vscode/srcdev-component-input-number.code-snippets +65 -0
  37. package/.vscode/srcdev-component-input-range.code-snippets +85 -0
  38. package/.vscode/srcdev-component-input-select.code-snippets +42 -0
  39. package/.vscode/srcdev-component-input-textarea.code-snippets +40 -0
  40. package/.vscode/srcdev-component-section-parallax.code-snippets +27 -0
  41. package/.vscode/srcdev-component-select-menu.code-snippets +65 -0
  42. package/.vscode/srcdev-component-toggle-switch.code-snippets +49 -0
  43. package/.vscode/srcdev-component-ui-block-decorated.code-snippets +24 -0
  44. package/.vscode/srcdev-component-wipe-away-vertical.code-snippets +25 -0
  45. package/README.md +18 -37
  46. package/app/components/{alert-mask → 01.atoms/alert-mask}/AlertMaskCore.vue +16 -17
  47. package/app/components/01.atoms/alert-mask/CONSUMER-STYLING.md +46 -0
  48. package/app/components/01.atoms/alert-mask/stories/AlertMaskCore.stories.ts +79 -0
  49. package/app/components/{alert-mask → 01.atoms/alert-mask}/tests/AlertMaskCore.spec.ts +10 -10
  50. package/app/components/01.atoms/animations/container-glow/CONSUMER-STYLING.md +86 -0
  51. package/app/components/01.atoms/animations/container-glow/ContainerGlow.vue +299 -0
  52. package/app/components/01.atoms/animations/container-glow/stories/ContainerGlow.stories.ts +175 -0
  53. package/app/components/01.atoms/animations/container-glow/tests/ContainerGlow.spec.ts +131 -0
  54. package/app/components/01.atoms/animations/container-glow/tests/__snapshots__/ContainerGlow.spec.ts.snap +14 -0
  55. package/app/components/01.atoms/animations/glowing-border/CONSUMER-STYLING.md +61 -0
  56. package/app/components/01.atoms/animations/glowing-border/GlowingBorder.vue +132 -0
  57. package/app/components/01.atoms/animations/glowing-border/stories/GlowingBorder.stories.ts +165 -0
  58. package/app/components/01.atoms/animations/glowing-border/tests/GlowingBorder.spec.ts +94 -0
  59. package/app/components/01.atoms/animations/glowing-border/tests/__snapshots__/GlowingBorder.spec.ts.snap +3 -0
  60. package/app/components/01.atoms/animations/marquee-scroller/CONSUMER-STYLING.md +1 -1
  61. package/app/components/01.atoms/animations/marquee-scroller/MarqueeScroller.vue +1 -1
  62. package/app/components/01.atoms/animations/marquee-scroller/stories/MarqueeScroller.stories.ts +1 -1
  63. package/app/components/01.atoms/animations/section-parallax/CONSUMER-STYLING.md +44 -0
  64. package/app/components/{parallax → 01.atoms/animations/section-parallax}/SectionParallax.vue +11 -10
  65. package/app/components/01.atoms/animations/section-parallax/stories/SectionParallax.stories.ts +98 -0
  66. package/app/components/01.atoms/animations/section-parallax/tests/SectionParallax.spec.ts +102 -0
  67. package/app/components/01.atoms/animations/section-parallax/tests/__snapshots__/SectionParallax.spec.ts.snap +9 -0
  68. package/app/components/01.atoms/animations/view-timeline/CONSUMER-STYLING.md +70 -0
  69. package/app/components/01.atoms/animations/view-timeline/WipeAwayVertical.vue +181 -0
  70. package/app/components/01.atoms/animations/view-timeline/stories/WipeAwayVertical.stories.ts +269 -0
  71. package/app/components/01.atoms/animations/view-timeline/tests/WipeAwayVertical.spec.ts +127 -0
  72. package/app/components/01.atoms/canvas-switcher/CONSUMER-STYLING.md +3 -3
  73. package/app/components/01.atoms/canvas-switcher/CanvasSwitcher.vue +2 -2
  74. package/app/components/01.atoms/canvas-switcher/stories/CanvasSwitcher.stories.ts +1 -1
  75. package/app/components/01.atoms/card/CardCore.vue +4 -4
  76. package/app/components/01.atoms/clipped-panel/CONSUMER-STYLING.md +2 -2
  77. package/app/components/01.atoms/clipped-panel/ClippedPanel.vue +2 -2
  78. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md +42 -0
  79. package/app/components/{layout-grids/LayoutGridA.vue → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/DashboardQuadGrid.vue} +23 -13
  80. package/app/components/{layout-grids/stories/LayoutGridA.stories.ts → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/stories/DashboardQuadGrid.stories.ts} +151 -23
  81. package/app/components/{layout-grids/tests/LayoutGridA.spec.ts → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/tests/DashboardQuadGrid.spec.ts} +25 -25
  82. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/CONSUMER-STYLING.md +41 -0
  83. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/DashboardStatsGrid.vue +133 -0
  84. package/app/components/{layout-grids/stories/LayoutGridB.stories.ts → 01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/stories/DashboardStatsGrid.stories.ts} +24 -24
  85. package/app/components/{layout-grids/tests/LayoutGridB.spec.ts → 01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/tests/DashboardStatsGrid.spec.ts} +23 -23
  86. package/app/components/01.atoms/display-tooltip/CONSUMER-STYLING.md +8 -8
  87. package/app/components/01.atoms/display-tooltip/DisplayTooltip.vue +8 -8
  88. package/app/components/01.atoms/pop-over/CONSUMER-STYLING.md +6 -6
  89. package/app/components/01.atoms/pop-over/PopOver.vue +6 -6
  90. package/app/components/01.atoms/ui-block-decorated/CONSUMER-STYLING.md +39 -0
  91. package/app/components/01.atoms/ui-block-decorated/UiBlockDecorated.vue +141 -0
  92. package/app/components/01.atoms/ui-block-decorated/stories/UiBlockDecorated.stories.ts +148 -0
  93. package/app/components/01.atoms/ui-block-decorated/tests/UiBlockDecorated.spec.ts +80 -0
  94. package/app/components/01.atoms/ui-block-decorated/tests/__snapshots__/UiBlockDecorated.spec.ts.snap +3 -0
  95. package/app/components/02.molecules/alert-masked-content/tests/AlertMaskedContent.spec.ts +6 -6
  96. package/app/components/02.molecules/display-theme-switch/CONSUMER-STYLING.md +34 -0
  97. package/app/components/02.molecules/display-theme-switch/DisplayThemeSwitch.vue +102 -0
  98. package/app/components/02.molecules/display-theme-switch/stories/DisplayThemeSwitch.stories.ts +115 -0
  99. package/app/components/02.molecules/display-theme-switch/tests/DisplayThemeSwitch.spec.ts +86 -0
  100. package/app/components/02.molecules/navigation/deep-expanding-menu/CONSUMER-STYLING.md +1 -1
  101. package/app/components/02.molecules/navigation/deep-expanding-menu/DeepExpandingMenu.vue +1 -1
  102. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/CONSUMER-STYLING.md +1 -1
  103. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/DeepExpandingMenuClassic.vue +1 -1
  104. package/app/components/02.molecules/select-menu/CONSUMER-STYLING.md +168 -0
  105. package/app/components/02.molecules/select-menu/SelectMenu.vue +384 -0
  106. package/app/components/02.molecules/select-menu/stories/SelectMenu.stories.ts +359 -0
  107. package/app/components/02.molecules/select-menu/tests/SelectMenu.spec.ts +476 -0
  108. package/app/components/02.molecules/select-menu/tests/__snapshots__/SelectMenu.spec.ts.snap +42 -0
  109. package/app/components/03.organisms/image-galleries/carousel-flip/CONSUMER-STYLING.md +46 -0
  110. package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/CarouselFlip.vue +63 -42
  111. package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/stories/CarouselFlip.stories.ts +114 -29
  112. package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/tests/CarouselFlip.spec.ts +80 -0
  113. package/app/components/05.forms/form-field/FormField.vue +1 -1
  114. package/app/components/05.forms/form-wrapper/FormWrapper.vue +1 -1
  115. package/app/components/05.forms/input-button/CONSUMER-STYLING.md +2 -7
  116. package/app/components/05.forms/input-button/InputButtonCore.vue +1 -5
  117. package/app/components/05.forms/input-number/CONSUMER-STYLING.md +26 -6
  118. package/app/components/05.forms/input-number/InputNumberCore.vue +125 -85
  119. package/app/components/05.forms/input-number/stories/InputNumberCore.stories.ts +92 -0
  120. package/app/components/05.forms/input-number/tests/InputNumberCore.spec.ts +119 -0
  121. package/app/components/05.forms/input-number/variants/InputNumberDefault.vue +30 -22
  122. package/app/components/05.forms/input-number/variants/stories/InputNumberDefault.stories.ts +123 -0
  123. package/app/components/05.forms/input-number/variants/tests/InputNumberDefault.spec.ts +143 -0
  124. package/app/components/05.forms/input-range/CONSUMER-STYLING.md +45 -0
  125. package/app/components/05.forms/input-range/InputRangeCore.vue +79 -143
  126. package/app/components/05.forms/input-range/stories/InputRangeCore.stories.ts +126 -0
  127. package/app/components/05.forms/input-range/tests/InputRangeCore.spec.ts +124 -0
  128. package/app/components/05.forms/input-range/variants/InputRangeDefault.vue +9 -32
  129. package/app/components/05.forms/input-range/variants/stories/InputRangeDefault.stories.ts +108 -0
  130. package/app/components/05.forms/input-range/variants/tests/InputRangeDefault.spec.ts +140 -0
  131. package/app/components/05.forms/input-select/InputSelectCore.vue +23 -30
  132. package/app/components/05.forms/input-select/stories/InputSelectCore.stories.ts +2 -2
  133. package/app/components/05.forms/input-select/stories/InputSelectWithLabel.stories.ts +115 -0
  134. package/app/components/05.forms/input-select/tests/InputSelectCore.spec.ts +129 -0
  135. package/app/components/05.forms/input-select/variants/InputSelectWithLabel.vue +1 -1
  136. package/app/components/05.forms/input-select/variants/tests/InputSelectWithLabel.spec.ts +87 -0
  137. package/app/components/05.forms/input-text/tests/InputTextAsNumberWithLabel.spec.ts +14 -0
  138. package/app/components/05.forms/input-text/variants/InputTextAsNumberWithLabel.vue +1 -1
  139. package/app/components/05.forms/input-textarea/CONSUMER-STYLING.md +55 -0
  140. package/app/components/05.forms/input-textarea/InputTextareaCore.vue +24 -16
  141. package/app/components/05.forms/input-textarea/stories/InputTextareaWithLabel.stories.ts +1 -1
  142. package/app/components/05.forms/input-textarea/tests/InputTextareaCore.spec.ts +119 -0
  143. package/app/components/05.forms/input-textarea/variants/InputTextareaWithLabel.vue +3 -13
  144. package/app/components/05.forms/input-textarea/variants/tests/InputTextareaWithLabel.spec.ts +93 -0
  145. package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +220 -0
  146. package/app/components/05.forms/toggle-switch/CONSUMER-STYLING.md +29 -0
  147. package/app/components/05.forms/toggle-switch/ToggleSwitchCore.vue +120 -119
  148. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchCore.stories.ts +1 -1
  149. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabel.stories.ts +1 -1
  150. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabelInline.stories.ts +2 -2
  151. package/app/components/05.forms/toggle-switch/tests/ToggleSwitchCore.spec.ts +101 -0
  152. package/app/components/05.forms/toggle-switch/variants/ToggleSwitchWithLabel.vue +3 -5
  153. package/app/components/05.forms/toggle-switch/variants/ToggleSwitchWithLabelInline.vue +1 -2
  154. package/app/components/05.forms/toggle-switch/variants/tests/ToggleSwitchWithLabel.spec.ts +77 -0
  155. package/app/components/05.forms/toggle-switch/variants/tests/ToggleSwitchWithLabelInline.spec.ts +66 -0
  156. package/app/components/05.forms/triple-toggle-switch/CONSUMER-STYLING.md +20 -6
  157. package/app/components/05.forms/triple-toggle-switch/TripleToggleSwitchCore.vue +12 -10
  158. package/app/types/components/container-glow.d.ts +8 -0
  159. package/app/types/components/index.ts +2 -1
  160. package/app/types/components/select-menu.d.ts +5 -0
  161. package/package.json +3 -2
  162. package/.vscode/srcdev-component-toggle-switch-core.code-snippets +0 -13
  163. package/app/components/05.forms/input-range-fancy/InputRangeFancyCore.vue +0 -426
  164. package/app/components/05.forms/input-range-fancy/InputRangeFancyWithLabel.vue +0 -94
  165. package/app/components/carousels/CarouselBasic.vue +0 -317
  166. package/app/components/carousels/CarouselInfinite.vue +0 -358
  167. package/app/components/container-glow/ContainerGlowCore.vue +0 -296
  168. package/app/components/container-glow/stories/ContainerGlowCore.stories.ts +0 -337
  169. package/app/components/display-theme-switch/DisplayThemeSwitch.vue +0 -189
  170. package/app/components/display-theme-switch/stories/DisplayThemeSwitch.stories.ts +0 -154
  171. package/app/components/glowing-border/GlowingBorder.vue +0 -142
  172. package/app/components/glowing-border/stories/GlowingBorder.stories.ts +0 -141
  173. package/app/components/layout-grids/LayoutGridB.vue +0 -134
  174. package/app/components/test-storybook/TestStorybook.vue +0 -49
  175. package/app/components/test-storybook/stories/TestStorybook.stories.ts +0 -28
  176. package/app/components/ui-block-decorated/UiBlockDecorated.vue +0 -167
  177. package/app/components/view-timeline/WipeAwayVertical.vue +0 -180
  178. package/app/types/components/carousel-basic.d.ts +0 -19
@@ -5,10 +5,10 @@
5
5
  :class="[elementClasses]"
6
6
  :data-button-layout="buttonLayout"
7
7
  role="region"
8
- aria-label="Image carousel"
8
+ :aria-label="ariaLabel"
9
9
  >
10
10
  <div aria-live="polite" aria-atomic="true" class="sr-only">
11
- Item {{ currentActiveIndex + 1 }} of {{ itemCount }}
11
+ {{ props.itemPositionAnnouncement(currentActiveIndex + 1, itemCount) }}
12
12
  </div>
13
13
 
14
14
  <div
@@ -17,7 +17,7 @@
17
17
  class="item-container"
18
18
  :class="{ 'allow-overflow': allowCarouselOverflow }"
19
19
  role="group"
20
- aria-label="Carousel items"
20
+ :aria-label="itemsAriaLabel"
21
21
  >
22
22
  <div
23
23
  v-for="(item, index) in carouselDataIds"
@@ -39,7 +39,7 @@
39
39
  <button
40
40
  class="btn-marker"
41
41
  :class="[{ active: displayActiveIndex === index - 1 }]"
42
- :aria-label="`Jump to item ${index}`"
42
+ :aria-label="`${jumpToItemLabel} ${index}`"
43
43
  @click.prevent="jumpToFrame(index - 1)"
44
44
  ></button>
45
45
  </li>
@@ -48,16 +48,15 @@
48
48
  </div>
49
49
 
50
50
  <div class="buttons-container">
51
- <button
52
- type="button"
53
- class="btn-action btn-prev"
54
- aria-label="Go to previous item"
55
- @click.prevent="actionPrevious()"
56
- >
57
- <Icon name="ic:outline-keyboard-arrow-left" class="arrows-icon" />
51
+ <button type="button" class="btn-action btn-prev" :aria-label="previousLabel" @click.prevent="actionPrevious()">
52
+ <slot name="prev-icon">
53
+ <Icon :name="prevIcon" class="arrows-icon" />
54
+ </slot>
58
55
  </button>
59
- <button type="button" class="btn-action btn-next" aria-label="Go to next item" @click.prevent="actionNext()">
60
- <Icon name="ic:outline-keyboard-arrow-right" class="arrows-icon" />
56
+ <button type="button" class="btn-action btn-next" :aria-label="nextLabel" @click.prevent="actionNext()">
57
+ <slot name="next-icon">
58
+ <Icon :name="nextIcon" class="arrows-icon" />
59
+ </slot>
61
60
  </button>
62
61
  </div>
63
62
  </section>
@@ -75,6 +74,14 @@ interface Props {
75
74
  useSpringEffect?: boolean;
76
75
  buttonLayout?: "sides" | "controls-flanking" | "controls-grouped-right" | "overlay";
77
76
  showControls?: boolean;
77
+ ariaLabel?: string;
78
+ itemsAriaLabel?: string;
79
+ previousLabel?: string;
80
+ nextLabel?: string;
81
+ jumpToItemLabel?: string;
82
+ itemPositionAnnouncement?: (current: number, total: number) => string;
83
+ prevIcon?: string;
84
+ nextIcon?: string;
78
85
  }
79
86
 
80
87
  const props = withDefaults(defineProps<Props>(), {
@@ -86,8 +93,17 @@ const props = withDefaults(defineProps<Props>(), {
86
93
  useSpringEffect: false,
87
94
  buttonLayout: "sides",
88
95
  showControls: true,
96
+ ariaLabel: "Image carousel",
97
+ itemsAriaLabel: "Carousel items",
98
+ previousLabel: "Go to previous item",
99
+ nextLabel: "Go to next item",
100
+ jumpToItemLabel: "Jump to item",
101
+ itemPositionAnnouncement: (current: number, total: number) => `Item ${current} of ${total}`,
102
+ prevIcon: "ic:outline-keyboard-arrow-left",
103
+ nextIcon: "ic:outline-keyboard-arrow-right",
89
104
  });
90
105
 
106
+
91
107
  const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
92
108
 
93
109
  const carouselWrapperRef = ref<HTMLDivElement | null>(null);
@@ -107,19 +123,11 @@ const transitionSpeedStr = props.transitionSpeed + "ms";
107
123
 
108
124
  const itemWidth = ref(0);
109
125
  const itemWidthOffsetStr = computed(() => {
110
- // if (props.allowCarouselOverflow) {
111
126
  if (props.useFlipAnimation) {
112
- return `calc(-${initialItemOffset.value} * ${itemWidth.value}px - var(--_carousel-item-track-gap))`; // Good
127
+ return `calc(-${initialItemOffset.value} * ${itemWidth.value}px - var(--_carousel-item-track-gap))`;
113
128
  } else {
114
- return `calc(-${initialItemOffset.value} * ${itemWidth.value}px - (2 * var(--_carousel-item-track-gap)))`; // Good
129
+ return `calc(-${initialItemOffset.value} * ${itemWidth.value}px - (2 * var(--_carousel-item-track-gap)))`;
115
130
  }
116
- // } else {
117
- // if (props.useFlipAnimation) {
118
- // return `calc(-${initialItemOffset.value} * ${itemWidth.value}px - var(--_carousel-item-track-gap))` // Goof
119
- // } else {
120
- // return `calc(-${initialItemOffset.value} * ${itemWidth.value}px - (2 * var(--_carousel-item-track-gap)))` // Good
121
- // }
122
- // }
123
131
  });
124
132
  const currentActiveIndex = ref(0);
125
133
 
@@ -256,7 +264,10 @@ const reorderItems = (skipAnimation: boolean = false) => {
256
264
  item.style.transform = `translateX(${deltaX}px)`;
257
265
 
258
266
  requestAnimationFrame(() => {
259
- const shouldTransition = carouselInitComplete.value && userHasInteracted.value;
267
+ const shouldTransition =
268
+ carouselInitComplete.value &&
269
+ userHasInteracted.value &&
270
+ !window.matchMedia("(prefers-reduced-motion: reduce)").matches;
260
271
  let transitionProperties = "none";
261
272
 
262
273
  if (shouldTransition) {
@@ -416,7 +427,7 @@ onMounted(() => {
416
427
  <style lang="css">
417
428
  @layer components {
418
429
  .carousel-flip {
419
- --_carousel-item-track-gap: 10px;
430
+ --_carousel-item-track-gap: var(--carousel-flip-gap, 1rem);
420
431
 
421
432
  display: grid;
422
433
  grid-template-columns: auto 1fr auto;
@@ -424,7 +435,7 @@ onMounted(() => {
424
435
  grid-template-areas:
425
436
  "prev carousel next"
426
437
  ". controls . ";
427
- gap: 10px;
438
+ gap: var(--carousel-flip-layout-gap, 1rem);
428
439
  opacity: 0;
429
440
 
430
441
  &.mounted {
@@ -451,7 +462,7 @@ onMounted(() => {
451
462
  position: relative;
452
463
  isolation: isolate;
453
464
 
454
- max-inline-size: var(--_carousel-display-max-width);
465
+ max-inline-size: var(--carousel-flip-display-max-width, 80rem);
455
466
  margin-inline: auto;
456
467
 
457
468
  &.allow-overflow {
@@ -466,17 +477,20 @@ onMounted(() => {
466
477
  margin-inline: auto;
467
478
 
468
479
  max-inline-size: calc(
469
- var(--_carousel-container-max-inline-size) + var(--_carousel-item-track-gap) -
470
- (2 * var(--_carousel-item-edge-preview-width))
480
+ var(--carousel-flip-item-max-width, 80rem) + var(--_carousel-item-track-gap) -
481
+ (2 * var(--carousel-flip-edge-preview-width, 4rem))
471
482
  );
472
483
 
473
484
  translate: calc(
474
- v-bind(itemWidthOffsetStr) - var(--_carousel-item-track-gap) + var(--_carousel-item-edge-preview-width)
485
+ v-bind(itemWidthOffsetStr) - var(--_carousel-item-track-gap) +
486
+ var(--carousel-flip-edge-preview-width, 4rem)
475
487
  )
476
488
  0;
477
489
 
478
490
  &.loaded {
479
- transition: transform v-bind(transitionSpeedStr) ease;
491
+ @media (prefers-reduced-motion: no-preference) {
492
+ transition: transform v-bind(transitionSpeedStr) ease;
493
+ }
480
494
  }
481
495
  }
482
496
  }
@@ -486,14 +500,14 @@ onMounted(() => {
486
500
  display: flex;
487
501
  align-items: center;
488
502
  justify-content: center;
489
- max-inline-size: var(--_carousel-display-max-width);
503
+ max-inline-size: var(--carousel-flip-display-max-width, 80rem);
490
504
  margin-inline: auto;
491
505
 
492
506
  .markers-container {
493
507
  .markers-list {
494
508
  display: flex;
495
509
  flex-direction: row;
496
- gap: 10px;
510
+ gap: var(--carousel-flip-marker-gap, 1rem);
497
511
  list-style-type: none;
498
512
  margin: unset;
499
513
  padding: unset;
@@ -507,7 +521,13 @@ onMounted(() => {
507
521
  transition: background-color v-bind(transitionSpeedStr) linear;
508
522
 
509
523
  &.active {
510
- background-color: light-dark(var(--slate-10), var(--slate-00));
524
+ background-color: var(--carousel-flip-marker-active-colour, var(--slate-10));
525
+ }
526
+
527
+ &:focus-visible {
528
+ outline: var(--carousel-flip-focus-outline-width, 0.2rem) solid
529
+ var(--carousel-flip-focus-outline-colour, var(--theme-ring));
530
+ outline-offset: var(--carousel-flip-focus-outline-offset, 0.2rem);
511
531
  }
512
532
  }
513
533
  }
@@ -538,14 +558,15 @@ onMounted(() => {
538
558
  cursor: pointer;
539
559
  height: fit-content;
540
560
 
541
- background-color: white;
542
- border: 1px solid light-dark(hsl(0, 29%, 3%), hsl(0, 0%, 92%));
543
- border-radius: 100vw;
544
- padding: 8px;
561
+ background-color: var(--carousel-flip-button-background-colour, white);
562
+ border: var(--carousel-flip-button-border-width, 0.1rem) solid
563
+ var(--carousel-flip-button-border-colour, hsl(0, 29%, 3%));
564
+ border-radius: var(--carousel-flip-button-border-radius, 100vw);
565
+ padding: var(--carousel-flip-button-padding, 0.8rem);
545
566
 
546
567
  .arrows-icon {
547
- width: 24px;
548
- height: 24px;
568
+ width: var(--carousel-flip-button-icon-size, 2.4rem);
569
+ height: var(--carousel-flip-button-icon-size, 2.4rem);
549
570
  }
550
571
  }
551
572
 
@@ -574,7 +595,7 @@ onMounted(() => {
574
595
  .buttons-container {
575
596
  display: flex;
576
597
  align-items: center;
577
- gap: 10px;
598
+ gap: var(--carousel-flip-buttons-gap, 1rem);
578
599
  grid-area: controls;
579
600
  justify-self: end;
580
601
  }
@@ -586,7 +607,7 @@ onMounted(() => {
586
607
  grid-area: carousel;
587
608
  align-self: end;
588
609
  z-index: 1;
589
- padding-block-end: 1rem;
610
+ padding-block-end: var(--carousel-flip-overlay-controls-offset, 1rem);
590
611
  }
591
612
  }
592
613
  }
@@ -19,6 +19,13 @@ interface CarouselFlipStoryArgs {
19
19
  buttonLayout: "sides" | "controls-flanking" | "controls-grouped-right" | "overlay";
20
20
  showControls: boolean;
21
21
  styleClassPassthrough: string[];
22
+ ariaLabel: string;
23
+ itemsAriaLabel: string;
24
+ previousLabel: string;
25
+ nextLabel: string;
26
+ jumpToItemLabel: string;
27
+ prevIcon: string;
28
+ nextIcon: string;
22
29
  // Mock data for stories
23
30
  carouselData: CarouselItem[];
24
31
  }
@@ -68,7 +75,7 @@ const sampleCarouselItems: CarouselItem[] = [
68
75
  ];
69
76
 
70
77
  export default {
71
- title: "Components/UI/CarouselFlip",
78
+ title: "Organisms/Image Galleries/Carousel Flip",
72
79
  component: StorybookComponent,
73
80
  argTypes: {
74
81
  // Carousel Configuration
@@ -129,6 +136,56 @@ export default {
129
136
  category: "Layout",
130
137
  },
131
138
  },
139
+ // Accessibility / localisation
140
+ ariaLabel: {
141
+ control: { type: "text" },
142
+ description: "Accessible name for the carousel region",
143
+ table: {
144
+ category: "Accessibility",
145
+ },
146
+ },
147
+ itemsAriaLabel: {
148
+ control: { type: "text" },
149
+ description: "Accessible name for the item group",
150
+ table: {
151
+ category: "Accessibility",
152
+ },
153
+ },
154
+ previousLabel: {
155
+ control: { type: "text" },
156
+ description: "Accessible label for the previous button",
157
+ table: {
158
+ category: "Accessibility",
159
+ },
160
+ },
161
+ nextLabel: {
162
+ control: { type: "text" },
163
+ description: "Accessible label for the next button",
164
+ table: {
165
+ category: "Accessibility",
166
+ },
167
+ },
168
+ jumpToItemLabel: {
169
+ control: { type: "text" },
170
+ description: "Accessible label prefix for marker buttons (index is appended)",
171
+ table: {
172
+ category: "Accessibility",
173
+ },
174
+ },
175
+ prevIcon: {
176
+ control: { type: "text" },
177
+ description: "Iconify name for the previous button icon",
178
+ table: {
179
+ category: "Icons",
180
+ },
181
+ },
182
+ nextIcon: {
183
+ control: { type: "text" },
184
+ description: "Iconify name for the next button icon",
185
+ table: {
186
+ category: "Icons",
187
+ },
188
+ },
132
189
  // Display data for reference
133
190
  carouselData: {
134
191
  control: { type: "object" },
@@ -147,6 +204,13 @@ export default {
147
204
  buttonLayout: "sides",
148
205
  showControls: true,
149
206
  styleClassPassthrough: ["carousel-flip-demo"],
207
+ ariaLabel: "Image carousel",
208
+ itemsAriaLabel: "Carousel items",
209
+ previousLabel: "Go to previous item",
210
+ nextLabel: "Go to next item",
211
+ jumpToItemLabel: "Jump to item",
212
+ prevIcon: "ic:outline-keyboard-arrow-left",
213
+ nextIcon: "ic:outline-keyboard-arrow-right",
150
214
  carouselData: sampleCarouselItems,
151
215
  },
152
216
  } as Meta<CarouselFlipStoryArgs>;
@@ -180,22 +244,22 @@ const Template: StoryFn<CarouselFlipStoryArgs> = (args) => ({
180
244
  }
181
245
 
182
246
  .carousel-flip-demo.carousel-flip {
183
- --_carousel-item-track-gap: 12px;
184
- --_carousel-container-max-inline-size: 800px;
185
- --_carousel-item-edge-preview-width: 12px;
247
+ --carousel-flip-gap: 12px;
248
+ --carousel-flip-item-max-width: 800px;
249
+ --carousel-flip-edge-preview-width: 12px;
186
250
  }
187
251
 
188
252
  @media (width >= 768px) {
189
253
  .carousel-flip-demo.carousel-flip {
190
- --_carousel-item-track-gap: 24px;
191
- --_carousel-item-edge-preview-width: 80px;
254
+ --carousel-flip-gap: 24px;
255
+ --carousel-flip-edge-preview-width: 80px;
192
256
  }
193
257
  }
194
258
 
195
259
  .carousel-flip-demo.carousel-flip .item-container {
196
260
  max-inline-size: 800px;
197
261
  margin-inline: auto;
198
- outline: 1px solid light-dark(var(--slate-08, #666), var(--slate-01, #f5f5f5));
262
+ outline: 1px solid var(--slate-08, #666);
199
263
  padding-block: 6px;
200
264
  padding-inline: 6px;
201
265
  }
@@ -208,12 +272,12 @@ const Template: StoryFn<CarouselFlipStoryArgs> = (args) => ({
208
272
  }
209
273
 
210
274
  .carousel-flip-demo.carousel-flip .item {
211
- background-color: light-dark(var(--slate-05, #e5e5e5), var(--slate-06, #999));
275
+ background-color: var(--slate-05, #e5e5e5);
212
276
  contain: layout style paint;
213
277
  }
214
278
 
215
279
  .carousel-flip-demo.carousel-flip .item:nth-child(odd) {
216
- background-color: light-dark(var(--slate-06, #999), var(--slate-05, #e5e5e5));
280
+ background-color: var(--slate-06, #999);
217
281
  }
218
282
 
219
283
  .custom-carousel-item {
@@ -223,11 +287,11 @@ const Template: StoryFn<CarouselFlipStoryArgs> = (args) => ({
223
287
  justify-content: center;
224
288
  aspect-ratio: 4 / 3;
225
289
  inline-size: 100%;
226
- color: light-dark(#aaa, #333);
290
+ color: #aaa;
227
291
  padding-block: 10px;
228
292
  padding-inline: 10px;
229
293
  border-radius: 4px;
230
- outline: 1px solid light-dark(#00000090, #ffffff90);
294
+ outline: 1px solid #00000090;
231
295
  }
232
296
 
233
297
  .carousel-flip-demo.carousel-flip .controls-container {
@@ -259,7 +323,7 @@ const Template: StoryFn<CarouselFlipStoryArgs> = (args) => ({
259
323
  width: var(--marker-width);
260
324
  height: var(--marker-height);
261
325
  border-radius: var(--marker-border-radius);
262
- background-color: light-dark(var(--slate-05, #e5e5e5), var(--slate-07, #666));
326
+ background-color: var(--slate-05, #e5e5e5);
263
327
  line-height: 3px;
264
328
  transition: all 0.3s linear;
265
329
  border: 1px solid transparent;
@@ -267,13 +331,13 @@ const Template: StoryFn<CarouselFlipStoryArgs> = (args) => ({
267
331
  }
268
332
 
269
333
  .carousel-flip-demo.carousel-flip .btn-marker.active {
270
- background-color: light-dark(var(--slate-012, #000), var(--slate-000, #fff));
334
+ background-color: var(--slate-012, #000);
271
335
  will-change: background-color;
272
336
  }
273
337
 
274
338
  .carousel-flip-demo.carousel-flip .btn-marker:hover,
275
339
  .carousel-flip-demo.carousel-flip .btn-marker:focus-visible {
276
- outline-color: light-dark(#000, #fff);
340
+ outline-color: #000;
277
341
  will-change: background-color, outline-color;
278
342
  }
279
343
  `;
@@ -290,13 +354,13 @@ const Template: StoryFn<CarouselFlipStoryArgs> = (args) => ({
290
354
  Displaying {{ args.carouselDataIds.length }} carousel items with {{ args.transitionSpeed }}ms transition speed
291
355
  </p>
292
356
  <div style="display: flex; gap: 12px; margin-bottom: 20px; flex-wrap: wrap;">
293
- <div style="padding: 4px 8px; border: 1px solid light-dark(#ccc, #666); border-radius: 4px; font-size: 12px; color: light-dark(#333, #ddd);">
357
+ <div style="padding: 4px 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 12px; color: #333;">
294
358
  Overflow: {{ args.allowCarouselOverflow ? 'Enabled' : 'Disabled' }}
295
359
  </div>
296
- <div style="padding: 4px 8px; border: 1px solid light-dark(#ccc, #666); border-radius: 4px; font-size: 12px; color: light-dark(#333, #ddd);">
360
+ <div style="padding: 4px 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 12px; color: #333;">
297
361
  Flip Animation: {{ args.useFlipAnimation ? 'Enabled' : 'Disabled' }}
298
362
  </div>
299
- <div style="padding: 4px 8px; border: 1px solid light-dark(#ccc, #666); border-radius: 4px; font-size: 12px; color: light-dark(#333, #ddd);">
363
+ <div style="padding: 4px 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 12px; color: #333;">
300
364
  Spring Effect: {{ args.useSpringEffect ? 'Enabled' : 'Disabled' }}
301
365
  </div>
302
366
  </div>
@@ -311,6 +375,13 @@ const Template: StoryFn<CarouselFlipStoryArgs> = (args) => ({
311
375
  :button-layout="args.buttonLayout"
312
376
  :show-controls="args.showControls"
313
377
  :style-class-passthrough="args.styleClassPassthrough"
378
+ :aria-label="args.ariaLabel"
379
+ :items-aria-label="args.itemsAriaLabel"
380
+ :previous-label="args.previousLabel"
381
+ :next-label="args.nextLabel"
382
+ :jump-to-item-label="args.jumpToItemLabel"
383
+ :prev-icon="args.prevIcon"
384
+ :next-icon="args.nextIcon"
314
385
  >
315
386
  <template v-for="(item, index) in filteredCarouselData" #[item.id] :key="index">
316
387
  <div class="custom-carousel-item">
@@ -427,22 +498,22 @@ const RichContentTemplate: StoryFn<CarouselFlipStoryArgs> = (args) => ({
427
498
  style.id = styleId;
428
499
  style.textContent = `
429
500
  .carousel-flip-demo.carousel-flip {
430
- --_carousel-item-track-gap: 12px;
431
- --_carousel-container-max-inline-size: 800px;
432
- --_carousel-item-edge-preview-width: 12px;
501
+ --carousel-flip-gap: 12px;
502
+ --carousel-flip-item-max-width: 800px;
503
+ --carousel-flip-edge-preview-width: 12px;
433
504
  }
434
505
 
435
506
  @media (width >= 768px) {
436
507
  .carousel-flip-demo.carousel-flip {
437
- --_carousel-item-track-gap: 24px;
438
- --_carousel-item-edge-preview-width: 80px;
508
+ --carousel-flip-gap: 24px;
509
+ --carousel-flip-edge-preview-width: 80px;
439
510
  }
440
511
  }
441
512
 
442
513
  .carousel-flip-demo.carousel-flip .item-container {
443
514
  max-inline-size: 800px;
444
515
  margin-inline: auto;
445
- outline: 1px solid light-dark(var(--slate-08, #666), var(--slate-01, #f5f5f5));
516
+ outline: 1px solid var(--slate-08, #666);
446
517
  padding-block: 6px;
447
518
  padding-inline: 6px;
448
519
  }
@@ -455,12 +526,12 @@ const RichContentTemplate: StoryFn<CarouselFlipStoryArgs> = (args) => ({
455
526
  }
456
527
 
457
528
  .carousel-flip-demo.carousel-flip .item {
458
- background-color: light-dark(var(--slate-05, #e5e5e5), var(--slate-06, #999));
529
+ background-color: var(--slate-05, #e5e5e5);
459
530
  contain: layout style paint;
460
531
  }
461
532
 
462
533
  .carousel-flip-demo.carousel-flip .item:nth-child(odd) {
463
- background-color: light-dark(var(--slate-06, #999), var(--slate-05, #e5e5e5));
534
+ background-color: var(--slate-06, #999);
464
535
  }
465
536
  `;
466
537
  document.head.appendChild(style);
@@ -486,6 +557,13 @@ const RichContentTemplate: StoryFn<CarouselFlipStoryArgs> = (args) => ({
486
557
  :button-layout="args.buttonLayout"
487
558
  :show-controls="args.showControls"
488
559
  :style-class-passthrough="args.styleClassPassthrough"
560
+ :aria-label="args.ariaLabel"
561
+ :items-aria-label="args.itemsAriaLabel"
562
+ :previous-label="args.previousLabel"
563
+ :next-label="args.nextLabel"
564
+ :jump-to-item-label="args.jumpToItemLabel"
565
+ :prev-icon="args.prevIcon"
566
+ :next-icon="args.nextIcon"
489
567
  >
490
568
  <template v-for="(item, index) in filteredCarouselData" #[item.id] :key="index">
491
569
  <div
@@ -604,16 +682,16 @@ const AnimationShowcaseTemplate: StoryFn<CarouselFlipStoryArgs> = (args) => ({
604
682
  Testing different animation combinations and timing
605
683
  </p>
606
684
  <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; margin-bottom: 20px;">
607
- <div style="padding: 8px; border: 1px solid light-dark(#ccc, #666); border-radius: 6px; text-align: center; font-size: 12px; color: light-dark(#333, #ddd); background: args.useFlipAnimation ? 'light-dark(#e7f5e7, #2d4a2d)' : 'transparent';">
685
+ <div style="padding: 8px; border: 1px solid #ccc; border-radius: 6px; text-align: center; font-size: 12px; color: #333; background: args.useFlipAnimation ? '#e7f5e7' : 'transparent';">
608
686
  Flip: {{ args.useFlipAnimation ? '✓' : '✗' }}
609
687
  </div>
610
- <div style="padding: 8px; border: 1px solid light-dark(#ccc, #666); border-radius: 6px; text-align: center; font-size: 12px; color: light-dark(#333, #ddd); background: args.useSpringEffect ? 'light-dark(#e7f5e7, #2d4a2d)' : 'transparent';">
688
+ <div style="padding: 8px; border: 1px solid #ccc; border-radius: 6px; text-align: center; font-size: 12px; color: #333; background: args.useSpringEffect ? '#e7f5e7' : 'transparent';">
611
689
  Spring: {{ args.useSpringEffect ? '✓' : '✗' }}
612
690
  </div>
613
- <div style="padding: 8px; border: 1px solid light-dark(#ccc, #666); border-radius: 6px; text-align: center; font-size: 12px; color: light-dark(#333, #ddd); background: args.allowCarouselOverflow ? 'light-dark(#e7f5e7, #2d4a2d)' : 'transparent';">
691
+ <div style="padding: 8px; border: 1px solid #ccc; border-radius: 6px; text-align: center; font-size: 12px; color: #333; background: args.allowCarouselOverflow ? '#e7f5e7' : 'transparent';">
614
692
  Overflow: {{ args.allowCarouselOverflow ? '✓' : '✗' }}
615
693
  </div>
616
- <div style="padding: 8px; border: 1px solid light-dark(#ccc, #666); border-radius: 6px; text-align: center; font-size: 12px; color: light-dark(#333, #ddd);">
694
+ <div style="padding: 8px; border: 1px solid #ccc; border-radius: 6px; text-align: center; font-size: 12px; color: #333;">
617
695
  Speed: {{ args.transitionSpeed }}ms
618
696
  </div>
619
697
  </div>
@@ -628,6 +706,13 @@ const AnimationShowcaseTemplate: StoryFn<CarouselFlipStoryArgs> = (args) => ({
628
706
  :button-layout="args.buttonLayout"
629
707
  :show-controls="args.showControls"
630
708
  :style-class-passthrough="args.styleClassPassthrough"
709
+ :aria-label="args.ariaLabel"
710
+ :items-aria-label="args.itemsAriaLabel"
711
+ :previous-label="args.previousLabel"
712
+ :next-label="args.nextLabel"
713
+ :jump-to-item-label="args.jumpToItemLabel"
714
+ :prev-icon="args.prevIcon"
715
+ :next-icon="args.nextIcon"
631
716
  >
632
717
  <template v-for="(item, index) in filteredCarouselData" #[item.id] :key="index">
633
718
  <div
@@ -3,6 +3,16 @@ import { mountSuspended } from "@nuxt/test-utils/runtime";
3
3
  import { nextTick } from "vue";
4
4
  import CarouselFlip from "../CarouselFlip.vue";
5
5
 
6
+ const stubMatchMedia = (prefersReducedMotion: boolean) => {
7
+ vi.stubGlobal(
8
+ "matchMedia",
9
+ vi.fn().mockReturnValue({
10
+ matches: prefersReducedMotion,
11
+ addEventListener: vi.fn(),
12
+ })
13
+ );
14
+ };
15
+
6
16
  interface CarouselFlipInstance {
7
17
  carouselWrapperRef: HTMLDivElement | null;
8
18
  carouselContainerRef: HTMLDivElement | null;
@@ -115,6 +125,7 @@ describe("CarouselFlip", () => {
115
125
  beforeEach(() => {
116
126
  vi.clearAllMocks();
117
127
  mockDirection.value = "";
128
+ stubMatchMedia(false);
118
129
  });
119
130
 
120
131
  afterEach(() => {
@@ -271,6 +282,75 @@ describe("CarouselFlip", () => {
271
282
 
272
283
  expect(wrapper.attributes("data-button-layout")).toBe("overlay");
273
284
  });
285
+
286
+ it("overrides accessible labels via props", async () => {
287
+ await createWrapper({
288
+ ariaLabel: "Featured looks",
289
+ itemsAriaLabel: "Look slides",
290
+ previousLabel: "Previous look",
291
+ nextLabel: "Next look",
292
+ jumpToItemLabel: "View look",
293
+ });
294
+
295
+ expect(wrapper.find('[role="region"]').attributes("aria-label")).toBe("Featured looks");
296
+ expect(wrapper.find('[role="group"]').attributes("aria-label")).toBe("Look slides");
297
+ expect(wrapper.find('[aria-label="Previous look"]').exists()).toBe(true);
298
+ expect(wrapper.find('[aria-label="Next look"]').exists()).toBe(true);
299
+ expect(wrapper.find('[aria-label="View look 1"]').exists()).toBe(true);
300
+ });
301
+
302
+ it("overrides the screen-reader position announcement via prop", async () => {
303
+ await createWrapper({
304
+ itemPositionAnnouncement: (current: number, total: number) => `Slide ${current} / ${total}`,
305
+ });
306
+
307
+ expect(wrapper.find('[aria-live="polite"]').text()).toBe("Slide 1 / 6");
308
+ });
309
+
310
+ it("overrides prev/next icons via props", async () => {
311
+ await createWrapper({ prevIcon: "mdi:chevron-left", nextIcon: "mdi:chevron-right" });
312
+
313
+ const prevButton = wrapper.find('[aria-label="Go to previous item"]');
314
+ const nextButton = wrapper.find('[aria-label="Go to next item"]');
315
+
316
+ expect(prevButton.html()).toContain("i-mdi:chevron-left");
317
+ expect(nextButton.html()).toContain("i-mdi:chevron-right");
318
+ });
319
+
320
+ it("allows overriding the prev/next icon via slots", async () => {
321
+ wrapper = await mountSuspended(CarouselFlip, {
322
+ props: { carouselDataIds: mockCarouselData },
323
+ slots: {
324
+ "prev-icon": '<span data-testid="custom-prev">Prev</span>',
325
+ "next-icon": '<span data-testid="custom-next">Next</span>',
326
+ },
327
+ global: {
328
+ components: {
329
+ Icon: {
330
+ template: '<svg data-testid="icon" :class="$attrs.class"><title>{{ name }}</title></svg>',
331
+ props: ["name"],
332
+ },
333
+ },
334
+ },
335
+ });
336
+
337
+ expect(wrapper.find('[data-testid="custom-prev"]').exists()).toBe(true);
338
+ expect(wrapper.find('[data-testid="custom-next"]').exists()).toBe(true);
339
+ });
340
+ });
341
+
342
+ describe("Reduced Motion", () => {
343
+ it("skips the JS-driven transition when prefers-reduced-motion is set", async () => {
344
+ stubMatchMedia(true);
345
+ await createWrapper();
346
+ await nextTick();
347
+
348
+ const nextButton = wrapper.find('[aria-label="Go to next item"]');
349
+ await nextButton.trigger("click");
350
+ await nextTick();
351
+
352
+ expect(component.currentActiveIndex).toBe(1);
353
+ });
274
354
  });
275
355
 
276
356
  describe("Initial Setup", () => {
@@ -18,7 +18,7 @@ interface Props {
18
18
  styleClassPassthrough?: string;
19
19
  }
20
20
 
21
- const props = withDefaults(defineProps<Props>(), {
21
+ withDefaults(defineProps<Props>(), {
22
22
  width: "narrow",
23
23
  fieldHasError: false,
24
24
  hasGutter: true,
@@ -9,7 +9,7 @@ interface Props {
9
9
  width?: "narrow" | "medium" | "wide";
10
10
  }
11
11
 
12
- const props = withDefaults(defineProps<Props>(), {
12
+ withDefaults(defineProps<Props>(), {
13
13
  width: "narrow",
14
14
  });
15
15
  </script>