srcdev-nuxt-components 9.4.9 → 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.
Files changed (112) hide show
  1. package/.claude/commands/migrate-component.md +20 -6
  2. package/.claude/component-ledger/audit.json +1 -1
  3. package/.claude/component-ledger/build.mjs +26 -0
  4. package/.claude/component-ledger/output.html +8 -3
  5. package/.claude/component-ledger/template.html +7 -2
  6. package/.claude/skills/component-dynamic-slots.md +1 -1
  7. package/.claude/skills/components/alert-mask-core.md +84 -0
  8. package/.claude/skills/components/canvas-switcher.md +1 -1
  9. package/.claude/skills/components/carousel-flip.md +112 -168
  10. package/.claude/skills/components/container-glow.md +123 -0
  11. package/.claude/skills/components/dashboard-quad-grid.md +98 -0
  12. package/.claude/skills/components/dashboard-stats-grid.md +103 -0
  13. package/.claude/skills/components/display-theme-switch.md +60 -0
  14. package/.claude/skills/components/glowing-border.md +66 -0
  15. package/.claude/skills/components/input-range-core.md +142 -0
  16. package/.claude/skills/components/section-parallax.md +32 -10
  17. package/.claude/skills/components/ui-block-decorated.md +56 -0
  18. package/.claude/skills/components/wipe-away-vertical.md +98 -0
  19. package/.claude/skills/icon-sets.md +1 -1
  20. package/.claude/skills/index.md +10 -1
  21. package/.vscode/css-custom-data.json +38 -0
  22. package/.vscode/settings.json +3 -0
  23. package/.vscode/srcdev-component-alert-mask-core.code-snippets +31 -0
  24. package/.vscode/srcdev-component-carousel-flip.code-snippets +41 -0
  25. package/.vscode/srcdev-component-container-glow.code-snippets +33 -0
  26. package/.vscode/srcdev-component-dashboard-quad-grid.code-snippets +36 -0
  27. package/.vscode/srcdev-component-dashboard-stats-grid.code-snippets +35 -0
  28. package/.vscode/srcdev-component-display-theme-switch.code-snippets +27 -0
  29. package/.vscode/srcdev-component-glowing-border.code-snippets +23 -0
  30. package/.vscode/srcdev-component-input-range.code-snippets +85 -0
  31. package/.vscode/srcdev-component-section-parallax.code-snippets +27 -0
  32. package/.vscode/srcdev-component-ui-block-decorated.code-snippets +24 -0
  33. package/.vscode/srcdev-component-wipe-away-vertical.code-snippets +25 -0
  34. package/README.md +18 -37
  35. package/app/components/{alert-mask → 01.atoms/alert-mask}/AlertMaskCore.vue +16 -17
  36. package/app/components/01.atoms/alert-mask/CONSUMER-STYLING.md +46 -0
  37. package/app/components/01.atoms/alert-mask/stories/AlertMaskCore.stories.ts +79 -0
  38. package/app/components/{alert-mask → 01.atoms/alert-mask}/tests/AlertMaskCore.spec.ts +10 -10
  39. package/app/components/01.atoms/animations/container-glow/CONSUMER-STYLING.md +86 -0
  40. package/app/components/01.atoms/animations/container-glow/ContainerGlow.vue +299 -0
  41. package/app/components/01.atoms/animations/container-glow/stories/ContainerGlow.stories.ts +175 -0
  42. package/app/components/01.atoms/animations/container-glow/tests/ContainerGlow.spec.ts +131 -0
  43. package/app/components/01.atoms/animations/container-glow/tests/__snapshots__/ContainerGlow.spec.ts.snap +14 -0
  44. package/app/components/01.atoms/animations/glowing-border/CONSUMER-STYLING.md +61 -0
  45. package/app/components/01.atoms/animations/glowing-border/GlowingBorder.vue +132 -0
  46. package/app/components/01.atoms/animations/glowing-border/stories/GlowingBorder.stories.ts +165 -0
  47. package/app/components/01.atoms/animations/glowing-border/tests/GlowingBorder.spec.ts +94 -0
  48. package/app/components/01.atoms/animations/glowing-border/tests/__snapshots__/GlowingBorder.spec.ts.snap +3 -0
  49. package/app/components/01.atoms/animations/section-parallax/CONSUMER-STYLING.md +44 -0
  50. package/app/components/{parallax → 01.atoms/animations/section-parallax}/SectionParallax.vue +11 -10
  51. package/app/components/01.atoms/animations/section-parallax/stories/SectionParallax.stories.ts +98 -0
  52. package/app/components/01.atoms/animations/section-parallax/tests/SectionParallax.spec.ts +102 -0
  53. package/app/components/01.atoms/animations/section-parallax/tests/__snapshots__/SectionParallax.spec.ts.snap +9 -0
  54. package/app/components/01.atoms/animations/view-timeline/CONSUMER-STYLING.md +70 -0
  55. package/app/components/01.atoms/animations/view-timeline/WipeAwayVertical.vue +181 -0
  56. package/app/components/01.atoms/animations/view-timeline/stories/WipeAwayVertical.stories.ts +269 -0
  57. package/app/components/01.atoms/animations/view-timeline/tests/WipeAwayVertical.spec.ts +127 -0
  58. package/app/components/01.atoms/canvas-switcher/CONSUMER-STYLING.md +1 -1
  59. package/app/components/01.atoms/canvas-switcher/stories/CanvasSwitcher.stories.ts +1 -1
  60. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md +42 -0
  61. package/app/components/{layout-grids/LayoutGridA.vue → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/DashboardQuadGrid.vue} +23 -13
  62. package/app/components/{layout-grids/stories/LayoutGridA.stories.ts → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/stories/DashboardQuadGrid.stories.ts} +151 -23
  63. package/app/components/{layout-grids/tests/LayoutGridA.spec.ts → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/tests/DashboardQuadGrid.spec.ts} +25 -25
  64. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/CONSUMER-STYLING.md +41 -0
  65. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/DashboardStatsGrid.vue +133 -0
  66. package/app/components/{layout-grids/stories/LayoutGridB.stories.ts → 01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/stories/DashboardStatsGrid.stories.ts} +24 -24
  67. package/app/components/{layout-grids/tests/LayoutGridB.spec.ts → 01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/tests/DashboardStatsGrid.spec.ts} +23 -23
  68. package/app/components/01.atoms/ui-block-decorated/CONSUMER-STYLING.md +39 -0
  69. package/app/components/01.atoms/ui-block-decorated/UiBlockDecorated.vue +141 -0
  70. package/app/components/01.atoms/ui-block-decorated/stories/UiBlockDecorated.stories.ts +148 -0
  71. package/app/components/01.atoms/ui-block-decorated/tests/UiBlockDecorated.spec.ts +80 -0
  72. package/app/components/01.atoms/ui-block-decorated/tests/__snapshots__/UiBlockDecorated.spec.ts.snap +3 -0
  73. package/app/components/02.molecules/alert-masked-content/tests/AlertMaskedContent.spec.ts +6 -6
  74. package/app/components/02.molecules/display-theme-switch/CONSUMER-STYLING.md +34 -0
  75. package/app/components/02.molecules/display-theme-switch/DisplayThemeSwitch.vue +102 -0
  76. package/app/components/02.molecules/display-theme-switch/stories/DisplayThemeSwitch.stories.ts +115 -0
  77. package/app/components/02.molecules/display-theme-switch/tests/DisplayThemeSwitch.spec.ts +86 -0
  78. package/app/components/03.organisms/image-galleries/carousel-flip/CONSUMER-STYLING.md +46 -0
  79. package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/CarouselFlip.vue +63 -42
  80. package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/stories/CarouselFlip.stories.ts +96 -11
  81. package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/tests/CarouselFlip.spec.ts +80 -0
  82. package/app/components/05.forms/form-field/FormField.vue +1 -1
  83. package/app/components/05.forms/form-wrapper/FormWrapper.vue +1 -1
  84. package/app/components/05.forms/input-range/CONSUMER-STYLING.md +45 -0
  85. package/app/components/05.forms/input-range/InputRangeCore.vue +79 -143
  86. package/app/components/05.forms/input-range/stories/InputRangeCore.stories.ts +126 -0
  87. package/app/components/05.forms/input-range/tests/InputRangeCore.spec.ts +124 -0
  88. package/app/components/05.forms/input-range/variants/InputRangeDefault.vue +9 -32
  89. package/app/components/05.forms/input-range/variants/stories/InputRangeDefault.stories.ts +108 -0
  90. package/app/components/05.forms/input-range/variants/tests/InputRangeDefault.spec.ts +140 -0
  91. package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +135 -0
  92. package/app/components/05.forms/triple-toggle-switch/CONSUMER-STYLING.md +16 -2
  93. package/app/components/05.forms/triple-toggle-switch/TripleToggleSwitchCore.vue +6 -4
  94. package/app/types/components/container-glow.d.ts +8 -0
  95. package/app/types/components/index.ts +1 -1
  96. package/package.json +3 -2
  97. package/app/components/05.forms/input-range-fancy/InputRangeFancyCore.vue +0 -426
  98. package/app/components/05.forms/input-range-fancy/InputRangeFancyWithLabel.vue +0 -94
  99. package/app/components/carousels/CarouselBasic.vue +0 -317
  100. package/app/components/carousels/CarouselInfinite.vue +0 -358
  101. package/app/components/container-glow/ContainerGlowCore.vue +0 -296
  102. package/app/components/container-glow/stories/ContainerGlowCore.stories.ts +0 -337
  103. package/app/components/display-theme-switch/DisplayThemeSwitch.vue +0 -189
  104. package/app/components/display-theme-switch/stories/DisplayThemeSwitch.stories.ts +0 -154
  105. package/app/components/glowing-border/GlowingBorder.vue +0 -142
  106. package/app/components/glowing-border/stories/GlowingBorder.stories.ts +0 -141
  107. package/app/components/layout-grids/LayoutGridB.vue +0 -134
  108. package/app/components/test-storybook/TestStorybook.vue +0 -49
  109. package/app/components/test-storybook/stories/TestStorybook.stories.ts +0 -28
  110. package/app/components/ui-block-decorated/UiBlockDecorated.vue +0 -167
  111. package/app/components/view-timeline/WipeAwayVertical.vue +0 -180
  112. 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
- Geometry (`--_form-*` sizing locals, `--form-element-*`) is shared/component-internal and untouched
49
- by this — see `theming-form-geometry-tokens.md` for the shared half.
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
- --_form-items-gap: 1rem;
119
- --_form-padding: 0.6rem;
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
@@ -0,0 +1,8 @@
1
+ export interface ContainerGlowConfig {
2
+ proximity?: number
3
+ spread?: number
4
+ blur?: number
5
+ gap?: number
6
+ vertical?: boolean
7
+ inactiveOpacity?: number
8
+ }
@@ -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.9",
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
  }