srcdev-nuxt-components 9.4.8 → 9.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/migrate-component.md +20 -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-range-core.md +142 -0
- package/.claude/skills/components/section-parallax.md +32 -10
- 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 +10 -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-range.code-snippets +85 -0
- package/.vscode/srcdev-component-section-parallax.code-snippets +27 -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/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 +1 -1
- package/app/components/01.atoms/canvas-switcher/stories/CanvasSwitcher.stories.ts +1 -1
- 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/navigation/skip-links/SkipLinks.vue +7 -1
- 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/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 +96 -11
- 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-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/patterns/stories/MigratedFieldsForm.stories.ts +135 -0
- package/app/components/05.forms/triple-toggle-switch/CONSUMER-STYLING.md +16 -2
- package/app/components/05.forms/triple-toggle-switch/TripleToggleSwitchCore.vue +6 -4
- package/app/types/components/container-glow.d.ts +8 -0
- package/app/types/components/index.ts +1 -1
- package/package.json +3 -2
- 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
|
@@ -0,0 +1,108 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,140 @@
|
|
|
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 "../InputRangeDefault.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("InputRangeDefault", () => {
|
|
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: "Price range" });
|
|
34
|
+
expect(wrapper.text()).toContain("Price range");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("renders a range input", async () => {
|
|
38
|
+
wrapper = await wrapperFactory();
|
|
39
|
+
expect(wrapper.find('input[type="range"]').exists()).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("sets data-invalid and the error class when fieldHasError is true", async () => {
|
|
43
|
+
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");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("does not render step buttons when the left/right slots are not used", async () => {
|
|
49
|
+
wrapper = await wrapperFactory();
|
|
50
|
+
expect(wrapper.findAllComponents({ name: "InputButtonCore" })).toHaveLength(0);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("renders step buttons with default labels when the left/right slots are used", async () => {
|
|
54
|
+
wrapper = await wrapperFactory(
|
|
55
|
+
{},
|
|
56
|
+
{
|
|
57
|
+
left: '<span data-testid="left-icon">-</span>',
|
|
58
|
+
right: '<span data-testid="right-icon">+</span>',
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
expect(wrapper.text()).toContain("Step down");
|
|
63
|
+
expect(wrapper.text()).toContain("Step up");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("overrides step button labels via stepDownLabel/stepUpLabel props", async () => {
|
|
67
|
+
wrapper = await wrapperFactory(
|
|
68
|
+
{ stepDownLabel: "Decrease", stepUpLabel: "Increase" },
|
|
69
|
+
{
|
|
70
|
+
left: '<span>-</span>',
|
|
71
|
+
right: '<span>+</span>',
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
expect(wrapper.text()).toContain("Decrease");
|
|
76
|
+
expect(wrapper.text()).toContain("Increase");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("increments the model value when the step-up button is clicked", async () => {
|
|
80
|
+
wrapper = await wrapperFactory(
|
|
81
|
+
{ modelValue: 50, step: 5 },
|
|
82
|
+
{ right: '<span>+</span>' }
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const buttons = wrapper.findAll("button");
|
|
86
|
+
const stepUpButton = buttons.find((btn) => btn.text().includes("+") || btn.attributes("aria-label")?.includes("Step up"));
|
|
87
|
+
await stepUpButton?.trigger("click");
|
|
88
|
+
|
|
89
|
+
expect(wrapper.emitted("update:modelValue")?.[0]).toEqual([55]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("decrements the model value when the step-down button is clicked", async () => {
|
|
93
|
+
wrapper = await wrapperFactory(
|
|
94
|
+
{ modelValue: 50, step: 5 },
|
|
95
|
+
{ left: '<span>-</span>' }
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const buttons = wrapper.findAll("button");
|
|
99
|
+
const stepDownButton = buttons.find((btn) => btn.text().includes("-") || btn.attributes("aria-label")?.includes("Step down"));
|
|
100
|
+
await stepDownButton?.trigger("click");
|
|
101
|
+
|
|
102
|
+
expect(wrapper.emitted("update:modelValue")?.[0]).toEqual([45]);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("does not step below min", async () => {
|
|
106
|
+
wrapper = await wrapperFactory(
|
|
107
|
+
{ modelValue: 0, min: 0, step: 5 },
|
|
108
|
+
{ left: '<span>-</span>' }
|
|
109
|
+
);
|
|
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(
|
|
120
|
+
{ modelValue: 100, max: 100, step: 5 },
|
|
121
|
+
{ right: '<span>+</span>' }
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const buttons = wrapper.findAll("button");
|
|
125
|
+
const stepUpButton = buttons.find((btn) => btn.text().includes("+"));
|
|
126
|
+
await stepUpButton?.trigger("click");
|
|
127
|
+
|
|
128
|
+
expect(wrapper.emitted("update:modelValue")).toBeUndefined();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("renders the error message when fieldHasError is true", async () => {
|
|
132
|
+
wrapper = await wrapperFactory({ fieldHasError: true, errorMessage: "This field is required" });
|
|
133
|
+
expect(wrapper.text()).toContain("This field is required");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("applies styleClassPassthrough", async () => {
|
|
137
|
+
wrapper = await wrapperFactory({ styleClassPassthrough: "custom-range" });
|
|
138
|
+
expect(wrapper.find(".input-range-with-label").classes()).toContain("custom-range");
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { Meta, StoryFn } from "@nuxtjs/storybook";
|
|
2
|
+
import { reactive } from "vue";
|
|
3
|
+
import InputTextWithLabel from "../../input-text/variants/InputTextWithLabel.vue";
|
|
4
|
+
import InputRangeDefault from "../../input-range/variants/InputRangeDefault.vue";
|
|
5
|
+
import InputButtonCore from "../../input-button/InputButtonCore.vue";
|
|
6
|
+
import FormField from "../../form-field/FormField.vue";
|
|
7
|
+
import HeroText from "../../../01.atoms/text-blocks/hero-text/HeroText.vue";
|
|
8
|
+
import type { InputUiVariant } from "~/types/forms/types.forms.d";
|
|
9
|
+
|
|
10
|
+
interface MigratedFieldsFormStoryArgs {
|
|
11
|
+
inputVariant: InputUiVariant;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Living reference, not a component of its own — one field per 05.forms component group that
|
|
15
|
+
// currently scores 5/5 in the Component Ledger (.claude/component-ledger/audit.json: tier folder,
|
|
16
|
+
// tests, story, skill doc, CONSUMER-STYLING.md, VS Code snippet). As of 2026-09-21 that's
|
|
17
|
+
// InputTextCore (via InputTextWithLabel) and InputRangeCore (via InputRangeDefault) — every other
|
|
18
|
+
// 05.forms component (select, number, checkbox, radio, toggle-switch, ...) is still mid-migration.
|
|
19
|
+
// Add a field here each time /migrate-component brings another 05.forms component up to 5/5, so
|
|
20
|
+
// this story doubles as a visible migration-progress tracker rather than living only in the
|
|
21
|
+
// ledger's HTML output. No validation wiring (useZodValidation/zod) here — that's the consuming
|
|
22
|
+
// app's concern, this is a visual/composition reference only.
|
|
23
|
+
export default {
|
|
24
|
+
title: "Patterns/Migrated Fields Form",
|
|
25
|
+
argTypes: {
|
|
26
|
+
inputVariant: {
|
|
27
|
+
control: { type: "select" },
|
|
28
|
+
options: ["normal", "outlined", "underlined"],
|
|
29
|
+
description: "Applied to every text-like field in the form at once",
|
|
30
|
+
table: { category: "Styling" },
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
args: {
|
|
34
|
+
inputVariant: "normal",
|
|
35
|
+
},
|
|
36
|
+
} as Meta<MigratedFieldsFormStoryArgs>;
|
|
37
|
+
|
|
38
|
+
const Template: StoryFn<MigratedFieldsFormStoryArgs> = (args) => ({
|
|
39
|
+
components: {
|
|
40
|
+
InputTextWithLabel,
|
|
41
|
+
InputRangeDefault,
|
|
42
|
+
InputButtonCore,
|
|
43
|
+
FormField,
|
|
44
|
+
HeroText,
|
|
45
|
+
},
|
|
46
|
+
setup() {
|
|
47
|
+
const state = reactive({
|
|
48
|
+
fullName: "",
|
|
49
|
+
budget: 50,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Demo-only "validation" — a real consuming app would wire this through something like
|
|
53
|
+
// useZodValidation instead (see this file's top comment). Kept deliberately simple so the
|
|
54
|
+
// Continue button has an obvious, reliable way to trigger both fields' error states for
|
|
55
|
+
// exercising InputTextWithLabel/InputRangeDefault's error UI in Storybook.
|
|
56
|
+
const errors = reactive({
|
|
57
|
+
fullName: "",
|
|
58
|
+
budget: "",
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const validate = () => {
|
|
62
|
+
errors.fullName = state.fullName.trim() ? "" : "Full name is required";
|
|
63
|
+
errors.budget = state.budget >= 100 ? "" : "Budget must be at least £100";
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const clearErrors = () => {
|
|
67
|
+
errors.fullName = "";
|
|
68
|
+
errors.budget = "";
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return { args, state, errors, validate, clearErrors };
|
|
72
|
+
},
|
|
73
|
+
template: `
|
|
74
|
+
<div style="margin: 36px; max-width: 480px;">
|
|
75
|
+
<HeroText
|
|
76
|
+
tag="h2"
|
|
77
|
+
font-size="heading"
|
|
78
|
+
:text-content="[{ text: 'Migrated fields', styleClass: 'normal' }]"
|
|
79
|
+
:style-class-passthrough="['mbe-20']"
|
|
80
|
+
/>
|
|
81
|
+
<p style="margin: 0 0 2rem 0; color: light-dark(#475569, #94a3b8); font-size: 1.4rem;">
|
|
82
|
+
One field per 05.forms component that's fully migrated (5/5 on the Component Ledger).
|
|
83
|
+
Click Continue with an empty name or a budget under £100 to see the error states.
|
|
84
|
+
</p>
|
|
85
|
+
|
|
86
|
+
<form novalidate @submit.prevent="validate">
|
|
87
|
+
<FormField width="wide" :has-gutter="false">
|
|
88
|
+
<InputTextWithLabel
|
|
89
|
+
v-model="state.fullName"
|
|
90
|
+
type="text"
|
|
91
|
+
name="fullName"
|
|
92
|
+
label="Full name"
|
|
93
|
+
placeholder="eg. Jane Smith"
|
|
94
|
+
:error-message="errors.fullName"
|
|
95
|
+
:field-has-error="!!errors.fullName"
|
|
96
|
+
:required="true"
|
|
97
|
+
:input-variant="args.inputVariant"
|
|
98
|
+
/>
|
|
99
|
+
</FormField>
|
|
100
|
+
|
|
101
|
+
<FormField width="wide" :has-gutter="false">
|
|
102
|
+
<InputRangeDefault
|
|
103
|
+
v-model="state.budget"
|
|
104
|
+
name="budget"
|
|
105
|
+
label="Budget (£)"
|
|
106
|
+
:min="0"
|
|
107
|
+
:max="500"
|
|
108
|
+
:step="10"
|
|
109
|
+
:error-message="errors.budget"
|
|
110
|
+
:field-has-error="!!errors.budget"
|
|
111
|
+
>
|
|
112
|
+
<template #left><span aria-hidden="true">−</span></template>
|
|
113
|
+
<template #right><span aria-hidden="true">+</span></template>
|
|
114
|
+
</InputRangeDefault>
|
|
115
|
+
</FormField>
|
|
116
|
+
|
|
117
|
+
<FormField width="wide" :has-gutter="false">
|
|
118
|
+
<div style="display: flex; gap: 1.2rem;">
|
|
119
|
+
<InputButtonCore type="submit" variant="primary" button-text="Continue" />
|
|
120
|
+
<InputButtonCore type="button" variant="tertiary" button-text="Clear errors" @click="clearErrors" />
|
|
121
|
+
</div>
|
|
122
|
+
</FormField>
|
|
123
|
+
</form>
|
|
124
|
+
|
|
125
|
+
<div
|
|
126
|
+
style="margin-top: 2rem; padding: 1.6rem; border-radius: 0.8rem; background: light-dark(#f8fafc, #1e293b); font-family: monospace; font-size: 1.3rem;"
|
|
127
|
+
>
|
|
128
|
+
<div>fullName: {{ state.fullName || '""' }}</div>
|
|
129
|
+
<div>budget: £{{ state.budget }}</div>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
`,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
export const Default = Template.bind({});
|
|
@@ -45,8 +45,22 @@ the shared `--theme-*` token every other themed component also falls back to. Se
|
|
|
45
45
|
> themselves are otherwise unchanged (so anything already targeting them by their corrected names
|
|
46
46
|
> keeps working). Making the component genuinely value-agnostic would be a larger, separate change.
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
## Sizing tokens
|
|
49
|
+
|
|
50
|
+
| Token | Default | Controls |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| `--triple-toggle-switch-gap` | `1rem` | Gap between the three option circles |
|
|
53
|
+
| `--triple-toggle-switch-padding` | `0.6rem` | Wrapper padding |
|
|
54
|
+
| `--triple-toggle-switch-option-padding` | `0.5rem` | Padding inside each option circle |
|
|
55
|
+
| `--triple-toggle-switch-icon-size` | `2rem` | Icon font-size inside each option |
|
|
56
|
+
|
|
57
|
+
Added 2026-09-20 when `DisplayThemeSwitch`'s `small` variant was found reaching directly into this
|
|
58
|
+
component's private `--_form-items-gap`/`--_form-padding`/`--_select-scheme-group-padding`/
|
|
59
|
+
`--_scheme-icon-font-size` locals via selector specificity — a consumer-relevant value (compact
|
|
60
|
+
sizing) hidden behind a private var with no public override hook, the same bug class as CLAUDE.md
|
|
61
|
+
pitfall #20. These four now wrap those locals as real public tokens; the shared geometry from
|
|
62
|
+
`--form-element-*` (border width, outline width) is still shared/component-internal and untouched
|
|
63
|
+
by this — see `theming-form-geometry-tokens.md` for that half.
|
|
50
64
|
|
|
51
65
|
---
|
|
52
66
|
|
|
@@ -115,13 +115,15 @@ onMounted(() => {
|
|
|
115
115
|
var(--form-element-outline-width)
|
|
116
116
|
);
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
/* Sizing/spacing, publicly overridable — DisplayThemeSwitch's compact "small" styleClassPassthrough
|
|
119
|
+
variant sets these directly rather than reaching into the --_ locals below. */
|
|
120
|
+
--_form-items-gap: var(--triple-toggle-switch-gap, 1rem);
|
|
121
|
+
--_form-padding: var(--triple-toggle-switch-padding, 0.6rem);
|
|
120
122
|
|
|
121
123
|
--_select-scheme-group-background-color: var(--_marker-surface);
|
|
122
124
|
--_select-scheme-group-background-image: none;
|
|
123
|
-
--_select-scheme-group-padding: 0.5rem;
|
|
124
|
-
--_scheme-icon-font-size: 2rem;
|
|
125
|
+
--_select-scheme-group-padding: var(--triple-toggle-switch-option-padding, 0.5rem);
|
|
126
|
+
--_scheme-icon-font-size: var(--triple-toggle-switch-icon-size, 2rem);
|
|
125
127
|
/* --_scheme-icon-colour: black; */
|
|
126
128
|
|
|
127
129
|
/* "system" not "auto" — matches useSettingsStore's actual colourScheme value/CSS class
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// Export types from the components directory
|
|
2
2
|
export * from "./semantic-theme.d"
|
|
3
|
-
export * from "./carousel-basic.d"
|
|
4
3
|
export * from "./slider-gallery.d"
|
|
5
4
|
export * from "./canvas-switcher.d"
|
|
6
5
|
export * from "./display-chip.d"
|
|
@@ -10,6 +9,7 @@ export * from "./display-toast.d"
|
|
|
10
9
|
export * from "./display-prompt.d"
|
|
11
10
|
export * from "./qr-code.d"
|
|
12
11
|
export * from "./alert-mask-core.d"
|
|
12
|
+
export * from "./container-glow.d"
|
|
13
13
|
export * from "./hero-text"
|
|
14
14
|
export * from "./navigation-horizontal.d"
|
|
15
15
|
export * from "./social-icons-list.d"
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "srcdev-nuxt-components",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "9.4.
|
|
4
|
+
"version": "9.4.10",
|
|
5
5
|
"main": "nuxt.config.ts",
|
|
6
6
|
"types": "types.d.ts",
|
|
7
7
|
"license": "MIT",
|
|
@@ -148,6 +148,7 @@
|
|
|
148
148
|
"zod": "4.4.3"
|
|
149
149
|
},
|
|
150
150
|
"overrides": {
|
|
151
|
-
"vue-router": "5.2.0"
|
|
151
|
+
"vue-router": "5.2.0",
|
|
152
|
+
"h3": "1.15.11"
|
|
152
153
|
}
|
|
153
154
|
}
|