srcdev-nuxt-components 9.4.9 → 9.4.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/migrate-component.md +38 -6
- package/.claude/component-ledger/audit.json +1 -1
- package/.claude/component-ledger/build.mjs +26 -0
- package/.claude/component-ledger/output.html +8 -3
- package/.claude/component-ledger/template.html +7 -2
- package/.claude/skills/component-dynamic-slots.md +1 -1
- package/.claude/skills/components/alert-mask-core.md +84 -0
- package/.claude/skills/components/canvas-switcher.md +1 -1
- package/.claude/skills/components/carousel-flip.md +112 -168
- package/.claude/skills/components/container-glow.md +123 -0
- package/.claude/skills/components/dashboard-quad-grid.md +98 -0
- package/.claude/skills/components/dashboard-stats-grid.md +103 -0
- package/.claude/skills/components/display-theme-switch.md +60 -0
- package/.claude/skills/components/glowing-border.md +66 -0
- package/.claude/skills/components/input-number-core.md +214 -0
- package/.claude/skills/components/input-range-core.md +142 -0
- package/.claude/skills/components/input-select-core.md +151 -0
- package/.claude/skills/components/input-text-core.md +16 -0
- package/.claude/skills/components/input-textarea-core.md +154 -0
- package/.claude/skills/components/section-parallax.md +32 -10
- package/.claude/skills/components/select-menu.md +195 -0
- package/.claude/skills/components/toggle-switch-core.md +150 -0
- package/.claude/skills/components/ui-block-decorated.md +56 -0
- package/.claude/skills/components/wipe-away-vertical.md +98 -0
- package/.claude/skills/icon-sets.md +1 -1
- package/.claude/skills/index.md +15 -1
- package/.vscode/css-custom-data.json +38 -0
- package/.vscode/settings.json +3 -0
- package/.vscode/srcdev-component-alert-mask-core.code-snippets +31 -0
- package/.vscode/srcdev-component-carousel-flip.code-snippets +41 -0
- package/.vscode/srcdev-component-container-glow.code-snippets +33 -0
- package/.vscode/srcdev-component-dashboard-quad-grid.code-snippets +36 -0
- package/.vscode/srcdev-component-dashboard-stats-grid.code-snippets +35 -0
- package/.vscode/srcdev-component-display-theme-switch.code-snippets +27 -0
- package/.vscode/srcdev-component-glowing-border.code-snippets +23 -0
- package/.vscode/srcdev-component-input-number.code-snippets +65 -0
- package/.vscode/srcdev-component-input-range.code-snippets +85 -0
- package/.vscode/srcdev-component-input-select.code-snippets +42 -0
- package/.vscode/srcdev-component-input-textarea.code-snippets +40 -0
- package/.vscode/srcdev-component-section-parallax.code-snippets +27 -0
- package/.vscode/srcdev-component-select-menu.code-snippets +65 -0
- package/.vscode/srcdev-component-toggle-switch.code-snippets +49 -0
- package/.vscode/srcdev-component-ui-block-decorated.code-snippets +24 -0
- package/.vscode/srcdev-component-wipe-away-vertical.code-snippets +25 -0
- package/README.md +18 -37
- package/app/components/{alert-mask → 01.atoms/alert-mask}/AlertMaskCore.vue +16 -17
- package/app/components/01.atoms/alert-mask/CONSUMER-STYLING.md +46 -0
- package/app/components/01.atoms/alert-mask/stories/AlertMaskCore.stories.ts +79 -0
- package/app/components/{alert-mask → 01.atoms/alert-mask}/tests/AlertMaskCore.spec.ts +10 -10
- package/app/components/01.atoms/animations/container-glow/CONSUMER-STYLING.md +86 -0
- package/app/components/01.atoms/animations/container-glow/ContainerGlow.vue +299 -0
- package/app/components/01.atoms/animations/container-glow/stories/ContainerGlow.stories.ts +175 -0
- package/app/components/01.atoms/animations/container-glow/tests/ContainerGlow.spec.ts +131 -0
- package/app/components/01.atoms/animations/container-glow/tests/__snapshots__/ContainerGlow.spec.ts.snap +14 -0
- package/app/components/01.atoms/animations/glowing-border/CONSUMER-STYLING.md +61 -0
- package/app/components/01.atoms/animations/glowing-border/GlowingBorder.vue +132 -0
- package/app/components/01.atoms/animations/glowing-border/stories/GlowingBorder.stories.ts +165 -0
- package/app/components/01.atoms/animations/glowing-border/tests/GlowingBorder.spec.ts +94 -0
- package/app/components/01.atoms/animations/glowing-border/tests/__snapshots__/GlowingBorder.spec.ts.snap +3 -0
- package/app/components/01.atoms/animations/marquee-scroller/CONSUMER-STYLING.md +1 -1
- package/app/components/01.atoms/animations/marquee-scroller/MarqueeScroller.vue +1 -1
- package/app/components/01.atoms/animations/marquee-scroller/stories/MarqueeScroller.stories.ts +1 -1
- package/app/components/01.atoms/animations/section-parallax/CONSUMER-STYLING.md +44 -0
- package/app/components/{parallax → 01.atoms/animations/section-parallax}/SectionParallax.vue +11 -10
- package/app/components/01.atoms/animations/section-parallax/stories/SectionParallax.stories.ts +98 -0
- package/app/components/01.atoms/animations/section-parallax/tests/SectionParallax.spec.ts +102 -0
- package/app/components/01.atoms/animations/section-parallax/tests/__snapshots__/SectionParallax.spec.ts.snap +9 -0
- package/app/components/01.atoms/animations/view-timeline/CONSUMER-STYLING.md +70 -0
- package/app/components/01.atoms/animations/view-timeline/WipeAwayVertical.vue +181 -0
- package/app/components/01.atoms/animations/view-timeline/stories/WipeAwayVertical.stories.ts +269 -0
- package/app/components/01.atoms/animations/view-timeline/tests/WipeAwayVertical.spec.ts +127 -0
- package/app/components/01.atoms/canvas-switcher/CONSUMER-STYLING.md +3 -3
- package/app/components/01.atoms/canvas-switcher/CanvasSwitcher.vue +2 -2
- package/app/components/01.atoms/canvas-switcher/stories/CanvasSwitcher.stories.ts +1 -1
- package/app/components/01.atoms/card/CardCore.vue +4 -4
- package/app/components/01.atoms/clipped-panel/CONSUMER-STYLING.md +2 -2
- package/app/components/01.atoms/clipped-panel/ClippedPanel.vue +2 -2
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md +42 -0
- package/app/components/{layout-grids/LayoutGridA.vue → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/DashboardQuadGrid.vue} +23 -13
- package/app/components/{layout-grids/stories/LayoutGridA.stories.ts → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/stories/DashboardQuadGrid.stories.ts} +151 -23
- package/app/components/{layout-grids/tests/LayoutGridA.spec.ts → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/tests/DashboardQuadGrid.spec.ts} +25 -25
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/CONSUMER-STYLING.md +41 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/DashboardStatsGrid.vue +133 -0
- package/app/components/{layout-grids/stories/LayoutGridB.stories.ts → 01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/stories/DashboardStatsGrid.stories.ts} +24 -24
- package/app/components/{layout-grids/tests/LayoutGridB.spec.ts → 01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/tests/DashboardStatsGrid.spec.ts} +23 -23
- package/app/components/01.atoms/display-tooltip/CONSUMER-STYLING.md +8 -8
- package/app/components/01.atoms/display-tooltip/DisplayTooltip.vue +8 -8
- package/app/components/01.atoms/pop-over/CONSUMER-STYLING.md +6 -6
- package/app/components/01.atoms/pop-over/PopOver.vue +6 -6
- package/app/components/01.atoms/ui-block-decorated/CONSUMER-STYLING.md +39 -0
- package/app/components/01.atoms/ui-block-decorated/UiBlockDecorated.vue +141 -0
- package/app/components/01.atoms/ui-block-decorated/stories/UiBlockDecorated.stories.ts +148 -0
- package/app/components/01.atoms/ui-block-decorated/tests/UiBlockDecorated.spec.ts +80 -0
- package/app/components/01.atoms/ui-block-decorated/tests/__snapshots__/UiBlockDecorated.spec.ts.snap +3 -0
- package/app/components/02.molecules/alert-masked-content/tests/AlertMaskedContent.spec.ts +6 -6
- package/app/components/02.molecules/display-theme-switch/CONSUMER-STYLING.md +34 -0
- package/app/components/02.molecules/display-theme-switch/DisplayThemeSwitch.vue +102 -0
- package/app/components/02.molecules/display-theme-switch/stories/DisplayThemeSwitch.stories.ts +115 -0
- package/app/components/02.molecules/display-theme-switch/tests/DisplayThemeSwitch.spec.ts +86 -0
- package/app/components/02.molecules/navigation/deep-expanding-menu/CONSUMER-STYLING.md +1 -1
- package/app/components/02.molecules/navigation/deep-expanding-menu/DeepExpandingMenu.vue +1 -1
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/CONSUMER-STYLING.md +1 -1
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/DeepExpandingMenuClassic.vue +1 -1
- package/app/components/02.molecules/select-menu/CONSUMER-STYLING.md +168 -0
- package/app/components/02.molecules/select-menu/SelectMenu.vue +384 -0
- package/app/components/02.molecules/select-menu/stories/SelectMenu.stories.ts +359 -0
- package/app/components/02.molecules/select-menu/tests/SelectMenu.spec.ts +476 -0
- package/app/components/02.molecules/select-menu/tests/__snapshots__/SelectMenu.spec.ts.snap +42 -0
- package/app/components/03.organisms/image-galleries/carousel-flip/CONSUMER-STYLING.md +46 -0
- package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/CarouselFlip.vue +63 -42
- package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/stories/CarouselFlip.stories.ts +114 -29
- package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/tests/CarouselFlip.spec.ts +80 -0
- package/app/components/05.forms/form-field/FormField.vue +1 -1
- package/app/components/05.forms/form-wrapper/FormWrapper.vue +1 -1
- package/app/components/05.forms/input-button/CONSUMER-STYLING.md +2 -7
- package/app/components/05.forms/input-button/InputButtonCore.vue +1 -5
- package/app/components/05.forms/input-number/CONSUMER-STYLING.md +26 -6
- package/app/components/05.forms/input-number/InputNumberCore.vue +125 -85
- package/app/components/05.forms/input-number/stories/InputNumberCore.stories.ts +92 -0
- package/app/components/05.forms/input-number/tests/InputNumberCore.spec.ts +119 -0
- package/app/components/05.forms/input-number/variants/InputNumberDefault.vue +30 -22
- package/app/components/05.forms/input-number/variants/stories/InputNumberDefault.stories.ts +123 -0
- package/app/components/05.forms/input-number/variants/tests/InputNumberDefault.spec.ts +143 -0
- package/app/components/05.forms/input-range/CONSUMER-STYLING.md +45 -0
- package/app/components/05.forms/input-range/InputRangeCore.vue +79 -143
- package/app/components/05.forms/input-range/stories/InputRangeCore.stories.ts +126 -0
- package/app/components/05.forms/input-range/tests/InputRangeCore.spec.ts +124 -0
- package/app/components/05.forms/input-range/variants/InputRangeDefault.vue +9 -32
- package/app/components/05.forms/input-range/variants/stories/InputRangeDefault.stories.ts +108 -0
- package/app/components/05.forms/input-range/variants/tests/InputRangeDefault.spec.ts +140 -0
- package/app/components/05.forms/input-select/InputSelectCore.vue +23 -30
- package/app/components/05.forms/input-select/stories/InputSelectCore.stories.ts +2 -2
- package/app/components/05.forms/input-select/stories/InputSelectWithLabel.stories.ts +115 -0
- package/app/components/05.forms/input-select/tests/InputSelectCore.spec.ts +129 -0
- package/app/components/05.forms/input-select/variants/InputSelectWithLabel.vue +1 -1
- package/app/components/05.forms/input-select/variants/tests/InputSelectWithLabel.spec.ts +87 -0
- package/app/components/05.forms/input-text/tests/InputTextAsNumberWithLabel.spec.ts +14 -0
- package/app/components/05.forms/input-text/variants/InputTextAsNumberWithLabel.vue +1 -1
- package/app/components/05.forms/input-textarea/CONSUMER-STYLING.md +55 -0
- package/app/components/05.forms/input-textarea/InputTextareaCore.vue +24 -16
- package/app/components/05.forms/input-textarea/stories/InputTextareaWithLabel.stories.ts +1 -1
- package/app/components/05.forms/input-textarea/tests/InputTextareaCore.spec.ts +119 -0
- package/app/components/05.forms/input-textarea/variants/InputTextareaWithLabel.vue +3 -13
- package/app/components/05.forms/input-textarea/variants/tests/InputTextareaWithLabel.spec.ts +93 -0
- package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +220 -0
- package/app/components/05.forms/toggle-switch/CONSUMER-STYLING.md +29 -0
- package/app/components/05.forms/toggle-switch/ToggleSwitchCore.vue +120 -119
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchCore.stories.ts +1 -1
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabel.stories.ts +1 -1
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabelInline.stories.ts +2 -2
- package/app/components/05.forms/toggle-switch/tests/ToggleSwitchCore.spec.ts +101 -0
- package/app/components/05.forms/toggle-switch/variants/ToggleSwitchWithLabel.vue +3 -5
- package/app/components/05.forms/toggle-switch/variants/ToggleSwitchWithLabelInline.vue +1 -2
- package/app/components/05.forms/toggle-switch/variants/tests/ToggleSwitchWithLabel.spec.ts +77 -0
- package/app/components/05.forms/toggle-switch/variants/tests/ToggleSwitchWithLabelInline.spec.ts +66 -0
- package/app/components/05.forms/triple-toggle-switch/CONSUMER-STYLING.md +20 -6
- package/app/components/05.forms/triple-toggle-switch/TripleToggleSwitchCore.vue +12 -10
- package/app/types/components/container-glow.d.ts +8 -0
- package/app/types/components/index.ts +2 -1
- package/app/types/components/select-menu.d.ts +5 -0
- package/package.json +3 -2
- package/.vscode/srcdev-component-toggle-switch-core.code-snippets +0 -13
- package/app/components/05.forms/input-range-fancy/InputRangeFancyCore.vue +0 -426
- package/app/components/05.forms/input-range-fancy/InputRangeFancyWithLabel.vue +0 -94
- package/app/components/carousels/CarouselBasic.vue +0 -317
- package/app/components/carousels/CarouselInfinite.vue +0 -358
- package/app/components/container-glow/ContainerGlowCore.vue +0 -296
- package/app/components/container-glow/stories/ContainerGlowCore.stories.ts +0 -337
- package/app/components/display-theme-switch/DisplayThemeSwitch.vue +0 -189
- package/app/components/display-theme-switch/stories/DisplayThemeSwitch.stories.ts +0 -154
- package/app/components/glowing-border/GlowingBorder.vue +0 -142
- package/app/components/glowing-border/stories/GlowingBorder.stories.ts +0 -141
- package/app/components/layout-grids/LayoutGridB.vue +0 -134
- package/app/components/test-storybook/TestStorybook.vue +0 -49
- package/app/components/test-storybook/stories/TestStorybook.stories.ts +0 -28
- package/app/components/ui-block-decorated/UiBlockDecorated.vue +0 -167
- package/app/components/view-timeline/WipeAwayVertical.vue +0 -180
- package/app/types/components/carousel-basic.d.ts +0 -19
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
class="input-number-with-label"
|
|
4
4
|
:data-theme="theme"
|
|
5
5
|
:data-invalid="fieldHasError ? '' : null"
|
|
6
|
-
:class="[elementClasses, `theme-${theme}`, { error: fieldHasError }]"
|
|
6
|
+
:class="[elementClasses, inputVariant, `theme-${theme}`, { error: fieldHasError }]"
|
|
7
7
|
>
|
|
8
8
|
<InputLabel
|
|
9
9
|
:id
|
|
10
10
|
:for="id"
|
|
11
11
|
:theme
|
|
12
12
|
:name
|
|
13
|
-
input-variant
|
|
13
|
+
:input-variant
|
|
14
14
|
:field-has-error
|
|
15
15
|
:style-class-passthrough="['input-number-label', 'body-normal-bold']"
|
|
16
16
|
>
|
|
@@ -39,9 +39,11 @@
|
|
|
39
39
|
:min
|
|
40
40
|
:max
|
|
41
41
|
:step
|
|
42
|
+
:placeholder
|
|
42
43
|
:theme
|
|
43
44
|
:required
|
|
44
45
|
:weight
|
|
46
|
+
:input-variant
|
|
45
47
|
:field-has-error
|
|
46
48
|
:style-class-passthrough
|
|
47
49
|
:aria-describedby
|
|
@@ -51,7 +53,7 @@
|
|
|
51
53
|
type="button"
|
|
52
54
|
:readonly="Number(modelValue) <= min"
|
|
53
55
|
:is-pending="false"
|
|
54
|
-
button-text="
|
|
56
|
+
:button-text="stepDownLabel"
|
|
55
57
|
:theme="theme"
|
|
56
58
|
variant="inline"
|
|
57
59
|
@click.stop.prevent="updateValue(-step, Number(modelValue) > min)"
|
|
@@ -66,7 +68,7 @@
|
|
|
66
68
|
type="button"
|
|
67
69
|
:readonly="Number(modelValue) >= max"
|
|
68
70
|
:is-pending="false"
|
|
69
|
-
button-text="
|
|
71
|
+
:button-text="stepUpLabel"
|
|
70
72
|
:theme="theme"
|
|
71
73
|
variant="inline"
|
|
72
74
|
@click.stop.prevent="updateValue(step, Number(modelValue) < max)"
|
|
@@ -82,7 +84,7 @@
|
|
|
82
84
|
</template>
|
|
83
85
|
|
|
84
86
|
<script setup lang="ts">
|
|
85
|
-
import type { FormWeight } from "~/types/forms/types.forms";
|
|
87
|
+
import type { FormUiTheme, FormWeight, InputUiVariant } from "~/types/forms/types.forms";
|
|
86
88
|
|
|
87
89
|
interface Props {
|
|
88
90
|
name: string;
|
|
@@ -94,9 +96,12 @@ interface Props {
|
|
|
94
96
|
errorMessage: object | string;
|
|
95
97
|
fieldHasError?: boolean;
|
|
96
98
|
required?: boolean;
|
|
97
|
-
theme?:
|
|
99
|
+
theme?: FormUiTheme;
|
|
98
100
|
weight?: FormWeight;
|
|
101
|
+
inputVariant?: InputUiVariant;
|
|
99
102
|
styleClassPassthrough?: string | string[];
|
|
103
|
+
stepDownLabel?: string;
|
|
104
|
+
stepUpLabel?: string;
|
|
100
105
|
}
|
|
101
106
|
|
|
102
107
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -106,45 +111,48 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
106
111
|
required: false,
|
|
107
112
|
theme: "default",
|
|
108
113
|
weight: "normal",
|
|
114
|
+
inputVariant: "normal",
|
|
109
115
|
styleClassPassthrough: () => [],
|
|
116
|
+
stepDownLabel: "Step down",
|
|
117
|
+
stepUpLabel: "Step up",
|
|
110
118
|
});
|
|
111
119
|
|
|
112
120
|
const slots = useSlots();
|
|
113
|
-
const { elementClasses
|
|
121
|
+
const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
|
|
114
122
|
|
|
115
123
|
const { id, errorId, descriptionId, ariaDescribedby } = useAriaDescribedById(
|
|
116
124
|
props.name,
|
|
117
125
|
toRef(props, "fieldHasError"),
|
|
118
126
|
slots
|
|
119
127
|
);
|
|
120
|
-
const modelValue = defineModel<number | readonly number[]>(
|
|
128
|
+
const modelValue = defineModel<number | readonly number[]>({
|
|
129
|
+
required: true,
|
|
130
|
+
});
|
|
121
131
|
|
|
122
132
|
const updateValue = (step: number, withinRangeLimit: boolean) => {
|
|
123
133
|
if (withinRangeLimit) {
|
|
124
134
|
modelValue.value = (Number(modelValue.value) + step) as number;
|
|
125
135
|
}
|
|
126
136
|
};
|
|
127
|
-
|
|
128
|
-
updateElementClasses(["has-left-button", "has-right-button"]);
|
|
129
137
|
</script>
|
|
130
138
|
|
|
131
139
|
<style lang="css">
|
|
132
140
|
@layer components {
|
|
133
|
-
.input-number-with-label {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
141
|
+
.input-number-with-label {
|
|
142
|
+
.input-number-label {
|
|
143
|
+
display: block;
|
|
144
|
+
margin-block: 0.8rem;
|
|
137
145
|
|
|
138
|
-
|
|
139
|
-
|
|
146
|
+
&:hover {
|
|
147
|
+
cursor: pointer;
|
|
148
|
+
}
|
|
140
149
|
}
|
|
141
|
-
}
|
|
142
150
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
151
|
+
.label-description {
|
|
152
|
+
font-family: var(--font-family);
|
|
153
|
+
font-size: 1.6rem;
|
|
154
|
+
margin-top: 1.2rem;
|
|
155
|
+
}
|
|
147
156
|
}
|
|
148
157
|
}
|
|
149
|
-
}
|
|
150
158
|
</style>
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { Meta, StoryFn } from "@nuxtjs/storybook";
|
|
2
|
+
import StorybookComponent from "../InputNumberDefault.vue";
|
|
3
|
+
import type { FormUiTheme } from "~/types/forms/types.forms.d";
|
|
4
|
+
|
|
5
|
+
interface InputNumberDefaultStoryArgs {
|
|
6
|
+
modelValue: number;
|
|
7
|
+
name: string;
|
|
8
|
+
label: string;
|
|
9
|
+
min: number;
|
|
10
|
+
max: number;
|
|
11
|
+
step: number;
|
|
12
|
+
placeholder: string;
|
|
13
|
+
errorMessage: string;
|
|
14
|
+
fieldHasError: boolean;
|
|
15
|
+
required: boolean;
|
|
16
|
+
theme: FormUiTheme;
|
|
17
|
+
inputVariant: string;
|
|
18
|
+
stepDownLabel: string;
|
|
19
|
+
stepUpLabel: string;
|
|
20
|
+
useStepButtons: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default {
|
|
24
|
+
title: "Components/Forms/Input Number/InputNumberDefault",
|
|
25
|
+
component: StorybookComponent,
|
|
26
|
+
argTypes: {
|
|
27
|
+
modelValue: { control: "number", table: { category: "Model" } },
|
|
28
|
+
name: { control: "text", table: { category: "Basic" } },
|
|
29
|
+
label: { control: "text", table: { category: "Basic" } },
|
|
30
|
+
min: { control: "number", table: { category: "Basic" } },
|
|
31
|
+
max: { control: "number", table: { category: "Basic" } },
|
|
32
|
+
step: { control: "number", table: { category: "Basic" } },
|
|
33
|
+
placeholder: { control: "text", table: { category: "Basic" } },
|
|
34
|
+
errorMessage: { control: "text", table: { category: "Validation" } },
|
|
35
|
+
fieldHasError: { control: "boolean", table: { category: "Validation" } },
|
|
36
|
+
required: { control: "boolean", table: { category: "Validation" } },
|
|
37
|
+
theme: {
|
|
38
|
+
control: { type: "select" },
|
|
39
|
+
options: ["default", "success", "error", "warning"],
|
|
40
|
+
table: { category: "Styling" },
|
|
41
|
+
},
|
|
42
|
+
inputVariant: {
|
|
43
|
+
control: { type: "select" },
|
|
44
|
+
options: ["normal", "outlined", "underlined"],
|
|
45
|
+
table: { category: "Styling" },
|
|
46
|
+
},
|
|
47
|
+
stepDownLabel: { control: "text", description: "Localisation override for the step-down button", table: { category: "Accessibility" } },
|
|
48
|
+
stepUpLabel: { control: "text", description: "Localisation override for the step-up button", table: { category: "Accessibility" } },
|
|
49
|
+
useStepButtons: { control: "boolean", description: "Render +/- step buttons via left/right slots", table: { category: "Slots" } },
|
|
50
|
+
},
|
|
51
|
+
args: {
|
|
52
|
+
modelValue: 5,
|
|
53
|
+
name: "quantity",
|
|
54
|
+
label: "Quantity",
|
|
55
|
+
min: 0,
|
|
56
|
+
max: 10,
|
|
57
|
+
step: 1,
|
|
58
|
+
placeholder: "",
|
|
59
|
+
errorMessage: "",
|
|
60
|
+
fieldHasError: false,
|
|
61
|
+
required: false,
|
|
62
|
+
theme: "default",
|
|
63
|
+
inputVariant: "normal",
|
|
64
|
+
stepDownLabel: "Step down",
|
|
65
|
+
stepUpLabel: "Step up",
|
|
66
|
+
useStepButtons: false,
|
|
67
|
+
},
|
|
68
|
+
} as Meta<InputNumberDefaultStoryArgs>;
|
|
69
|
+
|
|
70
|
+
const Template: StoryFn<InputNumberDefaultStoryArgs> = (args) => ({
|
|
71
|
+
components: { StorybookComponent },
|
|
72
|
+
setup() {
|
|
73
|
+
return { args };
|
|
74
|
+
},
|
|
75
|
+
template: `
|
|
76
|
+
<div style="margin: 36px; max-width: 320px;">
|
|
77
|
+
<StorybookComponent
|
|
78
|
+
v-model="args.modelValue"
|
|
79
|
+
:name="args.name"
|
|
80
|
+
:label="args.label"
|
|
81
|
+
:min="args.min"
|
|
82
|
+
:max="args.max"
|
|
83
|
+
:step="args.step"
|
|
84
|
+
:placeholder="args.placeholder"
|
|
85
|
+
:error-message="args.errorMessage"
|
|
86
|
+
:field-has-error="args.fieldHasError"
|
|
87
|
+
:required="args.required"
|
|
88
|
+
:theme="args.theme"
|
|
89
|
+
:input-variant="args.inputVariant"
|
|
90
|
+
:step-down-label="args.stepDownLabel"
|
|
91
|
+
:step-up-label="args.stepUpLabel"
|
|
92
|
+
>
|
|
93
|
+
<template v-if="args.useStepButtons" #left><span>−</span></template>
|
|
94
|
+
<template v-if="args.useStepButtons" #right><span>+</span></template>
|
|
95
|
+
</StorybookComponent>
|
|
96
|
+
</div>
|
|
97
|
+
`,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
export const Default = Template.bind({});
|
|
101
|
+
|
|
102
|
+
export const WithStepButtons = Template.bind({});
|
|
103
|
+
WithStepButtons.args = { useStepButtons: true };
|
|
104
|
+
|
|
105
|
+
export const WithError = Template.bind({});
|
|
106
|
+
WithError.args = {
|
|
107
|
+
fieldHasError: true,
|
|
108
|
+
errorMessage: "Please enter a valid quantity",
|
|
109
|
+
theme: "error",
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export const Required = Template.bind({});
|
|
113
|
+
Required.args = { required: true };
|
|
114
|
+
|
|
115
|
+
export const LocalisedLabels = Template.bind({});
|
|
116
|
+
LocalisedLabels.args = {
|
|
117
|
+
useStepButtons: true,
|
|
118
|
+
stepDownLabel: "Diminuer",
|
|
119
|
+
stepUpLabel: "Augmenter",
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const Underlined = Template.bind({});
|
|
123
|
+
Underlined.args = { inputVariant: "underlined", useStepButtons: true };
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// https://nuxt.com/docs/getting-started/testing#unit-testing
|
|
2
|
+
import type { VueWrapper } from "@vue/test-utils";
|
|
3
|
+
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
4
|
+
import ComponentUnderTest from "../InputNumberDefault.vue";
|
|
5
|
+
|
|
6
|
+
const initialPropsData = {
|
|
7
|
+
name: "testName",
|
|
8
|
+
label: "Test label",
|
|
9
|
+
min: 0,
|
|
10
|
+
max: 100,
|
|
11
|
+
errorMessage: "",
|
|
12
|
+
modelValue: 50,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
let wrapper: VueWrapper<InstanceType<typeof ComponentUnderTest>>;
|
|
16
|
+
const wrapperFactory = (propsData = {}, slots = {}) => {
|
|
17
|
+
const mockPropsData = { ...initialPropsData, ...propsData };
|
|
18
|
+
|
|
19
|
+
return mountSuspended(ComponentUnderTest, {
|
|
20
|
+
attachTo: document.body,
|
|
21
|
+
props: mockPropsData,
|
|
22
|
+
slots,
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
describe("InputNumberDefault", () => {
|
|
27
|
+
it("mounts without error", async () => {
|
|
28
|
+
wrapper = await wrapperFactory();
|
|
29
|
+
expect(wrapper.vm).toBeTruthy();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("renders the label text", async () => {
|
|
33
|
+
wrapper = await wrapperFactory({ label: "Quantity" });
|
|
34
|
+
expect(wrapper.text()).toContain("Quantity");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("renders a number input", async () => {
|
|
38
|
+
wrapper = await wrapperFactory();
|
|
39
|
+
expect(wrapper.find('input[type="number"]').exists()).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("forwards placeholder to the input", async () => {
|
|
43
|
+
wrapper = await wrapperFactory({ placeholder: "Enter a quantity" });
|
|
44
|
+
expect(wrapper.find("input").attributes("placeholder")).toBe("Enter a quantity");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("sets data-invalid and the error class when fieldHasError is true", async () => {
|
|
48
|
+
wrapper = await wrapperFactory({ fieldHasError: true });
|
|
49
|
+
expect(wrapper.find(".input-number-with-label").attributes("data-invalid")).toBe("");
|
|
50
|
+
expect(wrapper.find(".input-number-with-label").classes()).toContain("error");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("does not render step buttons when the left/right slots are not used", async () => {
|
|
54
|
+
wrapper = await wrapperFactory();
|
|
55
|
+
expect(wrapper.findAllComponents({ name: "InputButtonCore" })).toHaveLength(0);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("renders step buttons with default labels when the left/right slots are used", async () => {
|
|
59
|
+
wrapper = await wrapperFactory(
|
|
60
|
+
{},
|
|
61
|
+
{
|
|
62
|
+
left: '<span data-testid="left-icon">-</span>',
|
|
63
|
+
right: '<span data-testid="right-icon">+</span>',
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
expect(wrapper.text()).toContain("Step down");
|
|
68
|
+
expect(wrapper.text()).toContain("Step up");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("overrides step button labels via stepDownLabel/stepUpLabel props", async () => {
|
|
72
|
+
wrapper = await wrapperFactory(
|
|
73
|
+
{ stepDownLabel: "Decrease", stepUpLabel: "Increase" },
|
|
74
|
+
{
|
|
75
|
+
left: '<span>-</span>',
|
|
76
|
+
right: '<span>+</span>',
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
expect(wrapper.text()).toContain("Decrease");
|
|
81
|
+
expect(wrapper.text()).toContain("Increase");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("increments the model value when the step-up button is clicked", async () => {
|
|
85
|
+
wrapper = await wrapperFactory({ modelValue: 50, step: 5 }, { right: '<span>+</span>' });
|
|
86
|
+
|
|
87
|
+
const buttons = wrapper.findAll("button");
|
|
88
|
+
const stepUpButton = buttons.find(
|
|
89
|
+
(btn) => btn.text().includes("+") || btn.attributes("aria-label")?.includes("Step up")
|
|
90
|
+
);
|
|
91
|
+
await stepUpButton?.trigger("click");
|
|
92
|
+
|
|
93
|
+
expect(wrapper.emitted("update:modelValue")?.[0]).toEqual([55]);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("decrements the model value when the step-down button is clicked", async () => {
|
|
97
|
+
wrapper = await wrapperFactory({ modelValue: 50, step: 5 }, { left: '<span>-</span>' });
|
|
98
|
+
|
|
99
|
+
const buttons = wrapper.findAll("button");
|
|
100
|
+
const stepDownButton = buttons.find(
|
|
101
|
+
(btn) => btn.text().includes("-") || btn.attributes("aria-label")?.includes("Step down")
|
|
102
|
+
);
|
|
103
|
+
await stepDownButton?.trigger("click");
|
|
104
|
+
|
|
105
|
+
expect(wrapper.emitted("update:modelValue")?.[0]).toEqual([45]);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("does not step below min", async () => {
|
|
109
|
+
wrapper = await wrapperFactory({ modelValue: 0, min: 0, step: 5 }, { left: '<span>-</span>' });
|
|
110
|
+
|
|
111
|
+
const buttons = wrapper.findAll("button");
|
|
112
|
+
const stepDownButton = buttons.find((btn) => btn.text().includes("-"));
|
|
113
|
+
await stepDownButton?.trigger("click");
|
|
114
|
+
|
|
115
|
+
expect(wrapper.emitted("update:modelValue")).toBeUndefined();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("does not step above max", async () => {
|
|
119
|
+
wrapper = await wrapperFactory({ modelValue: 100, max: 100, step: 5 }, { right: '<span>+</span>' });
|
|
120
|
+
|
|
121
|
+
const buttons = wrapper.findAll("button");
|
|
122
|
+
const stepUpButton = buttons.find((btn) => btn.text().includes("+"));
|
|
123
|
+
await stepUpButton?.trigger("click");
|
|
124
|
+
|
|
125
|
+
expect(wrapper.emitted("update:modelValue")).toBeUndefined();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("renders the error message when fieldHasError is true", async () => {
|
|
129
|
+
wrapper = await wrapperFactory({ fieldHasError: true, errorMessage: "This field is required" });
|
|
130
|
+
expect(wrapper.text()).toContain("This field is required");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("applies styleClassPassthrough", async () => {
|
|
134
|
+
wrapper = await wrapperFactory({ styleClassPassthrough: "custom-number" });
|
|
135
|
+
expect(wrapper.find(".input-number-with-label").classes()).toContain("custom-number");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("forwards inputVariant to the wrapper, InputLabel, and InputNumberCore", async () => {
|
|
139
|
+
wrapper = await wrapperFactory({ inputVariant: "underlined" });
|
|
140
|
+
expect(wrapper.find(".input-number-with-label").classes()).toContain("underlined");
|
|
141
|
+
expect(wrapper.find(".input-number-wrapper").classes()).toContain("underlined");
|
|
142
|
+
});
|
|
143
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# InputRangeCore — Consumer Styling Guide
|
|
2
|
+
|
|
3
|
+
## Public token API
|
|
4
|
+
|
|
5
|
+
| Token | Default | Controls |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| `--input-range-accent-colour` | `var(--theme-accent)` | Native `accent-color` of the range input (track/thumb tint) |
|
|
8
|
+
| `--input-range-marker-background-colour` | `hsl(0, 29%, 3%)` | Background colour of each `markers` slot dot |
|
|
9
|
+
| `--input-range-marker-padding` | `0.5rem` | Padding of each marker dot |
|
|
10
|
+
| `--input-range-marker-border-radius` | `50%` | Corner radius of each marker dot |
|
|
11
|
+
| `--input-range-marker-outline-width` | `0.1rem` | Marker dot outline width |
|
|
12
|
+
| `--input-range-marker-outline-colour` | `var(--slate-04)` | Marker dot outline colour |
|
|
13
|
+
| `--input-range-marker-icon-size` | `2rem` | Font size of the marker icon (`.marker-icon`) |
|
|
14
|
+
| `--input-range-marker-icon-colour` | `var(--input-range-accent-colour, var(--theme-accent))` | Marker icon colour |
|
|
15
|
+
| `--input-range-datalist-font-size` | `1.4rem` | Font size of the `datalist` slot's option labels |
|
|
16
|
+
| `--input-range-datalist-font-weight` | `500` | Font weight of the `datalist` slot's option labels |
|
|
17
|
+
|
|
18
|
+
Geometry (thumb/track height, border radius) comes from the shared global form-geometry tokens
|
|
19
|
+
(`--input-range-button-size`, `--form-input-border-radius`), not from this component's own
|
|
20
|
+
tokens — see `theming-form-geometry-tokens.md`.
|
|
21
|
+
|
|
22
|
+
```css
|
|
23
|
+
.my-page {
|
|
24
|
+
--input-range-accent-colour: var(--brand-primary);
|
|
25
|
+
--input-range-marker-background-colour: var(--brand-primary);
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Or scope to a single instance via `styleClassPassthrough`:
|
|
30
|
+
|
|
31
|
+
```vue
|
|
32
|
+
<InputRangeCore style-class-passthrough="price-range" ... />
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Notes
|
|
36
|
+
|
|
37
|
+
- `accent-color` was previously read from `--theme-form-range-accent-color`, a custom property
|
|
38
|
+
never declared anywhere in the codebase (no fallback either) — per CSS spec this made the whole
|
|
39
|
+
`accent-color` declaration invalid at computed-value time, so it silently fell back to the
|
|
40
|
+
browser's native default accent colour instead of the intended theme colour. Fixed 2026-09-21;
|
|
41
|
+
see the component's skill doc for detail.
|
|
42
|
+
- The `weight` prop (`FormWeight`) generates an `input-range--{weight}` class on the input, but no
|
|
43
|
+
CSS in this component (or elsewhere in the library) currently styles that class — it's a no-op
|
|
44
|
+
today. Left as-is rather than redesigned, since the original intent wasn't clear; flagged here
|
|
45
|
+
so you don't spend time debugging "why doesn't `weight` do anything."
|