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
@@ -48,7 +48,7 @@ interface Props {
48
48
  ariaDescribedby?: string;
49
49
  }
50
50
 
51
- const props = withDefaults(defineProps<Props>(), {
51
+ withDefaults(defineProps<Props>(), {
52
52
  step: 1,
53
53
  placeholder: "",
54
54
  required: false,
@@ -61,173 +61,109 @@ const props = withDefaults(defineProps<Props>(), {
61
61
 
62
62
  const slots = useSlots();
63
63
 
64
- const FormUiTheme = computed(() => {
65
- return props.fieldHasError ? "error" : props.theme;
64
+ const modelValue = defineModel<number>({
65
+ required: true,
66
66
  });
67
-
68
- const modelValue = defineModel<number | readonly number[]>();
69
67
  </script>
70
68
 
71
69
  <style lang="css">
72
70
  @layer components {
73
- .input-range-wrapper {
74
- display: flex;
75
- align-items: center;
76
- justify-content: space-between;
77
- gap: 1rem;
78
-
79
- .slot {
80
- align-self: flex-start;
81
-
82
- .input-button-core {
83
- border-radius: var(--form-input-border-radius);
84
- width: var(--input-range-button-size);
85
- height: var(--input-range-button-size);
86
-
87
- .btn-icon {
88
- margin: initial;
89
- }
90
- }
91
- }
92
-
93
- .input-range-container {
94
- flex-grow: 1;
95
-
96
- display: grid;
97
- grid-template-areas: "element-stack";
98
- /* margin-top: 2px; */
99
-
100
- .input-range-markers {
101
- grid-area: element-stack;
102
- display: flex;
103
- align-items: center;
104
- justify-content: space-between;
105
- width: 100%;
106
- z-index: 2;
107
-
108
- .marker {
109
- background-color: black;
110
- padding: 0.5rem;
111
- border-radius: 50%;
112
- overflow: hidden;
113
- outline: 1px solid gray;
114
-
115
- &:hover {
116
- cursor: pointer;
117
- }
71
+ .input-range-wrapper {
72
+ display: flex;
73
+ align-items: center;
74
+ justify-content: space-between;
75
+ gap: 1rem;
76
+
77
+ .slot {
78
+ align-self: flex-start;
79
+
80
+ .input-button-core {
81
+ border-radius: var(--form-input-border-radius);
82
+ width: var(--input-range-button-size);
83
+ height: var(--input-range-button-size);
118
84
 
119
- .marker-icon {
120
- font-size: 2rem;
121
- display: block;
122
- color: var(--theme-form-range-accent-color);
85
+ .btn-icon {
86
+ margin: initial;
123
87
  }
124
88
  }
125
89
  }
126
90
 
127
- .input-range-core {
128
- grid-area: element-stack;
91
+ .input-range-container {
92
+ flex-grow: 1;
129
93
 
130
- accent-color: var(--theme-form-range-accent-color);
131
- height: var(--input-range-button-size);
132
- margin: 0;
133
- width: 100%;
94
+ display: grid;
95
+ grid-template-areas: "element-stack";
134
96
 
135
- /*
136
- &:hover {
137
- cursor: -webkit-grab;
138
- outline-color: red;
139
- }
140
- &:active {
141
- cursor: -webkit-grabbing;
142
- outline-color: blue;
143
- }
144
- &:focus-visible {
145
- outline-offset: 0.25rem;
146
- outline-color: transparent;
147
- }
148
- */
149
-
150
- &::-webkit-slider-thumb {
151
- /* appearance: none; */
152
- /* -webkit-appearance: none; */
153
- accent-color: blue;
154
- color: blue;
155
- background-color: 0.1rem solid green;
156
- outline: 0.1rem solid blue;
157
- border-radius: 50%;
158
- }
97
+ .input-range-markers {
98
+ grid-area: element-stack;
99
+ display: flex;
100
+ align-items: center;
101
+ justify-content: space-between;
102
+ width: 100%;
103
+ z-index: 2;
104
+
105
+ .marker {
106
+ background-color: var(--input-range-marker-background-colour, hsl(0, 29%, 3%));
107
+ padding: var(--input-range-marker-padding, 0.5rem);
108
+ border-radius: var(--input-range-marker-border-radius, 50%);
109
+ overflow: hidden;
110
+ outline: var(--input-range-marker-outline-width, 0.1rem) solid
111
+ var(--input-range-marker-outline-colour, var(--slate-04));
112
+
113
+ &:hover {
114
+ cursor: pointer;
115
+ }
159
116
 
160
- &::-webkit-slider-runnable-track {
161
- appearance: none;
162
- -webkit-appearance: none;
163
- /* background: hsl(10 80% 50% / 0.5); */
164
- /* box-shadow: 0.1rem 0.1rem 0.1rem #fff, 0rem 0rem 0.1rem #fff; */
117
+ .marker-icon {
118
+ font-size: var(--input-range-marker-icon-size, 2rem);
119
+ display: block;
120
+ color: var(--input-range-marker-icon-colour, var(--input-range-accent-colour, var(--theme-accent)));
121
+ }
122
+ }
165
123
  }
166
124
 
167
- /* For Chrome, Safari, Opera, and Edge */
168
- /* &::-webkit-slider-runnable-track {
169
- background: var(--theme-form-range-accent-color);
170
- height: var(--input-range-button-size);
171
- } */
125
+ .input-range-core {
126
+ grid-area: element-stack;
172
127
 
173
- /* For Firefox */
174
- /* &::-moz-range-track {
175
- background: var(--theme-form-range-accent-color);
128
+ accent-color: var(--input-range-accent-colour, var(--theme-accent));
176
129
  height: var(--input-range-button-size);
177
- } */
178
-
179
- /* Stling the thumb */
180
- /* &::-webkit-slider-thumb {
181
- -webkit-appearance: none;
182
- background: #5cd5eb;
183
- height: 2rem;
184
- width: 1rem;
185
- border-radius: 1rem;
186
- } */
187
-
188
- /* For Firefox */
189
- /* &::-moz-range-thumb {
190
- background: #5cd5eb;
191
- height: 2rem;
192
- width: 1rem;
193
- border-radius: 1rem;
194
- border: none;
195
- } */
196
-
197
- &:focus-visible {
198
- box-shadow: var(--form-focus-box-shadow);
199
- }
130
+ margin: 0;
131
+ width: 100%;
200
132
 
201
- &.has-markers {
202
- accent-color: var(--theme-form-range-accent-color);
203
- height: 2px;
204
- z-index: 2;
205
- translate: 0 13px;
133
+ &:focus-visible {
134
+ box-shadow: var(--form-focus-box-shadow);
135
+ }
206
136
 
207
- &::-webkit-slider-thumb {
208
- opacity: 0;
209
- &:hover {
210
- cursor: pointer;
137
+ &.has-markers {
138
+ accent-color: var(--input-range-accent-colour, var(--theme-accent));
139
+ height: 0.2rem;
140
+ z-index: 2;
141
+ translate: 0 1.3rem;
142
+
143
+ &::-webkit-slider-thumb {
144
+ opacity: 0;
145
+ &:hover {
146
+ cursor: pointer;
147
+ }
211
148
  }
212
149
  }
213
150
  }
214
- }
215
151
 
216
- .input-range-datalist {
217
- display: flex;
218
- flex-direction: column;
219
- font-family: var(--font-family);
220
- font-size: 1.4rem;
221
- font-weight: 500;
222
- justify-content: space-between;
223
- writing-mode: vertical-lr;
224
- width: 100%;
225
-
226
- option {
227
- padding: 0;
152
+ .input-range-datalist {
153
+ display: flex;
154
+ flex-direction: column;
155
+ font-family: var(--font-family);
156
+ font-size: var(--input-range-datalist-font-size, 1.4rem);
157
+ font-weight: var(--input-range-datalist-font-weight, 500);
158
+ justify-content: space-between;
159
+ writing-mode: vertical-lr;
160
+ width: 100%;
161
+
162
+ option {
163
+ padding: 0;
164
+ }
228
165
  }
229
166
  }
230
167
  }
231
168
  }
232
- }
233
169
  </style>
@@ -0,0 +1,126 @@
1
+ import type { Meta, StoryFn } from "@nuxtjs/storybook";
2
+ import { ref } from "vue";
3
+ import StorybookComponent from "../InputRangeCore.vue";
4
+
5
+ interface InputRangeCoreStoryArgs {
6
+ id: string;
7
+ name: string;
8
+ min: number;
9
+ max: number;
10
+ step: number;
11
+ required: boolean;
12
+ fieldHasError: boolean;
13
+ weight: string;
14
+ }
15
+
16
+ export default {
17
+ title: "Components/Forms/Input Range/InputRangeCore",
18
+ component: StorybookComponent,
19
+ argTypes: {
20
+ id: { control: "text", table: { category: "Basic" } },
21
+ name: { control: "text", table: { category: "Basic" } },
22
+ min: { control: "number", table: { category: "Basic" } },
23
+ max: { control: "number", table: { category: "Basic" } },
24
+ step: { control: "number", table: { category: "Basic" } },
25
+ required: { control: "boolean", table: { category: "Basic" } },
26
+ fieldHasError: { control: "boolean", table: { category: "States" } },
27
+ weight: { control: "text", table: { category: "Styling" } },
28
+ },
29
+ args: {
30
+ id: "range-default",
31
+ name: "rangeDefault",
32
+ min: 0,
33
+ max: 100,
34
+ step: 1,
35
+ required: false,
36
+ fieldHasError: false,
37
+ weight: "normal",
38
+ },
39
+ } as Meta<InputRangeCoreStoryArgs>;
40
+
41
+ const Template: StoryFn<InputRangeCoreStoryArgs> = (args) => ({
42
+ components: { StorybookComponent },
43
+ setup() {
44
+ const modelValue = ref(50);
45
+ return { args, modelValue };
46
+ },
47
+ template: `
48
+ <div style="margin: 36px; max-width: 320px;">
49
+ <StorybookComponent v-bind="args" v-model="modelValue" />
50
+ </div>
51
+ `,
52
+ });
53
+
54
+ export const Default = Template.bind({});
55
+
56
+ export const ErrorState = Template.bind({});
57
+ ErrorState.args = { fieldHasError: true };
58
+
59
+ const WithStepButtonsTemplate: StoryFn<InputRangeCoreStoryArgs> = (args) => ({
60
+ components: { StorybookComponent },
61
+ setup() {
62
+ const modelValue = ref(50);
63
+ return { args, modelValue };
64
+ },
65
+ template: `
66
+ <div style="margin: 36px; max-width: 320px;">
67
+ <StorybookComponent v-bind="args" v-model="modelValue">
68
+ <template #left>
69
+ <button type="button" @click="modelValue = Math.max(args.min, modelValue - args.step)">−</button>
70
+ </template>
71
+ <template #right>
72
+ <button type="button" @click="modelValue = Math.min(args.max, modelValue + args.step)">+</button>
73
+ </template>
74
+ </StorybookComponent>
75
+ </div>
76
+ `,
77
+ });
78
+
79
+ export const WithStepButtons = WithStepButtonsTemplate.bind({});
80
+
81
+ const WithMarkersTemplate: StoryFn<InputRangeCoreStoryArgs> = (args) => ({
82
+ components: { StorybookComponent },
83
+ setup() {
84
+ const modelValue = ref(50);
85
+ return { args, modelValue };
86
+ },
87
+ template: `
88
+ <div style="margin: 36px; max-width: 320px;">
89
+ <StorybookComponent v-bind="args" v-model="modelValue">
90
+ <template #markers>
91
+ <div class="input-range-markers">
92
+ <div class="marker"><span class="marker-icon">😀</span></div>
93
+ <div class="marker"><span class="marker-icon">🙂</span></div>
94
+ <div class="marker"><span class="marker-icon">😐</span></div>
95
+ </div>
96
+ </template>
97
+ </StorybookComponent>
98
+ </div>
99
+ `,
100
+ });
101
+
102
+ export const WithMarkers = WithMarkersTemplate.bind({});
103
+ WithMarkers.args = { min: 0, max: 2, step: 1 };
104
+
105
+ const WithDatalistTemplate: StoryFn<InputRangeCoreStoryArgs> = (args) => ({
106
+ components: { StorybookComponent },
107
+ setup() {
108
+ const modelValue = ref(50);
109
+ return { args, modelValue };
110
+ },
111
+ template: `
112
+ <div style="margin: 36px; max-width: 320px;">
113
+ <StorybookComponent v-bind="args" v-model="modelValue">
114
+ <template #datalist>
115
+ <datalist :id="\`\${args.name}-datalist\`" class="input-range-datalist">
116
+ <option value="0" label="Low"></option>
117
+ <option value="50" label="Medium"></option>
118
+ <option value="100" label="High"></option>
119
+ </datalist>
120
+ </template>
121
+ </StorybookComponent>
122
+ </div>
123
+ `,
124
+ });
125
+
126
+ export const WithDatalist = WithDatalistTemplate.bind({});
@@ -0,0 +1,124 @@
1
+ // https://nuxt.com/docs/getting-started/testing#unit-testing
2
+ import type { VueWrapper } from "@vue/test-utils";
3
+ import { mountSuspended } from "@nuxt/test-utils/runtime";
4
+ import ComponentUnderTest from "../InputRangeCore.vue";
5
+
6
+ const initialPropsData = {
7
+ id: "test-range",
8
+ name: "testName",
9
+ min: 0,
10
+ max: 100,
11
+ modelValue: 50,
12
+ };
13
+
14
+ let wrapper: VueWrapper<InstanceType<typeof ComponentUnderTest>>;
15
+ const wrapperFactory = (propsData = {}, slots = {}) => {
16
+ const mockPropsData = { ...initialPropsData, ...propsData };
17
+
18
+ return mountSuspended(ComponentUnderTest, {
19
+ attachTo: document.body,
20
+ props: mockPropsData,
21
+ slots,
22
+ });
23
+ };
24
+
25
+ describe("InputRangeCore", () => {
26
+ it("mounts without error", async () => {
27
+ wrapper = await wrapperFactory();
28
+ expect(wrapper.vm).toBeTruthy();
29
+ });
30
+
31
+ it("renders a native range input with the given min/max/step", async () => {
32
+ wrapper = await wrapperFactory({ min: 10, max: 90, step: 5 });
33
+ const input = wrapper.find("input");
34
+
35
+ expect(input.attributes("type")).toBe("range");
36
+ expect(input.attributes("min")).toBe("10");
37
+ expect(input.attributes("max")).toBe("90");
38
+ expect(input.attributes("step")).toBe("5");
39
+ });
40
+
41
+ it("defaults step to 1", async () => {
42
+ wrapper = await wrapperFactory();
43
+ expect(wrapper.find("input").attributes("step")).toBe("1");
44
+ });
45
+
46
+ it("binds v-model to the input value", async () => {
47
+ wrapper = await wrapperFactory({ modelValue: 42 });
48
+ expect(wrapper.find("input").element.value).toBe("42");
49
+ });
50
+
51
+ it("forwards aria-describedby", async () => {
52
+ wrapper = await wrapperFactory({ ariaDescribedby: "some-description-id" });
53
+ expect(wrapper.find("input").attributes("aria-describedby")).toBe("some-description-id");
54
+ });
55
+
56
+ it("sets data-theme from the theme prop", async () => {
57
+ wrapper = await wrapperFactory({ theme: "error" });
58
+ expect(wrapper.find(".input-range-wrapper").attributes("data-theme")).toBe("error");
59
+ });
60
+
61
+ it("does not render the left/right slot wrappers when the slots are not used", async () => {
62
+ wrapper = await wrapperFactory();
63
+ expect(wrapper.find(".slot.left").exists()).toBe(false);
64
+ expect(wrapper.find(".slot.right").exists()).toBe(false);
65
+ });
66
+
67
+ it("renders the left/right slots when provided", async () => {
68
+ wrapper = await wrapperFactory(
69
+ {},
70
+ {
71
+ left: '<button data-testid="left-slot">-</button>',
72
+ right: '<button data-testid="right-slot">+</button>',
73
+ }
74
+ );
75
+
76
+ expect(wrapper.find(".slot.left [data-testid='left-slot']").exists()).toBe(true);
77
+ expect(wrapper.find(".slot.right [data-testid='right-slot']").exists()).toBe(true);
78
+ });
79
+
80
+ it("renders the markers slot and adds has-markers class to the input", async () => {
81
+ wrapper = await wrapperFactory(
82
+ {},
83
+ {
84
+ markers: '<div class="input-range-markers" data-testid="markers"></div>',
85
+ }
86
+ );
87
+
88
+ expect(wrapper.find('[data-testid="markers"]').exists()).toBe(true);
89
+ expect(wrapper.find("input").classes()).toContain("has-markers");
90
+ });
91
+
92
+ it("does not add has-markers class when the markers slot is not used", async () => {
93
+ wrapper = await wrapperFactory();
94
+ expect(wrapper.find("input").classes()).not.toContain("has-markers");
95
+ });
96
+
97
+ it("sets the list attribute to `{name}-datalist` only when the datalist slot is used", async () => {
98
+ wrapper = await wrapperFactory(
99
+ { name: "price" },
100
+ { datalist: '<datalist id="price-datalist"></datalist>' }
101
+ );
102
+ expect(wrapper.find("input").attributes("list")).toBe("price-datalist");
103
+ });
104
+
105
+ it("leaves the list attribute empty when the datalist slot is not used", async () => {
106
+ wrapper = await wrapperFactory();
107
+ expect(wrapper.find("input").attributes("list")).toBe("");
108
+ });
109
+
110
+ it("applies the weight class from the weight prop", async () => {
111
+ wrapper = await wrapperFactory({ weight: "wght-700" });
112
+ expect(wrapper.find("input").classes()).toContain("input-range--wght-700");
113
+ });
114
+
115
+ it("applies styleClassPassthrough to the input", async () => {
116
+ wrapper = await wrapperFactory({ styleClassPassthrough: ["custom-range-class"] });
117
+ expect(wrapper.find("input").classes()).toContain("custom-range-class");
118
+ });
119
+
120
+ it("sets required attribute when required is true", async () => {
121
+ wrapper = await wrapperFactory({ required: true });
122
+ expect(wrapper.find("input").attributes("required")).toBeDefined();
123
+ });
124
+ });
@@ -54,7 +54,7 @@
54
54
  type="button"
55
55
  :readonly="Number(modelValue) === min"
56
56
  :is-pending="false"
57
- button-text="Step down"
57
+ :button-text="stepDownLabel"
58
58
  :theme
59
59
  variant="secondary"
60
60
  @click.stop.prevent="updateRange(-step, Number(modelValue) > min)"
@@ -69,7 +69,7 @@
69
69
  type="button"
70
70
  :readonly="Number(modelValue) === max"
71
71
  :is-pending="false"
72
- button-text="Step up"
72
+ :button-text="stepUpLabel"
73
73
  :theme
74
74
  variant="secondary"
75
75
  @click.stop.prevent="updateRange(step, Number(modelValue) < max)"
@@ -106,7 +106,8 @@ interface Props {
106
106
  theme?: FormUiTheme;
107
107
  weight?: FormWeight;
108
108
  styleClassPassthrough?: string | string[];
109
- deepCssClassPassthrough?: string;
109
+ stepDownLabel?: string;
110
+ stepUpLabel?: string;
110
111
  }
111
112
 
112
113
  const props = withDefaults(defineProps<Props>(), {
@@ -117,25 +118,22 @@ const props = withDefaults(defineProps<Props>(), {
117
118
  theme: "default",
118
119
  weight: "wght-400",
119
120
  styleClassPassthrough: () => [],
120
- deepCssClassPassthrough: "",
121
+ stepDownLabel: "Step down",
122
+ stepUpLabel: "Step up",
121
123
  });
122
124
 
123
125
  const slots = useSlots();
124
126
  const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
125
127
 
126
- // const id = useId();
127
-
128
128
  const { id, errorId, descriptionId, ariaDescribedby } = useAriaDescribedById(
129
129
  props.name,
130
130
  toRef(props, "fieldHasError"),
131
131
  slots
132
132
  );
133
133
 
134
- // const FormUiTheme = computed(() => {
135
- // return props.fieldHasError ? "error" : props.theme;
136
- // });
137
-
138
- const modelValue = defineModel<number | readonly number[]>();
134
+ const modelValue = defineModel<number>({
135
+ required: true,
136
+ });
139
137
 
140
138
  const updateRange = (step: number, withinRangeLimit: boolean) => {
141
139
  if (withinRangeLimit) {
@@ -143,24 +141,3 @@ const updateRange = (step: number, withinRangeLimit: boolean) => {
143
141
  }
144
142
  };
145
143
  </script>
146
-
147
- <style lang="css">
148
- @layer components {
149
- .input-range-with-label {
150
- .input-range-label {
151
- display: block;
152
- margin-block: 0.8rem;
153
-
154
- &:hover {
155
- cursor: pointer;
156
- }
157
- }
158
-
159
- .label-description {
160
- font-family: var(--font-family);
161
- font-size: 1.6rem;
162
- margin-top: 1.2rem;
163
- }
164
- }
165
- }
166
- </style>