srcdev-nuxt-components 9.6.7 → 9.6.9

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 (152) hide show
  1. package/.claude/rules/consumer/ids-from-useid.md +14 -0
  2. package/.claude/skills/component-compliance-checklist.md +18 -1
  3. package/.claude/skills/component-inline-action-button.md +1 -1
  4. package/.claude/skills/component-local-style-override.md +1 -1
  5. package/.claude/skills/component-naming.md +13 -13
  6. package/.claude/skills/components/container-glow.md +2 -2
  7. package/.claude/skills/components/form-field.md +2 -2
  8. package/.claude/skills/components/form-wrapper.md +1 -1
  9. package/.claude/skills/components/glowing-border.md +1 -1
  10. package/.claude/skills/components/input-checkbox-radio.md +19 -3
  11. package/.claude/skills/components/input-checkbox.md +14 -3
  12. package/.claude/skills/components/input-description.md +7 -7
  13. package/.claude/skills/components/input-error.md +3 -3
  14. package/.claude/skills/components/input-label.md +7 -8
  15. package/.claude/skills/components/input-number.md +60 -29
  16. package/.claude/skills/components/input-radio.md +15 -1
  17. package/.claude/skills/components/{input-range-core.md → input-range.md} +53 -18
  18. package/.claude/skills/components/input-select.md +153 -0
  19. package/.claude/skills/components/input-text.md +220 -0
  20. package/.claude/skills/components/input-textarea.md +147 -0
  21. package/.claude/skills/components/select-menu.md +4 -4
  22. package/.claude/skills/components/toggle-switch.md +143 -0
  23. package/.claude/skills/composable-zod-validation.md +1 -1
  24. package/.claude/skills/icon-sets.md +1 -1
  25. package/.claude/skills/icon-sizing.md +1 -1
  26. package/.claude/skills/index.md +10 -10
  27. package/.claude/skills/storybook-add-story.md +1 -1
  28. package/.claude/skills/testing-add-unit-test.md +17 -0
  29. package/.claude/skills/theming-component-token-pattern.md +8 -8
  30. package/.claude/skills/theming-form-geometry-tokens.md +1 -1
  31. package/.vscode/srcdev-component-input-checkbox-radio.code-snippets +0 -1
  32. package/.vscode/srcdev-component-input-range.code-snippets +38 -18
  33. package/.vscode/srcdev-component-input-select.code-snippets +20 -14
  34. package/.vscode/srcdev-component-input-text.code-snippets +46 -40
  35. package/.vscode/srcdev-component-input-textarea.code-snippets +19 -14
  36. package/.vscode/srcdev-component-toggle-switch.code-snippets +19 -29
  37. package/app/assets/styles/setup/04.elements/forms/00.element-defaults.css +2 -2
  38. package/app/assets/styles/setup/04.elements/forms/06.button-geometry.css +1 -1
  39. package/app/components/01.atoms/animations/glowing-border/CONSUMER-STYLING.md +2 -2
  40. package/app/components/01.atoms/animations/glowing-border/stories/GlowingBorder.stories.ts +4 -4
  41. package/app/components/02.molecules/progress-tracker/stories/ProgressTracker.stories.ts +4 -4
  42. package/app/components/05.forms/form-errors/CONSUMER-STYLING.md +1 -1
  43. package/app/components/05.forms/form-field/CONSUMER-STYLING.md +2 -2
  44. package/app/components/05.forms/input-button/stories/InputButton.stories.ts +3 -3
  45. package/app/components/05.forms/input-checkbox/CONSUMER-STYLING.md +10 -0
  46. package/app/components/05.forms/input-checkbox/MultipleCheckboxes.vue +18 -4
  47. package/app/components/05.forms/input-checkbox/SingleCheckbox.vue +14 -2
  48. package/app/components/05.forms/input-checkbox/stories/MultipleCheckboxes.stories.ts +146 -37
  49. package/app/components/05.forms/input-checkbox/stories/SingleCheckbox.stories.ts +107 -30
  50. package/app/components/05.forms/input-checkbox/tests/MultipleCheckboxes.spec.ts +38 -4
  51. package/app/components/05.forms/input-checkbox/tests/SingleCheckbox.spec.ts +5 -0
  52. package/app/components/05.forms/input-checkbox-radio/CONSUMER-STYLING.md +10 -3
  53. package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadio.vue +1 -0
  54. package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioButton.vue +11 -10
  55. package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioField.vue +3 -0
  56. package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadio.stories.ts +44 -17
  57. package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadioButton.stories.ts +164 -58
  58. package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadioField.stories.ts +94 -21
  59. package/app/components/05.forms/input-checkbox-radio/tests/InputCheckboxRadio.spec.ts +14 -3
  60. package/app/components/05.forms/input-checkbox-radio/tests/InputCheckboxRadioButton.spec.ts +38 -3
  61. package/app/components/05.forms/input-checkbox-radio/tests/InputCheckboxRadioField.spec.ts +14 -3
  62. package/app/components/05.forms/input-description/CONSUMER-STYLING.md +7 -7
  63. package/app/components/05.forms/input-label/CONSUMER-STYLING.md +6 -6
  64. package/app/components/05.forms/input-number/CONSUMER-STYLING.md +50 -6
  65. package/app/components/05.forms/input-number/InputNumber.vue +39 -27
  66. package/app/components/05.forms/input-number/InputNumberField.vue +29 -16
  67. package/app/components/05.forms/input-number/stories/InputNumber.stories.ts +76 -25
  68. package/app/components/05.forms/input-number/stories/InputNumberField.stories.ts +116 -50
  69. package/app/components/05.forms/input-number/tests/InputNumber.spec.ts +49 -13
  70. package/app/components/05.forms/input-number/tests/InputNumberField.spec.ts +59 -5
  71. package/app/components/05.forms/input-radio/CONSUMER-STYLING.md +11 -0
  72. package/app/components/05.forms/input-radio/MultipleRadiobuttons.vue +14 -3
  73. package/app/components/05.forms/input-radio/stories/MultipleRadiobuttons.stories.ts +138 -38
  74. package/app/components/05.forms/input-radio/tests/MultipleRadioButtons.spec.ts +50 -3
  75. package/app/components/05.forms/input-range/CONSUMER-STYLING.md +68 -26
  76. package/app/components/05.forms/input-range/InputRange.vue +247 -0
  77. package/app/components/05.forms/input-range/{InputRangeDefault.vue → InputRangeField.vue} +41 -18
  78. package/app/components/05.forms/input-range/stories/InputRange.stories.ts +187 -0
  79. package/app/components/05.forms/input-range/stories/InputRangeField.stories.ts +196 -0
  80. package/app/components/05.forms/input-range/tests/{InputRangeCore.spec.ts → InputRange.spec.ts} +39 -15
  81. package/app/components/05.forms/input-range/tests/{InputRangeDefault.spec.ts → InputRangeField.spec.ts} +71 -7
  82. package/app/components/05.forms/input-select/CONSUMER-STYLING.md +80 -10
  83. package/app/components/05.forms/input-select/InputSelect.vue +198 -0
  84. package/app/components/05.forms/input-select/InputSelectField.vue +130 -0
  85. package/app/components/05.forms/input-select/stories/InputSelect.stories.ts +184 -0
  86. package/app/components/05.forms/input-select/stories/InputSelectField.stories.ts +187 -0
  87. package/app/components/05.forms/input-select/tests/{InputSelectCore.spec.ts → InputSelect.spec.ts} +75 -12
  88. package/app/components/05.forms/input-select/tests/InputSelectField.spec.ts +121 -0
  89. package/app/components/05.forms/input-text/CONSUMER-STYLING.md +85 -15
  90. package/app/components/05.forms/input-text/{InputPasswordWithLabel.vue → InputPasswordField.vue} +16 -19
  91. package/app/components/05.forms/input-text/{InputTextCore.vue → InputText.vue} +42 -78
  92. package/app/components/05.forms/input-text/{InputTextAsNumberWithLabel.vue → InputTextAsNumberField.vue} +66 -62
  93. package/app/components/05.forms/input-text/InputTextField.vue +168 -0
  94. package/app/components/05.forms/input-text/stories/InputPasswordField.stories.ts +163 -0
  95. package/app/components/05.forms/input-text/stories/InputText.stories.ts +194 -0
  96. package/app/components/05.forms/input-text/stories/InputTextAsNumberField.stories.ts +182 -0
  97. package/app/components/05.forms/input-text/stories/InputTextField.stories.ts +206 -0
  98. package/app/components/05.forms/input-text/tests/{InputPasswordWithLabel.spec.ts → InputPasswordField.spec.ts} +42 -3
  99. package/app/components/05.forms/input-text/tests/InputText.spec.ts +166 -0
  100. package/app/components/05.forms/input-text/tests/InputTextAsNumberField.spec.ts +135 -0
  101. package/app/components/05.forms/input-text/tests/InputTextField.spec.ts +130 -0
  102. package/app/components/05.forms/input-textarea/CONSUMER-STYLING.md +94 -12
  103. package/app/components/05.forms/input-textarea/InputTextarea.vue +165 -0
  104. package/app/components/05.forms/input-textarea/InputTextareaField.vue +159 -0
  105. package/app/components/05.forms/input-textarea/stories/InputTextarea.stories.ts +198 -0
  106. package/app/components/05.forms/input-textarea/stories/InputTextareaField.stories.ts +161 -0
  107. package/app/components/05.forms/input-textarea/tests/InputTextarea.spec.ts +149 -0
  108. package/app/components/05.forms/input-textarea/tests/InputTextareaField.spec.ts +122 -0
  109. package/app/components/05.forms/patterns/stories/ContactForm.stories.ts +13 -13
  110. package/app/components/05.forms/patterns/stories/LoginForm.stories.ts +6 -6
  111. package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +34 -36
  112. package/app/components/05.forms/toggle-switch/CONSUMER-STYLING.md +71 -50
  113. package/app/components/05.forms/toggle-switch/{ToggleSwitchCore.vue → ToggleSwitch.vue} +58 -73
  114. package/app/components/05.forms/toggle-switch/{ToggleSwitchWithLabel.vue → ToggleSwitchField.vue} +65 -19
  115. package/app/components/05.forms/toggle-switch/stories/ToggleSwitch.stories.ts +148 -0
  116. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchField.stories.ts +176 -0
  117. package/app/components/05.forms/toggle-switch/tests/ToggleSwitch.spec.ts +145 -0
  118. package/app/components/05.forms/toggle-switch/tests/ToggleSwitchField.spec.ts +155 -0
  119. package/package.json +1 -1
  120. package/.claude/skills/components/input-select-core.md +0 -151
  121. package/.claude/skills/components/input-text-core.md +0 -204
  122. package/.claude/skills/components/input-textarea-core.md +0 -154
  123. package/.claude/skills/components/toggle-switch-core.md +0 -169
  124. package/app/components/05.forms/input-range/InputRangeCore.vue +0 -169
  125. package/app/components/05.forms/input-range/stories/InputRangeCore.stories.ts +0 -126
  126. package/app/components/05.forms/input-range/stories/InputRangeDefault.stories.ts +0 -108
  127. package/app/components/05.forms/input-select/InputSelectCore.vue +0 -188
  128. package/app/components/05.forms/input-select/InputSelectWithLabel.vue +0 -118
  129. package/app/components/05.forms/input-select/stories/InputSelectCore.stories.ts +0 -69
  130. package/app/components/05.forms/input-select/stories/InputSelectWithLabel.stories.ts +0 -115
  131. package/app/components/05.forms/input-select/tests/InputSelectWithLabel.spec.ts +0 -87
  132. package/app/components/05.forms/input-text/InputTextWithLabel.vue +0 -154
  133. package/app/components/05.forms/input-text/stories/InputPasswordWithLabel.stories.ts +0 -368
  134. package/app/components/05.forms/input-text/stories/InputTextAsNumberWithLabel.stories.ts +0 -480
  135. package/app/components/05.forms/input-text/stories/InputTextCore.stories.ts +0 -267
  136. package/app/components/05.forms/input-text/stories/InputTextWithLabel.stories.ts +0 -319
  137. package/app/components/05.forms/input-text/tests/InputTextAsNumberWithLabel.spec.ts +0 -75
  138. package/app/components/05.forms/input-text/tests/InputTextCore.spec.ts +0 -60
  139. package/app/components/05.forms/input-text/tests/InputTextWithLabel.spec.ts +0 -71
  140. package/app/components/05.forms/input-textarea/InputTextareaCore.vue +0 -159
  141. package/app/components/05.forms/input-textarea/InputTextareaWithLabel.vue +0 -136
  142. package/app/components/05.forms/input-textarea/stories/InputTextareaCore.stories.ts +0 -247
  143. package/app/components/05.forms/input-textarea/stories/InputTextareaWithLabel.stories.ts +0 -458
  144. package/app/components/05.forms/input-textarea/tests/InputTextareaCore.spec.ts +0 -119
  145. package/app/components/05.forms/input-textarea/tests/InputTextareaWithLabel.spec.ts +0 -93
  146. package/app/components/05.forms/toggle-switch/ToggleSwitchWithLabelInline.vue +0 -81
  147. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchCore.stories.ts +0 -251
  148. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabel.stories.ts +0 -251
  149. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabelInline.stories.ts +0 -283
  150. package/app/components/05.forms/toggle-switch/tests/ToggleSwitchCore.spec.ts +0 -101
  151. package/app/components/05.forms/toggle-switch/tests/ToggleSwitchWithLabel.spec.ts +0 -133
  152. package/app/components/05.forms/toggle-switch/tests/ToggleSwitchWithLabelInline.spec.ts +0 -66
