srcdev-nuxt-components 9.6.10 → 9.6.12
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 +14 -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/form-field.md +15 -0
- package/.claude/skills/components/form-fieldset.md +18 -2
- 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/marquee-scroller.md +51 -14
- 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-form-field.code-snippets +7 -6
- package/.vscode/srcdev-component-form-fieldset.code-snippets +12 -12
- package/.vscode/srcdev-component-marquee-scroller.code-snippets +31 -28
- 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/01.atoms/animations/marquee-scroller/CONSUMER-STYLING.md +77 -54
- package/app/components/01.atoms/animations/marquee-scroller/MarqueeScroller.vue +103 -91
- package/app/components/01.atoms/animations/marquee-scroller/stories/MarqueeScroller.stories.ts +132 -3
- package/app/components/01.atoms/animations/marquee-scroller/tests/MarqueeScroller.spec.ts +190 -102
- package/app/components/01.atoms/animations/marquee-scroller/tests/__snapshots__/MarqueeScroller.spec.ts.snap +17 -13
- 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/form-field/CONSUMER-STYLING.md +7 -0
- package/app/components/05.forms/form-field/stories/FormField.stories.ts +116 -2
- package/app/components/05.forms/form-fieldset/CONSUMER-STYLING.md +11 -1
- package/app/components/05.forms/form-fieldset/FormFieldset.vue +4 -2
- package/app/components/05.forms/form-fieldset/stories/FormFieldset.stories.ts +68 -4
- package/app/components/05.forms/form-fieldset/tests/FormFieldset.spec.ts +10 -0
- 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,342 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
3
|
-
import AlertMaskCore from "../AlertMaskCore.vue";
|
|
4
|
-
import type { AlertMaskConfig } from "../../../../types/components/alert-mask-core.d";
|
|
5
|
-
|
|
6
|
-
// Type interface for AlertMaskCore component instance
|
|
7
|
-
interface AlertMaskCoreInstance {
|
|
8
|
-
cfg: {
|
|
9
|
-
backgroundColour: string;
|
|
10
|
-
borderColour: string;
|
|
11
|
-
radiusLeft: number;
|
|
12
|
-
radiusRight: number;
|
|
13
|
-
borderLeft: number;
|
|
14
|
-
borderTop: number;
|
|
15
|
-
borderRight: number;
|
|
16
|
-
borderBottom: number;
|
|
17
|
-
};
|
|
18
|
-
alertContentRef: HTMLElement | null;
|
|
19
|
-
svgWidth: number;
|
|
20
|
-
svgHeight: number;
|
|
21
|
-
outerPath: string;
|
|
22
|
-
innerPath: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Mock ResizeObserver
|
|
26
|
-
// Vitest 4: a mock's implementation must be a regular function (not an
|
|
27
|
-
// arrow function) to remain constructible via `new`.
|
|
28
|
-
const mockResizeObserver = vi.fn(function () {
|
|
29
|
-
return {
|
|
30
|
-
observe: vi.fn(),
|
|
31
|
-
unobserve: vi.fn(),
|
|
32
|
-
disconnect: vi.fn(),
|
|
33
|
-
};
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
vi.stubGlobal("ResizeObserver", mockResizeObserver);
|
|
37
|
-
|
|
38
|
-
describe("AlertMaskCore", () => {
|
|
39
|
-
beforeEach(() => {
|
|
40
|
-
vi.clearAllMocks();
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
afterEach(() => {
|
|
44
|
-
vi.restoreAllMocks();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("mounts without error", async () => {
|
|
48
|
-
const wrapper = await mountSuspended(AlertMaskCore);
|
|
49
|
-
expect(wrapper.vm).toBeTruthy();
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it("renders base structure correctly", async () => {
|
|
53
|
-
const wrapper = await mountSuspended(AlertMaskCore);
|
|
54
|
-
|
|
55
|
-
expect(wrapper.find(".alert-mask-core").exists()).toBe(true);
|
|
56
|
-
expect(wrapper.find(".alert-mask-decorator").exists()).toBe(true);
|
|
57
|
-
expect(wrapper.find(".alert-mask-content").exists()).toBe(true);
|
|
58
|
-
expect(wrapper.find(".alert-mask-content-slot").exists()).toBe(true);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it("renders default slot content", async () => {
|
|
62
|
-
const wrapper = await mountSuspended(AlertMaskCore, {
|
|
63
|
-
slots: {
|
|
64
|
-
default: "<p>Test alert content</p>",
|
|
65
|
-
},
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
expect(wrapper.find(".alert-mask-content-slot p").text()).toBe("Test alert content");
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("applies style class passthrough correctly", async () => {
|
|
72
|
-
const wrapper = await mountSuspended(AlertMaskCore, {
|
|
73
|
-
props: {
|
|
74
|
-
styleClassPassthrough: ["custom-class", "another-class"],
|
|
75
|
-
},
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
const alertMask = wrapper.find(".alert-mask-core");
|
|
79
|
-
expect(alertMask.classes()).toContain("custom-class");
|
|
80
|
-
expect(alertMask.classes()).toContain("another-class");
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("applies style class passthrough as string", async () => {
|
|
84
|
-
const wrapper = await mountSuspended(AlertMaskCore, {
|
|
85
|
-
props: {
|
|
86
|
-
styleClassPassthrough: "single-class",
|
|
87
|
-
},
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
const alertMask = wrapper.find(".alert-mask-core");
|
|
91
|
-
expect(alertMask.classes()).toContain("single-class");
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("uses default configuration when no config provided", async () => {
|
|
95
|
-
const wrapper = await mountSuspended(AlertMaskCore);
|
|
96
|
-
|
|
97
|
-
// Check that computed config uses defaults
|
|
98
|
-
const vm = wrapper.vm as unknown as AlertMaskCoreInstance;
|
|
99
|
-
expect(vm.cfg.backgroundColour).toBe("rgba(0,0,0,0.25)");
|
|
100
|
-
expect(vm.cfg.borderColour).toBe("var(--orange-08)");
|
|
101
|
-
expect(vm.cfg.radiusLeft).toBe(12);
|
|
102
|
-
expect(vm.cfg.radiusRight).toBe(12);
|
|
103
|
-
expect(vm.cfg.borderLeft).toBe(8);
|
|
104
|
-
expect(vm.cfg.borderTop).toBe(8);
|
|
105
|
-
expect(vm.cfg.borderRight).toBe(8);
|
|
106
|
-
expect(vm.cfg.borderBottom).toBe(8);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it("applies custom configuration correctly", async () => {
|
|
110
|
-
const config: AlertMaskConfig = {
|
|
111
|
-
backgroundColour: "rgba(255,0,0,0.5)",
|
|
112
|
-
borderColour: "#ff0000",
|
|
113
|
-
radiusLeft: 20,
|
|
114
|
-
radiusRight: 15,
|
|
115
|
-
borderLeft: 12,
|
|
116
|
-
borderTop: 10,
|
|
117
|
-
borderRight: 8,
|
|
118
|
-
borderBottom: 6,
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
const wrapper = await mountSuspended(AlertMaskCore, {
|
|
122
|
-
props: { config },
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
const vm = wrapper.vm as unknown as AlertMaskCoreInstance;
|
|
126
|
-
expect(vm.cfg.backgroundColour).toBe("rgba(255,0,0,0.5)");
|
|
127
|
-
expect(vm.cfg.borderColour).toBe("#ff0000");
|
|
128
|
-
expect(vm.cfg.radiusLeft).toBe(20);
|
|
129
|
-
expect(vm.cfg.radiusRight).toBe(15);
|
|
130
|
-
expect(vm.cfg.borderLeft).toBe(12);
|
|
131
|
-
expect(vm.cfg.borderTop).toBe(10);
|
|
132
|
-
expect(vm.cfg.borderRight).toBe(8);
|
|
133
|
-
expect(vm.cfg.borderBottom).toBe(6);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it("applies partial configuration with defaults", async () => {
|
|
137
|
-
const config: AlertMaskConfig = {
|
|
138
|
-
backgroundColour: "blue",
|
|
139
|
-
radiusLeft: 25,
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const wrapper = await mountSuspended(AlertMaskCore, {
|
|
143
|
-
props: { config },
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
const vm = wrapper.vm as unknown as AlertMaskCoreInstance;
|
|
147
|
-
expect(vm.cfg.backgroundColour).toBe("blue");
|
|
148
|
-
expect(vm.cfg.radiusLeft).toBe(25);
|
|
149
|
-
// Check defaults are maintained for other values
|
|
150
|
-
expect(vm.cfg.borderColour).toBe("var(--orange-08)");
|
|
151
|
-
expect(vm.cfg.radiusRight).toBe(12);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
it("sets up template ref for content element", async () => {
|
|
155
|
-
const wrapper = await mountSuspended(AlertMaskCore, {
|
|
156
|
-
slots: {
|
|
157
|
-
default: "<div>Content</div>",
|
|
158
|
-
},
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
await nextTick();
|
|
162
|
-
const vm = wrapper.vm as unknown as AlertMaskCoreInstance;
|
|
163
|
-
expect(vm.alertContentRef).toBeTruthy();
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it("creates SVG with proper structure", async () => {
|
|
167
|
-
const wrapper = await mountSuspended(AlertMaskCore);
|
|
168
|
-
|
|
169
|
-
const svg = wrapper.find(".alert-mask-decorator");
|
|
170
|
-
expect(svg.element.tagName).toBe("svg");
|
|
171
|
-
|
|
172
|
-
// Check for defs and mask
|
|
173
|
-
expect(wrapper.find("defs").exists()).toBe(true);
|
|
174
|
-
const mask = wrapper.find("mask");
|
|
175
|
-
expect(mask.exists()).toBe(true);
|
|
176
|
-
expect(wrapper.find("path[mask]").attributes("mask")).toBe(`url(#${mask.attributes("id")})`);
|
|
177
|
-
|
|
178
|
-
// Check for paths
|
|
179
|
-
const paths = wrapper.findAll("path");
|
|
180
|
-
expect(paths.length).toBe(4); // 2 in mask, 2 visible paths
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
it("gives each instance its own mask id so multiple masks on a page don't share one shape", async () => {
|
|
184
|
-
const wrapper = await mountSuspended({
|
|
185
|
-
components: { AlertMaskCore },
|
|
186
|
-
template: "<div><AlertMaskCore></AlertMaskCore><AlertMaskCore></AlertMaskCore></div>",
|
|
187
|
-
});
|
|
188
|
-
const ids = wrapper.findAll("mask").map((mask) => mask.attributes("id"));
|
|
189
|
-
expect(ids).toHaveLength(2);
|
|
190
|
-
expect(ids[0]).toBeTruthy();
|
|
191
|
-
expect(ids[0]).not.toBe(ids[1]);
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it("hides the decorative SVG from assistive technology", async () => {
|
|
195
|
-
const wrapper = await mountSuspended(AlertMaskCore);
|
|
196
|
-
expect(wrapper.find(".alert-mask-decorator").attributes("aria-hidden")).toBe("true");
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it("applies config colours as fill styles so CSS var() values resolve", async () => {
|
|
200
|
-
const wrapper = await mountSuspended(AlertMaskCore, {
|
|
201
|
-
props: { config: { borderColour: "var(--my-border)", backgroundColour: "var(--my-bg)" } },
|
|
202
|
-
});
|
|
203
|
-
const paths = wrapper.findAll(".alert-mask-decorator > path");
|
|
204
|
-
expect((paths[0]!.element as unknown as SVGElement).style.fill).toBe("var(--my-border)");
|
|
205
|
-
expect((paths[1]!.element as unknown as SVGElement).style.fill).toBe("var(--my-bg)");
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
it("disconnects its ResizeObserver on unmount", async () => {
|
|
209
|
-
const wrapper = await mountSuspended(AlertMaskCore);
|
|
210
|
-
const observer = mockResizeObserver.mock.results.at(-1)?.value as { disconnect: ReturnType<typeof vi.fn> };
|
|
211
|
-
wrapper.unmount();
|
|
212
|
-
expect(observer.disconnect).toHaveBeenCalled();
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it("applies CSS custom properties for content positioning", async () => {
|
|
216
|
-
const config: AlertMaskConfig = {
|
|
217
|
-
borderLeft: 15,
|
|
218
|
-
borderTop: 20,
|
|
219
|
-
borderRight: 25,
|
|
220
|
-
borderBottom: 30,
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
const wrapper = await mountSuspended(AlertMaskCore, {
|
|
224
|
-
props: { config },
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
const contentEl = wrapper.find(".alert-mask-content");
|
|
228
|
-
const style = (contentEl.element as HTMLElement).style;
|
|
229
|
-
|
|
230
|
-
expect(style.getPropertyValue("--_inset-inline-start")).toBe("15px");
|
|
231
|
-
expect(style.getPropertyValue("--_inset-block-start")).toBe("20px");
|
|
232
|
-
expect(style.getPropertyValue("--_inset-inline-end")).toBe("25px");
|
|
233
|
-
expect(style.getPropertyValue("--_inset-block-end")).toBe("30px");
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
it("handles zero border values correctly", async () => {
|
|
237
|
-
const config: AlertMaskConfig = {
|
|
238
|
-
borderLeft: 0,
|
|
239
|
-
borderTop: 0,
|
|
240
|
-
borderRight: 0,
|
|
241
|
-
borderBottom: 0,
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
const wrapper = await mountSuspended(AlertMaskCore, {
|
|
245
|
-
props: { config },
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
const contentEl = wrapper.find(".alert-mask-content");
|
|
249
|
-
const style = (contentEl.element as HTMLElement).style;
|
|
250
|
-
|
|
251
|
-
expect(style.getPropertyValue("--_inset-inline-start")).toBe("0px");
|
|
252
|
-
expect(style.getPropertyValue("--_inset-block-start")).toBe("0px");
|
|
253
|
-
expect(style.getPropertyValue("--_inset-inline-end")).toBe("0px");
|
|
254
|
-
expect(style.getPropertyValue("--_inset-block-end")).toBe("0px");
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
it("sets up ResizeObserver on mount", async () => {
|
|
258
|
-
await mountSuspended(AlertMaskCore, {
|
|
259
|
-
slots: {
|
|
260
|
-
default: "<div>Content to observe</div>",
|
|
261
|
-
},
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
// ResizeObserver should be instantiated
|
|
265
|
-
expect(mockResizeObserver).toHaveBeenCalled();
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
it("generates SVG paths when dimensions are available", async () => {
|
|
269
|
-
const wrapper = await mountSuspended(AlertMaskCore);
|
|
270
|
-
const vm = wrapper.vm as unknown as AlertMaskCoreInstance;
|
|
271
|
-
|
|
272
|
-
// Set dimensions
|
|
273
|
-
vm.svgWidth = 200;
|
|
274
|
-
vm.svgHeight = 100;
|
|
275
|
-
|
|
276
|
-
await nextTick();
|
|
277
|
-
|
|
278
|
-
// Check that paths are generated (not empty)
|
|
279
|
-
expect(vm.outerPath).toBeTruthy();
|
|
280
|
-
expect(vm.innerPath).toBeTruthy();
|
|
281
|
-
expect(vm.outerPath.length).toBeGreaterThan(0);
|
|
282
|
-
expect(vm.innerPath.length).toBeGreaterThan(0);
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
it("returns empty paths when dimensions are zero", async () => {
|
|
286
|
-
const wrapper = await mountSuspended(AlertMaskCore);
|
|
287
|
-
const vm = wrapper.vm as unknown as AlertMaskCoreInstance;
|
|
288
|
-
|
|
289
|
-
// Explicitly set dimensions to zero to test the condition
|
|
290
|
-
vm.svgWidth = 0;
|
|
291
|
-
vm.svgHeight = 0;
|
|
292
|
-
|
|
293
|
-
await nextTick();
|
|
294
|
-
|
|
295
|
-
expect(vm.outerPath).toBe("");
|
|
296
|
-
expect(vm.innerPath).toBe("");
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
it("updates SVG height CSS custom property", async () => {
|
|
300
|
-
const wrapper = await mountSuspended(AlertMaskCore);
|
|
301
|
-
const vm = wrapper.vm as unknown as AlertMaskCoreInstance;
|
|
302
|
-
|
|
303
|
-
vm.svgHeight = 150;
|
|
304
|
-
await nextTick();
|
|
305
|
-
|
|
306
|
-
const svg = wrapper.find(".alert-mask-decorator");
|
|
307
|
-
const style = (svg.element as HTMLElement).style;
|
|
308
|
-
expect(style.getPropertyValue("--_height")).toBe("150px");
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
it("handles empty configuration object", async () => {
|
|
312
|
-
const wrapper = await mountSuspended(AlertMaskCore, {
|
|
313
|
-
props: {
|
|
314
|
-
config: {},
|
|
315
|
-
},
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
// Should use all defaults when empty config provided
|
|
319
|
-
const vm = wrapper.vm as unknown as AlertMaskCoreInstance;
|
|
320
|
-
expect(vm.cfg.backgroundColour).toBe("rgba(0,0,0,0.25)");
|
|
321
|
-
expect(vm.cfg.borderColour).toBe("var(--orange-08)");
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
it("maintains reactivity with config changes", async () => {
|
|
325
|
-
const wrapper = await mountSuspended(AlertMaskCore, {
|
|
326
|
-
props: {
|
|
327
|
-
config: { backgroundColour: "red" },
|
|
328
|
-
},
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
let vm = wrapper.vm as unknown as AlertMaskCoreInstance;
|
|
332
|
-
expect(vm.cfg.backgroundColour).toBe("red");
|
|
333
|
-
|
|
334
|
-
// Update props
|
|
335
|
-
await wrapper.setProps({
|
|
336
|
-
config: { backgroundColour: "blue" },
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
vm = wrapper.vm as unknown as AlertMaskCoreInstance;
|
|
340
|
-
expect(vm.cfg.backgroundColour).toBe("blue");
|
|
341
|
-
});
|
|
342
|
-
});
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
# TripleToggleSwitch — Consumer Styling Guide
|
|
2
|
-
|
|
3
|
-
> **Renamed 2026-09-27**: `TripleToggleSwitchCore` → `TripleToggleSwitch`. The root class was
|
|
4
|
-
> already `.triple-toggle-switch`, and no token names changed.
|
|
5
|
-
|
|
6
|
-
## Public token API
|
|
7
|
-
|
|
8
|
-
Colour tokens fall back to the shared `--theme-*` tokens every other themed component uses, so
|
|
9
|
-
leave them unset to follow the site theme. See `theming-component-token-pattern.md`.
|
|
10
|
-
|
|
11
|
-
### Wrapper
|
|
12
|
-
|
|
13
|
-
| Token | Default | Controls |
|
|
14
|
-
|---|---|---|
|
|
15
|
-
| `--triple-toggle-switch-surface` | `var(--theme-input-surface)` | Wrapper background |
|
|
16
|
-
| `--triple-toggle-switch-border` | `var(--theme-border)` | Wrapper border colour |
|
|
17
|
-
| `--triple-toggle-switch-ring-focus` | `var(--theme-ring)` | Wrapper outline colour while a radio has `:focus-visible` |
|
|
18
|
-
| `--triple-toggle-switch-padding` | `0.6rem` | Wrapper padding |
|
|
19
|
-
| `--triple-toggle-switch-gap` | `1rem` | Gap between the three options (also drives the marker's position) |
|
|
20
|
-
|
|
21
|
-
### Selected-option marker
|
|
22
|
-
|
|
23
|
-
| Token | Default | Controls |
|
|
24
|
-
|---|---|---|
|
|
25
|
-
| `--triple-toggle-switch-marker-surface` | `var(--theme-input-surface)` | Marker background when the selected value isn't `system`/`light`/`dark` |
|
|
26
|
-
| `--triple-toggle-switch-marker-border` | `var(--slate-10)` | Marker border colour |
|
|
27
|
-
| `--triple-toggle-switch-marker-gradient-system` | green `radial-gradient(...)` | Marker `background-image` when `system` is selected |
|
|
28
|
-
| `--triple-toggle-switch-marker-gradient-light` | red/orange `radial-gradient(...)` | Marker `background-image` when `light` is selected |
|
|
29
|
-
| `--triple-toggle-switch-marker-gradient-dark` | black/grey `radial-gradient(...)` | Marker `background-image` when `dark` is selected |
|
|
30
|
-
|
|
31
|
-
> **Changed 2026-09-27**: the three marker gradients were hardcoded literals with no override hook.
|
|
32
|
-
> They're now public tokens; the defaults are unchanged. Any `<image>` value works
|
|
33
|
-
> (`linear-gradient(...)`, `url(...)`, `none`).
|
|
34
|
-
|
|
35
|
-
### Options
|
|
36
|
-
|
|
37
|
-
| Token | Default | Controls |
|
|
38
|
-
|---|---|---|
|
|
39
|
-
| `--triple-toggle-switch-option-padding` | `0.5rem` | Padding inside each option circle |
|
|
40
|
-
| `--triple-toggle-switch-icon-size` | `2rem` | Icon `font-size` (and the hidden radio's hit area) |
|
|
41
|
-
| `--triple-toggle-switch-option-border` | `#00000025` | Each option's resting border colour |
|
|
42
|
-
| `--triple-toggle-switch-option-border-hover` | `var(--slate-10)` | Option outline colour on icon hover |
|
|
43
|
-
| `--triple-toggle-switch-option-ring-focus` | `var(--theme-ring)` | Option outline colour on `:focus-visible` |
|
|
44
|
-
| `--triple-toggle-switch-option-icon-color` | `var(--slate-10)` | Icon colour, resting |
|
|
45
|
-
| `--triple-toggle-switch-option-icon-color-active` | `var(--slate-00)` | Icon colour, selected |
|
|
46
|
-
|
|
47
|
-
Also read (shared, not component-specific): `--form-element-border-width`,
|
|
48
|
-
`--form-element-outline-width`, `--theme-form-transition-duration`. See
|
|
49
|
-
`theming-form-geometry-tokens.md`.
|
|
50
|
-
|
|
51
|
-
Private tokens (not public API): `--_form-items-gap`, `--_scheme-icon-font-size` (each used twice,
|
|
52
|
-
fed by the public gap/icon-size tokens) and `--_select-scheme-group-background-color`/
|
|
53
|
-
`--_select-scheme-group-background-image` (swapped per selected value).
|
|
54
|
-
|
|
55
|
-
## State hooks
|
|
56
|
-
|
|
57
|
-
- **Selected value**: the marker gradients key off `:has(input[value="system" | "light" | "dark"]:checked)`
|
|
58
|
-
on the root. These three values are hardcoded, a legacy of the one real consumer
|
|
59
|
-
(`DisplayThemeSwitch`). Any other value shows `--triple-toggle-switch-marker-surface` instead.
|
|
60
|
-
- **Option icon classes**: each `.option-icon` carries the option's `id` as a class (e.g.
|
|
61
|
-
`.option-icon.light`) plus `.active` when selected.
|
|
62
|
-
- **Marker**: `.selected-option-marker` gains `.show` roughly 250ms after mount (it's positioned
|
|
63
|
-
from a measured option width, so it stays hidden until measured).
|
|
64
|
-
- **Theme**: `data-theme` on the root, from the `theme` prop.
|
|
65
|
-
- **Inner classes**: `.triple-toggle-switch-wrapper`, `.selected-option-marker-wrapper`,
|
|
66
|
-
`.selected-option-marker`, `.option-group-wrapper` (the `role="radiogroup"`), `.option-group`
|
|
67
|
-
(each `<label>`), `.option-icon`, `.option-input`.
|
|
68
|
-
|
|
69
|
-
## Motion
|
|
70
|
-
|
|
71
|
-
The marker slides between options over the `stepAnimationDuration` prop (default `250ms`). Under
|
|
72
|
-
`prefers-reduced-motion: reduce` the slide transition is removed and the marker jumps.
|
|
73
|
-
|
|
74
|
-
## Global theming
|
|
75
|
-
|
|
76
|
-
```css
|
|
77
|
-
:where(html) {
|
|
78
|
-
--triple-toggle-switch-surface: var(--rose-09);
|
|
79
|
-
--triple-toggle-switch-option-ring-focus: var(--rose-04);
|
|
80
|
-
}
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
## Local overrides
|
|
84
|
-
|
|
85
|
-
Set the tokens above on an element you own (a page or section class, or a class added with
|
|
86
|
-
`:style-class-passthrough`): they inherit down into the component. Keep the block **unlayered** (no
|
|
87
|
-
`@layer` wrapper) so it beats the library's `@layer components`. If your own file uses
|
|
88
|
-
`<style scoped>`, tokens set on your element still work, but selectors that reach inside the
|
|
89
|
-
component need `:deep()`. Patterns and examples: `.claude/skills/component-local-style-override.md`.
|
|
90
|
-
|
|
91
|
-
**Caveat:** when used through `DisplayThemeSwitch`, that wrapper re-declares the four sizing tokens
|
|
92
|
-
(`--triple-toggle-switch-gap`, `-padding`, `-option-padding`, `-icon-size`) on the root element, so
|
|
93
|
-
setting them on an ancestor has no effect there. Set them on the component itself (via
|
|
94
|
-
`style` or a passthrough class) instead. Used directly, `TripleToggleSwitch` re-declares nothing.
|
|
95
|
-
|
|
96
|
-
## Class passthrough
|
|
97
|
-
|
|
98
|
-
`style-class-passthrough` adds classes to the root `.triple-toggle-switch` element, alongside
|
|
99
|
-
`data-theme`. `DisplayThemeSwitch` uses it for its `colour-scheme-select` and `small` classes.
|
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="triple-toggle-switch" :class="[elementClasses]" :data-theme="theme">
|
|
3
|
-
<div class="triple-toggle-switch-wrapper">
|
|
4
|
-
<div class="selected-option-marker-wrapper">
|
|
5
|
-
<div class="selected-option-marker" :class="[{ show: showMarker }]"></div>
|
|
6
|
-
</div>
|
|
7
|
-
<div class="option-group-wrapper" role="radiogroup" :aria-label="ariaLabel || undefined">
|
|
8
|
-
<label
|
|
9
|
-
v-for="option in fieldData.data"
|
|
10
|
-
:key="option.id"
|
|
11
|
-
ref="optionGroup"
|
|
12
|
-
:for="option.id"
|
|
13
|
-
class="option-group"
|
|
14
|
-
>
|
|
15
|
-
<span class="sr-only">{{ option.label }}</span>
|
|
16
|
-
<Icon
|
|
17
|
-
v-if="option.icon"
|
|
18
|
-
:name="option.icon"
|
|
19
|
-
class="option-icon"
|
|
20
|
-
:class="[option.id, { active: modelValue === option.value }]"
|
|
21
|
-
/>
|
|
22
|
-
<input
|
|
23
|
-
:id="option.id"
|
|
24
|
-
v-model="modelValue"
|
|
25
|
-
type="radio"
|
|
26
|
-
:name="name"
|
|
27
|
-
class="option-input"
|
|
28
|
-
:value="option.value"
|
|
29
|
-
/>
|
|
30
|
-
</label>
|
|
31
|
-
</div>
|
|
32
|
-
</div>
|
|
33
|
-
</div>
|
|
34
|
-
</template>
|
|
35
|
-
|
|
36
|
-
<script setup lang="ts">
|
|
37
|
-
import type { FormUiTheme, IFormMultipleOptions } from "~/types/forms/types.forms";
|
|
38
|
-
|
|
39
|
-
interface Props {
|
|
40
|
-
name?: string;
|
|
41
|
-
ariaLabel?: string;
|
|
42
|
-
theme?: FormUiTheme;
|
|
43
|
-
stepAnimationDuration?: string;
|
|
44
|
-
styleClassPassthrough?: string | string[];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const props = withDefaults(defineProps<Props>(), {
|
|
48
|
-
name: "triple-toggle-switch",
|
|
49
|
-
ariaLabel: "",
|
|
50
|
-
theme: "default",
|
|
51
|
-
stepAnimationDuration: "250ms",
|
|
52
|
-
styleClassPassthrough: () => [],
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
const modelValue = defineModel<string | number | boolean>({ required: true });
|
|
56
|
-
const fieldData = defineModel<IFormMultipleOptions>("fieldData", { required: true });
|
|
57
|
-
|
|
58
|
-
const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
|
|
59
|
-
|
|
60
|
-
const { stepAnimationDuration } = toRefs(props);
|
|
61
|
-
|
|
62
|
-
const optionGroupRefs = useTemplateRef<HTMLLabelElement[]>("optionGroup");
|
|
63
|
-
|
|
64
|
-
const iconWidth = ref("0px");
|
|
65
|
-
const showMarker = ref(false);
|
|
66
|
-
|
|
67
|
-
const selectedOptionIndex = computed(() => {
|
|
68
|
-
if (!fieldData.value?.data) return 0;
|
|
69
|
-
const index = fieldData.value.data.findIndex((option) => option.value === modelValue.value);
|
|
70
|
-
return Math.max(0, index);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
const setupDefaults = async () => {
|
|
74
|
-
await nextTick();
|
|
75
|
-
|
|
76
|
-
const firstOption = optionGroupRefs.value?.[0];
|
|
77
|
-
if (firstOption) {
|
|
78
|
-
iconWidth.value = `${firstOption.getBoundingClientRect().width}px`;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
await new Promise((resolve) => {
|
|
82
|
-
requestAnimationFrame(() => {
|
|
83
|
-
setTimeout(resolve, 250);
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
showMarker.value = true;
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
onMounted(() => {
|
|
91
|
-
setupDefaults();
|
|
92
|
-
});
|
|
93
|
-
</script>
|
|
94
|
-
|
|
95
|
-
<style lang="css">
|
|
96
|
-
@layer components {
|
|
97
|
-
.triple-toggle-switch {
|
|
98
|
-
--_form-items-gap: var(--triple-toggle-switch-gap, 1rem);
|
|
99
|
-
--_select-scheme-group-background-color: var(--triple-toggle-switch-marker-surface, var(--theme-input-surface));
|
|
100
|
-
--_select-scheme-group-background-image: none;
|
|
101
|
-
--_scheme-icon-font-size: var(--triple-toggle-switch-icon-size, 2rem);
|
|
102
|
-
|
|
103
|
-
&:has(input[value="system"]:checked) {
|
|
104
|
-
--_select-scheme-group-background-color: transparent;
|
|
105
|
-
--_select-scheme-group-background-image: var(
|
|
106
|
-
--triple-toggle-switch-marker-gradient-system,
|
|
107
|
-
radial-gradient(circle, rgb(66, 180, 58) 0%, rgb(17, 199, 0) 27%, rgb(8, 117, 3) 100%)
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
&:has(input[value="light"]:checked) {
|
|
112
|
-
--_select-scheme-group-background-color: transparent;
|
|
113
|
-
--_select-scheme-group-background-image: var(
|
|
114
|
-
--triple-toggle-switch-marker-gradient-light,
|
|
115
|
-
radial-gradient(circle, rgba(180, 58, 91, 1) 0%, rgba(253, 29, 29, 1) 27%, rgba(252, 176, 69, 1) 100%)
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
&:has(input[value="dark"]:checked) {
|
|
120
|
-
--_select-scheme-group-background-color: transparent;
|
|
121
|
-
--_select-scheme-group-background-image: var(
|
|
122
|
-
--triple-toggle-switch-marker-gradient-dark,
|
|
123
|
-
radial-gradient(circle, rgb(50, 20, 25) 0%, rgb(0, 0, 0) 27%, rgb(100, 100, 100) 100%)
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
.triple-toggle-switch-wrapper {
|
|
128
|
-
display: inline-grid;
|
|
129
|
-
grid-template-areas: "select-stack";
|
|
130
|
-
width: fit-content;
|
|
131
|
-
|
|
132
|
-
background-color: var(--triple-toggle-switch-surface, var(--theme-input-surface));
|
|
133
|
-
border: var(--form-element-border-width) solid var(--triple-toggle-switch-border, var(--theme-border));
|
|
134
|
-
outline: var(--form-element-outline-width) solid transparent;
|
|
135
|
-
border-radius: 100vw;
|
|
136
|
-
padding: var(--triple-toggle-switch-padding, 0.6rem);
|
|
137
|
-
|
|
138
|
-
transition: all var(--theme-form-transition-duration) ease-in-out;
|
|
139
|
-
|
|
140
|
-
&:has(input:focus-visible) {
|
|
141
|
-
outline: var(--form-element-outline-width) solid var(--triple-toggle-switch-ring-focus, var(--theme-ring));
|
|
142
|
-
outline-offset: 0.2rem;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
.selected-option-marker-wrapper {
|
|
146
|
-
grid-area: select-stack;
|
|
147
|
-
z-index: 1;
|
|
148
|
-
display: flex;
|
|
149
|
-
align-items: center;
|
|
150
|
-
position: relative;
|
|
151
|
-
|
|
152
|
-
.selected-option-marker {
|
|
153
|
-
aspect-ratio: 1;
|
|
154
|
-
width: v-bind(iconWidth);
|
|
155
|
-
transition: all v-bind(stepAnimationDuration) ease-in-out;
|
|
156
|
-
background-color: var(--_select-scheme-group-background-color);
|
|
157
|
-
background-image: var(--_select-scheme-group-background-image);
|
|
158
|
-
border: var(--form-element-border-width) solid var(--triple-toggle-switch-marker-border, var(--slate-10));
|
|
159
|
-
border-radius: 50%;
|
|
160
|
-
|
|
161
|
-
position: absolute;
|
|
162
|
-
left: calc(
|
|
163
|
-
v-bind(selectedOptionIndex) * v-bind(iconWidth) + (var(--_form-items-gap) * v-bind(selectedOptionIndex))
|
|
164
|
-
);
|
|
165
|
-
|
|
166
|
-
opacity: 0;
|
|
167
|
-
|
|
168
|
-
&.show {
|
|
169
|
-
opacity: 1;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
@media (prefers-reduced-motion: reduce) {
|
|
173
|
-
transition: none;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
.option-group-wrapper {
|
|
179
|
-
display: grid;
|
|
180
|
-
grid-area: select-stack;
|
|
181
|
-
grid-template-columns: repeat(3, 1fr);
|
|
182
|
-
align-items: center;
|
|
183
|
-
width: fit-content;
|
|
184
|
-
z-index: 2;
|
|
185
|
-
gap: var(--_form-items-gap);
|
|
186
|
-
position: relative;
|
|
187
|
-
|
|
188
|
-
.option-group {
|
|
189
|
-
display: grid;
|
|
190
|
-
grid-template-areas: "icon-stack";
|
|
191
|
-
place-items: center;
|
|
192
|
-
background: transparent;
|
|
193
|
-
border: var(--form-element-border-width) solid var(--triple-toggle-switch-option-border, #00000025);
|
|
194
|
-
outline: var(--form-element-outline-width) solid transparent;
|
|
195
|
-
border-radius: 50%;
|
|
196
|
-
padding: var(--triple-toggle-switch-option-padding, 0.5rem);
|
|
197
|
-
overflow: hidden;
|
|
198
|
-
|
|
199
|
-
transition: all calc(var(--theme-form-transition-duration) / 3);
|
|
200
|
-
|
|
201
|
-
&:has(.option-icon:hover) {
|
|
202
|
-
outline: var(--form-element-outline-width) solid
|
|
203
|
-
var(--triple-toggle-switch-option-border-hover, var(--slate-10));
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
&:has(input:focus-visible) {
|
|
207
|
-
outline: var(--form-element-outline-width) solid
|
|
208
|
-
var(--triple-toggle-switch-option-ring-focus, var(--theme-ring));
|
|
209
|
-
outline-offset: 0.2rem;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
.option-icon {
|
|
213
|
-
grid-area: icon-stack;
|
|
214
|
-
display: block;
|
|
215
|
-
font-size: var(--_scheme-icon-font-size);
|
|
216
|
-
width: 1em;
|
|
217
|
-
height: 1em;
|
|
218
|
-
color: var(--triple-toggle-switch-option-icon-color, var(--slate-10));
|
|
219
|
-
transition: color calc(v-bind(stepAnimationDuration) / 2) ease-in-out;
|
|
220
|
-
|
|
221
|
-
&.active {
|
|
222
|
-
color: var(--triple-toggle-switch-option-icon-color-active, var(--slate-00));
|
|
223
|
-
transition-delay: calc(v-bind(stepAnimationDuration) / 2);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
@media (prefers-reduced-motion: reduce) {
|
|
227
|
-
transition: none;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
&:hover {
|
|
231
|
-
cursor: pointer;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
.option-input {
|
|
236
|
-
grid-area: icon-stack;
|
|
237
|
-
appearance: none;
|
|
238
|
-
opacity: 0;
|
|
239
|
-
width: var(--_scheme-icon-font-size);
|
|
240
|
-
height: var(--_scheme-icon-font-size);
|
|
241
|
-
|
|
242
|
-
&:hover {
|
|
243
|
-
cursor: pointer;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
</style>
|