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,102 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<ClientOnly>
|
|
3
|
+
<TripleToggleSwitchCore
|
|
4
|
+
v-model="colorModeVal"
|
|
5
|
+
v-model:field-data="sampleFieldData"
|
|
6
|
+
:style-class-passthrough="[`colour-scheme-select`, elementClasses]"
|
|
7
|
+
/>
|
|
8
|
+
</ClientOnly>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<script setup lang="ts">
|
|
12
|
+
import type { IFormMultipleOptions } from "~/types/forms/types.forms";
|
|
13
|
+
interface Props {
|
|
14
|
+
styleClassPassthrough?: string | string[];
|
|
15
|
+
systemLabel?: string;
|
|
16
|
+
lightLabel?: string;
|
|
17
|
+
darkLabel?: string;
|
|
18
|
+
systemIcon?: string;
|
|
19
|
+
lightIcon?: string;
|
|
20
|
+
darkIcon?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
24
|
+
styleClassPassthrough: () => [],
|
|
25
|
+
systemLabel: "System",
|
|
26
|
+
lightLabel: "Light",
|
|
27
|
+
darkLabel: "Dark",
|
|
28
|
+
systemIcon: "material-symbols:night-sight-auto-sharp",
|
|
29
|
+
lightIcon: "radix-icons:sun",
|
|
30
|
+
darkIcon: "radix-icons:moon",
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
|
|
34
|
+
|
|
35
|
+
const sampleFieldData = computed<IFormMultipleOptions>(() => ({
|
|
36
|
+
data: [
|
|
37
|
+
{
|
|
38
|
+
id: "system",
|
|
39
|
+
name: "colorModeVal",
|
|
40
|
+
value: "system",
|
|
41
|
+
label: props.systemLabel,
|
|
42
|
+
icon: props.systemIcon,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: "light",
|
|
46
|
+
name: "colorModeVal",
|
|
47
|
+
value: "light",
|
|
48
|
+
label: props.lightLabel,
|
|
49
|
+
icon: props.lightIcon,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "dark",
|
|
53
|
+
name: "colorModeVal",
|
|
54
|
+
value: "dark",
|
|
55
|
+
label: props.darkLabel,
|
|
56
|
+
icon: props.darkIcon,
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
total: 3,
|
|
60
|
+
skip: 0,
|
|
61
|
+
limit: 3,
|
|
62
|
+
}));
|
|
63
|
+
|
|
64
|
+
const { colourScheme, setColourScheme } = useSettingsStore();
|
|
65
|
+
|
|
66
|
+
const colorModeVal = ref(colourScheme);
|
|
67
|
+
watch(colorModeVal, (val) => {
|
|
68
|
+
setColourScheme(val);
|
|
69
|
+
});
|
|
70
|
+
</script>
|
|
71
|
+
|
|
72
|
+
<style lang="css">
|
|
73
|
+
@layer components {
|
|
74
|
+
.colour-scheme-select {
|
|
75
|
+
/* Everything DisplayThemeSwitch used to redeclare here beyond sizing (wrapper background/border/
|
|
76
|
+
focus ring, marker border, option border/outline/focus/hover, option-icon colours, and the
|
|
77
|
+
system/light/dark marker gradients) was a byte-for-byte duplicate of TripleToggleSwitchCore's
|
|
78
|
+
own defaults for the exact same selectors — a no-op at best, and for the ones that bypassed
|
|
79
|
+
TripleToggleSwitchCore's public --triple-toggle-switch-* tokens with a hardcoded literal
|
|
80
|
+
(background-color, border, focus outline, marker border, option border/hover/focus, icon
|
|
81
|
+
colour) an active bug: a consumer setting e.g. --triple-toggle-switch-surface globally would
|
|
82
|
+
have been silently overridden back to the hardcoded default by this block's higher
|
|
83
|
+
specificity. Removed 2026-09-20 — TripleToggleSwitchCore already owns all of that. Only the
|
|
84
|
+
genuine "small" sizing variant remains, now driving TripleToggleSwitchCore's own public
|
|
85
|
+
sizing tokens instead of reaching into its private --_form-* and --_scheme-icon-font-size
|
|
86
|
+
locals directly (see TripleToggleSwitchCore's CONSUMER-STYLING.md). */
|
|
87
|
+
&.triple-toggle-switch {
|
|
88
|
+
--triple-toggle-switch-gap: 0.4rem;
|
|
89
|
+
--triple-toggle-switch-padding: 0.4rem;
|
|
90
|
+
--triple-toggle-switch-option-padding: 0.4rem;
|
|
91
|
+
--triple-toggle-switch-icon-size: 2rem;
|
|
92
|
+
|
|
93
|
+
&.small {
|
|
94
|
+
--triple-toggle-switch-gap: 0.2rem;
|
|
95
|
+
--triple-toggle-switch-padding: 0.2rem;
|
|
96
|
+
--triple-toggle-switch-option-padding: 0.2rem;
|
|
97
|
+
--triple-toggle-switch-icon-size: 1.6rem;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
</style>
|
package/app/components/02.molecules/display-theme-switch/stories/DisplayThemeSwitch.stories.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import DisplayThemeSwitch from "../DisplayThemeSwitch.vue";
|
|
2
|
+
import type { Meta, StoryObj } from "@nuxtjs/storybook";
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof DisplayThemeSwitch> = {
|
|
5
|
+
title: "Molecules/DisplayThemeSwitch",
|
|
6
|
+
component: DisplayThemeSwitch,
|
|
7
|
+
argTypes: {
|
|
8
|
+
styleClassPassthrough: {
|
|
9
|
+
control: "object",
|
|
10
|
+
description: "Extra classes — pass \"small\" for a compact size",
|
|
11
|
+
table: { category: "Styling" },
|
|
12
|
+
},
|
|
13
|
+
systemLabel: {
|
|
14
|
+
control: "text",
|
|
15
|
+
description: "Accessible label for the system option",
|
|
16
|
+
table: { category: "Labels" },
|
|
17
|
+
},
|
|
18
|
+
lightLabel: {
|
|
19
|
+
control: "text",
|
|
20
|
+
description: "Accessible label for the light option",
|
|
21
|
+
table: { category: "Labels" },
|
|
22
|
+
},
|
|
23
|
+
darkLabel: {
|
|
24
|
+
control: "text",
|
|
25
|
+
description: "Accessible label for the dark option",
|
|
26
|
+
table: { category: "Labels" },
|
|
27
|
+
},
|
|
28
|
+
systemIcon: {
|
|
29
|
+
control: "text",
|
|
30
|
+
description: "Icon name for the system option",
|
|
31
|
+
table: { category: "Icons" },
|
|
32
|
+
},
|
|
33
|
+
lightIcon: {
|
|
34
|
+
control: "text",
|
|
35
|
+
description: "Icon name for the light option",
|
|
36
|
+
table: { category: "Icons" },
|
|
37
|
+
},
|
|
38
|
+
darkIcon: {
|
|
39
|
+
control: "text",
|
|
40
|
+
description: "Icon name for the dark option",
|
|
41
|
+
table: { category: "Icons" },
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
parameters: {
|
|
45
|
+
docs: {
|
|
46
|
+
description: {
|
|
47
|
+
component:
|
|
48
|
+
"A system/light/dark theme switch wrapping TripleToggleSwitchCore, wired to useSettingsStore. Colour and sizing are entirely delegated to TripleToggleSwitchCore's public --triple-toggle-switch-* tokens — see CONSUMER-STYLING.md.",
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export default meta;
|
|
55
|
+
type Story = StoryObj<typeof DisplayThemeSwitch>;
|
|
56
|
+
|
|
57
|
+
export const Default: Story = {
|
|
58
|
+
args: {},
|
|
59
|
+
render: (args) => ({
|
|
60
|
+
components: { DisplayThemeSwitch },
|
|
61
|
+
setup() {
|
|
62
|
+
return { args };
|
|
63
|
+
},
|
|
64
|
+
template: `<DisplayThemeSwitch v-bind="args" />`,
|
|
65
|
+
}),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const Small: Story = {
|
|
69
|
+
args: {
|
|
70
|
+
styleClassPassthrough: "small",
|
|
71
|
+
},
|
|
72
|
+
render: (args) => ({
|
|
73
|
+
components: { DisplayThemeSwitch },
|
|
74
|
+
setup() {
|
|
75
|
+
return { args };
|
|
76
|
+
},
|
|
77
|
+
template: `<DisplayThemeSwitch v-bind="args" />`,
|
|
78
|
+
}),
|
|
79
|
+
parameters: {
|
|
80
|
+
docs: {
|
|
81
|
+
description: {
|
|
82
|
+
story: "Compact sizing for use in a tight header/nav slot.",
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const InNavigation: Story = {
|
|
89
|
+
args: {
|
|
90
|
+
styleClassPassthrough: "small",
|
|
91
|
+
},
|
|
92
|
+
render: (args) => ({
|
|
93
|
+
components: { DisplayThemeSwitch },
|
|
94
|
+
setup() {
|
|
95
|
+
return { args };
|
|
96
|
+
},
|
|
97
|
+
template: `
|
|
98
|
+
<nav style="display: flex; align-items: center; justify-content: space-between; padding: 1.6rem 2.4rem; background: var(--theme-surface); border-radius: 0.8rem; border: 1px solid var(--theme-border);">
|
|
99
|
+
<div style="display: flex; align-items: center; gap: 1.6rem;">
|
|
100
|
+
<span style="font-weight: 600; color: var(--theme-text);">My App</span>
|
|
101
|
+
<a href="#" style="color: var(--theme-text); text-decoration: none;">Home</a>
|
|
102
|
+
<a href="#" style="color: var(--theme-text); text-decoration: none;">About</a>
|
|
103
|
+
</div>
|
|
104
|
+
<DisplayThemeSwitch v-bind="args" />
|
|
105
|
+
</nav>
|
|
106
|
+
`,
|
|
107
|
+
}),
|
|
108
|
+
parameters: {
|
|
109
|
+
docs: {
|
|
110
|
+
description: {
|
|
111
|
+
story: "Example of the theme switch integrated into a navigation bar context.",
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { mountSuspended, mockNuxtImport } from "@nuxt/test-utils/runtime";
|
|
3
|
+
import { nextTick, ref } from "vue";
|
|
4
|
+
import DisplayThemeSwitch from "../DisplayThemeSwitch.vue";
|
|
5
|
+
|
|
6
|
+
const { useSettingsStoreMock, setColourSchemeMock } = vi.hoisted(() => ({
|
|
7
|
+
useSettingsStoreMock: vi.fn(),
|
|
8
|
+
setColourSchemeMock: vi.fn(),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
mockNuxtImport("useSettingsStore", () => useSettingsStoreMock);
|
|
12
|
+
|
|
13
|
+
describe("DisplayThemeSwitch", () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
setColourSchemeMock.mockClear();
|
|
16
|
+
useSettingsStoreMock.mockReturnValue({
|
|
17
|
+
colourScheme: ref("system"),
|
|
18
|
+
setColourScheme: setColourSchemeMock,
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("mounts without error", async () => {
|
|
23
|
+
const wrapper = await mountSuspended(DisplayThemeSwitch);
|
|
24
|
+
await nextTick();
|
|
25
|
+
expect(wrapper.vm).toBeTruthy();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("renders TripleToggleSwitchCore with the colour-scheme-select class", async () => {
|
|
29
|
+
const wrapper = await mountSuspended(DisplayThemeSwitch);
|
|
30
|
+
await nextTick();
|
|
31
|
+
expect(wrapper.find(".colour-scheme-select").exists()).toBe(true);
|
|
32
|
+
expect(wrapper.find(".triple-toggle-switch").exists()).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("renders default option labels", async () => {
|
|
36
|
+
const wrapper = await mountSuspended(DisplayThemeSwitch);
|
|
37
|
+
await nextTick();
|
|
38
|
+
const labels = wrapper.findAll(".option-group .sr-only").map((el) => el.text());
|
|
39
|
+
expect(labels).toEqual(["System", "Light", "Dark"]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("renders custom option labels via props", async () => {
|
|
43
|
+
const wrapper = await mountSuspended(DisplayThemeSwitch, {
|
|
44
|
+
props: {
|
|
45
|
+
systemLabel: "Auto",
|
|
46
|
+
lightLabel: "Day",
|
|
47
|
+
darkLabel: "Night",
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
await nextTick();
|
|
51
|
+
const labels = wrapper.findAll(".option-group .sr-only").map((el) => el.text());
|
|
52
|
+
expect(labels).toEqual(["Auto", "Day", "Night"]);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("applies custom option icons via props", async () => {
|
|
56
|
+
const wrapper = await mountSuspended(DisplayThemeSwitch, {
|
|
57
|
+
props: {
|
|
58
|
+
systemIcon: "custom:system",
|
|
59
|
+
lightIcon: "custom:light",
|
|
60
|
+
darkIcon: "custom:dark",
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
await nextTick();
|
|
64
|
+
const icons = wrapper.findAll(".option-icon").map((el) => el.attributes("class"));
|
|
65
|
+
expect(icons.some((c) => c?.includes("system"))).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("applies the small class via styleClassPassthrough", async () => {
|
|
69
|
+
const wrapper = await mountSuspended(DisplayThemeSwitch, {
|
|
70
|
+
props: { styleClassPassthrough: "small" },
|
|
71
|
+
});
|
|
72
|
+
await nextTick();
|
|
73
|
+
expect(wrapper.find(".triple-toggle-switch").classes()).toContain("small");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("calls setColourScheme when the selected option changes", async () => {
|
|
77
|
+
const wrapper = await mountSuspended(DisplayThemeSwitch);
|
|
78
|
+
await nextTick();
|
|
79
|
+
|
|
80
|
+
const lightInput = wrapper.findAll("input.option-input")[1];
|
|
81
|
+
await lightInput?.setValue(true);
|
|
82
|
+
await nextTick();
|
|
83
|
+
|
|
84
|
+
expect(setColourSchemeMock).toHaveBeenCalledWith("light");
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
| `--deep-expanding-menu-gap` | `2.4rem` | Gap between top-level items |
|
|
8
8
|
| `--deep-expanding-menu-link-border-width` | `0.2rem` | Bottom border width on links and group toggles |
|
|
9
9
|
| `--deep-expanding-menu-link-padding-block` | `0.8rem` | Vertical padding on links and group toggles |
|
|
10
|
-
| `--deep-expanding-menu-link-border-colour-hover` | `
|
|
10
|
+
| `--deep-expanding-menu-link-border-colour-hover` | `var(--blue-10)` | Bottom border colour on hover/focus |
|
|
11
11
|
| `--deep-expanding-menu-icon-size` | `1.2rem` | Caret icon size |
|
|
12
12
|
| `--deep-expanding-menu-panel-width` | `min(100%, 50vw)` | Popover panel width |
|
|
13
13
|
| `--deep-expanding-menu-panel-background-colour` | `white` | Popover panel background |
|
package/app/components/02.molecules/navigation/deep-expanding-menu-classic/CONSUMER-STYLING.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
| `--deep-expanding-menu-classic-item-gap` | `2.4rem` | Gap between top-level items |
|
|
9
9
|
| `--deep-expanding-menu-classic-link-border-width` | `0.2rem` | Bottom border width on links and group toggles |
|
|
10
10
|
| `--deep-expanding-menu-classic-link-padding-block` | `0.8rem` | Vertical padding on links and group toggles |
|
|
11
|
-
| `--deep-expanding-menu-classic-link-border-colour-hover` | `
|
|
11
|
+
| `--deep-expanding-menu-classic-link-border-colour-hover` | `var(--blue-10)` | Bottom border colour on hover/focus |
|
|
12
12
|
| `--deep-expanding-menu-classic-icon-size` | `1.2rem` | Caret icon size |
|
|
13
13
|
| `--deep-expanding-menu-classic-panel-offset-top` | `4rem` | Panel offset from the toggle |
|
|
14
14
|
| `--deep-expanding-menu-classic-panel-width` | `20rem` | Panel width below the `768px` breakpoint |
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# SelectMenu — Consumer Styling Guide
|
|
2
|
+
|
|
3
|
+
## Public token API
|
|
4
|
+
|
|
5
|
+
All `--select-menu-*` tokens are the stable override surface, each falling back to a `--theme-*`
|
|
6
|
+
global token so it matches the rest of the design system out of the box.
|
|
7
|
+
|
|
8
|
+
### Trigger button
|
|
9
|
+
|
|
10
|
+
| Token | Default | Controls |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| `--select-menu-trigger-gap` | `0.8rem` | Gap between icon / label / chevron |
|
|
13
|
+
| `--select-menu-trigger-min-height` | `4.4rem` | Trigger minimum height |
|
|
14
|
+
| `--select-menu-trigger-padding-block` | `0.8rem` | Trigger vertical padding |
|
|
15
|
+
| `--select-menu-trigger-padding-inline` | `1.2rem` | Trigger horizontal padding |
|
|
16
|
+
| `--select-menu-trigger-border-width` | `0.1rem` | Trigger border thickness |
|
|
17
|
+
| `--select-menu-trigger-border` | `var(--theme-border)` | Trigger border colour (rest state) |
|
|
18
|
+
| `--select-menu-trigger-border-focus` | `var(--theme-border-focus)` | Trigger border colour on hover/focus |
|
|
19
|
+
| `--select-menu-trigger-border-radius` | `0.5rem` | Trigger corner rounding |
|
|
20
|
+
| `--select-menu-trigger-surface` | `var(--theme-input-surface)` | Trigger background |
|
|
21
|
+
| `--select-menu-trigger-text-color` | `var(--theme-text)` | Trigger label text colour |
|
|
22
|
+
| `--select-menu-trigger-font-size` | `1.6rem` | Trigger label font size |
|
|
23
|
+
| `--select-menu-trigger-outline-width` | `0.2rem` | Focus-visible outline width (trigger and items) |
|
|
24
|
+
| `--select-menu-trigger-icon-size` | `2rem` | Selected option's icon size in the trigger |
|
|
25
|
+
| `--select-menu-trigger-chevron-size` | `1.6rem` | Chevron icon size |
|
|
26
|
+
|
|
27
|
+
### Menu popover
|
|
28
|
+
|
|
29
|
+
| Token | Default | Controls |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| `--select-menu-block-distance` | `0.4rem` | Gap between trigger bottom and menu top |
|
|
32
|
+
| `--select-menu-popover-border-width` | `0.1rem` | Popover border thickness |
|
|
33
|
+
| `--select-menu-popover-border` | `var(--theme-border)` | Popover border colour |
|
|
34
|
+
| `--select-menu-popover-surface` | `var(--theme-input-surface)` | Popover background |
|
|
35
|
+
| `--select-menu-popover-border-radius` | `0.5rem` | Popover corner rounding |
|
|
36
|
+
| `--select-menu-popover-min-width` | `18rem` | Minimum popover width |
|
|
37
|
+
| `--select-menu-popover-max-height` | `32rem` | Maximum popover height before scrolling |
|
|
38
|
+
| `--select-menu-popover-shadow` | `0 0.4rem 1.6rem rgb(0 0 0 / 12%)` | Popover drop shadow |
|
|
39
|
+
| `--select-menu-transition-duration` | `200ms` | Open/close fade and chevron-rotate duration |
|
|
40
|
+
|
|
41
|
+
### Options
|
|
42
|
+
|
|
43
|
+
| Token | Default | Controls |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| `--select-menu-item-gap` | `0.8rem` | Gap between checkmark / icon / label in a row |
|
|
46
|
+
| `--select-menu-item-padding-block` | `1rem` | Option row vertical padding |
|
|
47
|
+
| `--select-menu-item-padding-inline` | `1.2rem` | Option row horizontal padding |
|
|
48
|
+
| `--select-menu-item-text-color` | `var(--theme-text)` | Option label colour |
|
|
49
|
+
| `--select-menu-item-font-size` | `1.5rem` | Option label font size |
|
|
50
|
+
| `--select-menu-item-surface-hover` | `var(--theme-input-surface-hover)` | Option row background on hover/focus |
|
|
51
|
+
| `--select-menu-item-check-size` | `1.6rem` | Checkmark icon box size |
|
|
52
|
+
| `--select-menu-item-check-color` | `currentcolor` | Checkmark icon colour |
|
|
53
|
+
| `--select-menu-item-icon-size` | `1.8rem` | Per-option icon size |
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Global theming — recommended approach
|
|
58
|
+
|
|
59
|
+
Create `assets/styles/setup/07.components/select-menu.css` in the consuming app and set tokens
|
|
60
|
+
on `:root`. This applies to every `SelectMenu` across the site.
|
|
61
|
+
|
|
62
|
+
```css
|
|
63
|
+
/* assets/styles/setup/07.components/select-menu.css */
|
|
64
|
+
:root {
|
|
65
|
+
--select-menu-trigger-border-radius: 999rem;
|
|
66
|
+
--select-menu-trigger-surface: var(--brand-surface);
|
|
67
|
+
--select-menu-trigger-border: var(--brand-border);
|
|
68
|
+
|
|
69
|
+
--select-menu-popover-surface: var(--brand-surface);
|
|
70
|
+
--select-menu-popover-border: var(--brand-border);
|
|
71
|
+
--select-menu-popover-shadow: 0 0.8rem 2.4rem rgba(0, 0, 0, 0.15);
|
|
72
|
+
|
|
73
|
+
--select-menu-item-surface-hover: var(--brand-surface-subtle);
|
|
74
|
+
--select-menu-item-check-color: var(--brand-accent);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Per-instance overrides via styleClassPassthrough
|
|
81
|
+
|
|
82
|
+
```vue
|
|
83
|
+
<SelectMenu
|
|
84
|
+
v-model="language"
|
|
85
|
+
:options="languageOptions"
|
|
86
|
+
label="Language"
|
|
87
|
+
:style-class-passthrough="['compact-switcher']"
|
|
88
|
+
/>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
```css
|
|
92
|
+
.select-menu.compact-switcher {
|
|
93
|
+
--select-menu-trigger-min-height: 3.2rem;
|
|
94
|
+
--select-menu-trigger-padding-inline: 0.8rem;
|
|
95
|
+
--select-menu-trigger-border-radius: 50%;
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Trigger content variants
|
|
102
|
+
|
|
103
|
+
`showIcon` / `showLabel` / `showChevron` control what renders in the trigger — combine them for
|
|
104
|
+
the three common shapes:
|
|
105
|
+
|
|
106
|
+
| Variant | Props |
|
|
107
|
+
|---|---|
|
|
108
|
+
| Icon-only (e.g. flag switcher in a tight header) | `:show-label="false" :show-chevron="false"` |
|
|
109
|
+
| Text + chevron (e.g. a category filter with no per-option icons) | `:show-icon="false"` (default `showLabel`/`showChevron`) |
|
|
110
|
+
| Icon + text + chevron (full select) | Defaults — no overrides needed |
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Single-select vs multi-select
|
|
115
|
+
|
|
116
|
+
By default `SelectMenu` is single-select: `v-model` is `string | number | undefined`, the selected
|
|
117
|
+
option shows a checkmark, and picking one closes the popover.
|
|
118
|
+
|
|
119
|
+
Set `multiple` for a checkbox-per-option multi-select instead: `v-model` becomes
|
|
120
|
+
`(string | number)[]`, every option shows a checkbox (checked/unchecked), and picking an option
|
|
121
|
+
toggles it in the array without closing the popover — so several can be picked in one open/close
|
|
122
|
+
cycle. By default the trigger text stays fixed on `placeholder`/`label` as a static category tag
|
|
123
|
+
(e.g. `"Services required"`) regardless of how many options are checked, since a comma-joined list
|
|
124
|
+
of checked options would grow unpredictably long. Set `showSelectionInTrigger` to opt into that
|
|
125
|
+
comma-joined list instead (falling back to `placeholder`/`label` when nothing is checked). The
|
|
126
|
+
trigger icon is not shown in multi-select mode (`showIcon` has no effect), since there's no single
|
|
127
|
+
selected option to represent.
|
|
128
|
+
|
|
129
|
+
```vue
|
|
130
|
+
<script setup lang="ts">
|
|
131
|
+
const treatments = ref<string[]>([]);
|
|
132
|
+
const treatmentOptions = [
|
|
133
|
+
{ value: "trim", label: "Trim" },
|
|
134
|
+
{ value: "layers", label: "Layers" },
|
|
135
|
+
{ value: "restyle", label: "Restyle" },
|
|
136
|
+
{ value: "straightening", label: "Straightening" },
|
|
137
|
+
];
|
|
138
|
+
</script>
|
|
139
|
+
|
|
140
|
+
<template>
|
|
141
|
+
<SelectMenu v-model="treatments" :options="treatmentOptions" label="Services required" multiple />
|
|
142
|
+
|
|
143
|
+
<!-- Trigger updates to "Layers, Restyle" etc. as options are checked -->
|
|
144
|
+
<SelectMenu
|
|
145
|
+
v-model="treatments"
|
|
146
|
+
:options="treatmentOptions"
|
|
147
|
+
label="Services required"
|
|
148
|
+
multiple
|
|
149
|
+
show-selection-in-trigger
|
|
150
|
+
/>
|
|
151
|
+
</template>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
For several independent single-select filter categories (rather than one multi-select category),
|
|
155
|
+
place multiple single-select `SelectMenu` instances side by side instead — see the "Filter Bar"
|
|
156
|
+
story.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Notes
|
|
161
|
+
|
|
162
|
+
- **Popover API + CSS anchor positioning** — same mechanism as `ActionMenu`. Broadly supported
|
|
163
|
+
(Chrome 114+, Firefox 125+, Safari 17+). No polyfill is included.
|
|
164
|
+
- **Popover left-aligns with the trigger** by default (`left: anchor(left)`), unlike `ActionMenu`
|
|
165
|
+
which right-aligns — matches native `<select>` dropdown behaviour. Flips above the trigger near
|
|
166
|
+
the bottom of the viewport (`position-try-fallbacks: flip-block`).
|
|
167
|
+
- **Chevron rotation** is pure CSS via `:has(.select-menu-popover:popover-open)` — no JS state
|
|
168
|
+
needed for the visual, though `isOpen` is still tracked internally to drive `aria-expanded`.
|