@@ -1,169 +0,0 @@
1
- <template>
2
- <div class="input-range-wrapper" :data-theme="theme">
3
- <div v-if="slots.left" class="slot left">
4
- <slot name="left"></slot>
5
- </div>
6
-
7
- <div class="input-range-container">
8
- <slot v-if="slots.markers" name="markers"></slot>
9
-
10
- <input
11
- :id
12
- ref="inputRange"
13
- v-model="modelValue"
14
- type="range"
15
- :name
16
- :required
17
- :min
18
- :max
19
- :step
20
- :list="slots.datalist ? name + '-datalist' : ''"
21
- :class="['input-range-core', `input-range--${weight}`, styleClassPassthrough, { 'has-markers': slots.markers }]"
22
- :aria-describedby
23
- />
24
-
25
- <slot v-if="slots.datalist" name="datalist"></slot>
26
- </div>
27
- <div v-if="slots.right" class="slot right">
28
- <slot name="right"></slot>
29
- </div>
30
- </div>
31
- </template>
32
-
33
- <script setup lang="ts">
34
- import type { FormUiTheme, FormWeight } from "~/types/forms/types.forms";
35
-
36
- interface Props {
37
- id: string;
38
- name: string;
39
- min: number;
40
- max: number;
41
- step?: number;
42
- placeholder?: string;
43
- required?: boolean;
44
- theme?: FormUiTheme;
45
- weight?: FormWeight;
46
- fieldHasError?: boolean;
47
- styleClassPassthrough?: string | string[];
48
- ariaDescribedby?: string;
49
- }
50
-
51
- withDefaults(defineProps<Props>(), {
52
- step: 1,
53
- placeholder: "",
54
- required: false,
55
- theme: "default",
56
- weight: "normal",
57
- fieldHasError: false,
58
- styleClassPassthrough: () => [],
59
- ariaDescribedby: "",
60
- });
61
-
62
- const slots = useSlots();
63
-
64
- const modelValue = defineModel<number>({
65
- required: true,
66
- });
67
- </script>
68
-
69
- <style lang="css">
70
- @layer components {
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 {
81
- border-radius: var(--form-input-border-radius);
82
- width: var(--input-range-button-size);
83
- height: var(--input-range-button-size);
84
-
85
- .btn-icon {
86
- margin: initial;
87
- }
88
- }
89
- }
90
-
91
- .input-range-container {
92
- flex-grow: 1;
93
-
94
- display: grid;
95
- grid-template-areas: "element-stack";
96
-
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
- }
116
-
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
- }
123
- }
124
-
125
- .input-range-core {
126
- grid-area: element-stack;
127
-
128
- accent-color: var(--input-range-accent-colour, var(--theme-accent));
129
- height: var(--input-range-button-size);
130
- margin: 0;
131
- width: 100%;
132
-
133
- &:focus-visible {
134
- box-shadow: var(--form-focus-box-shadow);
135
- }
136
-
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
- }
148
- }
149
- }
150
- }
151
-
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
- }
165
- }
166
- }
167
- }
168
- }
169
- </style>
@@ -1,126 +0,0 @@
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({});
@@ -1,108 +0,0 @@
1
- import type { Meta, StoryFn } from "@nuxtjs/storybook";
2
- import StorybookComponent from "../InputRangeDefault.vue";
3
- import type { FormUiTheme } from "~/types/forms/types.forms.d";
4
-
5
- interface InputRangeDefaultStoryArgs {
6
- modelValue: number;
7
- name: string;
8
- label: string;
9
- min: number;
10
- max: number;
11
- step: number;
12
- errorMessage: string;
13
- fieldHasError: boolean;
14
- required: boolean;
15
- theme: FormUiTheme;
16
- stepDownLabel: string;
17
- stepUpLabel: string;
18
- useStepButtons: boolean;
19
- }
20
-
21
- export default {
22
- title: "Components/Forms/Input Range/InputRangeDefault",
23
- component: StorybookComponent,
24
- argTypes: {
25
- modelValue: { control: "number", table: { category: "Model" } },
26
- name: { control: "text", table: { category: "Basic" } },
27
- label: { control: "text", table: { category: "Basic" } },
28
- min: { control: "number", table: { category: "Basic" } },
29
- max: { control: "number", table: { category: "Basic" } },
30
- step: { control: "number", table: { category: "Basic" } },
31
- errorMessage: { control: "text", table: { category: "Validation" } },
32
- fieldHasError: { control: "boolean", table: { category: "Validation" } },
33
- required: { control: "boolean", table: { category: "Validation" } },
34
- theme: {
35
- control: { type: "select" },
36
- options: ["default", "success", "error", "warning"],
37
- table: { category: "Styling" },
38
- },
39
- stepDownLabel: { control: "text", description: "Localisation override for the step-down button", table: { category: "Accessibility" } },
40
- stepUpLabel: { control: "text", description: "Localisation override for the step-up button", table: { category: "Accessibility" } },
41
- useStepButtons: { control: "boolean", description: "Render +/- step buttons via left/right slots", table: { category: "Slots" } },
42
- },
43
- args: {
44
- modelValue: 50,
45
- name: "priceRange",
46
- label: "Price range",
47
- min: 0,
48
- max: 100,
49
- step: 1,
50
- errorMessage: "",
51
- fieldHasError: false,
52
- required: false,
53
- theme: "default",
54
- stepDownLabel: "Step down",
55
- stepUpLabel: "Step up",
56
- useStepButtons: false,
57
- },
58
- } as Meta<InputRangeDefaultStoryArgs>;
59
-
60
- const Template: StoryFn<InputRangeDefaultStoryArgs> = (args) => ({
61
- components: { StorybookComponent },
62
- setup() {
63
- return { args };
64
- },
65
- template: `
66
- <div style="margin: 36px; max-width: 320px;">
67
- <StorybookComponent
68
- v-model="args.modelValue"
69
- :name="args.name"
70
- :label="args.label"
71
- :min="args.min"
72
- :max="args.max"
73
- :step="args.step"
74
- :error-message="args.errorMessage"
75
- :field-has-error="args.fieldHasError"
76
- :required="args.required"
77
- :theme="args.theme"
78
- :step-down-label="args.stepDownLabel"
79
- :step-up-label="args.stepUpLabel"
80
- >
81
- <template v-if="args.useStepButtons" #left><span>−</span></template>
82
- <template v-if="args.useStepButtons" #right><span>+</span></template>
83
- </StorybookComponent>
84
- </div>
85
- `,
86
- });
87
-
88
- export const Default = Template.bind({});
89
-
90
- export const WithStepButtons = Template.bind({});
91
- WithStepButtons.args = { useStepButtons: true };
92
-
93
- export const WithError = Template.bind({});
94
- WithError.args = {
95
- fieldHasError: true,
96
- errorMessage: "Please choose a value within the allowed range",
97
- theme: "error",
98
- };
99
-
100
- export const Required = Template.bind({});
101
- Required.args = { required: true };
102
-
103
- export const LocalisedLabels = Template.bind({});
104
- LocalisedLabels.args = {
105
- useStepButtons: true,
106
- stepDownLabel: "Diminuer",
107
- stepUpLabel: "Augmenter",
108
- };
@@ -1,188 +0,0 @@
1
- <template>
2
- <div
3
- class="input-select-wrapper"
4
- :data-theme="theme"
5
- :data-invalid="fieldHasError ? '' : null"
6
- :class="[inputVariant, { dirty: isDirty }, { active: isActive }, { error: fieldHasError }]"
7
- >
8
- <select
9
- :id
10
- v-model="modelValue"
11
- :aria-invalid="fieldHasError ? 'true' : undefined"
12
- :class="['input-select-core', elementClasses]"
13
- :name
14
- :title
15
- :required
16
- :aria-describedby="ariaDescribedby"
17
- @focusin="isActive = true"
18
- @focusout="isActive = false"
19
- @change="isDirty = true"
20
- >
21
- <option v-if="placeholder" value="" disabled :selected="!modelValue" class="input-select-core-option placeholder">
22
- {{ placeholder }}
23
- </option>
24
- <option
25
- v-for="item in fieldData.data"
26
- :key="item.id"
27
- :value="item.value"
28
- :selected="String(item.value) === String(modelValue)"
29
- class="input-select-core-option"
30
- >
31
- <Icon
32
- v-if="item.icon"
33
- :name="item.icon"
34
- class="input-select-core-option-decorator-icon"
35
- aria-hidden="true"
36
- focusable="false"
37
- />
38
- {{ item.label }}
39
- </option>
40
- </select>
41
- </div>
42
- </template>
43
-
44
- <script setup lang="ts">
45
- import type { IFormMultipleOptions, FormUiTheme, InputUiVariant } from "~/types/forms/types.forms";
46
-
47
- interface Props {
48
- id: string;
49
- name: string;
50
- placeholder?: string;
51
- title?: string;
52
- required?: boolean;
53
- fieldHasError?: boolean;
54
- styleClassPassthrough?: string | string[];
55
- theme?: FormUiTheme;
56
- inputVariant?: InputUiVariant;
57
- ariaDescribedby?: string;
58
- }
59
-
60
- const props = withDefaults(defineProps<Props>(), {
61
- placeholder: "",
62
- title: "Please select an option",
63
- required: false,
64
- fieldHasError: false,
65
- styleClassPassthrough: () => [],
66
- theme: "default",
67
- inputVariant: "normal",
68
- ariaDescribedby: "",
69
- });
70
-
71
- const modelValue = defineModel<string | number | readonly number[]>({ required: true });
72
- const isDirty = defineModel<boolean>("isDirty");
73
- const isActive = defineModel<boolean>("isActive");
74
- const fieldData = defineModel<IFormMultipleOptions>("fieldData", { required: true });
75
-
76
- const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
77
- </script>
78
-
79
- <style lang="css">
80
- @layer components {
81
- .input-select-wrapper {
82
- /* Public --input-select-* tokens, inline-fallback to the shared --theme-* tokens (see
83
- theming-component-token-pattern.md) — overriding one here doesn't touch every other themed
84
- input/control that also reads --theme-input-surface/--theme-border. --_border is the one
85
- genuinely reused value (border, underlined border-bottom, open-picker border); the rest of
86
- this component's tokens are used at exactly one point each, so they're inlined directly
87
- rather than wrapped in a private var with no composition or state-swap behind it (see
88
- pitfall #20 in CLAUDE.md). */
89
- --_border: var(--input-select-border, var(--theme-border));
90
- /* Split so hover (mouse) and :focus-visible (keyboard/assistive) can diverge — both default to
91
- the same --theme-border-focus today, identical appearance to before this token existed, but
92
- each now has its own override point. */
93
- --_border-focus: var(--input-select-border-focus, var(--theme-border-focus));
94
-
95
- background-color: var(--input-select-surface, var(--theme-input-surface));
96
- overflow: hidden;
97
-
98
- z-index: 2;
99
- position: relative;
100
- transition: all var(--theme-form-transition-duration) ease-in-out;
101
-
102
- &.normal {
103
- border: var(--form-element-border-width) solid var(--_border);
104
- border-radius: var(--form-input-border-radius);
105
- outline: var(--form-element-outline-width) solid transparent;
106
- }
107
-
108
- &.underlined {
109
- border-bottom: var(--form-element-border-bottom-width-underlined) solid var(--_border);
110
- }
111
-
112
- &:has(:hover) {
113
- outline: var(--form-element-outline-width-focus) solid
114
- var(--input-select-border-hover, var(--theme-border-focus));
115
- outline-offset: var(--form-element-outline-offset-focus);
116
- }
117
-
118
- &:has(select:focus-visible) {
119
- outline: var(--form-element-outline-width-focus) solid var(--_border-focus);
120
- outline-offset: var(--form-element-outline-offset-focus);
121
- }
122
-
123
- .input-select-core {
124
- appearance: none;
125
- background-color: transparent;
126
- display: flex;
127
- align-items: center;
128
-
129
- /* Start modern Select CSS */
130
- &,
131
- &::picker(select) {
132
- appearance: base-select;
133
- }
134
-
135
- &::picker(select) {
136
- transition:
137
- display allow-discrete var(--theme-form-transition-duration),
138
- opacity var(--theme-form-transition-duration),
139
- overlay var(--theme-form-transition-duration) allow-discrete;
140
- }
141
-
142
- &:not(:open)::picker(select) {
143
- opacity: 0;
144
- }
145
-
146
- &:open::picker(select) {
147
- opacity: 1;
148
- border: var(--form-element-border-width) solid var(--_border);
149
- outline: var(--form-element-outline-width) solid var(--_border-focus);
150
-
151
- @starting-style {
152
- opacity: 0;
153
- }
154
- }
155
-
156
- border: none;
157
- outline: none;
158
- border-radius: 0;
159
- width: 100%;
160
-
161
- /* END modern Select CSS */
162
-
163
- font-family: var(--font-family);
164
- font-size: var(--input-font-size);
165
- /* line-height: var(--input-element-line-height); */
166
- padding-block: var(--input-padding-block);
167
- padding-inline: var(--input-padding-inline);
168
- min-height: var(--input-min-height);
169
-
170
- .input-select-core-option {
171
- min-height: var(--input-select-option-min-height);
172
- gap: 1.5rem;
173
- transition: all var(--theme-form-transition-duration) ease-in-out;
174
-
175
- &:hover {
176
- background-color: var(--input-select-surface-hover, var(--theme-input-surface-hover));
177
- }
178
-
179
- .input-select-core-option-decorator-icon {
180
- display: inline-block;
181
- width: 1.5rem;
182
- height: 1.5rem;
183
- }
184
- }
185
- }
186
- }
187
- }
188
- </style>