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
@@ -3,14 +3,14 @@
3
3
  class="input-number-with-label"
4
4
  :data-theme="theme"
5
5
  :data-invalid="fieldHasError ? '' : null"
6
- :class="[elementClasses, `theme-${theme}`, { error: fieldHasError }]"
6
+ :class="[elementClasses, inputVariant, `theme-${theme}`, { error: fieldHasError }]"
7
7
  >
8
8
  <InputLabel
9
9
  :id
10
10
  :for="id"
11
11
  :theme
12
12
  :name
13
- input-variant="normal"
13
+ :input-variant
14
14
  :field-has-error
15
15
  :style-class-passthrough="['input-number-label', 'body-normal-bold']"
16
16
  >
@@ -39,9 +39,11 @@
39
39
  :min
40
40
  :max
41
41
  :step
42
+ :placeholder
42
43
  :theme
43
44
  :required
44
45
  :weight
46
+ :input-variant
45
47
  :field-has-error
46
48
  :style-class-passthrough
47
49
  :aria-describedby
@@ -51,7 +53,7 @@
51
53
  type="button"
52
54
  :readonly="Number(modelValue) <= min"
53
55
  :is-pending="false"
54
- button-text="Step down"
56
+ :button-text="stepDownLabel"
55
57
  :theme="theme"
56
58
  variant="inline"
57
59
  @click.stop.prevent="updateValue(-step, Number(modelValue) > min)"
@@ -66,7 +68,7 @@
66
68
  type="button"
67
69
  :readonly="Number(modelValue) >= max"
68
70
  :is-pending="false"
69
- button-text="Step up"
71
+ :button-text="stepUpLabel"
70
72
  :theme="theme"
71
73
  variant="inline"
72
74
  @click.stop.prevent="updateValue(step, Number(modelValue) < max)"
@@ -82,7 +84,7 @@
82
84
  </template>
83
85
 
84
86
  <script setup lang="ts">
85
- import type { FormWeight } from "~/types/forms/types.forms";
87
+ import type { FormUiTheme, FormWeight, InputUiVariant } from "~/types/forms/types.forms";
86
88
 
