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.
Files changed (178) hide show
  1. package/.claude/commands/migrate-component.md +38 -6
  2. package/.claude/component-ledger/audit.json +1 -1
  3. package/.claude/component-ledger/build.mjs +26 -0
  4. package/.claude/component-ledger/output.html +8 -3
  5. package/.claude/component-ledger/template.html +7 -2
  6. package/.claude/skills/component-dynamic-slots.md +1 -1
  7. package/.claude/skills/components/alert-mask-core.md +84 -0
  8. package/.claude/skills/components/canvas-switcher.md +1 -1
  9. package/.claude/skills/components/carousel-flip.md +112 -168
  10. package/.claude/skills/components/container-glow.md +123 -0
  11. package/.claude/skills/components/dashboard-quad-grid.md +98 -0
  12. package/.claude/skills/components/dashboard-stats-grid.md +103 -0
  13. package/.claude/skills/components/display-theme-switch.md +60 -0
  14. package/.claude/skills/components/glowing-border.md +66 -0
  15. package/.claude/skills/components/input-number-core.md +214 -0
  16. package/.claude/skills/components/input-range-core.md +142 -0
  17. package/.claude/skills/components/input-select-core.md +151 -0
  18. package/.claude/skills/components/input-text-core.md +16 -0
  19. package/.claude/skills/components/input-textarea-core.md +154 -0
  20. package/.claude/skills/components/section-parallax.md +32 -10
  21. package/.claude/skills/components/select-menu.md +195 -0
  22. package/.claude/skills/components/toggle-switch-core.md +150 -0
  23. package/.claude/skills/components/ui-block-decorated.md +56 -0
  24. package/.claude/skills/components/wipe-away-vertical.md +98 -0
  25. package/.claude/skills/icon-sets.md +1 -1
  26. package/.claude/skills/index.md +15 -1
  27. package/.vscode/css-custom-data.json +38 -0
  28. package/.vscode/settings.json +3 -0
  29. package/.vscode/srcdev-component-alert-mask-core.code-snippets +31 -0
  30. package/.vscode/srcdev-component-carousel-flip.code-snippets +41 -0
  31. package/.vscode/srcdev-component-container-glow.code-snippets +33 -0
  32. package/.vscode/srcdev-component-dashboard-quad-grid.code-snippets +36 -0
  33. package/.vscode/srcdev-component-dashboard-stats-grid.code-snippets +35 -0
  34. package/.vscode/srcdev-component-display-theme-switch.code-snippets +27 -0
  35. package/.vscode/srcdev-component-glowing-border.code-snippets +23 -0
  36. package/.vscode/srcdev-component-input-number.code-snippets +65 -0
  37. package/.vscode/srcdev-component-input-range.code-snippets +85 -0
  38. package/.vscode/srcdev-component-input-select.code-snippets +42 -0
  39. package/.vscode/srcdev-component-input-textarea.code-snippets +40 -0
  40. package/.vscode/srcdev-component-section-parallax.code-snippets +27 -0
  41. package/.vscode/srcdev-component-select-menu.code-snippets +65 -0
  42. package/.vscode/srcdev-component-toggle-switch.code-snippets +49 -0
  43. package/.vscode/srcdev-component-ui-block-decorated.code-snippets +24 -0
  44. package/.vscode/srcdev-component-wipe-away-vertical.code-snippets +25 -0
  45. package/README.md +18 -37
  46. package/app/components/{alert-mask → 01.atoms/alert-mask}/AlertMaskCore.vue +16 -17
  47. package/app/components/01.atoms/alert-mask/CONSUMER-STYLING.md +46 -0
  48. package/app/components/01.atoms/alert-mask/stories/AlertMaskCore.stories.ts +79 -0
  49. package/app/components/{alert-mask → 01.atoms/alert-mask}/tests/AlertMaskCore.spec.ts +10 -10
  50. package/app/components/01.atoms/animations/container-glow/CONSUMER-STYLING.md +86 -0
  51. package/app/components/01.atoms/animations/container-glow/ContainerGlow.vue +299 -0
  52. package/app/components/01.atoms/animations/container-glow/stories/ContainerGlow.stories.ts +175 -0
  53. package/app/components/01.atoms/animations/container-glow/tests/ContainerGlow.spec.ts +131 -0
  54. package/app/components/01.atoms/animations/container-glow/tests/__snapshots__/ContainerGlow.spec.ts.snap +14 -0
  55. package/app/components/01.atoms/animations/glowing-border/CONSUMER-STYLING.md +61 -0
  56. package/app/components/01.atoms/animations/glowing-border/GlowingBorder.vue +132 -0
  57. package/app/components/01.atoms/animations/glowing-border/stories/GlowingBorder.stories.ts +165 -0
  58. package/app/components/01.atoms/animations/glowing-border/tests/GlowingBorder.spec.ts +94 -0
  59. package/app/components/01.atoms/animations/glowing-border/tests/__snapshots__/GlowingBorder.spec.ts.snap +3 -0
  60. package/app/components/01.atoms/animations/marquee-scroller/CONSUMER-STYLING.md +1 -1
  61. package/app/components/01.atoms/animations/marquee-scroller/MarqueeScroller.vue +1 -1
  62. package/app/components/01.atoms/animations/marquee-scroller/stories/MarqueeScroller.stories.ts +1 -1
  63. package/app/components/01.atoms/animations/section-parallax/CONSUMER-STYLING.md +44 -0
  64. package/app/components/{parallax → 01.atoms/animations/section-parallax}/SectionParallax.vue +11 -10
  65. package/app/components/01.atoms/animations/section-parallax/stories/SectionParallax.stories.ts +98 -0
  66. package/app/components/01.atoms/animations/section-parallax/tests/SectionParallax.spec.ts +102 -0
  67. package/app/components/01.atoms/animations/section-parallax/tests/__snapshots__/SectionParallax.spec.ts.snap +9 -0
  68. package/app/components/01.atoms/animations/view-timeline/CONSUMER-STYLING.md +70 -0
  69. package/app/components/01.atoms/animations/view-timeline/WipeAwayVertical.vue +181 -0
  70. package/app/components/01.atoms/animations/view-timeline/stories/WipeAwayVertical.stories.ts +269 -0
  71. package/app/components/01.atoms/animations/view-timeline/tests/WipeAwayVertical.spec.ts +127 -0
  72. package/app/components/01.atoms/canvas-switcher/CONSUMER-STYLING.md +3 -3
  73. package/app/components/01.atoms/canvas-switcher/CanvasSwitcher.vue +2 -2
  74. package/app/components/01.atoms/canvas-switcher/stories/CanvasSwitcher.stories.ts +1 -1
  75. package/app/components/01.atoms/card/CardCore.vue +4 -4
  76. package/app/components/01.atoms/clipped-panel/CONSUMER-STYLING.md +2 -2
  77. package/app/components/01.atoms/clipped-panel/ClippedPanel.vue +2 -2
  78. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md +42 -0
  79. package/app/components/{layout-grids/LayoutGridA.vue → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/DashboardQuadGrid.vue} +23 -13
  80. package/app/components/{layout-grids/stories/LayoutGridA.stories.ts → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/stories/DashboardQuadGrid.stories.ts} +151 -23
  81. package/app/components/{layout-grids/tests/LayoutGridA.spec.ts → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/tests/DashboardQuadGrid.spec.ts} +25 -25
  82. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/CONSUMER-STYLING.md +41 -0
  83. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/DashboardStatsGrid.vue +133 -0
  84. package/app/components/{layout-grids/stories/LayoutGridB.stories.ts → 01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/stories/DashboardStatsGrid.stories.ts} +24 -24
  85. package/app/components/{layout-grids/tests/LayoutGridB.spec.ts → 01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/tests/DashboardStatsGrid.spec.ts} +23 -23
  86. package/app/components/01.atoms/display-tooltip/CONSUMER-STYLING.md +8 -8
  87. package/app/components/01.atoms/display-tooltip/DisplayTooltip.vue +8 -8
  88. package/app/components/01.atoms/pop-over/CONSUMER-STYLING.md +6 -6
  89. package/app/components/01.atoms/pop-over/PopOver.vue +6 -6
  90. package/app/components/01.atoms/ui-block-decorated/CONSUMER-STYLING.md +39 -0
  91. package/app/components/01.atoms/ui-block-decorated/UiBlockDecorated.vue +141 -0
  92. package/app/components/01.atoms/ui-block-decorated/stories/UiBlockDecorated.stories.ts +148 -0
  93. package/app/components/01.atoms/ui-block-decorated/tests/UiBlockDecorated.spec.ts +80 -0
  94. package/app/components/01.atoms/ui-block-decorated/tests/__snapshots__/UiBlockDecorated.spec.ts.snap +3 -0
  95. package/app/components/02.molecules/alert-masked-content/tests/AlertMaskedContent.spec.ts +6 -6
  96. package/app/components/02.molecules/display-theme-switch/CONSUMER-STYLING.md +34 -0
  97. package/app/components/02.molecules/display-theme-switch/DisplayThemeSwitch.vue +102 -0
  98. package/app/components/02.molecules/display-theme-switch/stories/DisplayThemeSwitch.stories.ts +115 -0
  99. package/app/components/02.molecules/display-theme-switch/tests/DisplayThemeSwitch.spec.ts +86 -0
  100. package/app/components/02.molecules/navigation/deep-expanding-menu/CONSUMER-STYLING.md +1 -1
  101. package/app/components/02.molecules/navigation/deep-expanding-menu/DeepExpandingMenu.vue +1 -1
  102. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/CONSUMER-STYLING.md +1 -1
  103. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/DeepExpandingMenuClassic.vue +1 -1
  104. package/app/components/02.molecules/select-menu/CONSUMER-STYLING.md +168 -0
  105. package/app/components/02.molecules/select-menu/SelectMenu.vue +384 -0
  106. package/app/components/02.molecules/select-menu/stories/SelectMenu.stories.ts +359 -0
  107. package/app/components/02.molecules/select-menu/tests/SelectMenu.spec.ts +476 -0
  108. package/app/components/02.molecules/select-menu/tests/__snapshots__/SelectMenu.spec.ts.snap +42 -0
  109. package/app/components/03.organisms/image-galleries/carousel-flip/CONSUMER-STYLING.md +46 -0
  110. package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/CarouselFlip.vue +63 -42
  111. package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/stories/CarouselFlip.stories.ts +114 -29
  112. package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/tests/CarouselFlip.spec.ts +80 -0
  113. package/app/components/05.forms/form-field/FormField.vue +1 -1
  114. package/app/components/05.forms/form-wrapper/FormWrapper.vue +1 -1
  115. package/app/components/05.forms/input-button/CONSUMER-STYLING.md +2 -7
  116. package/app/components/05.forms/input-button/InputButtonCore.vue +1 -5
  117. package/app/components/05.forms/input-number/CONSUMER-STYLING.md +26 -6
  118. package/app/components/05.forms/input-number/InputNumberCore.vue +125 -85
  119. package/app/components/05.forms/input-number/stories/InputNumberCore.stories.ts +92 -0
  120. package/app/components/05.forms/input-number/tests/InputNumberCore.spec.ts +119 -0
  121. package/app/components/05.forms/input-number/variants/InputNumberDefault.vue +30 -22
  122. package/app/components/05.forms/input-number/variants/stories/InputNumberDefault.stories.ts +123 -0
  123. package/app/components/05.forms/input-number/variants/tests/InputNumberDefault.spec.ts +143 -0
  124. package/app/components/05.forms/input-range/CONSUMER-STYLING.md +45 -0
  125. package/app/components/05.forms/input-range/InputRangeCore.vue +79 -143
  126. package/app/components/05.forms/input-range/stories/InputRangeCore.stories.ts +126 -0
  127. package/app/components/05.forms/input-range/tests/InputRangeCore.spec.ts +124 -0
  128. package/app/components/05.forms/input-range/variants/InputRangeDefault.vue +9 -32
  129. package/app/components/05.forms/input-range/variants/stories/InputRangeDefault.stories.ts +108 -0
  130. package/app/components/05.forms/input-range/variants/tests/InputRangeDefault.spec.ts +140 -0
  131. package/app/components/05.forms/input-select/InputSelectCore.vue +23 -30
  132. package/app/components/05.forms/input-select/stories/InputSelectCore.stories.ts +2 -2
  133. package/app/components/05.forms/input-select/stories/InputSelectWithLabel.stories.ts +115 -0
  134. package/app/components/05.forms/input-select/tests/InputSelectCore.spec.ts +129 -0
  135. package/app/components/05.forms/input-select/variants/InputSelectWithLabel.vue +1 -1
  136. package/app/components/05.forms/input-select/variants/tests/InputSelectWithLabel.spec.ts +87 -0
  137. package/app/components/05.forms/input-text/tests/InputTextAsNumberWithLabel.spec.ts +14 -0
  138. package/app/components/05.forms/input-text/variants/InputTextAsNumberWithLabel.vue +1 -1
  139. package/app/components/05.forms/input-textarea/CONSUMER-STYLING.md +55 -0
  140. package/app/components/05.forms/input-textarea/InputTextareaCore.vue +24 -16
  141. package/app/components/05.forms/input-textarea/stories/InputTextareaWithLabel.stories.ts +1 -1
  142. package/app/components/05.forms/input-textarea/tests/InputTextareaCore.spec.ts +119 -0
  143. package/app/components/05.forms/input-textarea/variants/InputTextareaWithLabel.vue +3 -13
  144. package/app/components/05.forms/input-textarea/variants/tests/InputTextareaWithLabel.spec.ts +93 -0
  145. package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +220 -0
  146. package/app/components/05.forms/toggle-switch/CONSUMER-STYLING.md +29 -0
  147. package/app/components/05.forms/toggle-switch/ToggleSwitchCore.vue +120 -119
  148. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchCore.stories.ts +1 -1
  149. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabel.stories.ts +1 -1
  150. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabelInline.stories.ts +2 -2
  151. package/app/components/05.forms/toggle-switch/tests/ToggleSwitchCore.spec.ts +101 -0
  152. package/app/components/05.forms/toggle-switch/variants/ToggleSwitchWithLabel.vue +3 -5
  153. package/app/components/05.forms/toggle-switch/variants/ToggleSwitchWithLabelInline.vue +1 -2
  154. package/app/components/05.forms/toggle-switch/variants/tests/ToggleSwitchWithLabel.spec.ts +77 -0
  155. package/app/components/05.forms/toggle-switch/variants/tests/ToggleSwitchWithLabelInline.spec.ts +66 -0
  156. package/app/components/05.forms/triple-toggle-switch/CONSUMER-STYLING.md +20 -6
  157. package/app/components/05.forms/triple-toggle-switch/TripleToggleSwitchCore.vue +12 -10
  158. package/app/types/components/container-glow.d.ts +8 -0
  159. package/app/types/components/index.ts +2 -1
  160. package/app/types/components/select-menu.d.ts +5 -0
  161. package/package.json +3 -2
  162. package/.vscode/srcdev-component-toggle-switch-core.code-snippets +0 -13
  163. package/app/components/05.forms/input-range-fancy/InputRangeFancyCore.vue +0 -426
  164. package/app/components/05.forms/input-range-fancy/InputRangeFancyWithLabel.vue +0 -94
  165. package/app/components/carousels/CarouselBasic.vue +0 -317
  166. package/app/components/carousels/CarouselInfinite.vue +0 -358
  167. package/app/components/container-glow/ContainerGlowCore.vue +0 -296
  168. package/app/components/container-glow/stories/ContainerGlowCore.stories.ts +0 -337
  169. package/app/components/display-theme-switch/DisplayThemeSwitch.vue +0 -189
  170. package/app/components/display-theme-switch/stories/DisplayThemeSwitch.stories.ts +0 -154
  171. package/app/components/glowing-border/GlowingBorder.vue +0 -142
  172. package/app/components/glowing-border/stories/GlowingBorder.stories.ts +0 -141
  173. package/app/components/layout-grids/LayoutGridB.vue +0 -134
  174. package/app/components/test-storybook/TestStorybook.vue +0 -49
  175. package/app/components/test-storybook/stories/TestStorybook.stories.ts +0 -28
  176. package/app/components/ui-block-decorated/UiBlockDecorated.vue +0 -167
  177. package/app/components/view-timeline/WipeAwayVertical.vue +0 -180
  178. 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="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 FormUiTheme = computed(() => {
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
+ });
@@ -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` | `light-dark(var(--slate-10), var(--slate-00))` | Selected-option marker border colour |
17
- | `--triple-toggle-switch-option-border` | `light-dark(#00000025, #ffffff50)` | Each option's resting border colour |
18
- | `--triple-toggle-switch-option-border-hover` | `light-dark(var(--slate-10), var(--slate-00))` | Each option's outline colour on 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` | `light-dark(var(--slate-10), var(--slate-03))` | Option icon colour, resting (shared by the `.system`/`.light`/`.dark` selectors — see note) |
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
- Geometry (`--_form-*` sizing locals, `--form-element-*`) is shared/component-internal and untouched
49
- by this — see `theming-form-geometry-tokens.md` for the shared half.
48
+ ## Sizing tokens
49
+
50
+ | Token | Default | Controls |
51
+ |---|---|---|
52
+ | `--triple-toggle-switch-gap` | `1rem` | Gap between the three option circles |
53
+ | `--triple-toggle-switch-padding` | `0.6rem` | Wrapper padding |
54
+ | `--triple-toggle-switch-option-padding` | `0.5rem` | Padding inside each option circle |
55
+ | `--triple-toggle-switch-icon-size` | `2rem` | Icon font-size inside each option |
56
+
57
+ Added 2026-09-20 when `DisplayThemeSwitch`'s `small` variant was found reaching directly into this
58
+ component's private `--_form-items-gap`/`--_form-padding`/`--_select-scheme-group-padding`/
59
+ `--_scheme-icon-font-size` locals via selector specificity — a consumer-relevant value (compact
60
+ sizing) hidden behind a private var with no public override hook, the same bug class as CLAUDE.md
61
+ pitfall #20. These four now wrap those locals as real public tokens; the shared geometry from
62
+ `--form-element-*` (border width, outline width) is still shared/component-internal and untouched
63
+ by this — see `theming-form-geometry-tokens.md` for that half.
50
64
 
51
65
  ---
52
66
 
@@ -115,13 +115,15 @@ onMounted(() => {
115
115
  var(--form-element-outline-width)
116
116
  );
117
117
 
118
- --_form-items-gap: 1rem;
119
- --_form-padding: 0.6rem;
118
+ /* Sizing/spacing, publicly overridable — DisplayThemeSwitch's compact "small" styleClassPassthrough
119
+ variant sets these directly rather than reaching into the --_ locals below. */
120
+ --_form-items-gap: var(--triple-toggle-switch-gap, 1rem);
121
+ --_form-padding: var(--triple-toggle-switch-padding, 0.6rem);
120
122
 
121
123
  --_select-scheme-group-background-color: var(--_marker-surface);
122
124
  --_select-scheme-group-background-image: none;
123
- --_select-scheme-group-padding: 0.5rem;
124
- --_scheme-icon-font-size: 2rem;
125
+ --_select-scheme-group-padding: var(--triple-toggle-switch-option-padding, 0.5rem);
126
+ --_scheme-icon-font-size: var(--triple-toggle-switch-icon-size, 2rem);
125
127
  /* --_scheme-icon-colour: black; */
126
128
 
127
129
  /* "system" not "auto" — matches useSettingsStore's actual colourScheme value/CSS class
@@ -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, light-dark(var(--slate-10), var(--slate-00)));
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, light-dark(#00000025, #ffffff50));
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, light-dark(var(--slate-10), var(--slate-00)));
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, light-dark(var(--slate-10), var(--slate-03)));
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, light-dark(var(--slate-10), var(--slate-03)));
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, light-dark(var(--slate-10), var(--slate-03)));
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));
@@ -0,0 +1,8 @@
1
+ export interface ContainerGlowConfig {
2
+ proximity?: number
3
+ spread?: number
4
+ blur?: number
5
+ gap?: number
6
+ vertical?: boolean
7
+ inactiveOpacity?: number
8
+ }
@@ -1,6 +1,5 @@
1
1
  // Export types from the components directory
2
2
  export * from "./semantic-theme.d"
3
- export * from "./carousel-basic.d"
4
3
  export * from "./slider-gallery.d"
5
4
  export * from "./canvas-switcher.d"
6
5
  export * from "./display-chip.d"
@@ -10,6 +9,7 @@ export * from "./display-toast.d"
10
9
  export * from "./display-prompt.d"
11
10
  export * from "./qr-code.d"
12
11
  export * from "./alert-mask-core.d"
12
+ export * from "./container-glow.d"
13
13
  export * from "./hero-text"
14
14
  export * from "./navigation-horizontal.d"
15
15
  export * from "./social-icons-list.d"
@@ -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"
@@ -0,0 +1,5 @@
1
+ export interface SelectMenuOption {
2
+ value: string | number
3
+ label: string
4
+ icon?: string
5
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "srcdev-nuxt-components",
3
3
  "type": "module",
4
- "version": "9.4.9",
4
+ "version": "9.4.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
- }