srcdev-nuxt-components 9.6.6 → 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.
Files changed (67) hide show
  1. package/.claude/rules/consumer/ids-from-useid.md +14 -0
  2. package/.claude/skills/component-compliance-checklist.md +6 -0
  3. package/.claude/skills/component-naming.md +2 -2
  4. package/.claude/skills/components/input-checkbox-radio.md +19 -3
  5. package/.claude/skills/components/input-checkbox.md +14 -3
  6. package/.claude/skills/components/input-description.md +1 -1
  7. package/.claude/skills/components/input-error.md +1 -1
  8. package/.claude/skills/components/input-label.md +2 -2
  9. package/.claude/skills/components/input-number.md +44 -13
  10. package/.claude/skills/components/input-radio.md +15 -1
  11. package/.claude/skills/components/{input-range-core.md → input-range.md} +53 -18
  12. package/.claude/skills/components/input-textarea-core.md +4 -4
  13. package/.claude/skills/components/page-hero-highlights.md +22 -18
  14. package/.claude/skills/index.md +6 -6
  15. package/.claude/skills/testing-add-unit-test.md +17 -0
  16. package/.vscode/srcdev-component-input-checkbox-radio.code-snippets +0 -1
  17. package/.vscode/srcdev-component-input-range.code-snippets +38 -18
  18. package/.vscode/srcdev-component-page-hero-highlights.code-snippets +2 -1
  19. package/app/components/04.templates/page-hero-highlights/CONSUMER-STYLING.md +32 -12
  20. package/app/components/04.templates/page-hero-highlights/PageHeroHighlights.vue +56 -47
  21. package/app/components/04.templates/page-hero-highlights/stories/PageHeroHighlights.stories.ts +158 -307
  22. package/app/components/04.templates/page-hero-highlights/stories/PageHeroHighlightsHeader.stories.ts +10 -0
  23. package/app/components/04.templates/page-hero-highlights/tests/PageHeroHighlights.spec.ts +49 -27
  24. package/app/components/04.templates/page-hero-highlights/tests/PageHeroHighlightsHeader.spec.ts +42 -7
  25. package/app/components/04.templates/page-hero-highlights/tests/__snapshots__/PageHeroHighlights.spec.ts.snap +12 -10
  26. package/app/components/05.forms/input-checkbox/CONSUMER-STYLING.md +10 -0
  27. package/app/components/05.forms/input-checkbox/MultipleCheckboxes.vue +18 -4
  28. package/app/components/05.forms/input-checkbox/SingleCheckbox.vue +14 -2
  29. package/app/components/05.forms/input-checkbox/stories/MultipleCheckboxes.stories.ts +146 -37
  30. package/app/components/05.forms/input-checkbox/stories/SingleCheckbox.stories.ts +107 -30
  31. package/app/components/05.forms/input-checkbox/tests/MultipleCheckboxes.spec.ts +38 -4
  32. package/app/components/05.forms/input-checkbox/tests/SingleCheckbox.spec.ts +5 -0
  33. package/app/components/05.forms/input-checkbox-radio/CONSUMER-STYLING.md +10 -3
  34. package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadio.vue +1 -0
  35. package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioButton.vue +11 -10
  36. package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioField.vue +3 -0
  37. package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadio.stories.ts +44 -17
  38. package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadioButton.stories.ts +164 -58
  39. package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadioField.stories.ts +94 -21
  40. package/app/components/05.forms/input-checkbox-radio/tests/InputCheckboxRadio.spec.ts +14 -3
  41. package/app/components/05.forms/input-checkbox-radio/tests/InputCheckboxRadioButton.spec.ts +38 -3
  42. package/app/components/05.forms/input-checkbox-radio/tests/InputCheckboxRadioField.spec.ts +14 -3
  43. package/app/components/05.forms/input-description/CONSUMER-STYLING.md +2 -2
  44. package/app/components/05.forms/input-label/CONSUMER-STYLING.md +2 -2
  45. package/app/components/05.forms/input-number/CONSUMER-STYLING.md +42 -2
  46. package/app/components/05.forms/input-number/InputNumber.vue +37 -25
  47. package/app/components/05.forms/input-number/InputNumberField.vue +29 -16
  48. package/app/components/05.forms/input-number/stories/InputNumber.stories.ts +76 -25
  49. package/app/components/05.forms/input-number/stories/InputNumberField.stories.ts +116 -50
  50. package/app/components/05.forms/input-number/tests/InputNumber.spec.ts +49 -13
  51. package/app/components/05.forms/input-number/tests/InputNumberField.spec.ts +59 -5
  52. package/app/components/05.forms/input-radio/CONSUMER-STYLING.md +11 -0
  53. package/app/components/05.forms/input-radio/MultipleRadiobuttons.vue +14 -3
  54. package/app/components/05.forms/input-radio/stories/MultipleRadiobuttons.stories.ts +138 -38
  55. package/app/components/05.forms/input-radio/tests/MultipleRadioButtons.spec.ts +50 -3
  56. package/app/components/05.forms/input-range/CONSUMER-STYLING.md +68 -26
  57. package/app/components/05.forms/input-range/InputRange.vue +247 -0
  58. package/app/components/05.forms/input-range/{InputRangeDefault.vue → InputRangeField.vue} +41 -18
  59. package/app/components/05.forms/input-range/stories/InputRange.stories.ts +187 -0
  60. package/app/components/05.forms/input-range/stories/InputRangeField.stories.ts +196 -0
  61. package/app/components/05.forms/input-range/tests/{InputRangeCore.spec.ts → InputRange.spec.ts} +39 -15
  62. package/app/components/05.forms/input-range/tests/{InputRangeDefault.spec.ts → InputRangeField.spec.ts} +71 -7
  63. package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +5 -5
  64. package/package.json +1 -1
  65. package/app/components/05.forms/input-range/InputRangeCore.vue +0 -169
  66. package/app/components/05.forms/input-range/stories/InputRangeCore.stories.ts +0 -126
  67. 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 "../InputRangeDefault.vue";
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("InputRangeDefault", () => {
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 and the error class when fieldHasError is true", async () => {
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-with-label").attributes("data-invalid")).toBe("");
45
- expect(wrapper.find(".input-range-with-label").classes()).toContain("error");
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-with-label").classes()).toContain("custom-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 InputRangeDefault from "../../input-range/InputRangeDefault.vue";
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, InputRangeDefault (InputRangeCore), InputNumberField
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
- InputRangeDefault,
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
- <InputRangeDefault
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
- </InputRangeDefault>
311
+ </InputRangeField>
312
312
  </FormField>
313
313
 
314
314
  <FormField width="wide" :has-gutter="false">
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "srcdev-nuxt-components",
3
3
  "type": "module",
4
- "version": "9.6.6",
4
+ "version": "9.6.8",
5
5
  "main": "nuxt.config.ts",
6
6
  "types": "types.d.ts",
7
7
  "license": "MIT",
@@ -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
- };