srcdev-nuxt-components 9.4.9 → 9.4.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/commands/migrate-component.md +38 -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-number-core.md +214 -0
- package/.claude/skills/components/input-range-core.md +142 -0
- package/.claude/skills/components/input-select-core.md +151 -0
- package/.claude/skills/components/input-text-core.md +16 -0
- package/.claude/skills/components/input-textarea-core.md +154 -0
- package/.claude/skills/components/section-parallax.md +32 -10
- package/.claude/skills/components/select-menu.md +195 -0
- package/.claude/skills/components/toggle-switch-core.md +150 -0
- 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 +15 -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-number.code-snippets +65 -0
- package/.vscode/srcdev-component-input-range.code-snippets +85 -0
- package/.vscode/srcdev-component-input-select.code-snippets +42 -0
- package/.vscode/srcdev-component-input-textarea.code-snippets +40 -0
- package/.vscode/srcdev-component-section-parallax.code-snippets +27 -0
- package/.vscode/srcdev-component-select-menu.code-snippets +65 -0
- package/.vscode/srcdev-component-toggle-switch.code-snippets +49 -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/marquee-scroller/CONSUMER-STYLING.md +1 -1
- package/app/components/01.atoms/animations/marquee-scroller/MarqueeScroller.vue +1 -1
- package/app/components/01.atoms/animations/marquee-scroller/stories/MarqueeScroller.stories.ts +1 -1
- 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 +3 -3
- package/app/components/01.atoms/canvas-switcher/CanvasSwitcher.vue +2 -2
- package/app/components/01.atoms/canvas-switcher/stories/CanvasSwitcher.stories.ts +1 -1
- package/app/components/01.atoms/card/CardCore.vue +4 -4
- package/app/components/01.atoms/clipped-panel/CONSUMER-STYLING.md +2 -2
- package/app/components/01.atoms/clipped-panel/ClippedPanel.vue +2 -2
- 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/display-tooltip/CONSUMER-STYLING.md +8 -8
- package/app/components/01.atoms/display-tooltip/DisplayTooltip.vue +8 -8
- package/app/components/01.atoms/pop-over/CONSUMER-STYLING.md +6 -6
- package/app/components/01.atoms/pop-over/PopOver.vue +6 -6
- 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/02.molecules/navigation/deep-expanding-menu/CONSUMER-STYLING.md +1 -1
- package/app/components/02.molecules/navigation/deep-expanding-menu/DeepExpandingMenu.vue +1 -1
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/CONSUMER-STYLING.md +1 -1
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/DeepExpandingMenuClassic.vue +1 -1
- package/app/components/02.molecules/select-menu/CONSUMER-STYLING.md +168 -0
- package/app/components/02.molecules/select-menu/SelectMenu.vue +384 -0
- package/app/components/02.molecules/select-menu/stories/SelectMenu.stories.ts +359 -0
- package/app/components/02.molecules/select-menu/tests/SelectMenu.spec.ts +476 -0
- package/app/components/02.molecules/select-menu/tests/__snapshots__/SelectMenu.spec.ts.snap +42 -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 +114 -29
- 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-button/CONSUMER-STYLING.md +2 -7
- package/app/components/05.forms/input-button/InputButtonCore.vue +1 -5
- package/app/components/05.forms/input-number/CONSUMER-STYLING.md +26 -6
- package/app/components/05.forms/input-number/InputNumberCore.vue +125 -85
- package/app/components/05.forms/input-number/stories/InputNumberCore.stories.ts +92 -0
- package/app/components/05.forms/input-number/tests/InputNumberCore.spec.ts +119 -0
- package/app/components/05.forms/input-number/variants/InputNumberDefault.vue +30 -22
- package/app/components/05.forms/input-number/variants/stories/InputNumberDefault.stories.ts +123 -0
- package/app/components/05.forms/input-number/variants/tests/InputNumberDefault.spec.ts +143 -0
- 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/input-select/InputSelectCore.vue +23 -30
- package/app/components/05.forms/input-select/stories/InputSelectCore.stories.ts +2 -2
- package/app/components/05.forms/input-select/stories/InputSelectWithLabel.stories.ts +115 -0
- package/app/components/05.forms/input-select/tests/InputSelectCore.spec.ts +129 -0
- package/app/components/05.forms/input-select/variants/InputSelectWithLabel.vue +1 -1
- package/app/components/05.forms/input-select/variants/tests/InputSelectWithLabel.spec.ts +87 -0
- package/app/components/05.forms/input-text/tests/InputTextAsNumberWithLabel.spec.ts +14 -0
- package/app/components/05.forms/input-text/variants/InputTextAsNumberWithLabel.vue +1 -1
- package/app/components/05.forms/input-textarea/CONSUMER-STYLING.md +55 -0
- package/app/components/05.forms/input-textarea/InputTextareaCore.vue +24 -16
- package/app/components/05.forms/input-textarea/stories/InputTextareaWithLabel.stories.ts +1 -1
- package/app/components/05.forms/input-textarea/tests/InputTextareaCore.spec.ts +119 -0
- package/app/components/05.forms/input-textarea/variants/InputTextareaWithLabel.vue +3 -13
- package/app/components/05.forms/input-textarea/variants/tests/InputTextareaWithLabel.spec.ts +93 -0
- package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +220 -0
- package/app/components/05.forms/toggle-switch/CONSUMER-STYLING.md +29 -0
- package/app/components/05.forms/toggle-switch/ToggleSwitchCore.vue +120 -119
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchCore.stories.ts +1 -1
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabel.stories.ts +1 -1
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabelInline.stories.ts +2 -2
- package/app/components/05.forms/toggle-switch/tests/ToggleSwitchCore.spec.ts +101 -0
- package/app/components/05.forms/toggle-switch/variants/ToggleSwitchWithLabel.vue +3 -5
- package/app/components/05.forms/toggle-switch/variants/ToggleSwitchWithLabelInline.vue +1 -2
- package/app/components/05.forms/toggle-switch/variants/tests/ToggleSwitchWithLabel.spec.ts +77 -0
- package/app/components/05.forms/toggle-switch/variants/tests/ToggleSwitchWithLabelInline.spec.ts +66 -0
- package/app/components/05.forms/triple-toggle-switch/CONSUMER-STYLING.md +20 -6
- package/app/components/05.forms/triple-toggle-switch/TripleToggleSwitchCore.vue +12 -10
- package/app/types/components/container-glow.d.ts +8 -0
- package/app/types/components/index.ts +2 -1
- package/app/types/components/select-menu.d.ts +5 -0
- package/package.json +3 -2
- package/.vscode/srcdev-component-toggle-switch-core.code-snippets +0 -13
- 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,101 @@
|
|
|
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 "../ToggleSwitchCore.vue";
|
|
5
|
+
|
|
6
|
+
const initialPropsData = {
|
|
7
|
+
id: "test-toggle",
|
|
8
|
+
name: "testName",
|
|
9
|
+
modelValue: false,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
let wrapper: VueWrapper<InstanceType<typeof ComponentUnderTest>>;
|
|
13
|
+
const wrapperFactory = (propsData = {}) => {
|
|
14
|
+
const mockPropsData = { ...initialPropsData, ...propsData };
|
|
15
|
+
|
|
16
|
+
return mountSuspended(ComponentUnderTest, {
|
|
17
|
+
attachTo: document.body,
|
|
18
|
+
props: mockPropsData,
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
describe("ToggleSwitchCore", () => {
|
|
23
|
+
it("mounts without error", async () => {
|
|
24
|
+
wrapper = await wrapperFactory();
|
|
25
|
+
expect(wrapper.vm).toBeTruthy();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("binds v-model to the checkbox checked state", async () => {
|
|
29
|
+
wrapper = await wrapperFactory({ modelValue: true });
|
|
30
|
+
const input = wrapper.find<HTMLInputElement>("input[type='checkbox']");
|
|
31
|
+
expect(input.element.checked).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("toggles modelValue when the wrapper is clicked", async () => {
|
|
35
|
+
wrapper = await wrapperFactory({ modelValue: false });
|
|
36
|
+
await wrapper.find(".toggle-switch-wrapper").trigger("click");
|
|
37
|
+
expect(wrapper.emitted("update:modelValue")?.at(-1)).toEqual([true]);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("toggles between custom trueValue/falseValue", async () => {
|
|
41
|
+
wrapper = await wrapperFactory({ modelValue: "off", trueValue: "on", falseValue: "off" });
|
|
42
|
+
await wrapper.find(".toggle-switch-wrapper").trigger("click");
|
|
43
|
+
expect(wrapper.emitted("update:modelValue")?.at(-1)).toEqual(["on"]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("sets required attribute when required is true", async () => {
|
|
47
|
+
wrapper = await wrapperFactory({ required: true });
|
|
48
|
+
expect(wrapper.find("input").attributes("required")).toBeDefined();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("does not set required attribute by default", async () => {
|
|
52
|
+
wrapper = await wrapperFactory();
|
|
53
|
+
expect(wrapper.find("input").attributes("required")).toBeUndefined();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("sets data-theme from the theme prop", async () => {
|
|
57
|
+
wrapper = await wrapperFactory({ theme: "success" });
|
|
58
|
+
expect(wrapper.find(".toggle-switch-core").attributes("data-theme")).toBe("success");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("sets data-theme to error and aria-invalid when fieldHasError is true, overriding theme", async () => {
|
|
62
|
+
wrapper = await wrapperFactory({ fieldHasError: true, theme: "success" });
|
|
63
|
+
expect(wrapper.find(".toggle-switch-core").attributes("data-theme")).toBe("error");
|
|
64
|
+
expect(wrapper.find("input").attributes("aria-invalid")).toBe("true");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("applies styleClassPassthrough to the root element", async () => {
|
|
68
|
+
wrapper = await wrapperFactory({ styleClassPassthrough: ["custom-toggle-class"] });
|
|
69
|
+
expect(wrapper.find(".toggle-switch-core").classes()).toContain("custom-toggle-class");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("renders default icons when no icon slots are provided", async () => {
|
|
73
|
+
wrapper = await wrapperFactory();
|
|
74
|
+
expect(wrapper.find(".toggle-switch-wrapper").classes()).toContain("use-default-icons");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("renders custom icon slot content instead of the default icon", async () => {
|
|
78
|
+
wrapper = await mountSuspended(ComponentUnderTest, {
|
|
79
|
+
attachTo: document.body,
|
|
80
|
+
props: initialPropsData,
|
|
81
|
+
slots: {
|
|
82
|
+
iconOn: () => "ON",
|
|
83
|
+
iconOff: () => "OFF",
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
expect(wrapper.find(".toggle-switch-wrapper").classes()).not.toContain("use-default-icons");
|
|
87
|
+
expect(wrapper.find(".icon-on").text()).toBe("ON");
|
|
88
|
+
expect(wrapper.find(".icon-off").text()).toBe("OFF");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("consumes round as a declared prop instead of leaking it as a DOM attribute", async () => {
|
|
92
|
+
wrapper = await wrapperFactory({ round: false });
|
|
93
|
+
expect(wrapper.props("round")).toBe(false);
|
|
94
|
+
expect(wrapper.attributes("round")).toBeUndefined();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("defaults round to true", async () => {
|
|
98
|
+
wrapper = await wrapperFactory();
|
|
99
|
+
expect(wrapper.props("round")).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="toggle-switch-with-label" :class="[elementClasses]" :data-theme="
|
|
2
|
+
<div class="toggle-switch-with-label" :class="[elementClasses]" :data-theme="formUiTheme">
|
|
3
3
|
<InputLabel
|
|
4
4
|
:id
|
|
5
5
|
:for="toggleSwitchId"
|
|
@@ -68,7 +68,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
68
68
|
|
|
69
69
|
const slots = useSlots();
|
|
70
70
|
|
|
71
|
-
const
|
|
71
|
+
const formUiTheme = computed(() => {
|
|
72
72
|
return props.fieldHasError ? "error" : props.theme;
|
|
73
73
|
});
|
|
74
74
|
|
|
@@ -84,15 +84,13 @@ const ariaDescribedby = computed(() => {
|
|
|
84
84
|
return props.fieldHasError ? errorId.value : ariaDescribedbyId;
|
|
85
85
|
});
|
|
86
86
|
|
|
87
|
-
const modelValue = defineModel<string | number | boolean>();
|
|
87
|
+
const modelValue = defineModel<string | number | boolean>({ required: true });
|
|
88
88
|
const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
|
|
89
89
|
</script>
|
|
90
90
|
|
|
91
91
|
<style lang="css">
|
|
92
92
|
@layer components {
|
|
93
93
|
.toggle-switch-with-label {
|
|
94
|
-
--_transition-duration: 0.4s;
|
|
95
|
-
|
|
96
94
|
.toggle-switch-label {
|
|
97
95
|
display: block;
|
|
98
96
|
}
|
|
@@ -63,14 +63,13 @@ const labelWeightClass = computed(() => {
|
|
|
63
63
|
return labelWeightClassMap[props.labelWeight] || "body-normal-semibold";
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
-
const modelValue = defineModel<string | number | boolean>();
|
|
66
|
+
const modelValue = defineModel<string | number | boolean>({ required: true });
|
|
67
67
|
const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
|
|
68
68
|
</script>
|
|
69
69
|
|
|
70
70
|
<style lang="css">
|
|
71
71
|
@layer components {
|
|
72
72
|
.toggle-switch-with-label-inline {
|
|
73
|
-
--_transition-duration: 0.4s;
|
|
74
73
|
display: flex;
|
|
75
74
|
align-items: center;
|
|
76
75
|
gap: 12px;
|
|
@@ -0,0 +1,77 @@
|
|
|
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 "../ToggleSwitchWithLabel.vue";
|
|
5
|
+
|
|
6
|
+
const initialPropsData = {
|
|
7
|
+
name: "notifications",
|
|
8
|
+
label: "Enable notifications",
|
|
9
|
+
errorMessage: "",
|
|
10
|
+
modelValue: false,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
let wrapper: VueWrapper<InstanceType<typeof ComponentUnderTest>>;
|
|
14
|
+
const wrapperFactory = (propsData = {}, slots = {}) => {
|
|
15
|
+
const mockPropsData = { ...initialPropsData, ...propsData };
|
|
16
|
+
|
|
17
|
+
return mountSuspended(ComponentUnderTest, {
|
|
18
|
+
attachTo: document.body,
|
|
19
|
+
props: mockPropsData,
|
|
20
|
+
slots,
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
describe("ToggleSwitchWithLabel", () => {
|
|
25
|
+
it("mounts without error", async () => {
|
|
26
|
+
wrapper = await wrapperFactory();
|
|
27
|
+
expect(wrapper.vm).toBeTruthy();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("renders the label text", async () => {
|
|
31
|
+
wrapper = await wrapperFactory({ label: "Enable dark mode" });
|
|
32
|
+
expect(wrapper.text()).toContain("Enable dark mode");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("renders a checkbox input", async () => {
|
|
36
|
+
wrapper = await wrapperFactory();
|
|
37
|
+
expect(wrapper.find("input[type='checkbox']").exists()).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("associates the label with the checkbox via matching for/id", async () => {
|
|
41
|
+
wrapper = await wrapperFactory();
|
|
42
|
+
const inputId = wrapper.find("input[type='checkbox']").attributes("id");
|
|
43
|
+
expect(wrapper.find("label").attributes("for")).toBe(inputId);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("renders the error message when fieldHasError is true", async () => {
|
|
47
|
+
wrapper = await wrapperFactory({ fieldHasError: true, errorMessage: "You must accept" });
|
|
48
|
+
expect(wrapper.text()).toContain("You must accept");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("renders the description slot", async () => {
|
|
52
|
+
wrapper = await wrapperFactory({}, { description: () => "Turn this on to receive notifications" });
|
|
53
|
+
expect(wrapper.text()).toContain("Turn this on to receive notifications");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("forwards required to the underlying input", async () => {
|
|
57
|
+
wrapper = await wrapperFactory({ required: true });
|
|
58
|
+
expect(wrapper.find("input").attributes("required")).toBeDefined();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("applies the error theme to ToggleSwitchCore when fieldHasError is true", async () => {
|
|
62
|
+
wrapper = await wrapperFactory({ fieldHasError: true, theme: "default" });
|
|
63
|
+
expect(wrapper.find(".toggle-switch-core").attributes("data-theme")).toBe("error");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("applies styleClassPassthrough to the root element", async () => {
|
|
67
|
+
wrapper = await wrapperFactory({ styleClassPassthrough: ["custom-toggle"] });
|
|
68
|
+
expect(wrapper.find(".toggle-switch-with-label").classes()).toContain("custom-toggle");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("forwards round to ToggleSwitchCore without leaking it as a DOM attribute", async () => {
|
|
72
|
+
wrapper = await wrapperFactory({ round: false });
|
|
73
|
+
const core = wrapper.findComponent({ name: "ToggleSwitchCore" });
|
|
74
|
+
expect(core.props("round")).toBe(false);
|
|
75
|
+
expect(wrapper.find(".toggle-switch-core").attributes("round")).toBeUndefined();
|
|
76
|
+
});
|
|
77
|
+
});
|
package/app/components/05.forms/toggle-switch/variants/tests/ToggleSwitchWithLabelInline.spec.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
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 "../ToggleSwitchWithLabelInline.vue";
|
|
5
|
+
|
|
6
|
+
const initialPropsData = {
|
|
7
|
+
name: "notifications",
|
|
8
|
+
label: "Enable notifications",
|
|
9
|
+
modelValue: false,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
let wrapper: VueWrapper<InstanceType<typeof ComponentUnderTest>>;
|
|
13
|
+
const wrapperFactory = (propsData = {}, slots = {}) => {
|
|
14
|
+
const mockPropsData = { ...initialPropsData, ...propsData };
|
|
15
|
+
|
|
16
|
+
return mountSuspended(ComponentUnderTest, {
|
|
17
|
+
attachTo: document.body,
|
|
18
|
+
props: mockPropsData,
|
|
19
|
+
slots,
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
describe("ToggleSwitchWithLabelInline", () => {
|
|
24
|
+
it("mounts without error", async () => {
|
|
25
|
+
wrapper = await wrapperFactory();
|
|
26
|
+
expect(wrapper.vm).toBeTruthy();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("renders the label text", async () => {
|
|
30
|
+
wrapper = await wrapperFactory({ label: "Dark mode" });
|
|
31
|
+
expect(wrapper.text()).toContain("Dark mode");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("renders a checkbox input", async () => {
|
|
35
|
+
wrapper = await wrapperFactory();
|
|
36
|
+
expect(wrapper.find("input[type='checkbox']").exists()).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("associates the label with the checkbox via matching for/id", async () => {
|
|
40
|
+
wrapper = await wrapperFactory();
|
|
41
|
+
const inputId = wrapper.find("input[type='checkbox']").attributes("id");
|
|
42
|
+
expect(wrapper.find("label").attributes("for")).toBe(inputId);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("defaults labelWeight to normal", async () => {
|
|
46
|
+
wrapper = await wrapperFactory();
|
|
47
|
+
expect(wrapper.find(".input-switch-label").classes()).toContain("body-normal");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("maps labelWeight to the matching body-* class", async () => {
|
|
51
|
+
wrapper = await wrapperFactory({ labelWeight: "bold" });
|
|
52
|
+
expect(wrapper.find(".input-switch-label").classes()).toContain("body-normal-bold");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("applies styleClassPassthrough to the root element", async () => {
|
|
56
|
+
wrapper = await wrapperFactory({ styleClassPassthrough: ["custom-toggle-inline"] });
|
|
57
|
+
expect(wrapper.find(".toggle-switch-with-label-inline").classes()).toContain("custom-toggle-inline");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("forwards round to ToggleSwitchCore without leaking it as a DOM attribute", async () => {
|
|
61
|
+
wrapper = await wrapperFactory({ round: false });
|
|
62
|
+
const core = wrapper.findComponent({ name: "ToggleSwitchCore" });
|
|
63
|
+
expect(core.props("round")).toBe(false);
|
|
64
|
+
expect(wrapper.find(".toggle-switch-core").attributes("round")).toBeUndefined();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -13,11 +13,11 @@ the shared `--theme-*` token every other themed component also falls back to. Se
|
|
|
13
13
|
| `--triple-toggle-switch-border` | `var(--theme-border)` | Wrapper border colour |
|
|
14
14
|
| `--triple-toggle-switch-ring-focus` | `var(--theme-ring)` | Wrapper outline colour on `:focus-visible` |
|
|
15
15
|
| `--triple-toggle-switch-marker-surface` | `var(--theme-input-surface)` | Selected-option marker background, when none of the `system`/`light`/`dark` `:has()` overrides below match |
|
|
16
|
-
| `--triple-toggle-switch-marker-border` | `
|
|
17
|
-
| `--triple-toggle-switch-option-border` |
|
|
18
|
-
| `--triple-toggle-switch-option-border-hover` | `
|
|
16
|
+
| `--triple-toggle-switch-marker-border` | `var(--slate-10)` | Selected-option marker border colour |
|
|
17
|
+
| `--triple-toggle-switch-option-border` | `#00000025` | Each option's resting border colour |
|
|
18
|
+
| `--triple-toggle-switch-option-border-hover` | `var(--slate-10)` | Each option's outline colour on hover |
|
|
19
19
|
| `--triple-toggle-switch-option-ring-focus` | `var(--theme-ring)` | Each option's outline colour on `:focus-visible` |
|
|
20
|
-
| `--triple-toggle-switch-option-icon-color` | `
|
|
20
|
+
| `--triple-toggle-switch-option-icon-color` | `var(--slate-10)` | Option icon colour, resting (shared by the `.system`/`.light`/`.dark` selectors — see note) |
|
|
21
21
|
| `--triple-toggle-switch-option-icon-color-active` | `var(--slate-00)` | Option icon colour, selected |
|
|
22
22
|
|
|
23
23
|
> **Two bugs fixed in this migration**:
|
|
@@ -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
|
|
@@ -191,7 +193,7 @@ onMounted(() => {
|
|
|
191
193
|
background-color: var(--_select-scheme-group-background-color);
|
|
192
194
|
background-image: var(--_select-scheme-group-background-image);
|
|
193
195
|
border: var(--form-element-border-width) solid
|
|
194
|
-
var(--triple-toggle-switch-marker-border,
|
|
196
|
+
var(--triple-toggle-switch-marker-border, var(--slate-10));
|
|
195
197
|
|
|
196
198
|
border-radius: 50%;
|
|
197
199
|
|
|
@@ -225,7 +227,7 @@ onMounted(() => {
|
|
|
225
227
|
place-content: center;
|
|
226
228
|
background: transparent;
|
|
227
229
|
border: var(--form-element-border-width) solid
|
|
228
|
-
var(--triple-toggle-switch-option-border,
|
|
230
|
+
var(--triple-toggle-switch-option-border, #00000025);
|
|
229
231
|
outline: var(--form-element-outline-width) solid transparent;
|
|
230
232
|
border-radius: 50%;
|
|
231
233
|
padding: var(--_select-scheme-group-padding);
|
|
@@ -235,7 +237,7 @@ onMounted(() => {
|
|
|
235
237
|
|
|
236
238
|
&:has(.option-icon:hover) {
|
|
237
239
|
outline: var(--form-element-outline-width) solid
|
|
238
|
-
var(--triple-toggle-switch-option-border-hover,
|
|
240
|
+
var(--triple-toggle-switch-option-border-hover, var(--slate-10));
|
|
239
241
|
}
|
|
240
242
|
&:has(input:focus-visible) {
|
|
241
243
|
outline: var(--form-element-outline-width) solid var(--triple-toggle-switch-option-ring-focus, var(--theme-ring));
|
|
@@ -255,7 +257,7 @@ onMounted(() => {
|
|
|
255
257
|
font-size: var(--_scheme-icon-font-size);
|
|
256
258
|
|
|
257
259
|
&.system {
|
|
258
|
-
color: var(--triple-toggle-switch-option-icon-color,
|
|
260
|
+
color: var(--triple-toggle-switch-option-icon-color, var(--slate-10));
|
|
259
261
|
|
|
260
262
|
&.active {
|
|
261
263
|
color: var(--triple-toggle-switch-option-icon-color-active, var(--slate-00));
|
|
@@ -263,7 +265,7 @@ onMounted(() => {
|
|
|
263
265
|
}
|
|
264
266
|
|
|
265
267
|
&.light {
|
|
266
|
-
color: var(--triple-toggle-switch-option-icon-color,
|
|
268
|
+
color: var(--triple-toggle-switch-option-icon-color, var(--slate-10));
|
|
267
269
|
|
|
268
270
|
&.active {
|
|
269
271
|
color: var(--triple-toggle-switch-option-icon-color-active, var(--slate-00));
|
|
@@ -271,7 +273,7 @@ onMounted(() => {
|
|
|
271
273
|
}
|
|
272
274
|
|
|
273
275
|
&.dark {
|
|
274
|
-
color: var(--triple-toggle-switch-option-icon-color,
|
|
276
|
+
color: var(--triple-toggle-switch-option-icon-color, var(--slate-10));
|
|
275
277
|
|
|
276
278
|
&.active {
|
|
277
279
|
color: var(--triple-toggle-switch-option-icon-color-active, var(--slate-00));
|
|
@@ -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"
|
|
@@ -20,3 +20,4 @@ export * from "./display-tooltip-defined.d"
|
|
|
20
20
|
export * from "./marquee-scroller.d"
|
|
21
21
|
export * from "./rotating-carousel-image.d"
|
|
22
22
|
export * from "./skip-links.d"
|
|
23
|
+
export * from "./select-menu.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.11",
|
|
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
|
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"SRCDEV ToggleSwitchCore": {
|
|
3
|
-
"description": "ToggleSwitchCore — bare toggle switch (no label, use ToggleSwitchWithLabel for that)",
|
|
4
|
-
"scope": "vue,html",
|
|
5
|
-
"body": [
|
|
6
|
-
"<ToggleSwitchCore",
|
|
7
|
-
" v-model=\"$1modelValue\"",
|
|
8
|
-
" id=\"$2toggle-id\"",
|
|
9
|
-
" name=\"$3toggleName\"",
|
|
10
|
-
"/>"
|
|
11
|
-
]
|
|
12
|
-
}
|
|
13
|
-
}
|