srcdev-nuxt-components 9.4.5 → 9.4.7
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/commands/migrate-component.md +74 -5
- package/.claude/component-ledger/audit.json +1 -1
- package/.claude/component-ledger/build.mjs +27 -0
- package/.claude/component-ledger/output.html +8 -3
- package/.claude/component-ledger/template.html +7 -2
- package/.claude/skills/components/breadcrumb.md +1 -0
- package/.claude/skills/components/column-flow-grid.md +93 -0
- package/.claude/skills/components/content-docs.md +2 -0
- package/.claude/skills/components/display-tooltip-defined.md +3 -0
- package/.claude/skills/components/display-tooltip.md +1 -0
- package/.claude/skills/components/entry-animation.md +88 -0
- package/.claude/skills/components/input-copy.md +2 -0
- package/.claude/skills/components/input-text-core.md +186 -0
- package/.claude/skills/components/marquee-scroller.md +130 -0
- package/.claude/skills/components/masonry-grid.md +153 -0
- package/.claude/skills/components/navigation-items.md +1 -0
- package/.claude/skills/components/pop-over.md +100 -0
- package/.claude/skills/components/responsive-header.md +3 -0
- package/.claude/skills/components/site-header.md +4 -0
- package/.claude/skills/components/site-navigation.md +1 -0
- package/.claude/skills/components/tab-navigation.md +1 -0
- package/.claude/skills/index.md +7 -1
- package/.vscode/srcdev-component-breadcrumb.code-snippets +13 -0
- package/.vscode/srcdev-component-column-flow-grid.code-snippets +39 -0
- package/.vscode/srcdev-component-content-docs.code-snippets +18 -0
- package/.vscode/srcdev-component-display-tooltip-defined.code-snippets +15 -0
- package/.vscode/srcdev-component-display-tooltip.code-snippets +11 -0
- package/.vscode/srcdev-component-entry-animation.code-snippets +29 -0
- package/.vscode/srcdev-component-input-text.code-snippets +107 -0
- package/.vscode/srcdev-component-marquee-scroller.code-snippets +93 -0
- package/.vscode/srcdev-component-masonry-grid.code-snippets +51 -0
- package/.vscode/srcdev-component-pop-over.code-snippets +44 -0
- package/.vscode/srcdev-component-responsive-header.code-snippets +12 -0
- package/.vscode/srcdev-component-site-header.code-snippets +17 -0
- package/.vscode/srcdev-component-site-navigation.code-snippets +30 -0
- package/.vscode/srcdev-component-slider-gallery.code-snippets +38 -0
- package/.vscode/srcdev-component-tab-navigation.code-snippets +30 -0
- package/app/components/01.atoms/animations/entry/EntryAnimation.vue +7 -1
- package/app/components/01.atoms/animations/entry/stories/EntryAnimation.stories.ts +47 -0
- package/app/components/01.atoms/animations/entry/tests/EntryAnimation.spec.ts +57 -0
- package/app/components/01.atoms/animations/marquee-scroller/CONSUMER-STYLING.md +94 -0
- package/app/components/01.atoms/animations/marquee-scroller/MarqueeScroller.vue +331 -0
- package/app/components/01.atoms/animations/marquee-scroller/stories/MarqueeScroller.stories.ts +151 -0
- package/app/components/01.atoms/animations/marquee-scroller/tests/MarqueeScroller.spec.ts +315 -0
- package/app/components/01.atoms/animations/marquee-scroller/tests/__snapshots__/MarqueeScroller.spec.ts.snap +28 -0
- package/app/components/01.atoms/canvas-switcher/stories/CanvasSwitcher.stories.ts +9 -10
- package/app/components/01.atoms/content-wrappers/docs-pages/ContentDocs.vue +8 -2
- package/app/components/01.atoms/content-wrappers/docs-pages/stories/ContentDocs.stories.ts +12 -13
- package/app/components/01.atoms/display-tooltip/DisplayTooltip.vue +4 -1
- package/app/components/01.atoms/grids/column-flow-grid/CONSUMER-STYLING.md +33 -0
- package/app/components/01.atoms/grids/column-flow-grid/ColumnFlowGrid.vue +55 -0
- package/app/components/01.atoms/grids/column-flow-grid/stories/ColumnFlowGrid.stories.ts +178 -0
- package/app/components/01.atoms/grids/column-flow-grid/tests/ColumnFlowGrid.spec.ts +75 -0
- package/app/components/01.atoms/grids/masonry-grid/CONSUMER-STYLING.md +35 -0
- package/app/components/01.atoms/grids/masonry-grid/MasonryGrid.vue +178 -0
- package/app/components/01.atoms/grids/masonry-grid/stories/MasonryGrid.stories.ts +199 -0
- package/app/components/01.atoms/grids/masonry-grid/tests/MasonryGrid.spec.ts +158 -0
- package/app/components/01.atoms/navigation/breadcrumb/Breadcrumb.vue +4 -1
- package/app/components/01.atoms/pop-over/CONSUMER-STYLING.md +54 -0
- package/app/components/01.atoms/pop-over/PopOver.vue +201 -0
- package/app/components/01.atoms/pop-over/stories/PopOver.stories.ts +141 -0
- package/app/components/01.atoms/pop-over/tests/PopOver.spec.ts +195 -0
- package/app/components/01.atoms/pop-over/tests/__snapshots__/PopOver.spec.ts.snap +11 -0
- package/app/components/02.molecules/action-menu/stories/ActionMenu.stories.ts +8 -2
- package/app/components/02.molecules/display-tooltip-defined/DisplayTooltipDefined.vue +16 -3
- package/app/components/02.molecules/display-tooltip-defined/tests/__snapshots__/DisplayTooltipDefined.spec.ts.snap +1 -1
- package/app/components/02.molecules/input-copy/InputCopy.vue +14 -0
- package/app/components/02.molecules/input-copy/stories/InputCopy.stories.ts +15 -0
- package/app/components/02.molecules/input-copy/tests/InputCopy.spec.ts +41 -0
- package/app/components/02.molecules/navigation/site-navigation/SiteNavigation.vue +4 -1
- package/app/components/02.molecules/navigation/tab-navigation/TabNavigation.vue +4 -1
- package/app/components/03.organisms/image-galleries/slider-gallery/SliderGallery.vue +16 -4
- package/app/components/03.organisms/responsive-header/NavigationItems.vue +4 -1
- package/app/components/03.organisms/responsive-header/ResponsiveHeader.vue +16 -3
- package/app/components/03.organisms/site-header/SiteHeader.vue +16 -1
- package/app/components/03.organisms/site-header/tests/__snapshots__/SiteHeader.spec.ts.snap +1 -1
- package/app/components/05.forms/input-select/InputSelectCore.vue +1 -1
- package/app/components/05.forms/input-text/InputTextCore.vue +6 -0
- package/app/components/05.forms/input-text/stories/InputPasswordWithLabel.stories.ts +49 -20
- package/app/components/05.forms/input-text/stories/InputTextAsNumberWithLabel.stories.ts +41 -22
- package/app/components/05.forms/input-text/stories/InputTextCore.stories.ts +48 -22
- package/app/components/05.forms/input-text/stories/InputTextWithLabel.stories.ts +43 -17
- package/app/components/05.forms/input-text/tests/InputPasswordWithLabel.spec.ts +56 -0
- package/app/components/05.forms/input-text/tests/InputTextAsNumberWithLabel.spec.ts +61 -0
- package/app/components/05.forms/input-text/tests/InputTextCore.spec.ts +60 -0
- package/app/components/05.forms/input-text/tests/InputTextWithLabel.spec.ts +46 -0
- package/app/components/05.forms/input-text/variants/InputPasswordWithLabel.vue +7 -1
- package/app/components/05.forms/input-text/variants/InputTextAsNumberWithLabel.vue +8 -2
- package/app/components/05.forms/input-text/variants/InputTextWithLabel.vue +6 -0
- package/app/components/05.forms/input-textarea/stories/InputTextareaCore.stories.ts +20 -20
- package/app/components/05.forms/input-textarea/stories/InputTextareaWithLabel.stories.ts +32 -26
- package/app/types/components/index.ts +1 -0
- package/app/types/components/marquee-scroller.d.ts +10 -0
- package/app/types/forms/types.forms.d.ts +1 -1
- package/package.json +1 -1
- package/app/components/marquee-scroller/MarqueeScroller.vue +0 -289
- package/app/components/masonry-grid/MasonryGrid.vue +0 -68
- package/app/components/masonry-grid-ordered/MasonryGridOrdered.vue +0 -163
- package/app/components/masonry-grid-ordered/MasonryGridOrderedGridExperiment.vue +0 -259
- package/app/components/masonry-grid-ordered/stories/MasonryGridOrdered.stories.ts +0 -354
- package/app/components/masonry-grid-sorted/MasonryGridSorted.vue +0 -120
- package/app/components/pop-over/PopOver.vue +0 -90
- package/app/layouts/default.vue +0 -308
- package/app/layouts/site-navigation-demo.vue +0 -188
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
:name
|
|
28
28
|
:required
|
|
29
29
|
:maxlength
|
|
30
|
+
:min
|
|
31
|
+
:max
|
|
30
32
|
:class="['input-text-core', elementClasses, { dirty: isDirty }, { active: isActive }]"
|
|
31
33
|
:aria-invalid="fieldHasError ? 'true' : undefined"
|
|
32
34
|
:aria-describedby
|
|
@@ -49,6 +51,10 @@ interface Props {
|
|
|
49
51
|
type?: InputTypesText;
|
|
50
52
|
inputmode?: InputMode;
|
|
51
53
|
maxlength?: number;
|
|
54
|
+
/** Passed straight through to the native input — meaningful for type="date"/"number" etc. */
|
|
55
|
+
min?: string | number;
|
|
56
|
+
/** Passed straight through to the native input — meaningful for type="date"/"number" etc. */
|
|
57
|
+
max?: string | number;
|
|
52
58
|
id: string;
|
|
53
59
|
name: string;
|
|
54
60
|
required?: boolean;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Meta, StoryFn } from "@nuxtjs/storybook";
|
|
2
|
+
import { computed } from "vue";
|
|
2
3
|
import StorybookComponent from "../variants/InputPasswordWithLabel.vue";
|
|
3
4
|
import type { FormUiTheme, InputUiVariant } from "~/types/forms/types.forms.d";
|
|
4
5
|
|
|
@@ -12,12 +13,14 @@ interface InputPasswordWithLabelStoryArgs {
|
|
|
12
13
|
fieldHasError: boolean;
|
|
13
14
|
required: boolean;
|
|
14
15
|
theme: FormUiTheme;
|
|
15
|
-
|
|
16
|
+
inputVariant: InputUiVariant;
|
|
16
17
|
styleClassPassthrough: string[];
|
|
17
18
|
useDescriptionSlot: boolean;
|
|
18
19
|
useDescriptionHtmlSlot: boolean;
|
|
19
20
|
descriptionContent: string;
|
|
20
21
|
descriptionHtmlContent: string;
|
|
22
|
+
showPasswordText: string;
|
|
23
|
+
hidePasswordText: string;
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
export default {
|
|
@@ -95,7 +98,7 @@ export default {
|
|
|
95
98
|
category: "Styling",
|
|
96
99
|
},
|
|
97
100
|
},
|
|
98
|
-
|
|
101
|
+
inputVariant: {
|
|
99
102
|
control: { type: "select" },
|
|
100
103
|
options: ["normal", "outlined", "underlined"],
|
|
101
104
|
description: "Input variant style",
|
|
@@ -140,6 +143,22 @@ export default {
|
|
|
140
143
|
category: "Slots",
|
|
141
144
|
},
|
|
142
145
|
},
|
|
146
|
+
|
|
147
|
+
// Localisation
|
|
148
|
+
showPasswordText: {
|
|
149
|
+
control: "text",
|
|
150
|
+
description: "Accessible name for the toggle button while the password is hidden",
|
|
151
|
+
table: {
|
|
152
|
+
category: "Localisation",
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
hidePasswordText: {
|
|
156
|
+
control: "text",
|
|
157
|
+
description: "Accessible name for the toggle button while the password is visible",
|
|
158
|
+
table: {
|
|
159
|
+
category: "Localisation",
|
|
160
|
+
},
|
|
161
|
+
},
|
|
143
162
|
},
|
|
144
163
|
args: {
|
|
145
164
|
modelValue: "",
|
|
@@ -158,34 +177,35 @@ export default {
|
|
|
158
177
|
descriptionContent: "Password should be at least 8 characters long",
|
|
159
178
|
descriptionHtmlContent:
|
|
160
179
|
"Password must contain: <strong>8+ characters</strong>, <em>uppercase</em>, <em>lowercase</em>, <em>numbers</em>",
|
|
180
|
+
showPasswordText: "Show password",
|
|
181
|
+
hidePasswordText: "Hide password",
|
|
161
182
|
},
|
|
162
183
|
} as Meta<typeof StorybookComponent>;
|
|
163
184
|
|
|
185
|
+
// `args` is Storybook's own reactive object — bind to it directly (`args.x`) rather than
|
|
186
|
+
// destructuring it into local variables/refs, which would snapshot the values once at setup()
|
|
187
|
+
// and stop reflecting later Controls-panel changes. `componentArgs` strips the non-prop
|
|
188
|
+
// slot-toggle args so they don't leak onto the component as unknown attributes.
|
|
164
189
|
const Template: StoryFn<InputPasswordWithLabelStoryArgs> = (args) => ({
|
|
165
190
|
components: { StorybookComponent },
|
|
166
191
|
setup() {
|
|
167
|
-
const
|
|
168
|
-
|
|
192
|
+
const componentArgs = computed(() => {
|
|
193
|
+
const { useDescriptionSlot, useDescriptionHtmlSlot, descriptionContent, descriptionHtmlContent, ...rest } = args;
|
|
194
|
+
return rest;
|
|
195
|
+
});
|
|
169
196
|
|
|
170
|
-
return {
|
|
171
|
-
passwordValue,
|
|
172
|
-
args: otherArgs,
|
|
173
|
-
descriptionContent: args.descriptionContent,
|
|
174
|
-
descriptionHtmlContent: args.descriptionHtmlContent,
|
|
175
|
-
useDescriptionSlot: args.useDescriptionSlot,
|
|
176
|
-
useDescriptionHtmlSlot: args.useDescriptionHtmlSlot,
|
|
177
|
-
};
|
|
197
|
+
return { args, componentArgs };
|
|
178
198
|
},
|
|
179
199
|
template: `
|
|
180
200
|
<StorybookComponent
|
|
181
|
-
v-model="
|
|
182
|
-
v-bind="
|
|
201
|
+
v-model="args.modelValue"
|
|
202
|
+
v-bind="componentArgs"
|
|
183
203
|
>
|
|
184
|
-
<template v-if="useDescriptionSlot" #description>{{ descriptionContent }}</template>
|
|
185
|
-
<template v-if="useDescriptionHtmlSlot" #descriptionHtml v-html="descriptionHtmlContent"></template>
|
|
204
|
+
<template v-if="args.useDescriptionSlot" #description>{{ args.descriptionContent }}</template>
|
|
205
|
+
<template v-if="args.useDescriptionHtmlSlot" #descriptionHtml v-html="args.descriptionHtmlContent"></template>
|
|
186
206
|
</StorybookComponent>
|
|
187
207
|
<div class="mt-4 text-sm text-gray-600">
|
|
188
|
-
Current value: {{
|
|
208
|
+
Current value: {{ args.modelValue ? '•'.repeat(args.modelValue.length) : 'Empty' }}
|
|
189
209
|
</div>
|
|
190
210
|
`,
|
|
191
211
|
});
|
|
@@ -239,18 +259,27 @@ PasswordTooShort.args = {
|
|
|
239
259
|
|
|
240
260
|
export const Outlined = Template.bind({});
|
|
241
261
|
Outlined.args = {
|
|
242
|
-
|
|
262
|
+
inputVariant: "outlined",
|
|
243
263
|
label: "Confirm Password",
|
|
244
264
|
placeholder: "Confirm your password",
|
|
245
265
|
};
|
|
246
266
|
|
|
247
267
|
export const Underlined = Template.bind({});
|
|
248
268
|
Underlined.args = {
|
|
249
|
-
|
|
269
|
+
inputVariant: "underlined",
|
|
250
270
|
label: "Master Password",
|
|
251
271
|
placeholder: "Enter master password",
|
|
252
272
|
};
|
|
253
273
|
|
|
274
|
+
export const LocalisedToggleText = Template.bind({});
|
|
275
|
+
LocalisedToggleText.storyName = "Localised Toggle Text";
|
|
276
|
+
LocalisedToggleText.args = {
|
|
277
|
+
label: "Mot de passe",
|
|
278
|
+
placeholder: "Saisissez votre mot de passe",
|
|
279
|
+
showPasswordText: "Afficher le mot de passe",
|
|
280
|
+
hidePasswordText: "Masquer le mot de passe",
|
|
281
|
+
};
|
|
282
|
+
|
|
254
283
|
export const AllVariants = Template.bind({});
|
|
255
284
|
AllVariants.storyName = "All Input Variants";
|
|
256
285
|
AllVariants.render = (args) => ({
|
|
@@ -279,7 +308,7 @@ AllVariants.render = (args) => ({
|
|
|
279
308
|
<h3 class="text-lg font-semibold capitalize">{{ variant }} Variant</h3>
|
|
280
309
|
<StorybookComponent
|
|
281
310
|
v-model="passwordValues[variant]"
|
|
282
|
-
v-bind="{ ...args,
|
|
311
|
+
v-bind="{ ...args, inputVariant: variant, label: variant + ' Password', name: variant + '-password' }"
|
|
283
312
|
/>
|
|
284
313
|
</div>
|
|
285
314
|
</div>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Meta, StoryFn } from "@nuxtjs/storybook";
|
|
2
|
+
import { computed } from "vue";
|
|
2
3
|
import StorybookComponent from "../variants/InputTextAsNumberWithLabel.vue";
|
|
3
4
|
import type { FormUiTheme, InputUiVariant } from "~/types/forms/types.forms.d";
|
|
4
5
|
|
|
@@ -12,7 +13,7 @@ interface InputTextAsNumberWithLabelStoryArgs {
|
|
|
12
13
|
fieldHasError: boolean;
|
|
13
14
|
required: boolean;
|
|
14
15
|
theme: FormUiTheme;
|
|
15
|
-
|
|
16
|
+
inputVariant: InputUiVariant;
|
|
16
17
|
min: number;
|
|
17
18
|
max: number;
|
|
18
19
|
step: number;
|
|
@@ -23,6 +24,8 @@ interface InputTextAsNumberWithLabelStoryArgs {
|
|
|
23
24
|
useRightSlot: boolean;
|
|
24
25
|
leftSlotContent: string;
|
|
25
26
|
rightSlotContent: string;
|
|
27
|
+
stepDownText: string;
|
|
28
|
+
stepUpText: string;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
export default {
|
|
@@ -123,7 +126,7 @@ export default {
|
|
|
123
126
|
category: "Styling",
|
|
124
127
|
},
|
|
125
128
|
},
|
|
126
|
-
|
|
129
|
+
inputVariant: {
|
|
127
130
|
control: { type: "select" },
|
|
128
131
|
options: ["normal", "outlined", "underlined"],
|
|
129
132
|
description: "Input variant style",
|
|
@@ -182,6 +185,22 @@ export default {
|
|
|
182
185
|
category: "Slots",
|
|
183
186
|
},
|
|
184
187
|
},
|
|
188
|
+
|
|
189
|
+
// Localisation
|
|
190
|
+
stepDownText: {
|
|
191
|
+
control: "text",
|
|
192
|
+
description: "Accessible name for the decrement button",
|
|
193
|
+
table: {
|
|
194
|
+
category: "Localisation",
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
stepUpText: {
|
|
198
|
+
control: "text",
|
|
199
|
+
description: "Accessible name for the increment button",
|
|
200
|
+
table: {
|
|
201
|
+
category: "Localisation",
|
|
202
|
+
},
|
|
203
|
+
},
|
|
185
204
|
},
|
|
186
205
|
args: {
|
|
187
206
|
modelValue: undefined,
|
|
@@ -204,37 +223,37 @@ export default {
|
|
|
204
223
|
useRightSlot: true,
|
|
205
224
|
leftSlotContent: "−",
|
|
206
225
|
rightSlotContent: "+",
|
|
226
|
+
stepDownText: "Step down",
|
|
227
|
+
stepUpText: "Step up",
|
|
207
228
|
},
|
|
208
229
|
} as Meta<typeof StorybookComponent>;
|
|
209
230
|
|
|
231
|
+
// `args` is Storybook's own reactive object — bind to it directly (`args.x`) rather than
|
|
232
|
+
// destructuring it into local variables/refs, which would snapshot the values once at setup()
|
|
233
|
+
// and stop reflecting later Controls-panel changes. `componentArgs` strips the non-prop
|
|
234
|
+
// slot-toggle args so they don't leak onto the component as unknown attributes.
|
|
210
235
|
const Template: StoryFn<InputTextAsNumberWithLabelStoryArgs> = (args) => ({
|
|
211
236
|
components: { StorybookComponent },
|
|
212
237
|
setup() {
|
|
213
|
-
const
|
|
214
|
-
|
|
238
|
+
const componentArgs = computed(() => {
|
|
239
|
+
const { useDescriptionSlot, descriptionContent, useLeftSlot, useRightSlot, leftSlotContent, rightSlotContent, ...rest } =
|
|
240
|
+
args;
|
|
241
|
+
return rest;
|
|
242
|
+
});
|
|
215
243
|
|
|
216
|
-
return {
|
|
217
|
-
numberValue,
|
|
218
|
-
args: otherArgs,
|
|
219
|
-
descriptionContent: args.descriptionContent,
|
|
220
|
-
useDescriptionSlot: args.useDescriptionSlot,
|
|
221
|
-
leftSlotContent: args.leftSlotContent,
|
|
222
|
-
rightSlotContent: args.rightSlotContent,
|
|
223
|
-
useLeftSlot: args.useLeftSlot,
|
|
224
|
-
useRightSlot: args.useRightSlot,
|
|
225
|
-
};
|
|
244
|
+
return { args, componentArgs };
|
|
226
245
|
},
|
|
227
246
|
template: `
|
|
228
247
|
<StorybookComponent
|
|
229
|
-
v-model="
|
|
230
|
-
v-bind="
|
|
248
|
+
v-model="args.modelValue"
|
|
249
|
+
v-bind="componentArgs"
|
|
231
250
|
>
|
|
232
|
-
<template v-if="useDescriptionSlot" #description>{{ descriptionContent }}</template>
|
|
233
|
-
<template v-if="useLeftSlot" #left>{{ leftSlotContent }}</template>
|
|
234
|
-
<template v-if="useRightSlot" #right>{{ rightSlotContent }}</template>
|
|
251
|
+
<template v-if="args.useDescriptionSlot" #description>{{ args.descriptionContent }}</template>
|
|
252
|
+
<template v-if="args.useLeftSlot" #left>{{ args.leftSlotContent }}</template>
|
|
253
|
+
<template v-if="args.useRightSlot" #right>{{ args.rightSlotContent }}</template>
|
|
235
254
|
</StorybookComponent>
|
|
236
255
|
<div class="mt-4 text-sm text-gray-600">
|
|
237
|
-
Current value: {{
|
|
256
|
+
Current value: {{ args.modelValue ?? 'undefined' }}
|
|
238
257
|
</div>
|
|
239
258
|
`,
|
|
240
259
|
});
|
|
@@ -331,7 +350,7 @@ Required.args = {
|
|
|
331
350
|
export const Outlined = Template.bind({});
|
|
332
351
|
Outlined.args = {
|
|
333
352
|
modelValue: 42,
|
|
334
|
-
|
|
353
|
+
inputVariant: "outlined",
|
|
335
354
|
label: "Temperature (°C)",
|
|
336
355
|
placeholder: "Enter temperature",
|
|
337
356
|
min: -50,
|
|
@@ -344,7 +363,7 @@ Outlined.args = {
|
|
|
344
363
|
export const Underlined = Template.bind({});
|
|
345
364
|
Underlined.args = {
|
|
346
365
|
modelValue: 10,
|
|
347
|
-
|
|
366
|
+
inputVariant: "underlined",
|
|
348
367
|
label: "Items Count",
|
|
349
368
|
placeholder: "Number of items",
|
|
350
369
|
min: 0,
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { Meta, StoryFn } from "@nuxtjs/storybook";
|
|
2
|
+
import { computed } from "vue";
|
|
2
3
|
import StorybookComponent from "../InputTextCore.vue";
|
|
3
4
|
import type { FormUiTheme, InputUiVariant } from "~/types/forms/types.forms.d";
|
|
4
5
|
|
|
5
6
|
interface InputTextCoreStoryArgs {
|
|
6
7
|
modelValue: string;
|
|
7
|
-
type: "text" | "email" | "password" | "tel" | "url" | "search";
|
|
8
|
+
type: "text" | "email" | "password" | "tel" | "url" | "search" | "date";
|
|
8
9
|
maxlength: number;
|
|
10
|
+
min: string | number;
|
|
11
|
+
max: string | number;
|
|
9
12
|
name: string;
|
|
10
13
|
placeholder: string;
|
|
11
14
|
label: string;
|
|
@@ -13,7 +16,7 @@ interface InputTextCoreStoryArgs {
|
|
|
13
16
|
fieldHasError: boolean;
|
|
14
17
|
required: boolean;
|
|
15
18
|
theme: FormUiTheme;
|
|
16
|
-
|
|
19
|
+
inputVariant: InputUiVariant;
|
|
17
20
|
styleClassPassthrough: string[];
|
|
18
21
|
leftSlotContent: string;
|
|
19
22
|
rightSlotContent: string;
|
|
@@ -37,7 +40,7 @@ export default {
|
|
|
37
40
|
// Basic Configuration
|
|
38
41
|
type: {
|
|
39
42
|
control: { type: "select" },
|
|
40
|
-
options: ["text", "email", "password", "tel", "url", "search"],
|
|
43
|
+
options: ["text", "email", "password", "tel", "url", "search", "date"],
|
|
41
44
|
description: "Input type",
|
|
42
45
|
table: {
|
|
43
46
|
category: "Basic",
|
|
@@ -50,6 +53,20 @@ export default {
|
|
|
50
53
|
category: "Basic",
|
|
51
54
|
},
|
|
52
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
|
+
},
|
|
53
70
|
name: {
|
|
54
71
|
control: "text",
|
|
55
72
|
description: "Input name attribute",
|
|
@@ -104,7 +121,7 @@ export default {
|
|
|
104
121
|
category: "Styling",
|
|
105
122
|
},
|
|
106
123
|
},
|
|
107
|
-
|
|
124
|
+
inputVariant: {
|
|
108
125
|
control: { type: "select" },
|
|
109
126
|
options: ["normal", "outlined", "underlined"],
|
|
110
127
|
description: "Input variant style",
|
|
@@ -170,31 +187,30 @@ export default {
|
|
|
170
187
|
},
|
|
171
188
|
} as Meta<typeof StorybookComponent>;
|
|
172
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.
|
|
173
194
|
const Template: StoryFn<InputTextCoreStoryArgs> = (args) => ({
|
|
174
195
|
components: { StorybookComponent },
|
|
175
196
|
setup() {
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
leftSlotContent: args.leftSlotContent,
|
|
183
|
-
rightSlotContent: args.rightSlotContent,
|
|
184
|
-
useLeftSlot: args.useLeftSlot,
|
|
185
|
-
useRightSlot: args.useRightSlot,
|
|
186
|
-
};
|
|
197
|
+
const componentArgs = computed(() => {
|
|
198
|
+
const { useLeftSlot, useRightSlot, leftSlotContent, rightSlotContent, ...rest } = args;
|
|
199
|
+
return rest;
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
return { args, componentArgs };
|
|
187
203
|
},
|
|
188
204
|
template: `
|
|
189
205
|
<StorybookComponent
|
|
190
|
-
v-model="
|
|
191
|
-
v-bind="
|
|
206
|
+
v-model="args.modelValue"
|
|
207
|
+
v-bind="componentArgs"
|
|
192
208
|
>
|
|
193
|
-
<template v-if="useLeftSlot" #left>{{ leftSlotContent }}</template>
|
|
194
|
-
<template v-if="useRightSlot" #right>{{ rightSlotContent }}</template>
|
|
209
|
+
<template v-if="args.useLeftSlot" #left>{{ args.leftSlotContent }}</template>
|
|
210
|
+
<template v-if="args.useRightSlot" #right>{{ args.rightSlotContent }}</template>
|
|
195
211
|
</StorybookComponent>
|
|
196
212
|
<div class="mt-4 text-sm text-gray-600">
|
|
197
|
-
Current value: {{
|
|
213
|
+
Current value: {{ args.modelValue }}
|
|
198
214
|
</div>
|
|
199
215
|
`,
|
|
200
216
|
});
|
|
@@ -230,12 +246,22 @@ WithSlots.args = {
|
|
|
230
246
|
|
|
231
247
|
export const Outlined = Template.bind({});
|
|
232
248
|
Outlined.args = {
|
|
233
|
-
|
|
249
|
+
inputVariant: "outlined",
|
|
234
250
|
placeholder: "Outlined input",
|
|
235
251
|
};
|
|
236
252
|
|
|
237
253
|
export const Underlined = Template.bind({});
|
|
238
254
|
Underlined.args = {
|
|
239
|
-
|
|
255
|
+
inputVariant: "underlined",
|
|
240
256
|
placeholder: "Underlined input",
|
|
241
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,11 +1,14 @@
|
|
|
1
1
|
import type { Meta, StoryFn } from "@nuxtjs/storybook";
|
|
2
|
+
import { computed } from "vue";
|
|
2
3
|
import StorybookComponent from "../variants/InputTextWithLabel.vue";
|
|
3
4
|
import type { FormUiTheme, InputUiVariant } from "~/types/forms/types.forms.d";
|
|
4
5
|
|
|
5
6
|
interface InputTextWithLabelStoryArgs {
|
|
6
7
|
modelValue: string;
|
|
7
|
-
type: "text" | "email" | "password" | "tel" | "url" | "search";
|
|
8
|
+
type: "text" | "email" | "password" | "tel" | "url" | "search" | "date";
|
|
8
9
|
maxlength: number;
|
|
10
|
+
min: string | number;
|
|
11
|
+
max: string | number;
|
|
9
12
|
name: string;
|
|
10
13
|
placeholder: string;
|
|
11
14
|
label: string;
|
|
@@ -37,7 +40,7 @@ export default {
|
|
|
37
40
|
// Basic Configuration
|
|
38
41
|
type: {
|
|
39
42
|
control: { type: "select" },
|
|
40
|
-
options: ["text", "email", "password", "tel", "url", "search"],
|
|
43
|
+
options: ["text", "email", "password", "tel", "url", "search", "date"],
|
|
41
44
|
description: "Input type",
|
|
42
45
|
table: {
|
|
43
46
|
category: "Basic",
|
|
@@ -50,6 +53,20 @@ export default {
|
|
|
50
53
|
category: "Basic",
|
|
51
54
|
},
|
|
52
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
|
+
},
|
|
53
70
|
name: {
|
|
54
71
|
control: "text",
|
|
55
72
|
description: "Input name attribute",
|
|
@@ -170,31 +187,30 @@ export default {
|
|
|
170
187
|
},
|
|
171
188
|
} as Meta<typeof StorybookComponent>;
|
|
172
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.
|
|
173
194
|
const Template: StoryFn<InputTextWithLabelStoryArgs> = (args) => ({
|
|
174
195
|
components: { StorybookComponent },
|
|
175
196
|
setup() {
|
|
176
|
-
const
|
|
177
|
-
|
|
197
|
+
const componentArgs = computed(() => {
|
|
198
|
+
const { useDescriptionSlot, useDescriptionHtmlSlot, descriptionContent, descriptionHtmlContent, ...rest } = args;
|
|
199
|
+
return rest;
|
|
200
|
+
});
|
|
178
201
|
|
|
179
|
-
return {
|
|
180
|
-
inputValue,
|
|
181
|
-
args: otherArgs,
|
|
182
|
-
descriptionContent: args.descriptionContent,
|
|
183
|
-
descriptionHtmlContent: args.descriptionHtmlContent,
|
|
184
|
-
useDescriptionSlot: args.useDescriptionSlot,
|
|
185
|
-
useDescriptionHtmlSlot: args.useDescriptionHtmlSlot,
|
|
186
|
-
};
|
|
202
|
+
return { args, componentArgs };
|
|
187
203
|
},
|
|
188
204
|
template: `
|
|
189
205
|
<StorybookComponent
|
|
190
|
-
v-model="
|
|
191
|
-
v-bind="
|
|
206
|
+
v-model="args.modelValue"
|
|
207
|
+
v-bind="componentArgs"
|
|
192
208
|
>
|
|
193
|
-
<template v-if="useDescriptionSlot" #description>{{ descriptionContent }}</template>
|
|
194
|
-
<template v-if="useDescriptionHtmlSlot" #descriptionHtml v-html="descriptionHtmlContent"></template>
|
|
209
|
+
<template v-if="args.useDescriptionSlot" #description>{{ args.descriptionContent }}</template>
|
|
210
|
+
<template v-if="args.useDescriptionHtmlSlot" #descriptionHtml v-html="args.descriptionHtmlContent"></template>
|
|
195
211
|
</StorybookComponent>
|
|
196
212
|
<div class="mt-4 text-sm text-gray-600">
|
|
197
|
-
Current value: {{
|
|
213
|
+
Current value: {{ args.modelValue }}
|
|
198
214
|
</div>
|
|
199
215
|
`,
|
|
200
216
|
});
|
|
@@ -256,6 +272,16 @@ Underlined.args = {
|
|
|
256
272
|
type: "url",
|
|
257
273
|
};
|
|
258
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
|
+
|
|
259
285
|
export const AllThemes = Template.bind({});
|
|
260
286
|
AllThemes.storyName = "All Themes";
|
|
261
287
|
AllThemes.args = {};
|
|
@@ -0,0 +1,56 @@
|
|
|
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 "../variants/InputPasswordWithLabel.vue";
|
|
5
|
+
|
|
6
|
+
const initialPropsData = {
|
|
7
|
+
name: "password",
|
|
8
|
+
label: "Password",
|
|
9
|
+
errorMessage: "",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
let wrapper: VueWrapper<InstanceType<typeof ComponentUnderTest>>;
|
|
13
|
+
const wrapperFactory = (propsData = {}) => {
|
|
14
|
+
const mockPropsData = { ...initialPropsData, ...propsData };
|
|
15
|
+
|
|
16
|
+
return mountSuspended(ComponentUnderTest, {
|
|
17
|
+
attachTo: document.body,
|
|
18
|
+
props: mockPropsData,
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
describe("InputPasswordWithLabel", () => {
|
|
23
|
+
it("mounts without error", async () => {
|
|
24
|
+
wrapper = await wrapperFactory();
|
|
25
|
+
expect(wrapper.vm).toBeTruthy();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("defaults to type=password and the default toggle button text", async () => {
|
|
29
|
+
wrapper = await wrapperFactory();
|
|
30
|
+
|
|
31
|
+
expect(wrapper.find("input").attributes("type")).toBe("password");
|
|
32
|
+
expect(wrapper.find("button").text()).toContain("Show password");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("toggles the input type and button text when the toggle button is clicked", async () => {
|
|
36
|
+
wrapper = await wrapperFactory();
|
|
37
|
+
|
|
38
|
+
await wrapper.find("button").trigger("click");
|
|
39
|
+
|
|
40
|
+
expect(wrapper.find("input").attributes("type")).toBe("text");
|
|
41
|
+
expect(wrapper.find("button").text()).toContain("Hide password");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("uses custom showPasswordText/hidePasswordText when provided", async () => {
|
|
45
|
+
wrapper = await wrapperFactory({
|
|
46
|
+
showPasswordText: "Afficher le mot de passe",
|
|
47
|
+
hidePasswordText: "Masquer le mot de passe",
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
expect(wrapper.find("button").text()).toContain("Afficher le mot de passe");
|
|
51
|
+
|
|
52
|
+
await wrapper.find("button").trigger("click");
|
|
53
|
+
|
|
54
|
+
expect(wrapper.find("button").text()).toContain("Masquer le mot de passe");
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
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 "../variants/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
|
+
});
|