srcdev-nuxt-components 9.6.8 → 9.6.10
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.
- package/.claude/skills/component-compliance-checklist.md +28 -1
- package/.claude/skills/component-inline-action-button.md +1 -1
- package/.claude/skills/component-local-style-override.md +1 -1
- package/.claude/skills/component-naming.md +17 -12
- package/.claude/skills/components/container-glow.md +2 -2
- package/.claude/skills/components/form-field.md +2 -2
- package/.claude/skills/components/form-wrapper.md +1 -1
- package/.claude/skills/components/glowing-border.md +1 -1
- package/.claude/skills/components/input-description.md +7 -7
- package/.claude/skills/components/input-error.md +3 -3
- package/.claude/skills/components/input-label.md +7 -8
- package/.claude/skills/components/input-number.md +17 -17
- package/.claude/skills/components/input-select.md +153 -0
- package/.claude/skills/components/input-text.md +220 -0
- package/.claude/skills/components/input-textarea.md +147 -0
- package/.claude/skills/components/select-menu.md +4 -4
- package/.claude/skills/components/toggle-switch.md +143 -0
- package/.claude/skills/composable-zod-validation.md +1 -1
- package/.claude/skills/css-nesting-conventions.md +22 -1
- package/.claude/skills/icon-sets.md +1 -1
- package/.claude/skills/icon-sizing.md +1 -1
- package/.claude/skills/index.md +5 -5
- package/.claude/skills/storybook-add-story.md +1 -1
- package/.claude/skills/theming-component-token-pattern.md +8 -8
- package/.claude/skills/theming-form-geometry-tokens.md +1 -1
- package/.vscode/srcdev-component-input-select.code-snippets +20 -14
- package/.vscode/srcdev-component-input-text.code-snippets +46 -40
- package/.vscode/srcdev-component-input-textarea.code-snippets +19 -14
- package/.vscode/srcdev-component-toggle-switch.code-snippets +19 -29
- package/app/assets/styles/setup/04.elements/forms/00.element-defaults.css +2 -2
- package/app/assets/styles/setup/04.elements/forms/06.button-geometry.css +1 -1
- package/app/components/01.atoms/animations/glowing-border/CONSUMER-STYLING.md +2 -2
- package/app/components/01.atoms/animations/glowing-border/stories/GlowingBorder.stories.ts +4 -4
- package/app/components/02.molecules/progress-tracker/stories/ProgressTracker.stories.ts +4 -4
- package/app/components/05.forms/form-errors/CONSUMER-STYLING.md +1 -1
- package/app/components/05.forms/form-field/CONSUMER-STYLING.md +2 -2
- package/app/components/05.forms/input-button/stories/InputButton.stories.ts +3 -3
- package/app/components/05.forms/input-description/CONSUMER-STYLING.md +6 -6
- package/app/components/05.forms/input-label/CONSUMER-STYLING.md +7 -7
- package/app/components/05.forms/input-number/CONSUMER-STYLING.md +9 -5
- package/app/components/05.forms/input-number/InputNumber.vue +2 -2
- package/app/components/05.forms/input-select/CONSUMER-STYLING.md +80 -10
- package/app/components/05.forms/input-select/InputSelect.vue +198 -0
- package/app/components/05.forms/input-select/InputSelectField.vue +130 -0
- package/app/components/05.forms/input-select/stories/InputSelect.stories.ts +184 -0
- package/app/components/05.forms/input-select/stories/InputSelectField.stories.ts +187 -0
- package/app/components/05.forms/input-select/tests/{InputSelectCore.spec.ts → InputSelect.spec.ts} +75 -12
- package/app/components/05.forms/input-select/tests/InputSelectField.spec.ts +121 -0
- package/app/components/05.forms/input-text/CONSUMER-STYLING.md +85 -15
- package/app/components/05.forms/input-text/{InputPasswordWithLabel.vue → InputPasswordField.vue} +16 -19
- package/app/components/05.forms/input-text/{InputTextCore.vue → InputText.vue} +42 -78
- package/app/components/05.forms/input-text/{InputTextAsNumberWithLabel.vue → InputTextAsNumberField.vue} +66 -62
- package/app/components/05.forms/input-text/InputTextField.vue +168 -0
- package/app/components/05.forms/input-text/stories/InputPasswordField.stories.ts +163 -0
- package/app/components/05.forms/input-text/stories/InputText.stories.ts +194 -0
- package/app/components/05.forms/input-text/stories/InputTextAsNumberField.stories.ts +182 -0
- package/app/components/05.forms/input-text/stories/InputTextField.stories.ts +206 -0
- package/app/components/05.forms/input-text/tests/{InputPasswordWithLabel.spec.ts → InputPasswordField.spec.ts} +42 -3
- package/app/components/05.forms/input-text/tests/InputText.spec.ts +166 -0
- package/app/components/05.forms/input-text/tests/InputTextAsNumberField.spec.ts +135 -0
- package/app/components/05.forms/input-text/tests/InputTextField.spec.ts +130 -0
- package/app/components/05.forms/input-textarea/CONSUMER-STYLING.md +94 -12
- package/app/components/05.forms/input-textarea/InputTextarea.vue +165 -0
- package/app/components/05.forms/input-textarea/InputTextareaField.vue +159 -0
- package/app/components/05.forms/input-textarea/stories/InputTextarea.stories.ts +198 -0
- package/app/components/05.forms/input-textarea/stories/InputTextareaField.stories.ts +161 -0
- package/app/components/05.forms/input-textarea/tests/InputTextarea.spec.ts +149 -0
- package/app/components/05.forms/input-textarea/tests/InputTextareaField.spec.ts +122 -0
- package/app/components/05.forms/patterns/stories/ContactForm.stories.ts +13 -13
- package/app/components/05.forms/patterns/stories/LoginForm.stories.ts +6 -6
- package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +30 -32
- package/app/components/05.forms/toggle-switch/CONSUMER-STYLING.md +71 -50
- package/app/components/05.forms/toggle-switch/{ToggleSwitchCore.vue → ToggleSwitch.vue} +58 -73
- package/app/components/05.forms/toggle-switch/{ToggleSwitchWithLabel.vue → ToggleSwitchField.vue} +65 -19
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitch.stories.ts +148 -0
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchField.stories.ts +176 -0
- package/app/components/05.forms/toggle-switch/tests/ToggleSwitch.spec.ts +145 -0
- package/app/components/05.forms/toggle-switch/tests/ToggleSwitchField.spec.ts +155 -0
- package/package.json +1 -1
- package/.claude/skills/components/input-select-core.md +0 -151
- package/.claude/skills/components/input-text-core.md +0 -204
- package/.claude/skills/components/input-textarea-core.md +0 -154
- package/.claude/skills/components/toggle-switch-core.md +0 -169
- package/app/components/05.forms/input-select/InputSelectCore.vue +0 -188
- package/app/components/05.forms/input-select/InputSelectWithLabel.vue +0 -118
- package/app/components/05.forms/input-select/stories/InputSelectCore.stories.ts +0 -69
- package/app/components/05.forms/input-select/stories/InputSelectWithLabel.stories.ts +0 -115
- package/app/components/05.forms/input-select/tests/InputSelectWithLabel.spec.ts +0 -87
- package/app/components/05.forms/input-text/InputTextWithLabel.vue +0 -154
- package/app/components/05.forms/input-text/stories/InputPasswordWithLabel.stories.ts +0 -368
- package/app/components/05.forms/input-text/stories/InputTextAsNumberWithLabel.stories.ts +0 -480
- package/app/components/05.forms/input-text/stories/InputTextCore.stories.ts +0 -267
- package/app/components/05.forms/input-text/stories/InputTextWithLabel.stories.ts +0 -319
- package/app/components/05.forms/input-text/tests/InputTextAsNumberWithLabel.spec.ts +0 -75
- package/app/components/05.forms/input-text/tests/InputTextCore.spec.ts +0 -60
- package/app/components/05.forms/input-text/tests/InputTextWithLabel.spec.ts +0 -71
- package/app/components/05.forms/input-textarea/InputTextareaCore.vue +0 -159
- package/app/components/05.forms/input-textarea/InputTextareaWithLabel.vue +0 -136
- package/app/components/05.forms/input-textarea/stories/InputTextareaCore.stories.ts +0 -247
- package/app/components/05.forms/input-textarea/stories/InputTextareaWithLabel.stories.ts +0 -458
- package/app/components/05.forms/input-textarea/tests/InputTextareaCore.spec.ts +0 -119
- package/app/components/05.forms/input-textarea/tests/InputTextareaWithLabel.spec.ts +0 -93
- package/app/components/05.forms/toggle-switch/ToggleSwitchWithLabelInline.vue +0 -81
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchCore.stories.ts +0 -251
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabel.stories.ts +0 -251
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabelInline.stories.ts +0 -283
- package/app/components/05.forms/toggle-switch/tests/ToggleSwitchCore.spec.ts +0 -101
- package/app/components/05.forms/toggle-switch/tests/ToggleSwitchWithLabel.spec.ts +0 -133
- package/app/components/05.forms/toggle-switch/tests/ToggleSwitchWithLabelInline.spec.ts +0 -66
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryFn } from "@nuxtjs/storybook";
|
|
2
|
-
import { computed } from "vue";
|
|
3
|
-
import StorybookComponent from "../InputTextCore.vue";
|
|
4
|
-
import type { FormUiTheme, InputUiVariant } from "~/types/forms/types.forms.d";
|
|
5
|
-
|
|
6
|
-
interface InputTextCoreStoryArgs {
|
|
7
|
-
modelValue: string;
|
|
8
|
-
type: "text" | "email" | "password" | "tel" | "url" | "search" | "date";
|
|
9
|
-
maxlength: number;
|
|
10
|
-
min: string | number;
|
|
11
|
-
max: string | number;
|
|
12
|
-
name: string;
|
|
13
|
-
placeholder: string;
|
|
14
|
-
label: string;
|
|
15
|
-
errorMessage: string;
|
|
16
|
-
fieldHasError: boolean;
|
|
17
|
-
required: boolean;
|
|
18
|
-
theme: FormUiTheme;
|
|
19
|
-
inputVariant: InputUiVariant;
|
|
20
|
-
styleClassPassthrough: string[];
|
|
21
|
-
leftSlotContent: string;
|
|
22
|
-
rightSlotContent: string;
|
|
23
|
-
useLeftSlot: boolean;
|
|
24
|
-
useRightSlot: boolean;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export default {
|
|
28
|
-
title: "Components/Forms/Input Text/InputTextCore",
|
|
29
|
-
component: StorybookComponent,
|
|
30
|
-
argTypes: {
|
|
31
|
-
// Model
|
|
32
|
-
modelValue: {
|
|
33
|
-
control: "text",
|
|
34
|
-
description: "The input value",
|
|
35
|
-
table: {
|
|
36
|
-
category: "Model",
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
|
|
40
|
-
// Basic Configuration
|
|
41
|
-
type: {
|
|
42
|
-
control: { type: "select" },
|
|
43
|
-
options: ["text", "email", "password", "tel", "url", "search", "date"],
|
|
44
|
-
description: "Input type",
|
|
45
|
-
table: {
|
|
46
|
-
category: "Basic",
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
maxlength: {
|
|
50
|
-
control: { type: "number", min: 1, max: 1000 },
|
|
51
|
-
description: "Maximum length of input",
|
|
52
|
-
table: {
|
|
53
|
-
category: "Basic",
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
min: {
|
|
57
|
-
control: "text",
|
|
58
|
-
description: 'Passed straight through to the native input — e.g. earliest allowed date for type="date", or lowest number for type="number".',
|
|
59
|
-
table: {
|
|
60
|
-
category: "Basic",
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
max: {
|
|
64
|
-
control: "text",
|
|
65
|
-
description: 'Passed straight through to the native input — e.g. latest allowed date for type="date", or highest number for type="number".',
|
|
66
|
-
table: {
|
|
67
|
-
category: "Basic",
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
name: {
|
|
71
|
-
control: "text",
|
|
72
|
-
description: "Input name attribute",
|
|
73
|
-
table: {
|
|
74
|
-
category: "Basic",
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
placeholder: {
|
|
78
|
-
control: "text",
|
|
79
|
-
description: "Placeholder text",
|
|
80
|
-
table: {
|
|
81
|
-
category: "Basic",
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
label: {
|
|
85
|
-
control: "text",
|
|
86
|
-
description: "Input label",
|
|
87
|
-
table: {
|
|
88
|
-
category: "Basic",
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
|
|
92
|
-
// Validation
|
|
93
|
-
errorMessage: {
|
|
94
|
-
control: "text",
|
|
95
|
-
description: "Error message to display",
|
|
96
|
-
table: {
|
|
97
|
-
category: "Validation",
|
|
98
|
-
},
|
|
99
|
-
},
|
|
100
|
-
fieldHasError: {
|
|
101
|
-
control: "boolean",
|
|
102
|
-
description: "Whether field has validation error",
|
|
103
|
-
table: {
|
|
104
|
-
category: "Validation",
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
required: {
|
|
108
|
-
control: "boolean",
|
|
109
|
-
description: "Whether field is required",
|
|
110
|
-
table: {
|
|
111
|
-
category: "Validation",
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
|
|
115
|
-
// Styling
|
|
116
|
-
theme: {
|
|
117
|
-
control: { type: "select" },
|
|
118
|
-
options: ["primary", "secondary", "tertiary", "ghost", "error", "success", "warning"],
|
|
119
|
-
description: "Theme for styling",
|
|
120
|
-
table: {
|
|
121
|
-
category: "Styling",
|
|
122
|
-
},
|
|
123
|
-
},
|
|
124
|
-
inputVariant: {
|
|
125
|
-
control: { type: "select" },
|
|
126
|
-
options: ["normal", "outlined", "underlined"],
|
|
127
|
-
description: "Input variant style",
|
|
128
|
-
table: {
|
|
129
|
-
category: "Styling",
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
|
-
styleClassPassthrough: {
|
|
133
|
-
control: "object",
|
|
134
|
-
description: "Additional CSS classes",
|
|
135
|
-
table: {
|
|
136
|
-
category: "Styling",
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
|
|
140
|
-
// Slots
|
|
141
|
-
useLeftSlot: {
|
|
142
|
-
control: "boolean",
|
|
143
|
-
description: "Use left slot",
|
|
144
|
-
table: {
|
|
145
|
-
category: "Slots",
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
useRightSlot: {
|
|
149
|
-
control: "boolean",
|
|
150
|
-
description: "Use right slot",
|
|
151
|
-
table: {
|
|
152
|
-
category: "Slots",
|
|
153
|
-
},
|
|
154
|
-
},
|
|
155
|
-
leftSlotContent: {
|
|
156
|
-
control: "text",
|
|
157
|
-
description: "Content for left slot",
|
|
158
|
-
table: {
|
|
159
|
-
category: "Slots",
|
|
160
|
-
},
|
|
161
|
-
},
|
|
162
|
-
rightSlotContent: {
|
|
163
|
-
control: "text",
|
|
164
|
-
description: "Content for right slot",
|
|
165
|
-
table: {
|
|
166
|
-
category: "Slots",
|
|
167
|
-
},
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
|
-
args: {
|
|
171
|
-
modelValue: "",
|
|
172
|
-
type: "text",
|
|
173
|
-
maxlength: 255,
|
|
174
|
-
name: "example-input",
|
|
175
|
-
placeholder: "Enter text...",
|
|
176
|
-
label: "Example Label",
|
|
177
|
-
errorMessage: "",
|
|
178
|
-
fieldHasError: false,
|
|
179
|
-
required: false,
|
|
180
|
-
theme: "default",
|
|
181
|
-
inputVariant: "normal",
|
|
182
|
-
styleClassPassthrough: [],
|
|
183
|
-
useLeftSlot: false,
|
|
184
|
-
useRightSlot: false,
|
|
185
|
-
leftSlotContent: "L",
|
|
186
|
-
rightSlotContent: "R",
|
|
187
|
-
},
|
|
188
|
-
} as Meta<typeof StorybookComponent>;
|
|
189
|
-
|
|
190
|
-
// `args` is Storybook's own reactive object — bind to it directly (`args.x`) rather than
|
|
191
|
-
// destructuring it into local variables/refs, which would snapshot the values once at setup()
|
|
192
|
-
// and stop reflecting later Controls-panel changes. `componentArgs` strips the non-prop
|
|
193
|
-
// slot-toggle args so they don't leak onto the component as unknown attributes.
|
|
194
|
-
const Template: StoryFn<InputTextCoreStoryArgs> = (args) => ({
|
|
195
|
-
components: { StorybookComponent },
|
|
196
|
-
setup() {
|
|
197
|
-
const componentArgs = computed(() => {
|
|
198
|
-
const { useLeftSlot, useRightSlot, leftSlotContent, rightSlotContent, ...rest } = args;
|
|
199
|
-
return rest;
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
return { args, componentArgs };
|
|
203
|
-
},
|
|
204
|
-
template: `
|
|
205
|
-
<StorybookComponent
|
|
206
|
-
v-model="args.modelValue"
|
|
207
|
-
v-bind="componentArgs"
|
|
208
|
-
>
|
|
209
|
-
<template v-if="args.useLeftSlot" #left>{{ args.leftSlotContent }}</template>
|
|
210
|
-
<template v-if="args.useRightSlot" #right>{{ args.rightSlotContent }}</template>
|
|
211
|
-
</StorybookComponent>
|
|
212
|
-
<div class="mt-4 text-sm text-gray-600">
|
|
213
|
-
Current value: {{ args.modelValue }}
|
|
214
|
-
</div>
|
|
215
|
-
`,
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
export const Default = Template.bind({});
|
|
219
|
-
Default.args = {};
|
|
220
|
-
|
|
221
|
-
export const WithPlaceholder = Template.bind({});
|
|
222
|
-
WithPlaceholder.args = {
|
|
223
|
-
placeholder: "Type something here...",
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
export const Required = Template.bind({});
|
|
227
|
-
Required.args = {
|
|
228
|
-
required: true,
|
|
229
|
-
label: "Required Field",
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
export const WithError = Template.bind({});
|
|
233
|
-
WithError.args = {
|
|
234
|
-
fieldHasError: true,
|
|
235
|
-
errorMessage: "This field is required",
|
|
236
|
-
theme: "error",
|
|
237
|
-
};
|
|
238
|
-
|
|
239
|
-
export const WithSlots = Template.bind({});
|
|
240
|
-
WithSlots.args = {
|
|
241
|
-
useLeftSlot: true,
|
|
242
|
-
useRightSlot: true,
|
|
243
|
-
leftSlotContent: "📧",
|
|
244
|
-
rightSlotContent: "🔍",
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
export const Outlined = Template.bind({});
|
|
248
|
-
Outlined.args = {
|
|
249
|
-
inputVariant: "outlined",
|
|
250
|
-
placeholder: "Outlined input",
|
|
251
|
-
};
|
|
252
|
-
|
|
253
|
-
export const Underlined = Template.bind({});
|
|
254
|
-
Underlined.args = {
|
|
255
|
-
inputVariant: "underlined",
|
|
256
|
-
placeholder: "Underlined input",
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
export const DateWithMinMax = Template.bind({});
|
|
260
|
-
DateWithMinMax.args = {
|
|
261
|
-
type: "date",
|
|
262
|
-
label: "Appointment date",
|
|
263
|
-
name: "appointmentDate",
|
|
264
|
-
// Prevents picking a date in the past in the native browser date picker.
|
|
265
|
-
min: new Date().toISOString().split("T")[0],
|
|
266
|
-
max: "2027-12-31",
|
|
267
|
-
};
|
|
@@ -1,319 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryFn } from "@nuxtjs/storybook";
|
|
2
|
-
import { computed } from "vue";
|
|
3
|
-
import StorybookComponent from "../InputTextWithLabel.vue";
|
|
4
|
-
import type { FormUiTheme, InputUiVariant } from "~/types/forms/types.forms.d";
|
|
5
|
-
|
|
6
|
-
interface InputTextWithLabelStoryArgs {
|
|
7
|
-
modelValue: string;
|
|
8
|
-
type: "text" | "email" | "password" | "tel" | "url" | "search" | "date";
|
|
9
|
-
maxlength: number;
|
|
10
|
-
min: string | number;
|
|
11
|
-
max: string | number;
|
|
12
|
-
name: string;
|
|
13
|
-
placeholder: string;
|
|
14
|
-
label: string;
|
|
15
|
-
errorMessage: string;
|
|
16
|
-
fieldHasError: boolean;
|
|
17
|
-
required: boolean;
|
|
18
|
-
theme: FormUiTheme;
|
|
19
|
-
inputVariant: InputUiVariant;
|
|
20
|
-
styleClassPassthrough: string[];
|
|
21
|
-
useDescriptionSlot: boolean;
|
|
22
|
-
useDescriptionHtmlSlot: boolean;
|
|
23
|
-
descriptionContent: string;
|
|
24
|
-
descriptionHtmlContent: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export default {
|
|
28
|
-
title: "Components/Forms/Input Text/InputTextWithLabel",
|
|
29
|
-
component: StorybookComponent,
|
|
30
|
-
argTypes: {
|
|
31
|
-
// Model
|
|
32
|
-
modelValue: {
|
|
33
|
-
control: "text",
|
|
34
|
-
description: "The input value",
|
|
35
|
-
table: {
|
|
36
|
-
category: "Model",
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
|
|
40
|
-
// Basic Configuration
|
|
41
|
-
type: {
|
|
42
|
-
control: { type: "select" },
|
|
43
|
-
options: ["text", "email", "password", "tel", "url", "search", "date"],
|
|
44
|
-
description: "Input type",
|
|
45
|
-
table: {
|
|
46
|
-
category: "Basic",
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
maxlength: {
|
|
50
|
-
control: { type: "number", min: 1, max: 1000 },
|
|
51
|
-
description: "Maximum length of input",
|
|
52
|
-
table: {
|
|
53
|
-
category: "Basic",
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
min: {
|
|
57
|
-
control: "text",
|
|
58
|
-
description: 'Passed straight through to the native input — e.g. earliest allowed date for type="date".',
|
|
59
|
-
table: {
|
|
60
|
-
category: "Basic",
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
max: {
|
|
64
|
-
control: "text",
|
|
65
|
-
description: 'Passed straight through to the native input — e.g. latest allowed date for type="date".',
|
|
66
|
-
table: {
|
|
67
|
-
category: "Basic",
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
name: {
|
|
71
|
-
control: "text",
|
|
72
|
-
description: "Input name attribute",
|
|
73
|
-
table: {
|
|
74
|
-
category: "Basic",
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
placeholder: {
|
|
78
|
-
control: "text",
|
|
79
|
-
description: "Placeholder text",
|
|
80
|
-
table: {
|
|
81
|
-
category: "Basic",
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
label: {
|
|
85
|
-
control: "text",
|
|
86
|
-
description: "Input label",
|
|
87
|
-
table: {
|
|
88
|
-
category: "Basic",
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
|
|
92
|
-
// Validation
|
|
93
|
-
errorMessage: {
|
|
94
|
-
control: "text",
|
|
95
|
-
description: "Error message to display",
|
|
96
|
-
table: {
|
|
97
|
-
category: "Validation",
|
|
98
|
-
},
|
|
99
|
-
},
|
|
100
|
-
fieldHasError: {
|
|
101
|
-
control: "boolean",
|
|
102
|
-
description: "Whether field has validation error",
|
|
103
|
-
table: {
|
|
104
|
-
category: "Validation",
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
required: {
|
|
108
|
-
control: "boolean",
|
|
109
|
-
description: "Whether field is required",
|
|
110
|
-
table: {
|
|
111
|
-
category: "Validation",
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
|
|
115
|
-
// Styling
|
|
116
|
-
theme: {
|
|
117
|
-
control: { type: "select" },
|
|
118
|
-
options: ["primary", "secondary", "tertiary", "ghost", "error", "success", "warning"],
|
|
119
|
-
description: "Theme for styling",
|
|
120
|
-
table: {
|
|
121
|
-
category: "Styling",
|
|
122
|
-
},
|
|
123
|
-
},
|
|
124
|
-
inputVariant: {
|
|
125
|
-
control: { type: "select" },
|
|
126
|
-
options: ["normal", "outlined", "underlined"],
|
|
127
|
-
description: "Input variant style",
|
|
128
|
-
table: {
|
|
129
|
-
category: "Styling",
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
|
-
styleClassPassthrough: {
|
|
133
|
-
control: "object",
|
|
134
|
-
description: "Additional CSS classes",
|
|
135
|
-
table: {
|
|
136
|
-
category: "Styling",
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
|
|
140
|
-
// Slots
|
|
141
|
-
useDescriptionSlot: {
|
|
142
|
-
control: "boolean",
|
|
143
|
-
description: "Use description slot",
|
|
144
|
-
table: {
|
|
145
|
-
category: "Slots",
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
useDescriptionHtmlSlot: {
|
|
149
|
-
control: "boolean",
|
|
150
|
-
description: "Use description HTML slot",
|
|
151
|
-
table: {
|
|
152
|
-
category: "Slots",
|
|
153
|
-
},
|
|
154
|
-
},
|
|
155
|
-
descriptionContent: {
|
|
156
|
-
control: "text",
|
|
157
|
-
description: "Content for description slot",
|
|
158
|
-
table: {
|
|
159
|
-
category: "Slots",
|
|
160
|
-
},
|
|
161
|
-
},
|
|
162
|
-
descriptionHtmlContent: {
|
|
163
|
-
control: "text",
|
|
164
|
-
description: "HTML content for description slot",
|
|
165
|
-
table: {
|
|
166
|
-
category: "Slots",
|
|
167
|
-
},
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
|
-
args: {
|
|
171
|
-
modelValue: "",
|
|
172
|
-
type: "text",
|
|
173
|
-
maxlength: 255,
|
|
174
|
-
name: "example-input",
|
|
175
|
-
placeholder: "Enter your text...",
|
|
176
|
-
label: "Text Input",
|
|
177
|
-
errorMessage: "",
|
|
178
|
-
fieldHasError: false,
|
|
179
|
-
required: false,
|
|
180
|
-
theme: "default",
|
|
181
|
-
inputVariant: "normal",
|
|
182
|
-
styleClassPassthrough: [],
|
|
183
|
-
useDescriptionSlot: false,
|
|
184
|
-
useDescriptionHtmlSlot: false,
|
|
185
|
-
descriptionContent: "This is a helpful description",
|
|
186
|
-
descriptionHtmlContent: "<strong>Bold</strong> HTML description",
|
|
187
|
-
},
|
|
188
|
-
} as Meta<typeof StorybookComponent>;
|
|
189
|
-
|
|
190
|
-
// `args` is Storybook's own reactive object — bind to it directly (`args.x`) rather than
|
|
191
|
-
// destructuring it into local variables/refs, which would snapshot the values once at setup()
|
|
192
|
-
// and stop reflecting later Controls-panel changes. `componentArgs` strips the non-prop
|
|
193
|
-
// slot-toggle args so they don't leak onto the component as unknown attributes.
|
|
194
|
-
const Template: StoryFn<InputTextWithLabelStoryArgs> = (args) => ({
|
|
195
|
-
components: { StorybookComponent },
|
|
196
|
-
setup() {
|
|
197
|
-
const componentArgs = computed(() => {
|
|
198
|
-
const { useDescriptionSlot, useDescriptionHtmlSlot, descriptionContent, descriptionHtmlContent, ...rest } = args;
|
|
199
|
-
return rest;
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
return { args, componentArgs };
|
|
203
|
-
},
|
|
204
|
-
template: `
|
|
205
|
-
<StorybookComponent
|
|
206
|
-
v-model="args.modelValue"
|
|
207
|
-
v-bind="componentArgs"
|
|
208
|
-
>
|
|
209
|
-
<template v-if="args.useDescriptionSlot" #description>{{ args.descriptionContent }}</template>
|
|
210
|
-
<template v-if="args.useDescriptionHtmlSlot" #descriptionHtml v-html="args.descriptionHtmlContent"></template>
|
|
211
|
-
</StorybookComponent>
|
|
212
|
-
<div class="mt-4 text-sm text-gray-600">
|
|
213
|
-
Current value: {{ args.modelValue }}
|
|
214
|
-
</div>
|
|
215
|
-
`,
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
export const Default = Template.bind({});
|
|
219
|
-
Default.args = {};
|
|
220
|
-
|
|
221
|
-
export const WithDescription = Template.bind({});
|
|
222
|
-
WithDescription.args = {
|
|
223
|
-
useDescriptionSlot: true,
|
|
224
|
-
label: "Email Address",
|
|
225
|
-
placeholder: "Enter your email address",
|
|
226
|
-
type: "email",
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
export const WithHtmlDescription = Template.bind({});
|
|
230
|
-
WithHtmlDescription.args = {
|
|
231
|
-
useDescriptionHtmlSlot: true,
|
|
232
|
-
label: "Password",
|
|
233
|
-
placeholder: "Enter a strong password",
|
|
234
|
-
type: "password",
|
|
235
|
-
descriptionHtmlContent:
|
|
236
|
-
"Password must contain at least <strong>8 characters</strong> and include <em>uppercase</em>, <em>lowercase</em>, and <em>numbers</em>",
|
|
237
|
-
};
|
|
238
|
-
|
|
239
|
-
export const Required = Template.bind({});
|
|
240
|
-
Required.args = {
|
|
241
|
-
required: true,
|
|
242
|
-
label: "Full Name",
|
|
243
|
-
placeholder: "Enter your full name",
|
|
244
|
-
useDescriptionSlot: true,
|
|
245
|
-
descriptionContent: "This field is required",
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
export const WithError = Template.bind({});
|
|
249
|
-
WithError.args = {
|
|
250
|
-
fieldHasError: true,
|
|
251
|
-
errorMessage: "Please enter a valid email address",
|
|
252
|
-
theme: "error",
|
|
253
|
-
label: "Email Address",
|
|
254
|
-
placeholder: "Enter your email",
|
|
255
|
-
type: "email",
|
|
256
|
-
modelValue: "invalid-email",
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
export const Outlined = Template.bind({});
|
|
260
|
-
Outlined.args = {
|
|
261
|
-
inputVariant: "outlined",
|
|
262
|
-
label: "Phone Number",
|
|
263
|
-
placeholder: "Enter your phone number",
|
|
264
|
-
type: "tel",
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
export const Underlined = Template.bind({});
|
|
268
|
-
Underlined.args = {
|
|
269
|
-
inputVariant: "underlined",
|
|
270
|
-
label: "Website URL",
|
|
271
|
-
placeholder: "https://example.com",
|
|
272
|
-
type: "url",
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
export const DateWithMinMax = Template.bind({});
|
|
276
|
-
DateWithMinMax.args = {
|
|
277
|
-
type: "date",
|
|
278
|
-
label: "Appointment date",
|
|
279
|
-
name: "appointmentDate",
|
|
280
|
-
// Prevents picking a date in the past in the native browser date picker.
|
|
281
|
-
min: new Date().toISOString().split("T")[0],
|
|
282
|
-
max: "2027-12-31",
|
|
283
|
-
};
|
|
284
|
-
|
|
285
|
-
export const AllThemes = Template.bind({});
|
|
286
|
-
AllThemes.storyName = "All Themes";
|
|
287
|
-
AllThemes.args = {};
|
|
288
|
-
AllThemes.render = (args) => ({
|
|
289
|
-
components: { StorybookComponent },
|
|
290
|
-
setup() {
|
|
291
|
-
const themes: FormUiTheme[] = ["default", "success", "error", "warning"];
|
|
292
|
-
const inputValues = reactive(
|
|
293
|
-
themes.reduce(
|
|
294
|
-
(acc, theme) => ({
|
|
295
|
-
...acc,
|
|
296
|
-
[theme]: "",
|
|
297
|
-
}),
|
|
298
|
-
{} as Record<string, string>
|
|
299
|
-
)
|
|
300
|
-
);
|
|
301
|
-
|
|
302
|
-
return {
|
|
303
|
-
themes,
|
|
304
|
-
inputValues,
|
|
305
|
-
args,
|
|
306
|
-
};
|
|
307
|
-
},
|
|
308
|
-
template: `
|
|
309
|
-
<div class="space-y-6">
|
|
310
|
-
<div v-for="theme in themes" :key="theme" class="space-y-2">
|
|
311
|
-
<h3 class="text-lg font-semibold capitalize">{{ theme }} Theme</h3>
|
|
312
|
-
<StorybookComponent
|
|
313
|
-
v-model="inputValues[theme]"
|
|
314
|
-
v-bind="{ ...args, theme, label: theme + ' Input', name: theme + '-input' }"
|
|
315
|
-
/>
|
|
316
|
-
</div>
|
|
317
|
-
</div>
|
|
318
|
-
`,
|
|
319
|
-
});
|
|
@@ -1,75 +0,0 @@
|
|
|
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 "../InputTextAsNumberWithLabel.vue";
|
|
5
|
-
|
|
6
|
-
const initialPropsData = {
|
|
7
|
-
name: "quantity",
|
|
8
|
-
label: "Quantity",
|
|
9
|
-
errorMessage: "",
|
|
10
|
-
min: 0,
|
|
11
|
-
max: 10,
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
let wrapper: VueWrapper<InstanceType<typeof ComponentUnderTest>>;
|
|
15
|
-
const wrapperFactory = (propsData = {}) => {
|
|
16
|
-
const mockPropsData = { ...initialPropsData, ...propsData };
|
|
17
|
-
|
|
18
|
-
return mountSuspended(ComponentUnderTest, {
|
|
19
|
-
attachTo: document.body,
|
|
20
|
-
props: mockPropsData,
|
|
21
|
-
slots: {
|
|
22
|
-
left: () => "−",
|
|
23
|
-
right: () => "+",
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
describe("InputTextAsNumberWithLabel", () => {
|
|
29
|
-
it("mounts without error", async () => {
|
|
30
|
-
wrapper = await wrapperFactory();
|
|
31
|
-
expect(wrapper.vm).toBeTruthy();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it("uses the default step button text", async () => {
|
|
35
|
-
wrapper = await wrapperFactory();
|
|
36
|
-
|
|
37
|
-
const [stepDown, stepUp] = wrapper.findAll("button");
|
|
38
|
-
expect(stepDown?.text()).toContain("Step down");
|
|
39
|
-
expect(stepUp?.text()).toContain("Step up");
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it("uses custom stepDownText/stepUpText when provided", async () => {
|
|
43
|
-
wrapper = await wrapperFactory({
|
|
44
|
-
stepDownText: "Diminuer",
|
|
45
|
-
stepUpText: "Augmenter",
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
const [stepDown, stepUp] = wrapper.findAll("button");
|
|
49
|
-
expect(stepDown?.text()).toContain("Diminuer");
|
|
50
|
-
expect(stepUp?.text()).toContain("Augmenter");
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("increments the value when the step-up button is clicked", async () => {
|
|
54
|
-
wrapper = await wrapperFactory({ modelValue: 5 });
|
|
55
|
-
|
|
56
|
-
const [, stepUp] = wrapper.findAll("button");
|
|
57
|
-
await stepUp?.trigger("click");
|
|
58
|
-
|
|
59
|
-
expect(wrapper.emitted("update:modelValue")?.[0]).toEqual([6]);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it("forwards the input-text-as-number marker class down to InputTextCore's own input", async () => {
|
|
63
|
-
// Regression test: the marker classes toggled via updateElementClasses() must reach the
|
|
64
|
-
// inner <input> (not just the outer wrapper), or the scoped .input-text-core.input-text-as-number
|
|
65
|
-
// CSS (compact fit-content width, centered text) silently never matches.
|
|
66
|
-
wrapper = await wrapperFactory();
|
|
67
|
-
expect(wrapper.find("input").classes()).toContain("input-text-as-number");
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it("still forwards a consumer-supplied styleClassPassthrough down to the input", async () => {
|
|
71
|
-
wrapper = await wrapperFactory({ styleClassPassthrough: ["custom-number-class"] });
|
|
72
|
-
expect(wrapper.find("input").classes()).toContain("custom-number-class");
|
|
73
|
-
expect(wrapper.find("input").classes()).toContain("input-text-as-number");
|
|
74
|
-
});
|
|
75
|
-
});
|