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,359 @@
1
+ import { ref } from "vue";
2
+ import type { Meta, StoryObj } from "@nuxtjs/storybook";
3
+ import SelectMenu from "../SelectMenu.vue";
4
+ import type { SelectMenuOption } from "~/types/components/select-menu";
5
+
6
+ // ─── Meta ─────────────────────────────────────────────────────────────────────
7
+
8
+ interface StoryArgs {
9
+ options?: SelectMenuOption[];
10
+ label?: string;
11
+ placeholder?: string;
12
+ showIcon?: boolean;
13
+ showLabel?: boolean;
14
+ showChevron?: boolean;
15
+ multiple?: boolean;
16
+ showSelectionInTrigger?: boolean;
17
+ modelValue?: string | number | (string | number)[];
18
+ styleClassPassthrough?: string | string[];
19
+ }
20
+
21
+ const meta: Meta<StoryArgs> = {
22
+ title: "Molecules/SelectMenu",
23
+ component: SelectMenu,
24
+ argTypes: {
25
+ label: {
26
+ control: { type: "text" },
27
+ description: "Accessible name for the trigger/listbox. Also the trigger's fallback text when nothing is selected and no placeholder is set.",
28
+ table: { category: "Content" },
29
+ },
30
+ placeholder: {
31
+ control: { type: "text" },
32
+ description: "Trigger text shown when nothing is selected. Falls back to `label`.",
33
+ table: { category: "Content" },
34
+ },
35
+ showIcon: {
36
+ control: { type: "boolean" },
37
+ description: "Show the selected option's icon in the trigger.",
38
+ table: { category: "Trigger content" },
39
+ },
40
+ showLabel: {
41
+ control: { type: "boolean" },
42
+ description: "Show the selected option's label text in the trigger. Turn off for an icon-only compact trigger.",
43
+ table: { category: "Trigger content" },
44
+ },
45
+ showChevron: {
46
+ control: { type: "boolean" },
47
+ description: "Show the trailing chevron in the trigger.",
48
+ table: { category: "Trigger content" },
49
+ },
50
+ multiple: {
51
+ control: { type: "boolean" },
52
+ description: "Allow selecting more than one option. Each option gets a checkbox indicator and v-model becomes an array. Selecting an option leaves the popover open.",
53
+ table: { category: "Content" },
54
+ },
55
+ showSelectionInTrigger: {
56
+ control: { type: "boolean" },
57
+ description: "In multiple mode, update the trigger text to a comma-separated list of the currently checked options instead of leaving it fixed on placeholder/label. No effect outside multiple mode.",
58
+ table: { category: "Content" },
59
+ },
60
+ styleClassPassthrough: {
61
+ table: { disable: true },
62
+ },
63
+ },
64
+ args: {
65
+ label: "Language",
66
+ showIcon: true,
67
+ showLabel: true,
68
+ showChevron: true,
69
+ },
70
+ parameters: {
71
+ docs: {
72
+ description: {
73
+ component:
74
+ "A custom listbox trigger + popover, built on the same Popover API / CSS anchor-positioning " +
75
+ "approach as `ActionMenu`, but for value selection rather than actions: options carry a " +
76
+ "value/label/icon, and `v-model` drives the current selection. By default it's single-select " +
77
+ "(the selected option shows a checkmark, picking one closes the menu); set `multiple` for a " +
78
+ "checkbox-per-option multi-select where `v-model` becomes an array and the menu stays open " +
79
+ "between picks. The trigger's content (icon, text, chevron) is independently togglable, so the " +
80
+ "same component covers an icon-only switcher, a text+chevron category filter, or a full " +
81
+ "icon+text+chevron select. Defaults to a look consistent with the library's other select-like " +
82
+ "inputs. Set shared CSS tokens globally — see `CONSUMER-STYLING.md` in the component folder.",
83
+ },
84
+ },
85
+ },
86
+ };
87
+
88
+ export default meta;
89
+ type Story = StoryObj<StoryArgs>;
90
+
91
+ // ─── Shared data ──────────────────────────────────────────────────────────────
92
+
93
+ const languageOptions: SelectMenuOption[] = [
94
+ { value: "en", label: "English", icon: "flag:gb-4x3" },
95
+ { value: "fr", label: "Français", icon: "flag:fr-4x3" },
96
+ { value: "de", label: "Deutsch", icon: "flag:de-4x3" },
97
+ { value: "es", label: "Español", icon: "flag:es-4x3" },
98
+ ];
99
+
100
+ const serviceOptions: SelectMenuOption[] = [
101
+ { value: "balayage", label: "Balayage" },
102
+ { value: "highlights", label: "Highlights" },
103
+ { value: "full-colour", label: "Full Colour" },
104
+ { value: "cut-and-finish", label: "Cut & Finish" },
105
+ ];
106
+
107
+ const treatmentOptions: SelectMenuOption[] = [
108
+ { value: "trim", label: "Trim" },
109
+ { value: "layers", label: "Layers" },
110
+ { value: "restyle", label: "Restyle" },
111
+ { value: "straightening", label: "Straightening" },
112
+ ];
113
+
114
+ // ─── Stories ──────────────────────────────────────────────────────────────────
115
+
116
+ /**
117
+ * Default — icon + text + chevron trigger, matching a typical language switcher.
118
+ */
119
+ export const Default: Story = {
120
+ args: {
121
+ label: "Language",
122
+ },
123
+ render: (args) => ({
124
+ components: { SelectMenu },
125
+ setup() {
126
+ const value = ref("en");
127
+ return { args, value, languageOptions };
128
+ },
129
+ template: `
130
+ <div style="padding: 4rem 8rem;">
131
+ <SelectMenu v-bind="args" v-model="value" :options="languageOptions" />
132
+ <p style="margin: 1.6rem 0 0; font-size: 1.3rem; opacity: 0.6;">
133
+ modelValue: <strong>{{ value === undefined ? 'undefined' : value }}</strong>
134
+ </p>
135
+ </div>
136
+ `,
137
+ }),
138
+ };
139
+
140
+ /**
141
+ * Icon-only trigger — the compact variant for tight header/nav spaces, showing
142
+ * only the selected option's icon (e.g. a flag) with no text or chevron.
143
+ */
144
+ export const IconOnly: Story = {
145
+ name: "Icon Only — Compact Switcher",
146
+ args: {
147
+ label: "Language",
148
+ showLabel: false,
149
+ showChevron: false,
150
+ },
151
+ render: (args) => ({
152
+ components: { SelectMenu },
153
+ setup() {
154
+ const value = ref("en");
155
+ return { args, value, languageOptions };
156
+ },
157
+ template: `
158
+ <div style="padding: 4rem 8rem;">
159
+ <SelectMenu v-bind="args" v-model="value" :options="languageOptions" />
160
+ <p style="margin: 1.6rem 0 0; font-size: 1.3rem; opacity: 0.6;">
161
+ modelValue: <strong>{{ value === undefined ? 'undefined' : value }}</strong>
162
+ </p>
163
+ </div>
164
+ `,
165
+ }),
166
+ };
167
+
168
+ /**
169
+ * Icon + chevron, no text — the selected option's icon plus the dropdown
170
+ * chevron, with the label text hidden. Useful when the icon alone (e.g. a
171
+ * flag) is identifiable, but you still want the chevron affordance showing
172
+ * it's an interactive dropdown.
173
+ */
174
+ export const IconWithChevron: Story = {
175
+ name: "Icon + Chevron — No Text",
176
+ args: {
177
+ label: "Language",
178
+ showLabel: false,
179
+ },
180
+ render: (args) => ({
181
+ components: { SelectMenu },
182
+ setup() {
183
+ const value = ref("en");
184
+ return { args, value, languageOptions };
185
+ },
186
+ template: `
187
+ <div style="padding: 4rem 8rem;">
188
+ <SelectMenu v-bind="args" v-model="value" :options="languageOptions" />
189
+ <p style="margin: 1.6rem 0 0; font-size: 1.3rem; opacity: 0.6;">
190
+ modelValue: <strong>{{ value === undefined ? 'undefined' : value }}</strong>
191
+ </p>
192
+ </div>
193
+ `,
194
+ }),
195
+ };
196
+
197
+ /**
198
+ * Category filter — text + chevron only (no per-option icons), with the
199
+ * trigger showing the category label ("Choose a service") until a value is
200
+ * picked. Useful for faceted filters where several SelectMenu instances sit
201
+ * side by side, each covering one filter category.
202
+ */
203
+ export const CategoryFilter: Story = {
204
+ name: "Category Filter",
205
+ args: {
206
+ label: "Choose a service",
207
+ showIcon: false,
208
+ },
209
+ render: (args) => ({
210
+ components: { SelectMenu },
211
+ setup() {
212
+ const value = ref<string | undefined>(undefined);
213
+ return { args, value, serviceOptions };
214
+ },
215
+ template: `
216
+ <div style="padding: 4rem 8rem;">
217
+ <SelectMenu v-bind="args" v-model="value" :options="serviceOptions" />
218
+ <p style="margin: 1.6rem 0 0; font-size: 1.3rem; opacity: 0.6;">
219
+ modelValue: <strong>{{ value === undefined ? 'undefined' : value }}</strong>
220
+ </p>
221
+ </div>
222
+ `,
223
+ }),
224
+ };
225
+
226
+ /**
227
+ * Multiple filter categories side by side — each SelectMenu instance is an
228
+ * independent single-select v-model, so a faceted filter bar is built by
229
+ * placing several instances next to each other.
230
+ */
231
+ export const FilterBar: Story = {
232
+ name: "Filter Bar — Multiple Categories",
233
+ args: {},
234
+ render: () => ({
235
+ components: { SelectMenu },
236
+ setup() {
237
+ const service = ref<string | undefined>(undefined);
238
+ const stylist = ref<string | undefined>(undefined);
239
+ const stylistOptions: SelectMenuOption[] = [
240
+ { value: "any", label: "Any stylist" },
241
+ { value: "alex", label: "Alex Morgan" },
242
+ { value: "sam", label: "Sam Patel" },
243
+ ];
244
+ return { service, stylist, serviceOptions, stylistOptions };
245
+ },
246
+ template: `
247
+ <div style="padding: 4rem 8rem;">
248
+ <div style="display: flex; gap: 1.2rem; flex-wrap: wrap;">
249
+ <SelectMenu label="Choose a service" v-model="service" :options="serviceOptions" />
250
+ <SelectMenu label="Choose a stylist" v-model="stylist" :options="stylistOptions" />
251
+ </div>
252
+ <p style="margin: 1.6rem 0 0; font-size: 1.3rem; opacity: 0.6;">
253
+ service: <strong>{{ service === undefined ? 'undefined' : service }}</strong>,
254
+ stylist: <strong>{{ stylist === undefined ? 'undefined' : stylist }}</strong>
255
+ </p>
256
+ </div>
257
+ `,
258
+ }),
259
+ };
260
+
261
+ /**
262
+ * Multi-select — `multiple` turns each option into a checkbox row and
263
+ * `v-model` becomes an array. Picking an option toggles it and leaves the
264
+ * popover open so more can be selected. The trigger text stays fixed on
265
+ * `label` (e.g. "Services required") as a static category tag regardless of
266
+ * how many options are checked — it does not update to list the selection.
267
+ */
268
+ export const MultiSelect: Story = {
269
+ name: "Multi-Select — Checkboxes",
270
+ args: {
271
+ label: "Services required",
272
+ showIcon: false,
273
+ multiple: true,
274
+ },
275
+ render: (args) => ({
276
+ components: { SelectMenu },
277
+ setup() {
278
+ const value = ref<string[]>([]);
279
+ return { args, value, treatmentOptions };
280
+ },
281
+ template: `
282
+ <div style="padding: 4rem 8rem;">
283
+ <SelectMenu v-bind="args" v-model="value" :options="treatmentOptions" />
284
+ <p style="margin: 1.6rem 0 0; font-size: 1.3rem; opacity: 0.6;">
285
+ modelValue: <strong>{{ value.length ? value.join(', ') : '[]' }}</strong>
286
+ </p>
287
+ </div>
288
+ `,
289
+ }),
290
+ };
291
+
292
+ /**
293
+ * Multi-select with the trigger showing the current selection —
294
+ * `showSelectionInTrigger` opts out of the static-label default, so the
295
+ * trigger updates to a comma-joined list of checked options as they're
296
+ * toggled, falling back to `label`/`placeholder` when nothing is checked.
297
+ */
298
+ export const MultiSelectShowingSelection: Story = {
299
+ name: "Multi-Select — Trigger Shows Selection",
300
+ args: {
301
+ label: "Services required",
302
+ showIcon: false,
303
+ multiple: true,
304
+ showSelectionInTrigger: true,
305
+ },
306
+ render: (args) => ({
307
+ components: { SelectMenu },
308
+ setup() {
309
+ const value = ref<string[]>(["layers", "restyle"]);
310
+ return { args, value, treatmentOptions };
311
+ },
312
+ template: `
313
+ <div style="padding: 4rem 8rem;">
314
+ <SelectMenu v-bind="args" v-model="value" :options="treatmentOptions" />
315
+ <p style="margin: 1.6rem 0 0; font-size: 1.3rem; opacity: 0.6;">
316
+ modelValue: <strong>{{ value.length ? value.join(', ') : '[]' }}</strong>
317
+ </p>
318
+ </div>
319
+ `,
320
+ }),
321
+ };
322
+
323
+ /**
324
+ * Custom tokens — a per-instance styleClassPassthrough overriding the public
325
+ * --select-menu-* tokens to demonstrate full visual customisation.
326
+ */
327
+ export const CustomTokens: Story = {
328
+ name: "Custom Tokens",
329
+ args: {
330
+ label: "Language",
331
+ },
332
+ render: (args) => ({
333
+ components: { SelectMenu },
334
+ setup() {
335
+ const value = ref("en");
336
+ return { args, value, languageOptions };
337
+ },
338
+ template: `
339
+ <div style="padding: 4rem 8rem;">
340
+ <style>
341
+ .select-menu.dark-pill {
342
+ --select-menu-trigger-border-radius: 999rem;
343
+ --select-menu-trigger-surface: #1a1a1a;
344
+ --select-menu-trigger-text-color: #ffffff;
345
+ --select-menu-trigger-border: #333333;
346
+ --select-menu-popover-surface: #1a1a1a;
347
+ --select-menu-popover-border: #333333;
348
+ --select-menu-item-text-color: #ffffff;
349
+ --select-menu-item-surface-hover: #2a2a2a;
350
+ }
351
+ </style>
352
+ <SelectMenu v-bind="args" v-model="value" :options="languageOptions" :style-class-passthrough="['dark-pill']" />
353
+ <p style="margin: 1.6rem 0 0; font-size: 1.3rem; opacity: 0.6;">
354
+ modelValue: <strong>{{ value === undefined ? 'undefined' : value }}</strong>
355
+ </p>
356
+ </div>
357
+ `,
358
+ }),
359
+ };