srcdev-nuxt-components 9.6.7 → 9.6.8
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/rules/consumer/ids-from-useid.md +14 -0
- package/.claude/skills/component-compliance-checklist.md +6 -0
- package/.claude/skills/component-naming.md +2 -2
- package/.claude/skills/components/input-checkbox-radio.md +19 -3
- package/.claude/skills/components/input-checkbox.md +14 -3
- package/.claude/skills/components/input-description.md +1 -1
- package/.claude/skills/components/input-error.md +1 -1
- package/.claude/skills/components/input-label.md +2 -2
- package/.claude/skills/components/input-number.md +44 -13
- package/.claude/skills/components/input-radio.md +15 -1
- package/.claude/skills/components/{input-range-core.md → input-range.md} +53 -18
- package/.claude/skills/components/input-textarea-core.md +4 -4
- package/.claude/skills/index.md +5 -5
- package/.claude/skills/testing-add-unit-test.md +17 -0
- package/.vscode/srcdev-component-input-checkbox-radio.code-snippets +0 -1
- package/.vscode/srcdev-component-input-range.code-snippets +38 -18
- package/app/components/05.forms/input-checkbox/CONSUMER-STYLING.md +10 -0
- package/app/components/05.forms/input-checkbox/MultipleCheckboxes.vue +18 -4
- package/app/components/05.forms/input-checkbox/SingleCheckbox.vue +14 -2
- package/app/components/05.forms/input-checkbox/stories/MultipleCheckboxes.stories.ts +146 -37
- package/app/components/05.forms/input-checkbox/stories/SingleCheckbox.stories.ts +107 -30
- package/app/components/05.forms/input-checkbox/tests/MultipleCheckboxes.spec.ts +38 -4
- package/app/components/05.forms/input-checkbox/tests/SingleCheckbox.spec.ts +5 -0
- package/app/components/05.forms/input-checkbox-radio/CONSUMER-STYLING.md +10 -3
- package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadio.vue +1 -0
- package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioButton.vue +11 -10
- package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioField.vue +3 -0
- package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadio.stories.ts +44 -17
- package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadioButton.stories.ts +164 -58
- package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadioField.stories.ts +94 -21
- package/app/components/05.forms/input-checkbox-radio/tests/InputCheckboxRadio.spec.ts +14 -3
- package/app/components/05.forms/input-checkbox-radio/tests/InputCheckboxRadioButton.spec.ts +38 -3
- package/app/components/05.forms/input-checkbox-radio/tests/InputCheckboxRadioField.spec.ts +14 -3
- package/app/components/05.forms/input-description/CONSUMER-STYLING.md +2 -2
- package/app/components/05.forms/input-label/CONSUMER-STYLING.md +2 -2
- package/app/components/05.forms/input-number/CONSUMER-STYLING.md +42 -2
- package/app/components/05.forms/input-number/InputNumber.vue +37 -25
- package/app/components/05.forms/input-number/InputNumberField.vue +29 -16
- package/app/components/05.forms/input-number/stories/InputNumber.stories.ts +76 -25
- package/app/components/05.forms/input-number/stories/InputNumberField.stories.ts +116 -50
- package/app/components/05.forms/input-number/tests/InputNumber.spec.ts +49 -13
- package/app/components/05.forms/input-number/tests/InputNumberField.spec.ts +59 -5
- package/app/components/05.forms/input-radio/CONSUMER-STYLING.md +11 -0
- package/app/components/05.forms/input-radio/MultipleRadiobuttons.vue +14 -3
- package/app/components/05.forms/input-radio/stories/MultipleRadiobuttons.stories.ts +138 -38
- package/app/components/05.forms/input-radio/tests/MultipleRadioButtons.spec.ts +50 -3
- package/app/components/05.forms/input-range/CONSUMER-STYLING.md +68 -26
- package/app/components/05.forms/input-range/InputRange.vue +247 -0
- package/app/components/05.forms/input-range/{InputRangeDefault.vue → InputRangeField.vue} +41 -18
- package/app/components/05.forms/input-range/stories/InputRange.stories.ts +187 -0
- package/app/components/05.forms/input-range/stories/InputRangeField.stories.ts +196 -0
- package/app/components/05.forms/input-range/tests/{InputRangeCore.spec.ts → InputRange.spec.ts} +39 -15
- package/app/components/05.forms/input-range/tests/{InputRangeDefault.spec.ts → InputRangeField.spec.ts} +71 -7
- package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +5 -5
- package/package.json +1 -1
- package/app/components/05.forms/input-range/InputRangeCore.vue +0 -169
- package/app/components/05.forms/input-range/stories/InputRangeCore.stories.ts +0 -126
- package/app/components/05.forms/input-range/stories/InputRangeDefault.stories.ts +0 -108
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// https://nuxt.com/docs/getting-started/testing#unit-testing
|
|
2
2
|
import type { VueWrapper } from "@vue/test-utils";
|
|
3
3
|
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
4
|
-
import ComponentUnderTest from "../
|
|
4
|
+
import ComponentUnderTest from "../InputRangeField.vue";
|
|
5
5
|
|
|
6
6
|
const initialPropsData = {
|
|
7
7
|
name: "testName",
|
|
@@ -13,6 +13,11 @@ const initialPropsData = {
|
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
let wrapper: VueWrapper<InstanceType<typeof ComponentUnderTest>>;
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
wrapper?.unmount();
|
|
19
|
+
vi.restoreAllMocks();
|
|
20
|
+
});
|
|
16
21
|
const wrapperFactory = (propsData = {}, slots = {}) => {
|
|
17
22
|
const mockPropsData = { ...initialPropsData, ...propsData };
|
|
18
23
|
|
|
@@ -23,7 +28,7 @@ const wrapperFactory = (propsData = {}, slots = {}) => {
|
|
|
23
28
|
});
|
|
24
29
|
};
|
|
25
30
|
|
|
26
|
-
describe("
|
|
31
|
+
describe("InputRangeField", () => {
|
|
27
32
|
it("mounts without error", async () => {
|
|
28
33
|
wrapper = await wrapperFactory();
|
|
29
34
|
expect(wrapper.vm).toBeTruthy();
|
|
@@ -39,10 +44,10 @@ describe("InputRangeDefault", () => {
|
|
|
39
44
|
expect(wrapper.find('input[type="range"]').exists()).toBe(true);
|
|
40
45
|
});
|
|
41
46
|
|
|
42
|
-
it("sets data-invalid
|
|
47
|
+
it("sets data-invalid, not a bare error class, when fieldHasError is true", async () => {
|
|
43
48
|
wrapper = await wrapperFactory({ fieldHasError: true });
|
|
44
|
-
expect(wrapper.find(".input-range-
|
|
45
|
-
expect(wrapper.find(".input-range-
|
|
49
|
+
expect(wrapper.find(".input-range-field").attributes("data-invalid")).toBe("");
|
|
50
|
+
expect(wrapper.find(".input-range-field").classes()).not.toContain("error");
|
|
46
51
|
});
|
|
47
52
|
|
|
48
53
|
it("does not render step buttons when the left/right slots are not used", async () => {
|
|
@@ -133,8 +138,67 @@ describe("InputRangeDefault", () => {
|
|
|
133
138
|
expect(wrapper.text()).toContain("This field is required");
|
|
134
139
|
});
|
|
135
140
|
|
|
136
|
-
it("applies styleClassPassthrough", async () => {
|
|
141
|
+
it("applies styleClassPassthrough to the root only, not the inner input", async () => {
|
|
137
142
|
wrapper = await wrapperFactory({ styleClassPassthrough: "custom-range" });
|
|
138
|
-
expect(wrapper.find(".input-range-
|
|
143
|
+
expect(wrapper.find(".input-range-field").classes()).toContain("custom-range");
|
|
144
|
+
expect(wrapper.find("input").classes()).not.toContain("custom-range");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("uses its own label class, not the number field's", async () => {
|
|
148
|
+
wrapper = await wrapperFactory();
|
|
149
|
+
expect(wrapper.find(".input-range-label").exists()).toBe(true);
|
|
150
|
+
expect(wrapper.find(".input-number-label").exists()).toBe(false);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("forwards the markers slot", async () => {
|
|
154
|
+
wrapper = await wrapperFactory({}, { markers: '<div class="input-range-markers" data-testid="markers"></div>' });
|
|
155
|
+
expect(wrapper.find('[data-testid="markers"]').exists()).toBe(true);
|
|
156
|
+
expect(wrapper.find(".input-range-wrapper").attributes("data-has-markers")).toBe("");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("forwards the datalist slot with its generated id", async () => {
|
|
160
|
+
wrapper = await wrapperFactory(
|
|
161
|
+
{},
|
|
162
|
+
{ datalist: `<template #datalist="{ datalistId }"><datalist :id="datalistId"></datalist></template>` }
|
|
163
|
+
);
|
|
164
|
+
const list = wrapper.find("input").attributes("list");
|
|
165
|
+
|
|
166
|
+
expect(list).toBeTruthy();
|
|
167
|
+
expect(wrapper.find("datalist").attributes("id")).toBe(list);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe("step buttons", () => {
|
|
171
|
+
const stepSlots = { left: "<span>-</span>", right: "<span>+</span>" };
|
|
172
|
+
const stepDown = () => wrapper.findAll("button").find((button) => button.text().includes("-"))!;
|
|
173
|
+
const stepUp = () => wrapper.findAll("button").find((button) => button.text().includes("+"))!;
|
|
174
|
+
|
|
175
|
+
it("rounds away floating-point drift on fractional steps", async () => {
|
|
176
|
+
wrapper = await wrapperFactory({ modelValue: 0.2, min: 0, max: 1, step: 0.1 }, stepSlots);
|
|
177
|
+
await stepUp().trigger("click");
|
|
178
|
+
expect(wrapper.emitted("update:modelValue")?.[0]).toEqual([0.3]);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("clamps to max when a step would overshoot it", async () => {
|
|
182
|
+
wrapper = await wrapperFactory({ modelValue: 98, min: 0, max: 100, step: 5 }, stepSlots);
|
|
183
|
+
await stepUp().trigger("click");
|
|
184
|
+
expect(wrapper.emitted("update:modelValue")?.[0]).toEqual([100]);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("clamps to min when a step would undershoot it", async () => {
|
|
188
|
+
wrapper = await wrapperFactory({ modelValue: 2, min: 0, max: 100, step: 5 }, stepSlots);
|
|
189
|
+
await stepDown().trigger("click");
|
|
190
|
+
expect(wrapper.emitted("update:modelValue")?.[0]).toEqual([0]);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("treats a negative or zero step as its size, or 1", async () => {
|
|
194
|
+
wrapper = await wrapperFactory({ modelValue: 50, step: -5 }, stepSlots);
|
|
195
|
+
await stepUp().trigger("click");
|
|
196
|
+
expect(wrapper.emitted("update:modelValue")?.[0]).toEqual([55]);
|
|
197
|
+
|
|
198
|
+
wrapper.unmount();
|
|
199
|
+
wrapper = await wrapperFactory({ modelValue: 50, step: 0 }, stepSlots);
|
|
200
|
+
await stepUp().trigger("click");
|
|
201
|
+
expect(wrapper.emitted("update:modelValue")?.[0]).toEqual([51]);
|
|
202
|
+
});
|
|
139
203
|
});
|
|
140
204
|
});
|
|
@@ -3,7 +3,7 @@ import { reactive, ref } from "vue";
|
|
|
3
3
|
import InputTextWithLabel from "../../input-text/InputTextWithLabel.vue";
|
|
4
4
|
import InputPasswordWithLabel from "../../input-text/InputPasswordWithLabel.vue";
|
|
5
5
|
import InputTextAsNumberWithLabel from "../../input-text/InputTextAsNumberWithLabel.vue";
|
|
6
|
-
import
|
|
6
|
+
import InputRangeField from "../../input-range/InputRangeField.vue";
|
|
7
7
|
import InputNumberField from "../../input-number/InputNumberField.vue";
|
|
8
8
|
import InputOtpField from "../../input-otp/InputOtpField.vue";
|
|
9
9
|
import InputTextareaWithLabel from "../../input-textarea/InputTextareaWithLabel.vue";
|
|
@@ -29,7 +29,7 @@ interface MigratedFieldsFormStoryArgs {
|
|
|
29
29
|
// rendered inside one. As of 2026-10-04 every 05.forms component is 5/5, so all of them are in:
|
|
30
30
|
// - Own field: InputTextWithLabel (InputTextCore; plus one per type/inputmode pair for checking
|
|
31
31
|
// on-device keyboards), InputPasswordWithLabel,
|
|
32
|
-
// InputTextAsNumberWithLabel,
|
|
32
|
+
// InputTextAsNumberWithLabel, InputRangeField (InputRange), InputNumberField
|
|
33
33
|
// (InputNumber), InputOtpField (InputOtp), InputTextareaWithLabel (InputTextareaCore), InputSelectWithLabel
|
|
34
34
|
// (InputSelectCore), ToggleSwitchWithLabel and ToggleSwitchWithLabelInline (ToggleSwitchCore),
|
|
35
35
|
// MultipleCheckboxes, SingleCheckbox, MultipleRadiobuttons, TripleToggleSwitch ("Theme
|
|
@@ -63,7 +63,7 @@ const Template: StoryFn<MigratedFieldsFormStoryArgs> = (args) => ({
|
|
|
63
63
|
InputTextWithLabel,
|
|
64
64
|
InputPasswordWithLabel,
|
|
65
65
|
InputTextAsNumberWithLabel,
|
|
66
|
-
|
|
66
|
+
InputRangeField,
|
|
67
67
|
InputNumberField,
|
|
68
68
|
InputOtpField,
|
|
69
69
|
InputTextareaWithLabel,
|
|
@@ -296,7 +296,7 @@ const Template: StoryFn<MigratedFieldsFormStoryArgs> = (args) => ({
|
|
|
296
296
|
</FormField>
|
|
297
297
|
|
|
298
298
|
<FormField width="wide" :has-gutter="false">
|
|
299
|
-
<
|
|
299
|
+
<InputRangeField
|
|
300
300
|
v-model="state.budget"
|
|
301
301
|
name="budget"
|
|
302
302
|
label="Budget (£)"
|
|
@@ -308,7 +308,7 @@ const Template: StoryFn<MigratedFieldsFormStoryArgs> = (args) => ({
|
|
|
308
308
|
>
|
|
309
309
|
<template #left><span aria-hidden="true">−</span></template>
|
|
310
310
|
<template #right><span aria-hidden="true">+</span></template>
|
|
311
|
-
</
|
|
311
|
+
</InputRangeField>
|
|
312
312
|
</FormField>
|
|
313
313
|
|
|
314
314
|
<FormField width="wide" :has-gutter="false">
|
package/package.json
CHANGED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="input-range-wrapper" :data-theme="theme">
|
|
3
|
-
<div v-if="slots.left" class="slot left">
|
|
4
|
-
<slot name="left"></slot>
|
|
5
|
-
</div>
|
|
6
|
-
|
|
7
|
-
<div class="input-range-container">
|
|
8
|
-
<slot v-if="slots.markers" name="markers"></slot>
|
|
9
|
-
|
|
10
|
-
<input
|
|
11
|
-
:id
|
|
12
|
-
ref="inputRange"
|
|
13
|
-
v-model="modelValue"
|
|
14
|
-
type="range"
|
|
15
|
-
:name
|
|
16
|
-
:required
|
|
17
|
-
:min
|
|
18
|
-
:max
|
|
19
|
-
:step
|
|
20
|
-
:list="slots.datalist ? name + '-datalist' : ''"
|
|
21
|
-
:class="['input-range-core', `input-range--${weight}`, styleClassPassthrough, { 'has-markers': slots.markers }]"
|
|
22
|
-
:aria-describedby
|
|
23
|
-
/>
|
|
24
|
-
|
|
25
|
-
<slot v-if="slots.datalist" name="datalist"></slot>
|
|
26
|
-
</div>
|
|
27
|
-
<div v-if="slots.right" class="slot right">
|
|
28
|
-
<slot name="right"></slot>
|
|
29
|
-
</div>
|
|
30
|
-
</div>
|
|
31
|
-
</template>
|
|
32
|
-
|
|
33
|
-
<script setup lang="ts">
|
|
34
|
-
import type { FormUiTheme, FormWeight } from "~/types/forms/types.forms";
|
|
35
|
-
|
|
36
|
-
interface Props {
|
|
37
|
-
id: string;
|
|
38
|
-
name: string;
|
|
39
|
-
min: number;
|
|
40
|
-
max: number;
|
|
41
|
-
step?: number;
|
|
42
|
-
placeholder?: string;
|
|
43
|
-
required?: boolean;
|
|
44
|
-
theme?: FormUiTheme;
|
|
45
|
-
weight?: FormWeight;
|
|
46
|
-
fieldHasError?: boolean;
|
|
47
|
-
styleClassPassthrough?: string | string[];
|
|
48
|
-
ariaDescribedby?: string;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
withDefaults(defineProps<Props>(), {
|
|
52
|
-
step: 1,
|
|
53
|
-
placeholder: "",
|
|
54
|
-
required: false,
|
|
55
|
-
theme: "default",
|
|
56
|
-
weight: "normal",
|
|
57
|
-
fieldHasError: false,
|
|
58
|
-
styleClassPassthrough: () => [],
|
|
59
|
-
ariaDescribedby: "",
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
const slots = useSlots();
|
|
63
|
-
|
|
64
|
-
const modelValue = defineModel<number>({
|
|
65
|
-
required: true,
|
|
66
|
-
});
|
|
67
|
-
</script>
|
|
68
|
-
|
|
69
|
-
<style lang="css">
|
|
70
|
-
@layer components {
|
|
71
|
-
.input-range-wrapper {
|
|
72
|
-
display: flex;
|
|
73
|
-
align-items: center;
|
|
74
|
-
justify-content: space-between;
|
|
75
|
-
gap: 1rem;
|
|
76
|
-
|
|
77
|
-
.slot {
|
|
78
|
-
align-self: flex-start;
|
|
79
|
-
|
|
80
|
-
.input-button {
|
|
81
|
-
border-radius: var(--form-input-border-radius);
|
|
82
|
-
width: var(--input-range-button-size);
|
|
83
|
-
height: var(--input-range-button-size);
|
|
84
|
-
|
|
85
|
-
.btn-icon {
|
|
86
|
-
margin: initial;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
.input-range-container {
|
|
92
|
-
flex-grow: 1;
|
|
93
|
-
|
|
94
|
-
display: grid;
|
|
95
|
-
grid-template-areas: "element-stack";
|
|
96
|
-
|
|
97
|
-
.input-range-markers {
|
|
98
|
-
grid-area: element-stack;
|
|
99
|
-
display: flex;
|
|
100
|
-
align-items: center;
|
|
101
|
-
justify-content: space-between;
|
|
102
|
-
width: 100%;
|
|
103
|
-
z-index: 2;
|
|
104
|
-
|
|
105
|
-
.marker {
|
|
106
|
-
background-color: var(--input-range-marker-background-colour, hsl(0, 29%, 3%));
|
|
107
|
-
padding: var(--input-range-marker-padding, 0.5rem);
|
|
108
|
-
border-radius: var(--input-range-marker-border-radius, 50%);
|
|
109
|
-
overflow: hidden;
|
|
110
|
-
outline: var(--input-range-marker-outline-width, 0.1rem) solid
|
|
111
|
-
var(--input-range-marker-outline-colour, var(--slate-04));
|
|
112
|
-
|
|
113
|
-
&:hover {
|
|
114
|
-
cursor: pointer;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
.marker-icon {
|
|
118
|
-
font-size: var(--input-range-marker-icon-size, 2rem);
|
|
119
|
-
display: block;
|
|
120
|
-
color: var(--input-range-marker-icon-colour, var(--input-range-accent-colour, var(--theme-accent)));
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
.input-range-core {
|
|
126
|
-
grid-area: element-stack;
|
|
127
|
-
|
|
128
|
-
accent-color: var(--input-range-accent-colour, var(--theme-accent));
|
|
129
|
-
height: var(--input-range-button-size);
|
|
130
|
-
margin: 0;
|
|
131
|
-
width: 100%;
|
|
132
|
-
|
|
133
|
-
&:focus-visible {
|
|
134
|
-
box-shadow: var(--form-focus-box-shadow);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
&.has-markers {
|
|
138
|
-
accent-color: var(--input-range-accent-colour, var(--theme-accent));
|
|
139
|
-
height: 0.2rem;
|
|
140
|
-
z-index: 2;
|
|
141
|
-
translate: 0 1.3rem;
|
|
142
|
-
|
|
143
|
-
&::-webkit-slider-thumb {
|
|
144
|
-
opacity: 0;
|
|
145
|
-
&:hover {
|
|
146
|
-
cursor: pointer;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
.input-range-datalist {
|
|
153
|
-
display: flex;
|
|
154
|
-
flex-direction: column;
|
|
155
|
-
font-family: var(--font-family);
|
|
156
|
-
font-size: var(--input-range-datalist-font-size, 1.4rem);
|
|
157
|
-
font-weight: var(--input-range-datalist-font-weight, 500);
|
|
158
|
-
justify-content: space-between;
|
|
159
|
-
writing-mode: vertical-lr;
|
|
160
|
-
width: 100%;
|
|
161
|
-
|
|
162
|
-
option {
|
|
163
|
-
padding: 0;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
</style>
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryFn } from "@nuxtjs/storybook";
|
|
2
|
-
import { ref } from "vue";
|
|
3
|
-
import StorybookComponent from "../InputRangeCore.vue";
|
|
4
|
-
|
|
5
|
-
interface InputRangeCoreStoryArgs {
|
|
6
|
-
id: string;
|
|
7
|
-
name: string;
|
|
8
|
-
min: number;
|
|
9
|
-
max: number;
|
|
10
|
-
step: number;
|
|
11
|
-
required: boolean;
|
|
12
|
-
fieldHasError: boolean;
|
|
13
|
-
weight: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export default {
|
|
17
|
-
title: "Components/Forms/Input Range/InputRangeCore",
|
|
18
|
-
component: StorybookComponent,
|
|
19
|
-
argTypes: {
|
|
20
|
-
id: { control: "text", table: { category: "Basic" } },
|
|
21
|
-
name: { control: "text", table: { category: "Basic" } },
|
|
22
|
-
min: { control: "number", table: { category: "Basic" } },
|
|
23
|
-
max: { control: "number", table: { category: "Basic" } },
|
|
24
|
-
step: { control: "number", table: { category: "Basic" } },
|
|
25
|
-
required: { control: "boolean", table: { category: "Basic" } },
|
|
26
|
-
fieldHasError: { control: "boolean", table: { category: "States" } },
|
|
27
|
-
weight: { control: "text", table: { category: "Styling" } },
|
|
28
|
-
},
|
|
29
|
-
args: {
|
|
30
|
-
id: "range-default",
|
|
31
|
-
name: "rangeDefault",
|
|
32
|
-
min: 0,
|
|
33
|
-
max: 100,
|
|
34
|
-
step: 1,
|
|
35
|
-
required: false,
|
|
36
|
-
fieldHasError: false,
|
|
37
|
-
weight: "normal",
|
|
38
|
-
},
|
|
39
|
-
} as Meta<InputRangeCoreStoryArgs>;
|
|
40
|
-
|
|
41
|
-
const Template: StoryFn<InputRangeCoreStoryArgs> = (args) => ({
|
|
42
|
-
components: { StorybookComponent },
|
|
43
|
-
setup() {
|
|
44
|
-
const modelValue = ref(50);
|
|
45
|
-
return { args, modelValue };
|
|
46
|
-
},
|
|
47
|
-
template: `
|
|
48
|
-
<div style="margin: 36px; max-width: 320px;">
|
|
49
|
-
<StorybookComponent v-bind="args" v-model="modelValue" />
|
|
50
|
-
</div>
|
|
51
|
-
`,
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
export const Default = Template.bind({});
|
|
55
|
-
|
|
56
|
-
export const ErrorState = Template.bind({});
|
|
57
|
-
ErrorState.args = { fieldHasError: true };
|
|
58
|
-
|
|
59
|
-
const WithStepButtonsTemplate: StoryFn<InputRangeCoreStoryArgs> = (args) => ({
|
|
60
|
-
components: { StorybookComponent },
|
|
61
|
-
setup() {
|
|
62
|
-
const modelValue = ref(50);
|
|
63
|
-
return { args, modelValue };
|
|
64
|
-
},
|
|
65
|
-
template: `
|
|
66
|
-
<div style="margin: 36px; max-width: 320px;">
|
|
67
|
-
<StorybookComponent v-bind="args" v-model="modelValue">
|
|
68
|
-
<template #left>
|
|
69
|
-
<button type="button" @click="modelValue = Math.max(args.min, modelValue - args.step)">−</button>
|
|
70
|
-
</template>
|
|
71
|
-
<template #right>
|
|
72
|
-
<button type="button" @click="modelValue = Math.min(args.max, modelValue + args.step)">+</button>
|
|
73
|
-
</template>
|
|
74
|
-
</StorybookComponent>
|
|
75
|
-
</div>
|
|
76
|
-
`,
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
export const WithStepButtons = WithStepButtonsTemplate.bind({});
|
|
80
|
-
|
|
81
|
-
const WithMarkersTemplate: StoryFn<InputRangeCoreStoryArgs> = (args) => ({
|
|
82
|
-
components: { StorybookComponent },
|
|
83
|
-
setup() {
|
|
84
|
-
const modelValue = ref(50);
|
|
85
|
-
return { args, modelValue };
|
|
86
|
-
},
|
|
87
|
-
template: `
|
|
88
|
-
<div style="margin: 36px; max-width: 320px;">
|
|
89
|
-
<StorybookComponent v-bind="args" v-model="modelValue">
|
|
90
|
-
<template #markers>
|
|
91
|
-
<div class="input-range-markers">
|
|
92
|
-
<div class="marker"><span class="marker-icon">😀</span></div>
|
|
93
|
-
<div class="marker"><span class="marker-icon">🙂</span></div>
|
|
94
|
-
<div class="marker"><span class="marker-icon">😐</span></div>
|
|
95
|
-
</div>
|
|
96
|
-
</template>
|
|
97
|
-
</StorybookComponent>
|
|
98
|
-
</div>
|
|
99
|
-
`,
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
export const WithMarkers = WithMarkersTemplate.bind({});
|
|
103
|
-
WithMarkers.args = { min: 0, max: 2, step: 1 };
|
|
104
|
-
|
|
105
|
-
const WithDatalistTemplate: StoryFn<InputRangeCoreStoryArgs> = (args) => ({
|
|
106
|
-
components: { StorybookComponent },
|
|
107
|
-
setup() {
|
|
108
|
-
const modelValue = ref(50);
|
|
109
|
-
return { args, modelValue };
|
|
110
|
-
},
|
|
111
|
-
template: `
|
|
112
|
-
<div style="margin: 36px; max-width: 320px;">
|
|
113
|
-
<StorybookComponent v-bind="args" v-model="modelValue">
|
|
114
|
-
<template #datalist>
|
|
115
|
-
<datalist :id="\`\${args.name}-datalist\`" class="input-range-datalist">
|
|
116
|
-
<option value="0" label="Low"></option>
|
|
117
|
-
<option value="50" label="Medium"></option>
|
|
118
|
-
<option value="100" label="High"></option>
|
|
119
|
-
</datalist>
|
|
120
|
-
</template>
|
|
121
|
-
</StorybookComponent>
|
|
122
|
-
</div>
|
|
123
|
-
`,
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
export const WithDatalist = WithDatalistTemplate.bind({});
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryFn } from "@nuxtjs/storybook";
|
|
2
|
-
import StorybookComponent from "../InputRangeDefault.vue";
|
|
3
|
-
import type { FormUiTheme } from "~/types/forms/types.forms.d";
|
|
4
|
-
|
|
5
|
-
interface InputRangeDefaultStoryArgs {
|
|
6
|
-
modelValue: number;
|
|
7
|
-
name: string;
|
|
8
|
-
label: string;
|
|
9
|
-
min: number;
|
|
10
|
-
max: number;
|
|
11
|
-
step: number;
|
|
12
|
-
errorMessage: string;
|
|
13
|
-
fieldHasError: boolean;
|
|
14
|
-
required: boolean;
|
|
15
|
-
theme: FormUiTheme;
|
|
16
|
-
stepDownLabel: string;
|
|
17
|
-
stepUpLabel: string;
|
|
18
|
-
useStepButtons: boolean;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export default {
|
|
22
|
-
title: "Components/Forms/Input Range/InputRangeDefault",
|
|
23
|
-
component: StorybookComponent,
|
|
24
|
-
argTypes: {
|
|
25
|
-
modelValue: { control: "number", table: { category: "Model" } },
|
|
26
|
-
name: { control: "text", table: { category: "Basic" } },
|
|
27
|
-
label: { control: "text", table: { category: "Basic" } },
|
|
28
|
-
min: { control: "number", table: { category: "Basic" } },
|
|
29
|
-
max: { control: "number", table: { category: "Basic" } },
|
|
30
|
-
step: { control: "number", table: { category: "Basic" } },
|
|
31
|
-
errorMessage: { control: "text", table: { category: "Validation" } },
|
|
32
|
-
fieldHasError: { control: "boolean", table: { category: "Validation" } },
|
|
33
|
-
required: { control: "boolean", table: { category: "Validation" } },
|
|
34
|
-
theme: {
|
|
35
|
-
control: { type: "select" },
|
|
36
|
-
options: ["default", "success", "error", "warning"],
|
|
37
|
-
table: { category: "Styling" },
|
|
38
|
-
},
|
|
39
|
-
stepDownLabel: { control: "text", description: "Localisation override for the step-down button", table: { category: "Accessibility" } },
|
|
40
|
-
stepUpLabel: { control: "text", description: "Localisation override for the step-up button", table: { category: "Accessibility" } },
|
|
41
|
-
useStepButtons: { control: "boolean", description: "Render +/- step buttons via left/right slots", table: { category: "Slots" } },
|
|
42
|
-
},
|
|
43
|
-
args: {
|
|
44
|
-
modelValue: 50,
|
|
45
|
-
name: "priceRange",
|
|
46
|
-
label: "Price range",
|
|
47
|
-
min: 0,
|
|
48
|
-
max: 100,
|
|
49
|
-
step: 1,
|
|
50
|
-
errorMessage: "",
|
|
51
|
-
fieldHasError: false,
|
|
52
|
-
required: false,
|
|
53
|
-
theme: "default",
|
|
54
|
-
stepDownLabel: "Step down",
|
|
55
|
-
stepUpLabel: "Step up",
|
|
56
|
-
useStepButtons: false,
|
|
57
|
-
},
|
|
58
|
-
} as Meta<InputRangeDefaultStoryArgs>;
|
|
59
|
-
|
|
60
|
-
const Template: StoryFn<InputRangeDefaultStoryArgs> = (args) => ({
|
|
61
|
-
components: { StorybookComponent },
|
|
62
|
-
setup() {
|
|
63
|
-
return { args };
|
|
64
|
-
},
|
|
65
|
-
template: `
|
|
66
|
-
<div style="margin: 36px; max-width: 320px;">
|
|
67
|
-
<StorybookComponent
|
|
68
|
-
v-model="args.modelValue"
|
|
69
|
-
:name="args.name"
|
|
70
|
-
:label="args.label"
|
|
71
|
-
:min="args.min"
|
|
72
|
-
:max="args.max"
|
|
73
|
-
:step="args.step"
|
|
74
|
-
:error-message="args.errorMessage"
|
|
75
|
-
:field-has-error="args.fieldHasError"
|
|
76
|
-
:required="args.required"
|
|
77
|
-
:theme="args.theme"
|
|
78
|
-
:step-down-label="args.stepDownLabel"
|
|
79
|
-
:step-up-label="args.stepUpLabel"
|
|
80
|
-
>
|
|
81
|
-
<template v-if="args.useStepButtons" #left><span>−</span></template>
|
|
82
|
-
<template v-if="args.useStepButtons" #right><span>+</span></template>
|
|
83
|
-
</StorybookComponent>
|
|
84
|
-
</div>
|
|
85
|
-
`,
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
export const Default = Template.bind({});
|
|
89
|
-
|
|
90
|
-
export const WithStepButtons = Template.bind({});
|
|
91
|
-
WithStepButtons.args = { useStepButtons: true };
|
|
92
|
-
|
|
93
|
-
export const WithError = Template.bind({});
|
|
94
|
-
WithError.args = {
|
|
95
|
-
fieldHasError: true,
|
|
96
|
-
errorMessage: "Please choose a value within the allowed range",
|
|
97
|
-
theme: "error",
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
export const Required = Template.bind({});
|
|
101
|
-
Required.args = { required: true };
|
|
102
|
-
|
|
103
|
-
export const LocalisedLabels = Template.bind({});
|
|
104
|
-
LocalisedLabels.args = {
|
|
105
|
-
useStepButtons: true,
|
|
106
|
-
stepDownLabel: "Diminuer",
|
|
107
|
-
stepUpLabel: "Augmenter",
|
|
108
|
-
};
|