srcdev-nuxt-components 9.6.10 → 9.6.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/rules/consumer/ids-from-useid.md +4 -2
- package/.claude/skills/component-compliance-checklist.md +9 -3
- package/.claude/skills/component-inline-action-button.md +5 -4
- package/.claude/skills/component-naming.md +2 -1
- package/.claude/skills/components/{alert-mask-core.md → alert-mask.md} +38 -23
- package/.claude/skills/components/alert-masked-content.md +3 -3
- package/.claude/skills/components/container-glow.md +1 -1
- package/.claude/skills/components/display-theme-switch.md +8 -8
- package/.claude/skills/components/input-button.md +26 -7
- package/.claude/skills/components/input-checkbox-radio.md +2 -0
- package/.claude/skills/components/input-description.md +5 -0
- package/.claude/skills/components/input-error.md +9 -2
- package/.claude/skills/components/input-label.md +8 -2
- package/.claude/skills/components/input-number.md +2 -0
- package/.claude/skills/components/input-otp.md +2 -0
- package/.claude/skills/components/input-range.md +2 -0
- package/.claude/skills/components/input-select.md +2 -0
- package/.claude/skills/components/input-text.md +2 -0
- package/.claude/skills/components/input-textarea.md +2 -0
- package/.claude/skills/components/pending-effect.md +5 -4
- package/.claude/skills/components/{triple-toggle-switch.md → segmented-control.md} +34 -19
- package/.claude/skills/components/services-card.md +1 -1
- package/.claude/skills/components/tabbed-content.md +1 -1
- package/.claude/skills/components/toggle-switch.md +2 -0
- package/.claude/skills/index.md +5 -5
- package/.claude/skills/storybook-add-story.md +3 -1
- package/.claude/skills/theming-component-token-pattern.md +24 -0
- package/.claude/skills/theming-form-geometry-tokens.md +1 -1
- package/.claude/skills/theming-partial-override.md +1 -1
- package/.vscode/srcdev-component-alert-mask.code-snippets +41 -0
- package/.vscode/{srcdev-component-triple-toggle-switch.code-snippets → srcdev-component-segmented-control.code-snippets} +6 -7
- package/app/assets/styles/setup/03.theming/_theme-slots.css +1 -1
- package/app/assets/styles/setup/04.elements/forms/03.generic-input-geometry.css +4 -0
- package/app/components/01.atoms/alert-mask/AlertMask.vue +194 -0
- package/app/components/01.atoms/alert-mask/CONSUMER-STYLING.md +57 -42
- package/app/components/01.atoms/alert-mask/stories/AlertMask.stories.ts +198 -0
- package/app/components/01.atoms/alert-mask/tests/AlertMask.spec.ts +233 -0
- package/app/components/02.molecules/alert-masked-content/AlertMaskedContent.vue +2 -2
- package/app/components/02.molecules/alert-masked-content/CONSUMER-STYLING.md +4 -4
- package/app/components/02.molecules/alert-masked-content/stories/AlertMaskedContent.stories.ts +60 -0
- package/app/components/02.molecules/alert-masked-content/tests/AlertMaskedContent.spec.ts +12 -12
- package/app/components/02.molecules/cookie-consent-banner/CONSUMER-STYLING.md +3 -3
- package/app/components/02.molecules/cookie-consent-banner/CookieConsentBanner.vue +1 -1
- package/app/components/02.molecules/cookie-consent-banner/tests/CookieConsentBanner.spec.ts +1 -1
- package/app/components/02.molecules/display-theme-switch/CONSUMER-STYLING.md +9 -9
- package/app/components/02.molecules/display-theme-switch/DisplayThemeSwitch.vue +16 -16
- package/app/components/02.molecules/display-theme-switch/stories/DisplayThemeSwitch.stories.ts +53 -1
- package/app/components/02.molecules/display-theme-switch/tests/DisplayThemeSwitch.spec.ts +8 -8
- package/app/components/02.molecules/input-copy/CONSUMER-STYLING.md +4 -4
- package/app/components/02.molecules/input-copy/InputCopy.vue +2 -2
- package/app/components/02.molecules/input-copy/tests/InputCopy.spec.ts +2 -2
- package/app/components/03.organisms/google-reviews/tests/GoogleReviews.spec.ts +4 -4
- package/app/components/03.organisms/services/services-card/CONSUMER-STYLING.md +2 -2
- package/app/components/03.organisms/services/services-grids/tests/ServicesCardGrid.spec.ts +2 -2
- package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServicesCardGrid.spec.ts.snap +6 -6
- package/app/components/05.forms/form-errors/CONSUMER-STYLING.md +8 -0
- package/app/components/05.forms/form-errors/InputError.vue +43 -37
- package/app/components/05.forms/form-errors/stories/InputError.stories.ts +79 -1
- package/app/components/05.forms/form-errors/tests/InputError.spec.ts +29 -1
- package/app/components/05.forms/input-button/CONSUMER-STYLING.md +80 -47
- package/app/components/05.forms/input-button/InputButton.vue +52 -100
- package/app/components/05.forms/input-button/playwright/input-button.playwright.ts +1 -1
- package/app/components/05.forms/input-button/stories/InputButton.stories.ts +145 -8
- package/app/components/05.forms/input-button/tests/InputButton.spec.ts +76 -46
- package/app/components/05.forms/input-button/tests/__snapshots__/InputButton.spec.ts.snap +17 -17
- package/app/components/05.forms/input-checkbox-radio/CONSUMER-STYLING.md +9 -1
- package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadio.vue +8 -0
- package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioButton.vue +2 -2
- package/app/components/05.forms/input-description/CONSUMER-STYLING.md +14 -2
- package/app/components/05.forms/input-description/InputDescription.vue +2 -1
- package/app/components/05.forms/input-description/stories/InputDescription.stories.ts +70 -4
- package/app/components/05.forms/input-description/tests/InputDescription.spec.ts +2 -1
- package/app/components/05.forms/input-label/CONSUMER-STYLING.md +8 -0
- package/app/components/05.forms/input-label/InputLabel.vue +1 -0
- package/app/components/05.forms/input-label/stories/InputLabel.stories.ts +68 -2
- package/app/components/05.forms/input-label/tests/InputLabel.spec.ts +13 -3
- package/app/components/05.forms/input-number/CONSUMER-STYLING.md +7 -1
- package/app/components/05.forms/input-number/InputNumber.vue +4 -5
- package/app/components/05.forms/input-otp/CONSUMER-STYLING.md +7 -1
- package/app/components/05.forms/input-otp/InputOtp.vue +2 -2
- package/app/components/05.forms/input-range/InputRange.vue +1 -1
- package/app/components/05.forms/input-select/CONSUMER-STYLING.md +7 -1
- package/app/components/05.forms/input-select/InputSelect.vue +14 -5
- package/app/components/05.forms/input-text/CONSUMER-STYLING.md +7 -1
- package/app/components/05.forms/input-text/InputText.vue +4 -4
- package/app/components/05.forms/input-textarea/CONSUMER-STYLING.md +7 -1
- package/app/components/05.forms/input-textarea/InputTextarea.vue +2 -3
- package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +4 -4
- package/app/components/05.forms/pending-effect/CONSUMER-STYLING.md +7 -3
- package/app/components/05.forms/pending-effect/PendingEffect.vue +3 -3
- package/app/components/05.forms/pending-effect/tests/PendingEffect.spec.ts +1 -1
- package/app/components/05.forms/segmented-control/CONSUMER-STYLING.md +125 -0
- package/app/components/05.forms/segmented-control/SegmentedControl.vue +231 -0
- package/app/components/05.forms/segmented-control/stories/SegmentedControl.stories.ts +215 -0
- package/app/components/05.forms/segmented-control/tests/SegmentedControl.spec.ts +202 -0
- package/app/components/05.forms/toggle-switch/CONSUMER-STYLING.md +7 -0
- package/app/components/05.forms/toggle-switch/ToggleSwitch.vue +2 -0
- package/app/types/components/index.ts +1 -1
- package/package.json +1 -1
- package/.vscode/srcdev-component-alert-mask-core.code-snippets +0 -31
- package/app/components/01.atoms/alert-mask/AlertMaskCore.vue +0 -166
- package/app/components/01.atoms/alert-mask/stories/AlertMaskCore.stories.ts +0 -105
- package/app/components/01.atoms/alert-mask/tests/AlertMaskCore.spec.ts +0 -342
- package/app/components/05.forms/triple-toggle-switch/CONSUMER-STYLING.md +0 -99
- package/app/components/05.forms/triple-toggle-switch/TripleToggleSwitch.vue +0 -251
- package/app/components/05.forms/triple-toggle-switch/stories/TripleToggleSwitch.stories.ts +0 -104
- package/app/components/05.forms/triple-toggle-switch/tests/TripleToggleSwitch.spec.ts +0 -138
- /package/app/types/components/{alert-mask-core.d.ts → alert-mask.d.ts} +0 -0
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryFn } from "@nuxtjs/storybook";
|
|
2
|
-
import { ref } from "vue";
|
|
3
|
-
import StorybookComponent from "../TripleToggleSwitch.vue";
|
|
4
|
-
import type { IFormMultipleOptions } from "~/types/forms/types.forms.d";
|
|
5
|
-
|
|
6
|
-
interface TripleToggleSwitchStoryArgs {
|
|
7
|
-
name: string;
|
|
8
|
-
ariaLabel: string;
|
|
9
|
-
stepAnimationDuration: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// TripleToggleSwitch keys its marker gradients off three option *values*: "system", "light",
|
|
13
|
-
// "dark" (see CONSUMER-STYLING.md). This story uses exactly those values so each gradient fires;
|
|
14
|
-
// any other value falls back to --triple-toggle-switch-marker-surface. DisplayThemeSwitch is the
|
|
15
|
-
// real-world consumer and adds the useSettingsStore wiring on top.
|
|
16
|
-
export default {
|
|
17
|
-
title: "Components/Forms/Triple Toggle Switch/TripleToggleSwitch",
|
|
18
|
-
component: StorybookComponent,
|
|
19
|
-
argTypes: {
|
|
20
|
-
name: { control: "text", table: { category: "Basic" } },
|
|
21
|
-
ariaLabel: {
|
|
22
|
-
control: "text",
|
|
23
|
-
description: "Accessible name for the radiogroup",
|
|
24
|
-
table: { category: "Accessibility" },
|
|
25
|
-
},
|
|
26
|
-
stepAnimationDuration: {
|
|
27
|
-
control: "text",
|
|
28
|
-
description: "CSS transition duration for the marker sliding between options",
|
|
29
|
-
table: { category: "Styling" },
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
args: {
|
|
33
|
-
name: "colour-scheme",
|
|
34
|
-
ariaLabel: "Colour scheme",
|
|
35
|
-
stepAnimationDuration: "250ms",
|
|
36
|
-
},
|
|
37
|
-
} as Meta<TripleToggleSwitchStoryArgs>;
|
|
38
|
-
|
|
39
|
-
const buildOptions = (): IFormMultipleOptions => ({
|
|
40
|
-
data: [
|
|
41
|
-
{ id: "system", name: "colour-scheme", value: "system", label: "System", icon: "material-symbols:night-sight-auto-sharp" },
|
|
42
|
-
{ id: "light", name: "colour-scheme", value: "light", label: "Light", icon: "radix-icons:sun" },
|
|
43
|
-
{ id: "dark", name: "colour-scheme", value: "dark", label: "Dark", icon: "radix-icons:moon" },
|
|
44
|
-
],
|
|
45
|
-
total: 3,
|
|
46
|
-
skip: 0,
|
|
47
|
-
limit: 3,
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
const Template: StoryFn<TripleToggleSwitchStoryArgs> = (args) => ({
|
|
51
|
-
components: { StorybookComponent },
|
|
52
|
-
setup() {
|
|
53
|
-
const modelValue = ref("system");
|
|
54
|
-
const fieldData = ref<IFormMultipleOptions>(buildOptions());
|
|
55
|
-
return { args, modelValue, fieldData };
|
|
56
|
-
},
|
|
57
|
-
template: `
|
|
58
|
-
<div style="margin: 36px;">
|
|
59
|
-
<StorybookComponent
|
|
60
|
-
v-model="modelValue"
|
|
61
|
-
v-model:field-data="fieldData"
|
|
62
|
-
:name="args.name"
|
|
63
|
-
:aria-label="args.ariaLabel"
|
|
64
|
-
:step-animation-duration="args.stepAnimationDuration"
|
|
65
|
-
/>
|
|
66
|
-
<div style="margin-top: 1.6rem; font-size: 1.4rem;">Current value: {{ modelValue }}</div>
|
|
67
|
-
</div>
|
|
68
|
-
`,
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
export const Default = Template.bind({});
|
|
72
|
-
|
|
73
|
-
const CustomTokensTemplate: StoryFn<TripleToggleSwitchStoryArgs> = (args) => ({
|
|
74
|
-
components: { StorybookComponent },
|
|
75
|
-
setup() {
|
|
76
|
-
const modelValue = ref("light");
|
|
77
|
-
const fieldData = ref<IFormMultipleOptions>(buildOptions());
|
|
78
|
-
return { args, modelValue, fieldData };
|
|
79
|
-
},
|
|
80
|
-
template: `
|
|
81
|
-
<div
|
|
82
|
-
style="
|
|
83
|
-
margin: 36px;
|
|
84
|
-
--triple-toggle-switch-gap: 0.4rem;
|
|
85
|
-
--triple-toggle-switch-padding: 0.4rem;
|
|
86
|
-
--triple-toggle-switch-icon-size: 1.6rem;
|
|
87
|
-
--triple-toggle-switch-marker-gradient-system: linear-gradient(135deg, #0ea5e9, #6366f1);
|
|
88
|
-
--triple-toggle-switch-marker-gradient-light: linear-gradient(135deg, #facc15, #f97316);
|
|
89
|
-
--triple-toggle-switch-marker-gradient-dark: linear-gradient(135deg, #1e293b, #475569);
|
|
90
|
-
"
|
|
91
|
-
>
|
|
92
|
-
<StorybookComponent
|
|
93
|
-
v-model="modelValue"
|
|
94
|
-
v-model:field-data="fieldData"
|
|
95
|
-
:name="args.name"
|
|
96
|
-
:aria-label="args.ariaLabel"
|
|
97
|
-
:step-animation-duration="args.stepAnimationDuration"
|
|
98
|
-
/>
|
|
99
|
-
<div style="margin-top: 1.6rem; font-size: 1.4rem;">Current value: {{ modelValue }}</div>
|
|
100
|
-
</div>
|
|
101
|
-
`,
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
export const CustomTokens = CustomTokensTemplate.bind({});
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from "vitest";
|
|
2
|
-
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
3
|
-
import { nextTick } from "vue";
|
|
4
|
-
import TripleToggleSwitch from "../TripleToggleSwitch.vue";
|
|
5
|
-
import type { IFormMultipleOptions } from "~/types/forms/types.forms";
|
|
6
|
-
|
|
7
|
-
const buildOptions = (): IFormMultipleOptions => ({
|
|
8
|
-
data: [
|
|
9
|
-
{ id: "system", name: "scheme", value: "system", label: "System", icon: "radix-icons:gear" },
|
|
10
|
-
{ id: "light", name: "scheme", value: "light", label: "Light", icon: "radix-icons:sun" },
|
|
11
|
-
{ id: "dark", name: "scheme", value: "dark", label: "Dark", icon: "radix-icons:moon" },
|
|
12
|
-
],
|
|
13
|
-
total: 3,
|
|
14
|
-
skip: 0,
|
|
15
|
-
limit: 3,
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
const mountSwitch = (props: Record<string, unknown> = {}) =>
|
|
19
|
-
mountSuspended(TripleToggleSwitch, {
|
|
20
|
-
props: {
|
|
21
|
-
modelValue: "system",
|
|
22
|
-
fieldData: buildOptions(),
|
|
23
|
-
...props,
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
interface TripleToggleSwitchInstance {
|
|
28
|
-
selectedOptionIndex: number;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
describe("TripleToggleSwitch", () => {
|
|
32
|
-
it("mounts without error", async () => {
|
|
33
|
-
const wrapper = await mountSwitch();
|
|
34
|
-
expect(wrapper.vm).toBeTruthy();
|
|
35
|
-
expect(wrapper.find(".triple-toggle-switch").exists()).toBe(true);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it("renders one radio per option with matching id, value and shared name", async () => {
|
|
39
|
-
const wrapper = await mountSwitch({ name: "colour-scheme" });
|
|
40
|
-
const inputs = wrapper.findAll("input.option-input");
|
|
41
|
-
expect(inputs).toHaveLength(3);
|
|
42
|
-
expect(inputs.map((i) => i.attributes("id"))).toEqual(["system", "light", "dark"]);
|
|
43
|
-
expect(inputs.map((i) => (i.element as HTMLInputElement).value)).toEqual(["system", "light", "dark"]);
|
|
44
|
-
inputs.forEach((i) => {
|
|
45
|
-
expect(i.attributes("type")).toBe("radio");
|
|
46
|
-
expect(i.attributes("name")).toBe("colour-scheme");
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("uses the default name when none is passed", async () => {
|
|
51
|
-
const wrapper = await mountSwitch();
|
|
52
|
-
expect(wrapper.find("input.option-input").attributes("name")).toBe("triple-toggle-switch");
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("links each label to its radio and renders the label text for screen readers", async () => {
|
|
56
|
-
const wrapper = await mountSwitch();
|
|
57
|
-
const labels = wrapper.findAll("label.option-group");
|
|
58
|
-
expect(labels.map((l) => l.attributes("for"))).toEqual(["system", "light", "dark"]);
|
|
59
|
-
expect(wrapper.findAll(".option-group .sr-only").map((s) => s.text())).toEqual(["System", "Light", "Dark"]);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it("exposes the options as a radiogroup", async () => {
|
|
63
|
-
const wrapper = await mountSwitch();
|
|
64
|
-
const group = wrapper.find(".option-group-wrapper");
|
|
65
|
-
expect(group.attributes("role")).toBe("radiogroup");
|
|
66
|
-
expect(group.attributes("aria-label")).toBeUndefined();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("applies ariaLabel to the radiogroup", async () => {
|
|
70
|
-
const wrapper = await mountSwitch({ ariaLabel: "Colour scheme" });
|
|
71
|
-
expect(wrapper.find(".option-group-wrapper").attributes("aria-label")).toBe("Colour scheme");
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("checks the radio matching modelValue", async () => {
|
|
75
|
-
const wrapper = await mountSwitch({ modelValue: "light" });
|
|
76
|
-
const checked = wrapper.findAll("input.option-input").map((i) => (i.element as HTMLInputElement).checked);
|
|
77
|
-
expect(checked).toEqual([false, true, false]);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("emits update:modelValue when another option is chosen", async () => {
|
|
81
|
-
const wrapper = await mountSwitch();
|
|
82
|
-
await wrapper.findAll("input.option-input")[2]!.setValue(true);
|
|
83
|
-
expect(wrapper.emitted("update:modelValue")?.[0]).toEqual(["dark"]);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it("marks only the selected option's icon as active", async () => {
|
|
87
|
-
const wrapper = await mountSwitch({ modelValue: "dark" });
|
|
88
|
-
const icons = wrapper.findAll(".option-icon");
|
|
89
|
-
expect(icons).toHaveLength(3);
|
|
90
|
-
expect(icons.map((i) => i.classes().includes("active"))).toEqual([false, false, true]);
|
|
91
|
-
expect(icons[0]!.classes()).toContain("system");
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("updates the active icon when modelValue changes", async () => {
|
|
95
|
-
const wrapper = await mountSwitch();
|
|
96
|
-
await wrapper.setProps({ modelValue: "light" });
|
|
97
|
-
const icons = wrapper.findAll(".option-icon");
|
|
98
|
-
expect(icons[1]!.classes()).toContain("active");
|
|
99
|
-
expect(icons[0]!.classes()).not.toContain("active");
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("omits the icon when an option has none", async () => {
|
|
103
|
-
const options = buildOptions();
|
|
104
|
-
delete options.data[1]!.icon;
|
|
105
|
-
const wrapper = await mountSwitch({ fieldData: options });
|
|
106
|
-
expect(wrapper.findAll(".option-icon")).toHaveLength(2);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it("computes the selected option index, falling back to 0 for an unknown value", async () => {
|
|
110
|
-
const wrapper = await mountSwitch({ modelValue: "dark" });
|
|
111
|
-
const vm = wrapper.vm as unknown as TripleToggleSwitchInstance;
|
|
112
|
-
expect(vm.selectedOptionIndex).toBe(2);
|
|
113
|
-
await wrapper.setProps({ modelValue: "unknown" });
|
|
114
|
-
expect(vm.selectedOptionIndex).toBe(0);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it("shows the marker only after the mount delay", async () => {
|
|
118
|
-
const wrapper = await mountSwitch();
|
|
119
|
-
const marker = wrapper.find(".selected-option-marker");
|
|
120
|
-
expect(marker.classes()).not.toContain("show");
|
|
121
|
-
vi.advanceTimersByTime(300);
|
|
122
|
-
await nextTick();
|
|
123
|
-
await nextTick();
|
|
124
|
-
expect(wrapper.find(".selected-option-marker").classes()).toContain("show");
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it("applies the theme as data-theme", async () => {
|
|
128
|
-
const wrapper = await mountSwitch({ theme: "success" });
|
|
129
|
-
expect(wrapper.find(".triple-toggle-switch").attributes("data-theme")).toBe("success");
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it("applies styleClassPassthrough classes to the root", async () => {
|
|
133
|
-
const wrapper = await mountSwitch({ styleClassPassthrough: ["small", "extra"] });
|
|
134
|
-
const root = wrapper.find(".triple-toggle-switch");
|
|
135
|
-
expect(root.classes()).toContain("small");
|
|
136
|
-
expect(root.classes()).toContain("extra");
|
|
137
|
-
});
|
|
138
|
-
});
|
|
File without changes
|