87
89
  interface Props {
88
90
  name: string;
@@ -94,9 +96,12 @@ interface Props {
94
96
  errorMessage: object | string;
95
97
  fieldHasError?: boolean;
96
98
  required?: boolean;
97
- theme?: "default" | "success" | "error" | "warning";
99
+ theme?: FormUiTheme;
98
100
  weight?: FormWeight;
101
+ inputVariant?: InputUiVariant;
99
102
  styleClassPassthrough?: string | string[];
103
+ stepDownLabel?: string;
104
+ stepUpLabel?: string;
100
105
  }
101
106
 
102
107
  const props = withDefaults(defineProps<Props>(), {
@@ -106,45 +111,48 @@ const props = withDefaults(defineProps<Props>(), {
106
111
  required: false,
107
112
  theme: "default",
108
113
  weight: "normal",
114
+ inputVariant: "normal",
109
115
  styleClassPassthrough: () => [],
116
+ stepDownLabel: "Step down",
117
+ stepUpLabel: "Step up",
110
118
  });
111
119
 
112
120
  const slots = useSlots();
113
- const { elementClasses, updateElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
121
+ const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
114
122
 
115
123
  const { id, errorId, descriptionId, ariaDescribedby } = useAriaDescribedById(
116
124
  props.name,
117
125
  toRef(props, "fieldHasError"),
118
126
  slots
119
127
  );
120
- const modelValue = defineModel<number | readonly number[]>();
128
+ const modelValue = defineModel<number | readonly number[]>({
129
+ required: true,
130
+ });
121
131
 
122
132
  const updateValue = (step: number, withinRangeLimit: boolean) => {
123
133
  if (withinRangeLimit) {
124
134
  modelValue.value = (Number(modelValue.value) + step) as number;
125
135
  }
126
136
  };
127
-
128
- updateElementClasses(["has-left-button", "has-right-button"]);
129
137
  </script>
130
138
 
131
139
  <style lang="css">
132
140
  @layer components {
133
- .input-number-with-label {
134
- .input-number-label {
135
- display: block;
136
- margin-block: 0.8rem;
141
+ .input-number-with-label {
142
+ .input-number-label {
143
+ display: block;
144
+ margin-block: 0.8rem;
137
145
 
138
- &:hover {
139
- cursor: pointer;
146
+ &:hover {
147
+ cursor: pointer;
148
+ }
140
149
  }
141
- }
142
150
 
143
- .label-description {
144
- font-family: var(--font-family);
145
- font-size: 1.6rem;
146
- margin-top: 1.2rem;
151
+ .label-description {
152
+ font-family: var(--font-family);
153
+ font-size: 1.6rem;
154
+ margin-top: 1.2rem;
155
+ }
147
156
  }
148
157
  }
149
- }
150
158
  </style>
@@ -0,0 +1,123 @@
1
+ import type { Meta, StoryFn } from "@nuxtjs/storybook";
2
+ import StorybookComponent from "../InputNumberDefault.vue";
3
+ import type { FormUiTheme } from "~/types/forms/types.forms.d";
4
+
5
+ interface InputNumberDefaultStoryArgs {
6
+ modelValue: number;
7
+ name: string;
8
+ label: string;
9
+ min: number;
10
+ max: number;
11
+ step: number;
12
+ placeholder: string;
13
+ errorMessage: string;
14
+ fieldHasError: boolean;
15
+ required: boolean;
16
+ theme: FormUiTheme;
17
+ inputVariant: string;
18
+ stepDownLabel: string;
19
+ stepUpLabel: string;
20
+ useStepButtons: boolean;
21
+ }
22
+
23
+ export default {
24
+ title: "Components/Forms/Input Number/InputNumberDefault",
25
+ component: StorybookComponent,
26
+ argTypes: {
27
+ modelValue: { control: "number", table: { category: "Model" } },
28
+ name: { control: "text", table: { category: "Basic" } },
29
+ label: { control: "text", table: { category: "Basic" } },
30
+ min: { control: "number", table: { category: "Basic" } },
31
+ max: { control: "number", table: { category: "Basic" } },
32
+ step: { control: "number", table: { category: "Basic" } },
33
+ placeholder: { control: "text", table: { category: "Basic" } },
34
+ errorMessage: { control: "text", table: { category: "Validation" } },
35
+ fieldHasError: { control: "boolean", table: { category: "Validation" } },
36
+ required: { control: "boolean", table: { category: "Validation" } },
37
+ theme: {
38
+ control: { type: "select" },
39
+ options: ["default", "success", "error", "warning"],
40
+ table: { category: "Styling" },
41
+ },
42
+ inputVariant: {
43
+ control: { type: "select" },
44
+ options: ["normal", "outlined", "underlined"],
45
+ table: { category: "Styling" },
46
+ },
47
+ stepDownLabel: { control: "text", description: "Localisation override for the step-down button", table: { category: "Accessibility" } },
48
+ stepUpLabel: { control: "text", description: "Localisation override for the step-up button", table: { category: "Accessibility" } },
49
+ useStepButtons: { control: "boolean", description: "Render +/- step buttons via left/right slots", table: { category: "Slots" } },
50
+ },
51
+ args: {
52
+ modelValue: 5,
53
+ name: "quantity",
54
+ label: "Quantity",
55
+ min: 0,
56
+ max: 10,
57
+ step: 1,
58
+ placeholder: "",
59
+ errorMessage: "",
60
+ fieldHasError: false,
61
+ required: false,
62
+ theme: "default",
63
+ inputVariant: "normal",
64
+ stepDownLabel: "Step down",
65
+ stepUpLabel: "Step up",
66
+ useStepButtons: false,
67
+ },
68
+ } as Meta<InputNumberDefaultStoryArgs>;
69
+
70
+ const Template: StoryFn<InputNumberDefaultStoryArgs> = (args) => ({
71
+ components: { StorybookComponent },
72
+ setup() {
73
+ return { args };
74
+ },
75
+ template: `
76
+ <div style="margin: 36px; max-width: 320px;">
77
+ <StorybookComponent
78
+ v-model="args.modelValue"
79
+ :name="args.name"
80
+ :label="args.label"
81
+ :min="args.min"
82
+ :max="args.max"
83
+ :step="args.step"
84
+ :placeholder="args.placeholder"
85
+ :error-message="args.errorMessage"
86
+ :field-has-error="args.fieldHasError"
87
+ :required="args.required"
88
+ :theme="args.theme"
89
+ :input-variant="args.inputVariant"
90
+ :step-down-label="args.stepDownLabel"
91
+ :step-up-label="args.stepUpLabel"
92
+ >
93
+ <template v-if="args.useStepButtons" #left><span>−</span></template>
94
+ <template v-if="args.useStepButtons" #right><span>+</span></template>
95
+ </StorybookComponent>
96
+ </div>
97
+ `,
98
+ });
99
+
100
+ export const Default = Template.bind({});
101
+
102
+ export const WithStepButtons = Template.bind({});
103
+ WithStepButtons.args = { useStepButtons: true };
104
+
105
+ export const WithError = Template.bind({});
106
+ WithError.args = {
107
+ fieldHasError: true,
108
+ errorMessage: "Please enter a valid quantity",
109
+ theme: "error",
110
+ };
111
+
112
+ export const Required = Template.bind({});
113
+ Required.args = { required: true };
114
+
115
+ export const LocalisedLabels = Template.bind({});
116
+ LocalisedLabels.args = {
117
+ useStepButtons: true,
118
+ stepDownLabel: "Diminuer",
119
+ stepUpLabel: "Augmenter",
120
+ };
121
+
122
+ export const Underlined = Template.bind({});
123
+ Underlined.args = { inputVariant: "underlined", useStepButtons: true };
@@ -0,0 +1,143 @@
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 "../InputNumberDefault.vue";
5
+
6
+ const initialPropsData = {
7
+ name: "testName",
8
+ label: "Test label",
9
+ min: 0,
10
+ max: 100,
11
+ errorMessage: "",
12
+ modelValue: 50,
13
+ };
14
+
15
+ let wrapper: VueWrapper<InstanceType<typeof ComponentUnderTest>>;
16
+ const wrapperFactory = (propsData = {}, slots = {}) => {
17
+ const mockPropsData = { ...initialPropsData, ...propsData };
18
+
19
+ return mountSuspended(ComponentUnderTest, {
20
+ attachTo: document.body,
21
+ props: mockPropsData,
22
+ slots,
23
+ });
24
+ };
25
+
26
+ describe("InputNumberDefault", () => {
27
+ it("mounts without error", async () => {
28
+ wrapper = await wrapperFactory();
29
+ expect(wrapper.vm).toBeTruthy();
30
+ });
31
+
32
+ it("renders the label text", async () => {
33
+ wrapper = await wrapperFactory({ label: "Quantity" });
34
+ expect(wrapper.text()).toContain("Quantity");
35
+ });
36
+
37
+ it("renders a number input", async () => {
38
+ wrapper = await wrapperFactory();
39
+ expect(wrapper.find('input[type="number"]').exists()).toBe(true);
40
+ });
41
+
42
+ it("forwards placeholder to the input", async () => {
43
+ wrapper = await wrapperFactory({ placeholder: "Enter a quantity" });
44
+ expect(wrapper.find("input").attributes("placeholder")).toBe("Enter a quantity");
45
+ });
46
+
47
+ it("sets data-invalid and the error class when fieldHasError is true", async () => {
48
+ wrapper = await wrapperFactory({ fieldHasError: true });
49
+ expect(wrapper.find(".input-number-with-label").attributes("data-invalid")).toBe("");
50
+ expect(wrapper.find(".input-number-with-label").classes()).toContain("error");
51
+ });
52
+
53
+ it("does not render step buttons when the left/right slots are not used", async () => {
54
+ wrapper = await wrapperFactory();
55
+ expect(wrapper.findAllComponents({ name: "InputButtonCore" })).toHaveLength(0);
56
+ });
57
+
58
+ it("renders step buttons with default labels when the left/right slots are used", async () => {
59
+ wrapper = await wrapperFactory(
60
+ {},
61
+ {
62
+ left: '<span data-testid="left-icon">-</span>',
63
+ right: '<span data-testid="right-icon">+</span>',
64
+ }
65
+ );
66
+
67
+ expect(wrapper.text()).toContain("Step down");
68
+ expect(wrapper.text()).toContain("Step up");
69
+ });
70
+
71
+ it("overrides step button labels via stepDownLabel/stepUpLabel props", async () => {
72
+ wrapper = await wrapperFactory(
73
+ { stepDownLabel: "Decrease", stepUpLabel: "Increase" },
74
+ {
75
+ left: '<span>-</span>',
76
+ right: '<span>+</span>',
77
+ }
78
+ );
79
+
80
+ expect(wrapper.text()).toContain("Decrease");
81
+ expect(wrapper.text()).toContain("Increase");
82
+ });
83
+
84
+ it("increments the model value when the step-up button is clicked", async () => {
85
+ wrapper = await wrapperFactory({ modelValue: 50, step: 5 }, { right: '<span>+</span>' });
86
+
87
+ const buttons = wrapper.findAll("button");
88
+ const stepUpButton = buttons.find(
89
+ (btn) => btn.text().includes("+") || btn.attributes("aria-label")?.includes("Step up")
90
+ );
91
+ await stepUpButton?.trigger("click");
92
+
93
+ expect(wrapper.emitted("update:modelValue")?.[0]).toEqual([55]);
94
+ });
95
+
96
+ it("decrements the model value when the step-down button is clicked", async () => {
97
+ wrapper = await wrapperFactory({ modelValue: 50, step: 5 }, { left: '<span>-</span>' });
98
+
99
+ const buttons = wrapper.findAll("button");
100
+ const stepDownButton = buttons.find(
101
+ (btn) => btn.text().includes("-") || btn.attributes("aria-label")?.includes("Step down")
102
+ );
103
+ await stepDownButton?.trigger("click");
104
+
105
+ expect(wrapper.emitted("update:modelValue")?.[0]).toEqual([45]);
106
+ });
107
+
108
+ it("does not step below min", async () => {
109
+ wrapper = await wrapperFactory({ modelValue: 0, min: 0, step: 5 }, { left: '<span>-</span>' });
110
+
111
+ const buttons = wrapper.findAll("button");
112
+ const stepDownButton = buttons.find((btn) => btn.text().includes("-"));
113
+ await stepDownButton?.trigger("click");
114
+
115
+ expect(wrapper.emitted("update:modelValue")).toBeUndefined();
116
+ });
117
+
118
+ it("does not step above max", async () => {
119
+ wrapper = await wrapperFactory({ modelValue: 100, max: 100, step: 5 }, { right: '<span>+</span>' });
120
+
121
+ const buttons = wrapper.findAll("button");
122
+ const stepUpButton = buttons.find((btn) => btn.text().includes("+"));
123
+ await stepUpButton?.trigger("click");
124
+
125
+ expect(wrapper.emitted("update:modelValue")).toBeUndefined();
126
+ });
127
+
128
+ it("renders the error message when fieldHasError is true", async () => {
129
+ wrapper = await wrapperFactory({ fieldHasError: true, errorMessage: "This field is required" });
130
+ expect(wrapper.text()).toContain("This field is required");
131
+ });
132
+
133
+ it("applies styleClassPassthrough", async () => {
134
+ wrapper = await wrapperFactory({ styleClassPassthrough: "custom-number" });
135
+ expect(wrapper.find(".input-number-with-label").classes()).toContain("custom-number");
136
+ });
137
+
138
+ it("forwards inputVariant to the wrapper, InputLabel, and InputNumberCore", async () => {
139
+ wrapper = await wrapperFactory({ inputVariant: "underlined" });
140
+ expect(wrapper.find(".input-number-with-label").classes()).toContain("underlined");
141
+ expect(wrapper.find(".input-number-wrapper").classes()).toContain("underlined");
142
+ });
143
+ });
@@ -0,0 +1,45 @@
1
+ # InputRangeCore — Consumer Styling Guide
2
+
3
+ ## Public token API
4
+
5
+ | Token | Default | Controls |
6
+ |---|---|---|
7
+ | `--input-range-accent-colour` | `var(--theme-accent)` | Native `accent-color` of the range input (track/thumb tint) |
8
+ | `--input-range-marker-background-colour` | `hsl(0, 29%, 3%)` | Background colour of each `markers` slot dot |
9
+ | `--input-range-marker-padding` | `0.5rem` | Padding of each marker dot |
10
+ | `--input-range-marker-border-radius` | `50%` | Corner radius of each marker dot |
11
+ | `--input-range-marker-outline-width` | `0.1rem` | Marker dot outline width |
12
+ | `--input-range-marker-outline-colour` | `var(--slate-04)` | Marker dot outline colour |
13
+ | `--input-range-marker-icon-size` | `2rem` | Font size of the marker icon (`.marker-icon`) |
14
+ | `--input-range-marker-icon-colour` | `var(--input-range-accent-colour, var(--theme-accent))` | Marker icon colour |
15
+ | `--input-range-datalist-font-size` | `1.4rem` | Font size of the `datalist` slot's option labels |
16
+ | `--input-range-datalist-font-weight` | `500` | Font weight of the `datalist` slot's option labels |
17
+
18
+ Geometry (thumb/track height, border radius) comes from the shared global form-geometry tokens
19
+ (`--input-range-button-size`, `--form-input-border-radius`), not from this component's own
20
+ tokens — see `theming-form-geometry-tokens.md`.
21
+
22
+ ```css
23
+ .my-page {
24
+ --input-range-accent-colour: var(--brand-primary);
25
+ --input-range-marker-background-colour: var(--brand-primary);
26
+ }
27
+ ```
28
+
29
+ Or scope to a single instance via `styleClassPassthrough`:
30
+
31
+ ```vue
32
+ <InputRangeCore style-class-passthrough="price-range" ... />
33
+ ```
34
+
35
+ ## Notes
36
+
37
+ - `accent-color` was previously read from `--theme-form-range-accent-color`, a custom property
38
+ never declared anywhere in the codebase (no fallback either) — per CSS spec this made the whole
39
+ `accent-color` declaration invalid at computed-value time, so it silently fell back to the
40
+ browser's native default accent colour instead of the intended theme colour. Fixed 2026-09-21;
41
+ see the component's skill doc for detail.
42
+ - The `weight` prop (`FormWeight`) generates an `input-range--{weight}` class on the input, but no
43
+ CSS in this component (or elsewhere in the library) currently styles that class — it's a no-op
44
+ today. Left as-is rather than redesigned, since the original intent wasn't clear; flagged here
45
+ so you don't spend time debugging "why doesn't `weight` do anything."