aural-ui 4.0.1 → 4.1.0
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/dist/components/aspect-ratio/AspectRatio.stories.tsx +290 -1228
- package/dist/components/avatar/Avatar.stories.tsx +219 -235
- package/dist/components/badge/Badge.stories.tsx +379 -116
- package/dist/components/banner/Banner.stories.tsx +445 -391
- package/dist/components/breadcrumb/Breadcrumb.stories.tsx +453 -199
- package/dist/components/button/Button.stories.tsx +585 -230
- package/dist/components/card/Card.stories.tsx +619 -301
- package/dist/components/char-count/CharCount.stories.tsx +350 -248
- package/dist/components/checkbox/Checkbox.stories.tsx +309 -167
- package/dist/components/chip/Chip.stories.tsx +362 -168
- package/dist/components/circular-loader/CircularLoader.stories.tsx +221 -636
- package/dist/components/clamp-lines/ClampLines.stories.tsx +246 -117
- package/dist/components/collapsible/Collapsible.stories.tsx +391 -252
- package/dist/components/command/Command.stories.tsx +530 -867
- package/dist/components/dialog/Dialog.stories.tsx +501 -950
- package/dist/components/divider/Divider.stories.tsx +264 -527
- package/dist/components/dot-loader/DotLoader.stories.tsx +256 -257
- package/dist/components/drawer/Drawer.stories.tsx +659 -1023
- package/dist/components/dropdown/Dropdown.stories.tsx +643 -1028
- package/dist/components/form/Form.stories.tsx +560 -274
- package/dist/components/helper-text/HelperText.stories.tsx +199 -200
- package/dist/components/hover-card/HoverCard.stories.tsx +318 -1254
- package/dist/components/icon-button/IconButton.stories.tsx +837 -194
- package/dist/components/if-else/if-else.stories.tsx +370 -83
- package/dist/components/input/Input.stories.tsx +436 -368
- package/dist/components/label/Label.stories.tsx +156 -154
- package/dist/components/list/List.stories.tsx +484 -835
- package/dist/components/marquee/Marquee.stories.tsx +356 -712
- package/dist/components/otp-inputs/OtpInputs.stories.tsx +352 -422
- package/dist/components/overlay/Overlay.stories.tsx +452 -824
- package/dist/components/pagination/Pagination.stories.tsx +721 -210
- package/dist/components/popover/Popover.stories.tsx +481 -896
- package/dist/components/radio/Radio.stories.tsx +432 -124
- package/dist/components/resizable/Resizable.stories.tsx +495 -799
- package/dist/components/scroll-area/ScrollArea.stories.tsx +383 -1059
- package/dist/components/search/Search.stories.tsx +312 -595
- package/dist/components/select/Select.stories.tsx +684 -789
- package/dist/components/sheet/Sheet.stories.tsx +671 -950
- package/dist/components/skelton/Skelton.stories.tsx +230 -764
- package/dist/components/slider/Slider.stories.tsx +383 -760
- package/dist/components/stepper/Stepper.stories.tsx +371 -514
- package/dist/components/switch/Switch.stories.tsx +461 -208
- package/dist/components/switch-case/SwitchCase.stories.tsx +367 -188
- package/dist/components/table/Table.stories.tsx +770 -916
- package/dist/components/tabs/Tabs.stories.tsx +458 -1455
- package/dist/components/tag/Tag.stories.tsx +714 -542
- package/dist/components/textarea/TextArea.stories.tsx +621 -562
- package/dist/components/thumbnail-tags/ThumbnailTags.stories.tsx +228 -154
- package/dist/components/toast/Toast.stories.tsx +452 -1339
- package/dist/components/toggle/Toggle.stories.tsx +488 -931
- package/dist/components/tooltip/Tooltip.stories.tsx +344 -1388
- package/dist/components/typography/Typography.stories.tsx +406 -89
- package/dist/hooks/use-change-state/UseChangeState.stories.tsx +309 -606
- package/dist/hooks/use-previous/UsePrevious.stories.tsx +367 -917
- package/dist/hooks/use-standalone-pagination/UseStandalonePagination.stories.tsx +639 -867
- package/dist/icons/Icons.stories.tsx +0 -12
- package/dist/icons/ai-avatar-icon/AiAvatarIcon.stories.tsx +223 -1060
- package/dist/icons/alert-icon/AlertIcon.stories.tsx +106 -968
- package/dist/icons/all-icons.tsx +37 -16
- package/dist/icons/angle-down-icon/AngleDownIcon.stories.tsx +137 -1010
- package/dist/icons/apple-logo-icon/AppleLogoIcon.stories.tsx +145 -935
- package/dist/icons/arrow-box-left-icon/ArrowBoxLeftIcon.stories.tsx +132 -1046
- package/dist/icons/arrow-corner-up-left-icon/ArrowCornerUpLeftIcon.stories.tsx +134 -986
- package/dist/icons/arrow-corner-up-right-icon/ArrowCornerUpRightIcon.stories.tsx +135 -1028
- package/dist/icons/arrow-left-icon/ArrowLeftIcon.stories.tsx +133 -971
- package/dist/icons/arrow-right-icon/ArrowRightIcon.stories.tsx +145 -1123
- package/dist/icons/arrow-right-up-icon/ArrowRightUpIcon.stories.tsx +143 -1252
- package/dist/icons/art-board-icon/ArtBoardIcon.stories.tsx +123 -632
- package/dist/icons/audio-bar-icon/AudioBarIcon.stories.tsx +141 -1223
- package/dist/icons/backward-ten-seconds-icon/BackwardTenSecondsIcon.stories.tsx +164 -1018
- package/dist/icons/bubble-check-icon/BubbleCheckIcon.stories.tsx +121 -1236
- package/dist/icons/bubble-crossed-icon/BubbleCrossedIcon.stories.tsx +121 -1213
- package/dist/icons/bubble-sparkle-icon/BubbleSparkleIcon.stories.tsx +116 -893
- package/dist/icons/camera-icon/CameraIcon.stories.tsx +109 -1254
- package/dist/icons/capital-a-letter-icon/CapitalALetterIcon.stories.tsx +114 -975
- package/dist/icons/chevron-double-left-icon/ChevronDoubleLeftIcon.stories.tsx +157 -994
- package/dist/icons/chevron-double-right-icon/ChevronDoubleRightIcon.stories.tsx +160 -992
- package/dist/icons/chevron-down-icon/ChevronDownIcon.stories.tsx +140 -970
- package/dist/icons/chevron-left-icon/ChevronLeftIcon.stories.tsx +126 -993
- package/dist/icons/chevron-right-icon/ChevronRightIcon.stories.tsx +144 -987
- package/dist/icons/chevron-up-icon/ChevronUpIcon.stories.tsx +141 -1007
- package/dist/icons/circle-tick-icon/CircleTickIcon.stories.tsx +147 -1187
- package/dist/icons/circular-play-icon/CircularPlayIcon.stories.tsx +110 -476
- package/dist/icons/coin-icon/CoinIcon.stories.tsx +120 -1364
- package/dist/icons/coin-toons-icon/CoinToonsIcon.stories.tsx +113 -1360
- package/dist/icons/column-wide-add-icon/ColumnWideAddIcon.stories.tsx +111 -942
- package/dist/icons/command-icon/CommandIcon.stories.tsx +124 -1087
- package/dist/icons/copy-icon/CopyIcon.stories.tsx +119 -996
- package/dist/icons/cross-circle-icon/CrossCircleIcon.stories.tsx +144 -1046
- package/dist/icons/cross-icon/CrossIcon.stories.tsx +136 -999
- package/dist/icons/download-icon/DownloadIcon.stories.tsx +123 -857
- package/dist/icons/edit-big-icon/EditBigIcon.stories.tsx +121 -1080
- package/dist/icons/email-icon/EmailIcon.stories.tsx +112 -979
- package/dist/icons/expand-icon/ExpandIcon.stories.tsx +109 -1146
- package/dist/icons/eye-close-icon/EyeCloseIcon.stories.tsx +141 -1068
- package/dist/icons/eye-open-icon/EyeOpenIcon.stories.tsx +140 -1081
- package/dist/icons/feature-shine-icon/FeatureShineIcon.stories.tsx +124 -1050
- package/dist/icons/file-chart-icon/FileChartIcon.stories.tsx +123 -1091
- package/dist/icons/file-text-icon/FileTextIcon.stories.tsx +122 -633
- package/dist/icons/filter-bar-row-icon/FilterBarRowIcon.stories.tsx +116 -1087
- package/dist/icons/forward-ten-seconds-icon/ForwardTenSecondsIcon.stories.tsx +166 -1020
- package/dist/icons/git-branch-icon/GitBranchIcon.stories.tsx +112 -1182
- package/dist/icons/git-fork-icon/GitForkIcon.stories.tsx +112 -1155
- package/dist/icons/globe-icon/GlobeIcon.stories.tsx +127 -325
- package/dist/icons/google-logo-icon/GoogleLogoIcon.stories.tsx +142 -985
- package/dist/icons/grip-vertical-icon/GripVerticalIcon.stories.tsx +116 -1217
- package/dist/icons/head-icon/HeadIcon.stories.tsx +108 -953
- package/dist/icons/heart-icon/HeartIcon.stories.tsx +117 -1060
- package/dist/icons/image-avatar-sparkle-icon/ImageAvatarSparkleIcon.stories.tsx +116 -716
- package/dist/icons/image-icon/ImageIcon.stories.tsx +102 -1164
- package/dist/icons/import-folder-icon/ImportFolderIcon.stories.tsx +108 -1233
- package/dist/icons/import-left-arrow-folder-icon/ImportLeftArrowFolderIcon.stories.tsx +133 -1289
- package/dist/icons/indian-flag-icon/IndianFlagIcon.stories.tsx +155 -1012
- package/dist/icons/instagram-icon/InstagramIcon.stories.tsx +158 -1438
- package/dist/icons/layout-column-icon/LayoutColumnIcon.stories.tsx +121 -1011
- package/dist/icons/layout-left-icon/LayoutLeftIcon.stories.tsx +116 -981
- package/dist/icons/layout-right-icon/LayoutRightIcon.stories.tsx +116 -979
- package/dist/icons/light-bulb-simple-icon/LightBulbSimpleIcon.stories.tsx +105 -1252
- package/dist/icons/linked-in-icon/LinkedInIcon.stories.tsx +151 -1554
- package/dist/icons/magic-book-icon/MagicBookIcon.stories.tsx +107 -1227
- package/dist/icons/magic-edit-icon/MagicEditIcon.stories.tsx +116 -707
- package/dist/icons/maintenance-icon/MaintenanceIcon.stories.tsx +119 -1226
- package/dist/icons/message-icon/MessageIcon.stories.tsx +111 -557
- package/dist/icons/minimize-icon/MinimizeIcon.stories.tsx +112 -1198
- package/dist/icons/moon-icon/MoonIcon.stories.tsx +117 -557
- package/dist/icons/move-horizontal-icon/MoveHorizontalIcon.stories.tsx +106 -1235
- package/dist/icons/move-vertical-icon/MoveVerticalIcon.stories.tsx +112 -1185
- package/dist/icons/musical-note-icon/MusicalNoteIcon.stories.tsx +116 -1012
- package/dist/icons/notepad-icon/NotepadIcon.stories.tsx +108 -1137
- package/dist/icons/notes-icon/NotesIcon.stories.tsx +116 -1138
- package/dist/icons/page-search-icon/PageSearchIcon.stories.tsx +106 -1146
- package/dist/icons/page-text-icon/PageTextIcon.stories.tsx +119 -719
- package/dist/icons/paint-roll-icon/PaintRollIcon.stories.tsx +110 -999
- package/dist/icons/paper-plane-icon/PaperPlaneIcon.stories.tsx +109 -912
- package/dist/icons/pause-icon/PauseIcon.stories.tsx +110 -1041
- package/dist/icons/pencil-icon/PencilIcon.stories.tsx +112 -1109
- package/dist/icons/phone-icon/PhoneIcon.stories.tsx +112 -1023
- package/dist/icons/plus-icon/PlusIcon.stories.tsx +103 -1132
- package/dist/icons/pocket-studio-icon/PocketStudioIcon.stories.tsx +104 -870
- package/dist/icons/scroll-down-icon/ScrollDownIcon.stories.tsx +99 -476
- package/dist/icons/search-icon/SearchIcon.stories.tsx +108 -1161
- package/dist/icons/setting-icon/SettingIcon.stories.tsx +104 -1009
- package/dist/icons/share-icon/ShareIcon.stories.tsx +117 -1064
- package/dist/icons/shield-icon/ShieldIcon.stories.tsx +114 -974
- package/dist/icons/site-logo-icon/SiteLogoIcon.stories.tsx +134 -1160
- package/dist/icons/skip-backward-icon/SkipBackwardIcon.stories.tsx +169 -1017
- package/dist/icons/skip-forward-icon/SkipForwardIcon.stories.tsx +161 -1016
- package/dist/icons/sparkles-soft-icon/SparklesSoftIcon.stories.tsx +102 -1001
- package/dist/icons/spinner-gradient-icon/SpinnerGradientIcon.stories.tsx +155 -593
- package/dist/icons/spinner-solid-icon/SpinnerSolidIcon.stories.tsx +155 -608
- package/dist/icons/spinner-solid-neutral-icon/SpinnerSolidINeutralcon.stories.tsx +142 -712
- package/dist/icons/star-icon/StarIcon.stories.tsx +120 -946
- package/dist/icons/store-coin-icon/StoreCoinIcon.stories.tsx +109 -1013
- package/dist/icons/suggestion-icon/SuggestionIcon.stories.tsx +113 -891
- package/dist/icons/sun-icon/SunIcon.stories.tsx +117 -864
- package/dist/icons/text-color-icon/TextColorIcon.stories.tsx +113 -989
- package/dist/icons/text-indicator-icon/TextIndicatorIcon.stories.tsx +120 -1027
- package/dist/icons/threads-icon/ThreadsIcon.stories.tsx +153 -1476
- package/dist/icons/tick-circle-icon/TickCircleIcon.stories.tsx +143 -1187
- package/dist/icons/tick-icon/TickIcon.stories.tsx +142 -1322
- package/dist/icons/trash-icon/TrashIcon.stories.tsx +105 -970
- package/dist/icons/twitter-x-icon/TwitterXIcon.stories.tsx +154 -1457
- package/dist/icons/upload-icon/UploadIcon.stories.tsx +112 -930
- package/dist/icons/vertical-menu-icon/VerticalMenuIcon.stories.tsx +115 -1019
- package/dist/icons/video-play-list-icon/VideoPlaylistIcon.stories.tsx +122 -1092
- package/dist/icons/voice-playing-icon/VoicePlayingIcon.stories.tsx +120 -1401
- package/dist/icons/volume-full-icon/VolumeFullIcon.stories.tsx +107 -1212
- package/dist/icons/volume-half-icon/VolumeHalfIcon.stories.tsx +109 -1122
- package/dist/icons/volume-off-icon/VolumeOffIcon.stories.tsx +112 -1124
- package/dist/icons/warning-icon/WarningIcon.stories.tsx +119 -1083
- package/dist/icons/youtube-icon/YoutubeIcon.stories.tsx +158 -983
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -15,89 +15,38 @@ import {
|
|
|
15
15
|
} from "@/ui/components/select"
|
|
16
16
|
import type { Meta, StoryObj } from "@storybook/react-vite"
|
|
17
17
|
|
|
18
|
-
import {
|
|
18
|
+
import { AuralComponentDocsPage } from "src/ui/story-spec/components/component-story-docs-page"
|
|
19
|
+
|
|
20
|
+
// ─── Meta ─────────────────────────────────────────────────────────────────────
|
|
19
21
|
|
|
20
22
|
const meta: Meta<typeof SelectField> = {
|
|
21
23
|
title: "Components/UI/Select",
|
|
22
24
|
component: SelectField,
|
|
23
25
|
parameters: {
|
|
24
26
|
layout: "centered",
|
|
25
|
-
backgrounds: {
|
|
26
|
-
default: "dark",
|
|
27
|
-
values: [
|
|
28
|
-
{ name: "dark", value: "#0a0a0a" },
|
|
29
|
-
{ name: "light", value: "#ffffff" },
|
|
30
|
-
],
|
|
31
|
-
},
|
|
32
27
|
docs: {
|
|
33
28
|
description: {
|
|
34
|
-
component:
|
|
35
|
-
A
|
|
36
|
-
Provides a dropdown selection interface with support for multiple styling variants, decorations,
|
|
37
|
-
required fields, helper text, grouping, separators, scroll buttons, and comprehensive keyboard navigation.
|
|
38
|
-
|
|
39
|
-
## Features
|
|
40
|
-
- Built on Radix UI for accessibility
|
|
41
|
-
- Multiple styling variants (default, error, warning, success)
|
|
42
|
-
- Multiple decorations (underline, outline, filled)
|
|
43
|
-
- Required field support with visual indicators
|
|
44
|
-
- Helper text support with variant styling
|
|
45
|
-
- Support for grouped options
|
|
46
|
-
- Scroll buttons for long lists
|
|
47
|
-
- Keyboard navigation
|
|
48
|
-
- Disabled states
|
|
49
|
-
- Atomic design composition
|
|
50
|
-
- Unstyled mode for custom styling
|
|
51
|
-
- CVA-based styling system
|
|
52
|
-
- Custom icons (ChevronDown, ChevronUp, Tick)
|
|
53
|
-
- Full accessibility support (ARIA attributes)
|
|
54
|
-
|
|
55
|
-
## Usage
|
|
56
|
-
|
|
57
|
-
### Simple Usage with SelectField
|
|
58
|
-
\`\`\`tsx
|
|
59
|
-
import { SelectField, SelectItem } from '@/ui/components/select'
|
|
60
|
-
|
|
61
|
-
<SelectField
|
|
62
|
-
label="Country"
|
|
63
|
-
placeholder="Select a country..."
|
|
64
|
-
required
|
|
65
|
-
helperText="Please select your country"
|
|
66
|
-
>
|
|
67
|
-
<SelectItem value="us">United States</SelectItem>
|
|
68
|
-
<SelectItem value="ca">Canada</SelectItem>
|
|
69
|
-
</SelectField>
|
|
70
|
-
\`\`\`
|
|
71
|
-
|
|
72
|
-
### Atomic Composition
|
|
73
|
-
\`\`\`tsx
|
|
74
|
-
import {
|
|
75
|
-
Select,
|
|
76
|
-
SelectContent,
|
|
77
|
-
SelectItem,
|
|
78
|
-
SelectRoot,
|
|
79
|
-
SelectTrigger,
|
|
80
|
-
SelectValue,
|
|
81
|
-
SelectWrapper,
|
|
82
|
-
} from '@/ui/components/select'
|
|
83
|
-
|
|
84
|
-
<SelectRoot fullWidth>
|
|
85
|
-
<SelectLabel required>Country</SelectLabel>
|
|
86
|
-
<SelectWrapper>
|
|
87
|
-
<Select>
|
|
88
|
-
<SelectTrigger>
|
|
89
|
-
<SelectValue placeholder="Select..." />
|
|
90
|
-
</SelectTrigger>
|
|
91
|
-
<SelectContent>
|
|
92
|
-
<SelectItem value="us">United States</SelectItem>
|
|
93
|
-
</SelectContent>
|
|
94
|
-
</Select>
|
|
95
|
-
</SelectWrapper>
|
|
96
|
-
<SelectHelperText>Please select your country</SelectHelperText>
|
|
97
|
-
</SelectRoot>
|
|
98
|
-
\`\`\`
|
|
99
|
-
`,
|
|
29
|
+
component:
|
|
30
|
+
"A customisable select built on Radix UI primitives with atomic composition support. Provides a dropdown selection interface with validation variants (default, error, warning, success), three decoration styles (underline, outline, filled), label, helper text, grouping, separators, scroll buttons, and full keyboard navigation and ARIA accessibility.",
|
|
100
31
|
},
|
|
32
|
+
page: () => (
|
|
33
|
+
<AuralComponentDocsPage
|
|
34
|
+
features={[
|
|
35
|
+
{
|
|
36
|
+
title: "4 Validation States",
|
|
37
|
+
description: "Default to success",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
title: "3 Decoration Styles",
|
|
41
|
+
description: "Underline, outline, filled",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
title: "Groups & Separators",
|
|
45
|
+
description: "Organised option lists",
|
|
46
|
+
},
|
|
47
|
+
]}
|
|
48
|
+
/>
|
|
49
|
+
),
|
|
101
50
|
},
|
|
102
51
|
},
|
|
103
52
|
tags: ["autodocs"],
|
|
@@ -105,108 +54,36 @@ import {
|
|
|
105
54
|
label: {
|
|
106
55
|
control: { type: "text" },
|
|
107
56
|
description: "Label text for the select",
|
|
108
|
-
table: {
|
|
109
|
-
type: { summary: "ReactNode" },
|
|
110
|
-
defaultValue: { summary: "undefined" },
|
|
111
|
-
},
|
|
112
57
|
},
|
|
113
58
|
placeholder: {
|
|
114
59
|
control: { type: "text" },
|
|
115
|
-
description: "Placeholder
|
|
116
|
-
table: {
|
|
117
|
-
type: { summary: "string" },
|
|
118
|
-
defaultValue: { summary: "undefined" },
|
|
119
|
-
},
|
|
60
|
+
description: "Placeholder shown when no option is selected",
|
|
120
61
|
},
|
|
121
62
|
helperText: {
|
|
122
63
|
control: { type: "text" },
|
|
123
64
|
description: "Helper text displayed below the select",
|
|
124
|
-
table: {
|
|
125
|
-
type: { summary: "ReactNode" },
|
|
126
|
-
defaultValue: { summary: "undefined" },
|
|
127
|
-
},
|
|
128
65
|
},
|
|
129
66
|
variant: {
|
|
130
67
|
control: { type: "select" },
|
|
131
68
|
options: ["default", "error", "warning", "success"],
|
|
132
|
-
description: "Visual
|
|
133
|
-
table: {
|
|
134
|
-
type: { summary: '"default" | "error" | "warning" | "success"' },
|
|
135
|
-
defaultValue: { summary: '"default"' },
|
|
136
|
-
},
|
|
69
|
+
description: "Visual validation state",
|
|
137
70
|
},
|
|
138
71
|
decoration: {
|
|
139
72
|
control: { type: "select" },
|
|
140
73
|
options: ["underline", "outline", "filled"],
|
|
141
|
-
description: "
|
|
142
|
-
table: {
|
|
143
|
-
type: { summary: '"underline" | "outline" | "filled"' },
|
|
144
|
-
defaultValue: { summary: '"underline"' },
|
|
145
|
-
},
|
|
74
|
+
description: "Border and background style",
|
|
146
75
|
},
|
|
147
76
|
required: {
|
|
148
77
|
control: { type: "boolean" },
|
|
149
|
-
description: "
|
|
150
|
-
table: {
|
|
151
|
-
type: { summary: "boolean" },
|
|
152
|
-
defaultValue: { summary: "false" },
|
|
153
|
-
},
|
|
78
|
+
description: "Adds an asterisk to the label and aria-required",
|
|
154
79
|
},
|
|
155
80
|
disabled: {
|
|
156
81
|
control: { type: "boolean" },
|
|
157
|
-
description: "
|
|
158
|
-
table: {
|
|
159
|
-
type: { summary: "boolean" },
|
|
160
|
-
defaultValue: { summary: "false" },
|
|
161
|
-
},
|
|
82
|
+
description: "Disables the select entirely",
|
|
162
83
|
},
|
|
163
84
|
fullWidth: {
|
|
164
85
|
control: { type: "boolean" },
|
|
165
|
-
description: "
|
|
166
|
-
table: {
|
|
167
|
-
type: { summary: "boolean" },
|
|
168
|
-
defaultValue: { summary: "false" },
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
value: {
|
|
172
|
-
control: { type: "text" },
|
|
173
|
-
description: "Controlled value of the select",
|
|
174
|
-
table: {
|
|
175
|
-
type: { summary: "string" },
|
|
176
|
-
defaultValue: { summary: "undefined" },
|
|
177
|
-
},
|
|
178
|
-
},
|
|
179
|
-
onValueChange: {
|
|
180
|
-
action: "valueChanged",
|
|
181
|
-
description: "Callback fired when the value changes",
|
|
182
|
-
table: {
|
|
183
|
-
type: { summary: "(value: string) => void" },
|
|
184
|
-
},
|
|
185
|
-
},
|
|
186
|
-
classes: {
|
|
187
|
-
control: { type: "object" },
|
|
188
|
-
description: "Override classes for different parts of the component",
|
|
189
|
-
table: {
|
|
190
|
-
type: {
|
|
191
|
-
summary:
|
|
192
|
-
"{ root?: string; label?: string; wrapper?: string; trigger?: string; content?: string; helperText?: string }",
|
|
193
|
-
},
|
|
194
|
-
defaultValue: { summary: "{}" },
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
name: {
|
|
198
|
-
control: "text",
|
|
199
|
-
description: "The name attribute for form submission",
|
|
200
|
-
table: {
|
|
201
|
-
type: { summary: "string" },
|
|
202
|
-
},
|
|
203
|
-
},
|
|
204
|
-
id: {
|
|
205
|
-
control: "text",
|
|
206
|
-
description: "The id attribute for the select",
|
|
207
|
-
table: {
|
|
208
|
-
type: { summary: "string" },
|
|
209
|
-
},
|
|
86
|
+
description: "Expands the select to fill its container",
|
|
210
87
|
},
|
|
211
88
|
},
|
|
212
89
|
args: {
|
|
@@ -215,757 +92,775 @@ import {
|
|
|
215
92
|
required: false,
|
|
216
93
|
disabled: false,
|
|
217
94
|
fullWidth: false,
|
|
95
|
+
label: "Label",
|
|
96
|
+
placeholder: "Select an option…",
|
|
218
97
|
},
|
|
219
98
|
}
|
|
220
99
|
|
|
221
100
|
export default meta
|
|
222
101
|
type Story = StoryObj<typeof meta>
|
|
223
102
|
|
|
224
|
-
|
|
225
|
-
args: {
|
|
226
|
-
label: "Fruit Selection",
|
|
227
|
-
placeholder: "Select a fruit",
|
|
228
|
-
decoration: "underline",
|
|
229
|
-
fullWidth: true,
|
|
230
|
-
},
|
|
231
|
-
render: (args) => (
|
|
232
|
-
<SelectField {...args}>
|
|
233
|
-
<SelectItem value="apple">Apple</SelectItem>
|
|
234
|
-
<SelectItem value="banana">Banana</SelectItem>
|
|
235
|
-
<SelectItem value="orange">Orange</SelectItem>
|
|
236
|
-
<SelectItem value="grape">Grape</SelectItem>
|
|
237
|
-
<SelectItem value="strawberry">Strawberry</SelectItem>
|
|
238
|
-
</SelectField>
|
|
239
|
-
),
|
|
240
|
-
}
|
|
103
|
+
// ─── 1. Playground ───────────────────────────────────────────────────────────
|
|
241
104
|
|
|
242
|
-
|
|
243
|
-
export const DecorationVariants: Story = {
|
|
244
|
-
render: () => (
|
|
245
|
-
<div className="max-w-md space-y-6">
|
|
246
|
-
<div>
|
|
247
|
-
<h3 className="text-fm-primary mb-2 text-sm font-medium">Underline</h3>
|
|
248
|
-
<SelectField
|
|
249
|
-
label="Underline Style"
|
|
250
|
-
placeholder="Minimalist underline style..."
|
|
251
|
-
decoration="underline"
|
|
252
|
-
fullWidth
|
|
253
|
-
>
|
|
254
|
-
<SelectItem value="option1">Option 1</SelectItem>
|
|
255
|
-
<SelectItem value="option2">Option 2</SelectItem>
|
|
256
|
-
<SelectItem value="option3">Option 3</SelectItem>
|
|
257
|
-
</SelectField>
|
|
258
|
-
</div>
|
|
259
|
-
|
|
260
|
-
<div>
|
|
261
|
-
<h3 className="text-fm-primary mb-2 text-sm font-medium">Outline</h3>
|
|
262
|
-
<SelectField
|
|
263
|
-
label="Outline Style"
|
|
264
|
-
placeholder="Traditional outlined style..."
|
|
265
|
-
decoration="outline"
|
|
266
|
-
fullWidth
|
|
267
|
-
>
|
|
268
|
-
<SelectItem value="option1">Option 1</SelectItem>
|
|
269
|
-
<SelectItem value="option2">Option 2</SelectItem>
|
|
270
|
-
<SelectItem value="option3">Option 3</SelectItem>
|
|
271
|
-
</SelectField>
|
|
272
|
-
</div>
|
|
273
|
-
|
|
274
|
-
<div>
|
|
275
|
-
<h3 className="text-fm-primary mb-2 text-sm font-medium">Filled</h3>
|
|
276
|
-
<SelectField
|
|
277
|
-
label="Filled Style"
|
|
278
|
-
placeholder="Modern filled style..."
|
|
279
|
-
decoration="filled"
|
|
280
|
-
fullWidth
|
|
281
|
-
>
|
|
282
|
-
<SelectItem value="option1">Option 1</SelectItem>
|
|
283
|
-
<SelectItem value="option2">Option 2</SelectItem>
|
|
284
|
-
<SelectItem value="option3">Option 3</SelectItem>
|
|
285
|
-
</SelectField>
|
|
286
|
-
</div>
|
|
287
|
-
</div>
|
|
288
|
-
),
|
|
105
|
+
export const Playground: Story = {
|
|
289
106
|
parameters: {
|
|
290
107
|
docs: {
|
|
291
108
|
description: {
|
|
292
109
|
story:
|
|
293
|
-
"
|
|
110
|
+
"Use the Storybook controls panel to configure every prop. The select below reflects your selections live.",
|
|
294
111
|
},
|
|
295
112
|
},
|
|
296
113
|
},
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
export const WithRequiredField: Story = {
|
|
300
|
-
args: {
|
|
301
|
-
label: "Required Selection",
|
|
302
|
-
placeholder: "Please select an option",
|
|
303
|
-
required: true,
|
|
304
|
-
helperText: "This field is required",
|
|
305
|
-
decoration: "outline",
|
|
306
|
-
fullWidth: true,
|
|
307
|
-
},
|
|
308
114
|
render: (args) => (
|
|
309
|
-
<
|
|
310
|
-
<
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
115
|
+
<div className="w-80 space-y-4">
|
|
116
|
+
<SelectField {...args} fullWidth>
|
|
117
|
+
<SelectItem value="option1">Option 1</SelectItem>
|
|
118
|
+
<SelectItem value="option2">Option 2</SelectItem>
|
|
119
|
+
<SelectItem value="option3">Option 3</SelectItem>
|
|
120
|
+
<SelectItem value="option4">Option 4</SelectItem>
|
|
121
|
+
</SelectField>
|
|
122
|
+
<div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border px-4 py-3">
|
|
123
|
+
<code className="text-fm-secondary text-fm-md leading-fm-md font-(--font-fm-mono)">
|
|
124
|
+
{`<SelectField variant="${args.variant}" decoration="${args.decoration}"${args.required ? " required" : ""}${args.disabled ? " disabled" : ""} />`}
|
|
125
|
+
</code>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
314
128
|
),
|
|
315
|
-
parameters: {
|
|
316
|
-
docs: {
|
|
317
|
-
description: {
|
|
318
|
-
story: "Select field with required indicator (asterisk) in the label.",
|
|
319
|
-
},
|
|
320
|
-
},
|
|
321
|
-
},
|
|
322
129
|
}
|
|
323
130
|
|
|
324
|
-
|
|
131
|
+
// ─── 2. AllVariants ──────────────────────────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
export const AllVariants: Story = {
|
|
325
134
|
parameters: {
|
|
326
135
|
docs: {
|
|
327
136
|
description: {
|
|
328
|
-
story:
|
|
329
|
-
|
|
330
|
-
to communicate different states or purposes:
|
|
331
|
-
- **Default**: Standard styling
|
|
332
|
-
- **Error**: Red styling for error states
|
|
333
|
-
- **Warning**: Yellow/orange styling for warnings
|
|
334
|
-
- **Success**: Green styling for success states
|
|
335
|
-
`,
|
|
137
|
+
story:
|
|
138
|
+
"Full matrix of all four validation variants (default, error, warning, success) across all three decoration styles (underline, outline, filled).",
|
|
336
139
|
},
|
|
337
140
|
},
|
|
338
141
|
},
|
|
339
|
-
render: () =>
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
variant
|
|
358
|
-
|
|
359
|
-
helperText
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
<SelectField
|
|
368
|
-
label="Warning Variant"
|
|
369
|
-
placeholder="Select an option..."
|
|
370
|
-
variant="warning"
|
|
371
|
-
decoration="outline"
|
|
372
|
-
helperText="Please double-check your selection"
|
|
373
|
-
fullWidth
|
|
374
|
-
>
|
|
375
|
-
<SelectItem value="option1">Option 1</SelectItem>
|
|
376
|
-
<SelectItem value="option2">Option 2</SelectItem>
|
|
377
|
-
<SelectItem value="option3">Option 3</SelectItem>
|
|
378
|
-
</SelectField>
|
|
379
|
-
|
|
380
|
-
<SelectField
|
|
381
|
-
label="Success Variant"
|
|
382
|
-
placeholder="Select an option..."
|
|
383
|
-
variant="success"
|
|
384
|
-
decoration="outline"
|
|
385
|
-
helperText="Great choice!"
|
|
386
|
-
fullWidth
|
|
387
|
-
>
|
|
388
|
-
<SelectItem value="option1">Option 1</SelectItem>
|
|
389
|
-
<SelectItem value="option2">Option 2</SelectItem>
|
|
390
|
-
<SelectItem value="option3">Option 3</SelectItem>
|
|
391
|
-
</SelectField>
|
|
392
|
-
</div>
|
|
393
|
-
),
|
|
394
|
-
}
|
|
142
|
+
render: () => {
|
|
143
|
+
const variants = [
|
|
144
|
+
{
|
|
145
|
+
variant: "default" as const,
|
|
146
|
+
label: "Default",
|
|
147
|
+
helperText: "Helper text",
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
variant: "error" as const,
|
|
151
|
+
label: "Error",
|
|
152
|
+
helperText: "Something went wrong",
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
variant: "warning" as const,
|
|
156
|
+
label: "Warning",
|
|
157
|
+
helperText: "Double-check your selection",
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
variant: "success" as const,
|
|
161
|
+
label: "Success",
|
|
162
|
+
helperText: "Great choice!",
|
|
163
|
+
},
|
|
164
|
+
]
|
|
165
|
+
const decorations = [
|
|
166
|
+
{ decoration: "underline" as const, label: "Underline" },
|
|
167
|
+
{ decoration: "outline" as const, label: "Outline" },
|
|
168
|
+
{ decoration: "filled" as const, label: "Filled" },
|
|
169
|
+
]
|
|
395
170
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
171
|
+
return (
|
|
172
|
+
<div className="space-y-10">
|
|
173
|
+
{decorations.map(({ decoration, label: decLabel }) => (
|
|
174
|
+
<div key={decoration}>
|
|
175
|
+
<h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md mb-4 font-medium">
|
|
176
|
+
{decLabel}
|
|
177
|
+
</h4>
|
|
178
|
+
<div className="flex flex-wrap gap-6">
|
|
179
|
+
{variants.map(({ variant, label, helperText }) => (
|
|
180
|
+
<div key={variant} className="w-48 space-y-2 text-center">
|
|
181
|
+
<SelectField
|
|
182
|
+
label={label}
|
|
183
|
+
placeholder="Select…"
|
|
184
|
+
variant={variant}
|
|
185
|
+
decoration={decoration}
|
|
186
|
+
helperText={helperText}
|
|
187
|
+
fullWidth
|
|
188
|
+
>
|
|
189
|
+
<SelectItem value="opt1">Option 1</SelectItem>
|
|
190
|
+
<SelectItem value="opt2">Option 2</SelectItem>
|
|
191
|
+
<SelectItem value="opt3">Option 3</SelectItem>
|
|
192
|
+
</SelectField>
|
|
193
|
+
<p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
|
|
194
|
+
{label}
|
|
195
|
+
</p>
|
|
196
|
+
</div>
|
|
197
|
+
))}
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
))}
|
|
201
|
+
</div>
|
|
202
|
+
)
|
|
403
203
|
},
|
|
404
|
-
render: (args) => (
|
|
405
|
-
<SelectField {...args}>
|
|
406
|
-
<SelectItem value="react">React</SelectItem>
|
|
407
|
-
<SelectItem value="vue">Vue.js</SelectItem>
|
|
408
|
-
<SelectItem value="angular">Angular</SelectItem>
|
|
409
|
-
<SelectItem value="svelte">Svelte</SelectItem>
|
|
410
|
-
</SelectField>
|
|
411
|
-
),
|
|
412
204
|
}
|
|
413
205
|
|
|
414
|
-
|
|
206
|
+
// ─── 3. Configurations ───────────────────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
export const Configurations: Story = {
|
|
415
209
|
parameters: {
|
|
416
210
|
docs: {
|
|
417
211
|
description: {
|
|
418
|
-
story:
|
|
419
|
-
|
|
420
|
-
Use this pattern when you need to control the select value programmatically.
|
|
421
|
-
`,
|
|
212
|
+
story:
|
|
213
|
+
"Non-variant configuration axes: required field indicator, disabled whole select, individual disabled options, and grouped options with separators.",
|
|
422
214
|
},
|
|
423
215
|
},
|
|
424
216
|
},
|
|
425
|
-
render:
|
|
426
|
-
|
|
217
|
+
render: () => (
|
|
218
|
+
<div className="w-full max-w-sm space-y-10">
|
|
219
|
+
{/* Required */}
|
|
220
|
+
<div>
|
|
221
|
+
<h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md mb-4 font-medium">
|
|
222
|
+
Required
|
|
223
|
+
</h4>
|
|
224
|
+
<div className="space-y-2 text-center">
|
|
225
|
+
<SelectField
|
|
226
|
+
label="Country"
|
|
227
|
+
placeholder="Select your country…"
|
|
228
|
+
required
|
|
229
|
+
helperText="This field is required"
|
|
230
|
+
decoration="outline"
|
|
231
|
+
fullWidth
|
|
232
|
+
>
|
|
233
|
+
<SelectItem value="us">United States</SelectItem>
|
|
234
|
+
<SelectItem value="ca">Canada</SelectItem>
|
|
235
|
+
<SelectItem value="uk">United Kingdom</SelectItem>
|
|
236
|
+
</SelectField>
|
|
237
|
+
<p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
|
|
238
|
+
required — asterisk added to label
|
|
239
|
+
</p>
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
427
242
|
|
|
428
|
-
|
|
429
|
-
<div
|
|
430
|
-
<
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
243
|
+
{/* Disabled whole select */}
|
|
244
|
+
<div>
|
|
245
|
+
<h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md mb-4 font-medium">
|
|
246
|
+
Disabled
|
|
247
|
+
</h4>
|
|
248
|
+
<div className="space-y-2 text-center">
|
|
249
|
+
<SelectField
|
|
250
|
+
label="Region"
|
|
251
|
+
placeholder="Not available"
|
|
252
|
+
disabled
|
|
253
|
+
helperText="This option is not available"
|
|
254
|
+
decoration="outline"
|
|
255
|
+
fullWidth
|
|
256
|
+
>
|
|
257
|
+
<SelectItem value="r1">Region 1</SelectItem>
|
|
258
|
+
<SelectItem value="r2">Region 2</SelectItem>
|
|
259
|
+
</SelectField>
|
|
260
|
+
<p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
|
|
261
|
+
disabled — entire select is non-interactive
|
|
436
262
|
</p>
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
|
|
266
|
+
{/* Disabled items */}
|
|
267
|
+
<div>
|
|
268
|
+
<h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md mb-4 font-medium">
|
|
269
|
+
Disabled Items
|
|
270
|
+
</h4>
|
|
271
|
+
<div className="space-y-2 text-center">
|
|
437
272
|
<SelectField
|
|
438
|
-
label="
|
|
439
|
-
placeholder="
|
|
440
|
-
|
|
441
|
-
onValueChange={setValue}
|
|
442
|
-
helperText="This select is controlled by React state"
|
|
273
|
+
label="Plan"
|
|
274
|
+
placeholder="Choose a plan…"
|
|
275
|
+
helperText="Some plans are not available in your region"
|
|
443
276
|
decoration="outline"
|
|
444
277
|
fullWidth
|
|
445
278
|
>
|
|
446
|
-
<SelectItem value="
|
|
447
|
-
<SelectItem value="
|
|
448
|
-
<SelectItem value="
|
|
449
|
-
|
|
450
|
-
|
|
279
|
+
<SelectItem value="starter">Starter — Free</SelectItem>
|
|
280
|
+
<SelectItem value="pro">Pro — $9/mo</SelectItem>
|
|
281
|
+
<SelectItem value="enterprise" disabled>
|
|
282
|
+
Enterprise — Contact sales
|
|
283
|
+
</SelectItem>
|
|
451
284
|
</SelectField>
|
|
285
|
+
<p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
|
|
286
|
+
disabled items — individual options unavailable
|
|
287
|
+
</p>
|
|
452
288
|
</div>
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
289
|
+
</div>
|
|
290
|
+
|
|
291
|
+
{/* Grouped options */}
|
|
292
|
+
<div>
|
|
293
|
+
<h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md mb-4 font-medium">
|
|
294
|
+
Grouped Options
|
|
295
|
+
</h4>
|
|
296
|
+
<div className="space-y-2 text-center">
|
|
297
|
+
<SelectField
|
|
298
|
+
label="Technology"
|
|
299
|
+
placeholder="Choose a technology…"
|
|
300
|
+
helperText="Frontend and backend options"
|
|
301
|
+
decoration="outline"
|
|
302
|
+
fullWidth
|
|
303
|
+
>
|
|
304
|
+
<SelectGroup>
|
|
305
|
+
<SelectLabel className="px-2 pt-2 pb-1">Frontend</SelectLabel>
|
|
306
|
+
<SelectItem value="react">React</SelectItem>
|
|
307
|
+
<SelectSeparator />
|
|
308
|
+
<SelectItem value="vue">Vue</SelectItem>
|
|
309
|
+
<SelectSeparator />
|
|
310
|
+
<SelectItem value="svelte">Svelte</SelectItem>
|
|
311
|
+
</SelectGroup>
|
|
312
|
+
<SelectSeparator />
|
|
313
|
+
<SelectGroup>
|
|
314
|
+
<SelectLabel className="px-2 pt-2 pb-1">Backend</SelectLabel>
|
|
315
|
+
<SelectItem value="node">Node.js</SelectItem>
|
|
316
|
+
<SelectSeparator />
|
|
317
|
+
<SelectItem value="python">Python</SelectItem>
|
|
318
|
+
<SelectSeparator />
|
|
319
|
+
<SelectItem value="go">Go</SelectItem>
|
|
320
|
+
</SelectGroup>
|
|
321
|
+
</SelectField>
|
|
322
|
+
<p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
|
|
323
|
+
SelectGroup + SelectLabel + SelectSeparator
|
|
324
|
+
</p>
|
|
460
325
|
</div>
|
|
461
326
|
</div>
|
|
462
|
-
|
|
463
|
-
|
|
327
|
+
</div>
|
|
328
|
+
),
|
|
464
329
|
}
|
|
465
330
|
|
|
466
|
-
|
|
467
|
-
render: () => (
|
|
468
|
-
<div className="max-w-md space-y-6">
|
|
469
|
-
<SelectField
|
|
470
|
-
label="Disabled Select"
|
|
471
|
-
placeholder="This select is disabled"
|
|
472
|
-
disabled
|
|
473
|
-
helperText="This select cannot be interacted with"
|
|
474
|
-
decoration="outline"
|
|
475
|
-
fullWidth
|
|
476
|
-
>
|
|
477
|
-
<SelectItem value="option1">Option 1</SelectItem>
|
|
478
|
-
<SelectItem value="option2">Option 2</SelectItem>
|
|
479
|
-
</SelectField>
|
|
331
|
+
// ─── 4. States ───────────────────────────────────────────────────────────────
|
|
480
332
|
|
|
481
|
-
|
|
482
|
-
label="Select with Disabled Items"
|
|
483
|
-
placeholder="Some items are disabled"
|
|
484
|
-
helperText="Some options may be unavailable"
|
|
485
|
-
decoration="filled"
|
|
486
|
-
fullWidth
|
|
487
|
-
>
|
|
488
|
-
<SelectItem value="available1">Available Option 1</SelectItem>
|
|
489
|
-
<SelectItem value="disabled1" disabled>
|
|
490
|
-
Disabled Option 1
|
|
491
|
-
</SelectItem>
|
|
492
|
-
<SelectItem value="available2">Available Option 2</SelectItem>
|
|
493
|
-
<SelectItem value="disabled2" disabled>
|
|
494
|
-
Disabled Option 2
|
|
495
|
-
</SelectItem>
|
|
496
|
-
</SelectField>
|
|
497
|
-
</div>
|
|
498
|
-
),
|
|
333
|
+
export const States: Story = {
|
|
499
334
|
parameters: {
|
|
500
335
|
docs: {
|
|
501
336
|
description: {
|
|
502
|
-
story:
|
|
337
|
+
story:
|
|
338
|
+
"All five states side by side: default (idle), disabled, error (validation failure), warning (advisory), and success (validated).",
|
|
503
339
|
},
|
|
504
340
|
},
|
|
505
341
|
},
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
export const WithGroups: Story = {
|
|
509
342
|
render: () => (
|
|
510
|
-
<
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
343
|
+
<div className="w-full max-w-sm space-y-8">
|
|
344
|
+
{[
|
|
345
|
+
{
|
|
346
|
+
label: "Default",
|
|
347
|
+
props: {
|
|
348
|
+
label: "Default",
|
|
349
|
+
placeholder: "Select an option…",
|
|
350
|
+
variant: "default" as const,
|
|
351
|
+
helperText: "Helper text",
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
label: "Disabled",
|
|
356
|
+
props: {
|
|
357
|
+
label: "Disabled",
|
|
358
|
+
placeholder: "Not available",
|
|
359
|
+
variant: "default" as const,
|
|
360
|
+
disabled: true,
|
|
361
|
+
helperText: "This field cannot be changed",
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
label: "Error",
|
|
366
|
+
props: {
|
|
367
|
+
label: "Error",
|
|
368
|
+
placeholder: "Select an option…",
|
|
369
|
+
variant: "error" as const,
|
|
370
|
+
helperText: "Please select a valid option",
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
label: "Warning",
|
|
375
|
+
props: {
|
|
376
|
+
label: "Warning",
|
|
377
|
+
placeholder: "Select an option…",
|
|
378
|
+
variant: "warning" as const,
|
|
379
|
+
helperText: "Double-check your selection",
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
label: "Success",
|
|
384
|
+
props: {
|
|
385
|
+
label: "Success",
|
|
386
|
+
placeholder: "Select an option…",
|
|
387
|
+
variant: "success" as const,
|
|
388
|
+
helperText: "Looks good!",
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
].map(({ label, props }) => (
|
|
392
|
+
<div key={label}>
|
|
393
|
+
<h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md mb-3 font-medium">
|
|
394
|
+
{label}
|
|
395
|
+
</h4>
|
|
396
|
+
<SelectField decoration="outline" fullWidth {...props}>
|
|
397
|
+
<SelectItem value="opt1">Option 1</SelectItem>
|
|
398
|
+
<SelectItem value="opt2">Option 2</SelectItem>
|
|
399
|
+
<SelectItem value="opt3">Option 3</SelectItem>
|
|
400
|
+
</SelectField>
|
|
401
|
+
</div>
|
|
402
|
+
))}
|
|
403
|
+
</div>
|
|
535
404
|
),
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// ─── 5. Interactive ──────────────────────────────────────────────────────────
|
|
408
|
+
|
|
409
|
+
export const Interactive: Story = {
|
|
536
410
|
parameters: {
|
|
537
411
|
docs: {
|
|
538
412
|
description: {
|
|
539
|
-
story:
|
|
540
|
-
|
|
541
|
-
Useful for organizing related options into categories.
|
|
542
|
-
`,
|
|
413
|
+
story:
|
|
414
|
+
"Live controlled select demo. Use the preset buttons to jump between states, or pick values directly from the dropdowns. The variant and helper text update dynamically based on selection.",
|
|
543
415
|
},
|
|
544
416
|
},
|
|
545
417
|
},
|
|
546
|
-
|
|
418
|
+
render: function InteractiveSelect() {
|
|
419
|
+
const [country, setCountry] = React.useState("")
|
|
420
|
+
const [framework, setFramework] = React.useState("")
|
|
421
|
+
const [decoration, setDecoration] = React.useState<
|
|
422
|
+
"underline" | "outline" | "filled"
|
|
423
|
+
>("outline")
|
|
547
424
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
onSubmit={(e) => {
|
|
553
|
-
e.preventDefault()
|
|
554
|
-
const formData = new FormData(e.currentTarget)
|
|
555
|
-
alert(
|
|
556
|
-
`Selected values:\nFruit: ${formData.get("fruit")}\nColor: ${formData.get("color")}`
|
|
557
|
-
)
|
|
558
|
-
}}
|
|
559
|
-
>
|
|
560
|
-
<SelectField
|
|
561
|
-
label="Favorite Fruit"
|
|
562
|
-
placeholder="Select a fruit"
|
|
563
|
-
name="fruit"
|
|
564
|
-
required
|
|
565
|
-
helperText="Required field"
|
|
566
|
-
decoration="outline"
|
|
567
|
-
fullWidth
|
|
568
|
-
>
|
|
569
|
-
<SelectItem value="apple">Apple</SelectItem>
|
|
570
|
-
<SelectItem value="banana">Banana</SelectItem>
|
|
571
|
-
<SelectItem value="orange">Orange</SelectItem>
|
|
572
|
-
</SelectField>
|
|
425
|
+
const countryVariant = country ? "success" : "default"
|
|
426
|
+
const countryHelper = country
|
|
427
|
+
? "Country selected successfully"
|
|
428
|
+
: "Please select your country"
|
|
573
429
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
decoration="filled"
|
|
580
|
-
fullWidth
|
|
581
|
-
>
|
|
582
|
-
<SelectItem value="red">Red</SelectItem>
|
|
583
|
-
<SelectItem value="blue">Blue</SelectItem>
|
|
584
|
-
<SelectItem value="green">Green</SelectItem>
|
|
585
|
-
</SelectField>
|
|
430
|
+
const presets = [
|
|
431
|
+
{ label: "Select country", country: "us", framework: "" },
|
|
432
|
+
{ label: "Select both", country: "ca", framework: "react" },
|
|
433
|
+
{ label: "Clear all", country: "", framework: "" },
|
|
434
|
+
]
|
|
586
435
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
436
|
+
return (
|
|
437
|
+
<div className="w-full p-8">
|
|
438
|
+
<div className="mx-auto max-w-3xl space-y-6">
|
|
439
|
+
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
|
440
|
+
{/* Controls panel */}
|
|
441
|
+
<div className="border-fm-divider-secondary bg-fm-surface-secondary space-y-5 rounded-xl border p-5">
|
|
442
|
+
<p className="text-fm-primary font-fm-brand text-fm-sm leading-fm-sm font-semibold tracking-widest uppercase">
|
|
443
|
+
Presets
|
|
444
|
+
</p>
|
|
445
|
+
<div className="space-y-2">
|
|
446
|
+
{presets.map(({ label, country: c, framework: f }) => (
|
|
447
|
+
<button
|
|
448
|
+
key={label}
|
|
449
|
+
onClick={() => {
|
|
450
|
+
setCountry(c)
|
|
451
|
+
setFramework(f)
|
|
452
|
+
}}
|
|
453
|
+
className="border-fm-divider-secondary text-fm-primary font-fm-text text-fm-sm leading-fm-sm hover:bg-fm-surface-primary w-full rounded-lg border px-3 py-2 text-left transition-colors"
|
|
454
|
+
>
|
|
455
|
+
{label}
|
|
456
|
+
</button>
|
|
457
|
+
))}
|
|
458
|
+
</div>
|
|
459
|
+
<div className="border-fm-divider-secondary border-t pt-4" />
|
|
460
|
+
<p className="text-fm-primary font-fm-brand text-fm-sm leading-fm-sm font-semibold tracking-widest uppercase">
|
|
461
|
+
Decoration
|
|
462
|
+
</p>
|
|
463
|
+
<div className="space-y-2">
|
|
464
|
+
{(["underline", "outline", "filled"] as const).map((d) => (
|
|
465
|
+
<button
|
|
466
|
+
key={d}
|
|
467
|
+
onClick={() => setDecoration(d)}
|
|
468
|
+
className={`border-fm-divider-secondary text-fm-primary font-fm-text text-fm-sm leading-fm-sm w-full rounded-lg border px-3 py-2 text-left capitalize transition-colors ${decoration === d ? "bg-fm-surface-primary" : "hover:bg-fm-surface-primary"}`}
|
|
469
|
+
>
|
|
470
|
+
{d}
|
|
471
|
+
</button>
|
|
472
|
+
))}
|
|
473
|
+
</div>
|
|
474
|
+
</div>
|
|
475
|
+
|
|
476
|
+
{/* Preview stage */}
|
|
477
|
+
<div className="flex flex-col gap-4 lg:col-span-2">
|
|
478
|
+
<SelectField
|
|
479
|
+
label="Country"
|
|
480
|
+
placeholder="Select your country…"
|
|
481
|
+
value={country}
|
|
482
|
+
onValueChange={setCountry}
|
|
483
|
+
required
|
|
484
|
+
variant={countryVariant}
|
|
485
|
+
helperText={countryHelper}
|
|
486
|
+
decoration={decoration}
|
|
487
|
+
fullWidth
|
|
488
|
+
>
|
|
489
|
+
<SelectItem value="us">United States</SelectItem>
|
|
490
|
+
<SelectItem value="ca">Canada</SelectItem>
|
|
491
|
+
<SelectItem value="uk">United Kingdom</SelectItem>
|
|
492
|
+
<SelectItem value="de">Germany</SelectItem>
|
|
493
|
+
<SelectItem value="fr">France</SelectItem>
|
|
494
|
+
</SelectField>
|
|
495
|
+
|
|
496
|
+
<SelectField
|
|
497
|
+
label="Framework"
|
|
498
|
+
placeholder="Choose your framework…"
|
|
499
|
+
value={framework}
|
|
500
|
+
onValueChange={setFramework}
|
|
501
|
+
helperText="This will personalise your experience"
|
|
502
|
+
decoration={decoration}
|
|
503
|
+
fullWidth
|
|
504
|
+
>
|
|
505
|
+
<SelectGroup>
|
|
506
|
+
<SelectLabel className="px-2 pt-2 pb-1">Popular</SelectLabel>
|
|
507
|
+
<SelectItem value="react">React</SelectItem>
|
|
508
|
+
<SelectSeparator />
|
|
509
|
+
<SelectItem value="vue">Vue.js</SelectItem>
|
|
510
|
+
<SelectSeparator />
|
|
511
|
+
<SelectItem value="angular">Angular</SelectItem>
|
|
512
|
+
</SelectGroup>
|
|
513
|
+
<SelectSeparator />
|
|
514
|
+
<SelectGroup>
|
|
515
|
+
<SelectLabel className="px-2 pt-2 pb-1">Others</SelectLabel>
|
|
516
|
+
<SelectItem value="svelte">Svelte</SelectItem>
|
|
517
|
+
<SelectSeparator />
|
|
518
|
+
<SelectItem value="solid">SolidJS</SelectItem>
|
|
519
|
+
</SelectGroup>
|
|
520
|
+
</SelectField>
|
|
521
|
+
|
|
522
|
+
<div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border px-4 py-3">
|
|
523
|
+
<code className="text-fm-secondary text-fm-md leading-fm-md font-(--font-fm-mono)">
|
|
524
|
+
{`country="${country || "(none)"}" framework="${framework || "(none)"}"`}
|
|
525
|
+
</code>
|
|
526
|
+
</div>
|
|
527
|
+
</div>
|
|
528
|
+
</div>
|
|
529
|
+
</div>
|
|
530
|
+
</div>
|
|
531
|
+
)
|
|
532
|
+
},
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// ─── 6. Parts ────────────────────────────────────────────────────────────────
|
|
536
|
+
|
|
537
|
+
export const Parts: Story = {
|
|
592
538
|
parameters: {
|
|
593
539
|
docs: {
|
|
594
540
|
description: {
|
|
595
|
-
story:
|
|
596
|
-
Select
|
|
597
|
-
for form validation and submission. The select values will be included in form data.
|
|
598
|
-
`,
|
|
541
|
+
story:
|
|
542
|
+
"Each atomic sub-component shown individually: SelectField (convenience wrapper), SelectRoot + SelectWrapper + Select + SelectTrigger + SelectContent (atomic composition), SelectLabel (Radix label bridged to the design system Label), SelectHelperText, SelectItem with tick indicator, SelectSeparator (Divider-based), and SelectGroup for categorising options.",
|
|
599
543
|
},
|
|
600
544
|
},
|
|
601
545
|
},
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
export const AtomicComposition: Story = {
|
|
605
546
|
render: () => {
|
|
606
|
-
const [
|
|
547
|
+
const [atomicValue, setAtomicValue] = React.useState("")
|
|
607
548
|
|
|
608
549
|
return (
|
|
609
|
-
<
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
550
|
+
<div className="w-full max-w-lg space-y-10">
|
|
551
|
+
{/* SelectField */}
|
|
552
|
+
<div>
|
|
553
|
+
<h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md mb-4 font-medium">
|
|
554
|
+
SelectField — convenience wrapper
|
|
555
|
+
</h4>
|
|
556
|
+
<div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border p-4">
|
|
557
|
+
<p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm mb-3">
|
|
558
|
+
Combines SelectRoot, SelectLabel, SelectWrapper, Select,
|
|
559
|
+
SelectTrigger, SelectContent, and SelectHelperText into a single
|
|
560
|
+
component.
|
|
561
|
+
</p>
|
|
562
|
+
<SelectField
|
|
563
|
+
label="Framework"
|
|
564
|
+
placeholder="Select a framework…"
|
|
565
|
+
helperText="Pick your preferred stack"
|
|
615
566
|
decoration="outline"
|
|
616
|
-
|
|
617
|
-
aria-describedby="custom-select-helper"
|
|
567
|
+
fullWidth
|
|
618
568
|
>
|
|
619
|
-
<
|
|
620
|
-
|
|
569
|
+
<SelectItem value="react">React</SelectItem>
|
|
570
|
+
<SelectItem value="vue">Vue</SelectItem>
|
|
571
|
+
</SelectField>
|
|
572
|
+
</div>
|
|
573
|
+
</div>
|
|
621
574
|
|
|
622
|
-
|
|
623
|
-
|
|
575
|
+
{/* Atomic composition */}
|
|
576
|
+
<div>
|
|
577
|
+
<h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md mb-4 font-medium">
|
|
578
|
+
Atomic Composition
|
|
579
|
+
</h4>
|
|
580
|
+
<div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border p-4">
|
|
581
|
+
<p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm mb-3">
|
|
582
|
+
Build the same result manually using individual primitives for
|
|
583
|
+
full layout control.
|
|
584
|
+
</p>
|
|
585
|
+
<SelectRoot fullWidth>
|
|
586
|
+
<SelectLabel required>Custom Select</SelectLabel>
|
|
587
|
+
<SelectWrapper>
|
|
588
|
+
<Select value={atomicValue} onValueChange={setAtomicValue}>
|
|
589
|
+
<SelectTrigger decoration="outline" variant="default">
|
|
590
|
+
<SelectValue placeholder="Built with atomic parts…" />
|
|
591
|
+
</SelectTrigger>
|
|
592
|
+
<SelectContent>
|
|
593
|
+
<SelectItem value="atomic">Atomic Design</SelectItem>
|
|
594
|
+
<SelectSeparator />
|
|
595
|
+
<SelectItem value="flexible">
|
|
596
|
+
Flexible Composition
|
|
597
|
+
</SelectItem>
|
|
598
|
+
<SelectSeparator />
|
|
599
|
+
<SelectItem value="accessible">
|
|
600
|
+
Accessible by Default
|
|
601
|
+
</SelectItem>
|
|
602
|
+
</SelectContent>
|
|
603
|
+
</Select>
|
|
604
|
+
</SelectWrapper>
|
|
605
|
+
<SelectHelperText variant="default">
|
|
606
|
+
Full control via individual primitives
|
|
607
|
+
</SelectHelperText>
|
|
608
|
+
</SelectRoot>
|
|
609
|
+
</div>
|
|
610
|
+
</div>
|
|
611
|
+
|
|
612
|
+
{/* SelectSeparator */}
|
|
613
|
+
<div>
|
|
614
|
+
<h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md mb-4 font-medium">
|
|
615
|
+
SelectSeparator
|
|
616
|
+
</h4>
|
|
617
|
+
<div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border p-4">
|
|
618
|
+
<p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm mb-3">
|
|
619
|
+
A Divider-based separator rendered between SelectItems to visually
|
|
620
|
+
group related options.
|
|
621
|
+
</p>
|
|
622
|
+
<SelectField
|
|
623
|
+
label="Fruit"
|
|
624
|
+
placeholder="Choose a fruit…"
|
|
625
|
+
decoration="outline"
|
|
626
|
+
fullWidth
|
|
627
|
+
>
|
|
628
|
+
<SelectItem value="apple">Apple</SelectItem>
|
|
624
629
|
<SelectSeparator />
|
|
625
|
-
<SelectItem value="
|
|
630
|
+
<SelectItem value="banana">Banana</SelectItem>
|
|
626
631
|
<SelectSeparator />
|
|
627
|
-
<SelectItem value="
|
|
628
|
-
</
|
|
629
|
-
</
|
|
630
|
-
</
|
|
631
|
-
|
|
632
|
-
<SelectHelperText id="custom-select-helper" variant="default">
|
|
633
|
-
This is built using atomic components for maximum flexibility
|
|
634
|
-
</SelectHelperText>
|
|
635
|
-
</SelectRoot>
|
|
632
|
+
<SelectItem value="cherry">Cherry</SelectItem>
|
|
633
|
+
</SelectField>
|
|
634
|
+
</div>
|
|
635
|
+
</div>
|
|
636
|
+
</div>
|
|
636
637
|
)
|
|
637
638
|
},
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// ─── 7. UseCases ─────────────────────────────────────────────────────────────
|
|
642
|
+
|
|
643
|
+
export const UseCases: Story = {
|
|
638
644
|
parameters: {
|
|
639
645
|
docs: {
|
|
640
646
|
description: {
|
|
641
647
|
story:
|
|
642
|
-
"
|
|
648
|
+
"Real product-shaped examples: a user profile form with country and language selection, a subscription plan picker with disabled options, and a dynamic filter with grouped content categories.",
|
|
643
649
|
},
|
|
644
650
|
},
|
|
645
651
|
},
|
|
646
|
-
|
|
652
|
+
render: function UseCasesSelect() {
|
|
653
|
+
const [country, setCountry] = React.useState("")
|
|
654
|
+
const [language, setLanguage] = React.useState("")
|
|
655
|
+
const [plan, setPlan] = React.useState("")
|
|
656
|
+
const [category, setCategory] = React.useState("")
|
|
647
657
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
>
|
|
695
|
-
<SelectItem value="option1">Option 1</SelectItem>
|
|
696
|
-
<SelectItem value="option2">Option 2</SelectItem>
|
|
697
|
-
</SelectField>
|
|
698
|
-
</div>
|
|
658
|
+
return (
|
|
659
|
+
<div className="mx-auto max-w-3xl space-y-8 p-8">
|
|
660
|
+
{/* Profile settings */}
|
|
661
|
+
<div className="space-y-4">
|
|
662
|
+
<h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
|
|
663
|
+
Profile Settings
|
|
664
|
+
</h4>
|
|
665
|
+
<div className="border-fm-divider-secondary bg-fm-surface-secondary max-w-md space-y-4 rounded-xl border p-5">
|
|
666
|
+
<SelectField
|
|
667
|
+
label="Country"
|
|
668
|
+
placeholder="Select your country…"
|
|
669
|
+
value={country}
|
|
670
|
+
onValueChange={setCountry}
|
|
671
|
+
required
|
|
672
|
+
variant={country ? "success" : "default"}
|
|
673
|
+
helperText={
|
|
674
|
+
country ? "Country saved" : "Used for regional content"
|
|
675
|
+
}
|
|
676
|
+
decoration="outline"
|
|
677
|
+
fullWidth
|
|
678
|
+
>
|
|
679
|
+
<SelectItem value="us">United States</SelectItem>
|
|
680
|
+
<SelectItem value="ca">Canada</SelectItem>
|
|
681
|
+
<SelectItem value="uk">United Kingdom</SelectItem>
|
|
682
|
+
<SelectItem value="de">Germany</SelectItem>
|
|
683
|
+
<SelectItem value="fr">France</SelectItem>
|
|
684
|
+
<SelectItem value="au">Australia</SelectItem>
|
|
685
|
+
</SelectField>
|
|
686
|
+
|
|
687
|
+
<SelectField
|
|
688
|
+
label="Language"
|
|
689
|
+
placeholder="Select your language…"
|
|
690
|
+
value={language}
|
|
691
|
+
onValueChange={setLanguage}
|
|
692
|
+
helperText="Preferred display language"
|
|
693
|
+
decoration="outline"
|
|
694
|
+
fullWidth
|
|
695
|
+
>
|
|
696
|
+
<SelectItem value="en">English</SelectItem>
|
|
697
|
+
<SelectItem value="de">Deutsch</SelectItem>
|
|
698
|
+
<SelectItem value="fr">Français</SelectItem>
|
|
699
|
+
<SelectItem value="es">Español</SelectItem>
|
|
700
|
+
<SelectItem value="ja">日本語</SelectItem>
|
|
701
|
+
</SelectField>
|
|
702
|
+
</div>
|
|
703
|
+
</div>
|
|
699
704
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
>
|
|
733
|
-
<SelectItem value="option1">Option 1</SelectItem>
|
|
734
|
-
<SelectItem value="option2">Option 2</SelectItem>
|
|
735
|
-
</SelectField>
|
|
736
|
-
<SelectField
|
|
737
|
-
label="Success"
|
|
738
|
-
placeholder="Success outline..."
|
|
739
|
-
variant="success"
|
|
740
|
-
decoration="outline"
|
|
741
|
-
helperText="Great choice!"
|
|
742
|
-
fullWidth
|
|
743
|
-
>
|
|
744
|
-
<SelectItem value="option1">Option 1</SelectItem>
|
|
745
|
-
<SelectItem value="option2">Option 2</SelectItem>
|
|
746
|
-
</SelectField>
|
|
747
|
-
</div>
|
|
705
|
+
{/* Subscription plan picker */}
|
|
706
|
+
<div className="space-y-4">
|
|
707
|
+
<h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
|
|
708
|
+
Subscription Plan
|
|
709
|
+
</h4>
|
|
710
|
+
<div className="border-fm-divider-secondary bg-fm-surface-secondary max-w-md rounded-xl border p-5">
|
|
711
|
+
<SelectField
|
|
712
|
+
label="Plan"
|
|
713
|
+
placeholder="Choose a plan…"
|
|
714
|
+
value={plan}
|
|
715
|
+
onValueChange={setPlan}
|
|
716
|
+
required
|
|
717
|
+
variant={plan ? "success" : "default"}
|
|
718
|
+
helperText={
|
|
719
|
+
plan === "enterprise"
|
|
720
|
+
? "Contact sales to complete upgrade"
|
|
721
|
+
: "You can change your plan at any time"
|
|
722
|
+
}
|
|
723
|
+
decoration="filled"
|
|
724
|
+
fullWidth
|
|
725
|
+
>
|
|
726
|
+
<SelectItem value="starter">Starter — Free forever</SelectItem>
|
|
727
|
+
<SelectSeparator />
|
|
728
|
+
<SelectItem value="pro">Pro — $9 / month</SelectItem>
|
|
729
|
+
<SelectSeparator />
|
|
730
|
+
<SelectItem value="team">Team — $29 / month</SelectItem>
|
|
731
|
+
<SelectSeparator />
|
|
732
|
+
<SelectItem value="enterprise" disabled>
|
|
733
|
+
Enterprise — Contact sales
|
|
734
|
+
</SelectItem>
|
|
735
|
+
</SelectField>
|
|
736
|
+
</div>
|
|
737
|
+
</div>
|
|
748
738
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
739
|
+
{/* Content filter */}
|
|
740
|
+
<div className="space-y-4">
|
|
741
|
+
<h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
|
|
742
|
+
Content Filter
|
|
743
|
+
</h4>
|
|
744
|
+
<div className="border-fm-divider-secondary bg-fm-surface-secondary max-w-md rounded-xl border p-5">
|
|
745
|
+
<SelectField
|
|
746
|
+
label="Category"
|
|
747
|
+
placeholder="Browse by category…"
|
|
748
|
+
value={category}
|
|
749
|
+
onValueChange={setCategory}
|
|
750
|
+
helperText="Filter the podcast library by topic"
|
|
751
|
+
decoration="underline"
|
|
752
|
+
fullWidth
|
|
753
|
+
>
|
|
754
|
+
<SelectGroup>
|
|
755
|
+
<SelectLabel className="px-2 pt-2 pb-1">
|
|
756
|
+
News & Culture
|
|
757
|
+
</SelectLabel>
|
|
758
|
+
<SelectItem value="news">News</SelectItem>
|
|
759
|
+
<SelectSeparator />
|
|
760
|
+
<SelectItem value="politics">Politics</SelectItem>
|
|
761
|
+
<SelectSeparator />
|
|
762
|
+
<SelectItem value="history">History</SelectItem>
|
|
763
|
+
</SelectGroup>
|
|
764
|
+
<SelectSeparator />
|
|
765
|
+
<SelectGroup>
|
|
766
|
+
<SelectLabel className="px-2 pt-2 pb-1">
|
|
767
|
+
Science & Tech
|
|
768
|
+
</SelectLabel>
|
|
769
|
+
<SelectItem value="science">Science</SelectItem>
|
|
770
|
+
<SelectSeparator />
|
|
771
|
+
<SelectItem value="technology">Technology</SelectItem>
|
|
772
|
+
<SelectSeparator />
|
|
773
|
+
<SelectItem value="ai">Artificial Intelligence</SelectItem>
|
|
774
|
+
</SelectGroup>
|
|
775
|
+
<SelectSeparator />
|
|
776
|
+
<SelectGroup>
|
|
777
|
+
<SelectLabel className="px-2 pt-2 pb-1">Lifestyle</SelectLabel>
|
|
778
|
+
<SelectItem value="health">Health & Fitness</SelectItem>
|
|
779
|
+
<SelectSeparator />
|
|
780
|
+
<SelectItem value="comedy">Comedy</SelectItem>
|
|
781
|
+
<SelectSeparator />
|
|
782
|
+
<SelectItem value="true-crime">True Crime</SelectItem>
|
|
783
|
+
</SelectGroup>
|
|
784
|
+
</SelectField>
|
|
785
|
+
</div>
|
|
786
|
+
</div>
|
|
796
787
|
</div>
|
|
797
|
-
|
|
798
|
-
|
|
788
|
+
)
|
|
789
|
+
},
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// ─── 8. Accessibility ────────────────────────────────────────────────────────
|
|
793
|
+
|
|
794
|
+
export const Accessibility: Story = {
|
|
799
795
|
parameters: {
|
|
800
796
|
docs: {
|
|
801
797
|
description: {
|
|
802
798
|
story:
|
|
803
|
-
"
|
|
799
|
+
"Accessibility-focused examples demonstrating ARIA attributes, required field announcements, error state with aria-invalid, and keyboard navigation support. The select is fully operable via keyboard: Space/Enter opens, arrow keys move through options, Escape closes.",
|
|
804
800
|
},
|
|
805
801
|
},
|
|
806
802
|
},
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
return (
|
|
815
|
-
<div className="max-w-lg space-y-6">
|
|
803
|
+
render: () => (
|
|
804
|
+
<div className="w-full max-w-lg space-y-8">
|
|
805
|
+
{/* Required + aria-required */}
|
|
806
|
+
<div>
|
|
807
|
+
<h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md mb-3 font-medium">
|
|
808
|
+
Required Field
|
|
809
|
+
</h4>
|
|
816
810
|
<SelectField
|
|
817
811
|
label="Country"
|
|
818
|
-
placeholder="Select your country"
|
|
819
|
-
value={country}
|
|
820
|
-
onValueChange={setCountry}
|
|
812
|
+
placeholder="Select your country…"
|
|
821
813
|
required
|
|
822
|
-
helperText=
|
|
823
|
-
country
|
|
824
|
-
? "Country selected successfully"
|
|
825
|
-
: "Please select your country"
|
|
826
|
-
}
|
|
827
|
-
variant={country ? "success" : "default"}
|
|
814
|
+
helperText="Required — used for regional settings"
|
|
828
815
|
decoration="outline"
|
|
829
816
|
fullWidth
|
|
830
817
|
>
|
|
831
818
|
<SelectItem value="us">United States</SelectItem>
|
|
832
819
|
<SelectItem value="ca">Canada</SelectItem>
|
|
833
820
|
<SelectItem value="uk">United Kingdom</SelectItem>
|
|
834
|
-
<SelectItem value="de">Germany</SelectItem>
|
|
835
|
-
<SelectItem value="fr">France</SelectItem>
|
|
836
821
|
</SelectField>
|
|
822
|
+
</div>
|
|
837
823
|
|
|
824
|
+
{/* Error + aria-invalid */}
|
|
825
|
+
<div>
|
|
826
|
+
<h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md mb-3 font-medium">
|
|
827
|
+
Error State (aria-invalid)
|
|
828
|
+
</h4>
|
|
838
829
|
<SelectField
|
|
839
|
-
label="
|
|
840
|
-
placeholder="
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
decoration="filled"
|
|
830
|
+
label="Plan"
|
|
831
|
+
placeholder="Select a plan…"
|
|
832
|
+
variant="error"
|
|
833
|
+
helperText="Please select a plan to continue"
|
|
834
|
+
decoration="outline"
|
|
845
835
|
fullWidth
|
|
846
836
|
>
|
|
847
|
-
<
|
|
848
|
-
|
|
849
|
-
<SelectItem value="react">React</SelectItem>
|
|
850
|
-
<SelectSeparator />
|
|
851
|
-
<SelectItem value="vue">Vue.js</SelectItem>
|
|
852
|
-
<SelectSeparator />
|
|
853
|
-
<SelectItem value="angular">Angular</SelectItem>
|
|
854
|
-
</SelectGroup>
|
|
855
|
-
<SelectSeparator />
|
|
856
|
-
<SelectGroup>
|
|
857
|
-
<SelectLabel className="px-2 pt-2 pb-1">Others</SelectLabel>
|
|
858
|
-
<SelectItem value="svelte">Svelte</SelectItem>
|
|
859
|
-
<SelectSeparator />
|
|
860
|
-
<SelectItem value="solid">SolidJS</SelectItem>
|
|
861
|
-
</SelectGroup>
|
|
837
|
+
<SelectItem value="starter">Starter</SelectItem>
|
|
838
|
+
<SelectItem value="pro">Pro</SelectItem>
|
|
862
839
|
</SelectField>
|
|
863
840
|
</div>
|
|
864
|
-
)
|
|
865
|
-
},
|
|
866
|
-
parameters: {
|
|
867
|
-
docs: {
|
|
868
|
-
description: {
|
|
869
|
-
story:
|
|
870
|
-
"An example showing multiple SelectField components in a form with different configurations, decorations, and dynamic states based on user input.",
|
|
871
|
-
},
|
|
872
|
-
},
|
|
873
|
-
},
|
|
874
|
-
}
|
|
875
841
|
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
<SelectSeparator />
|
|
900
|
-
<SelectItem value="orange">Orange</SelectItem>
|
|
901
|
-
<SelectSeparator />
|
|
902
|
-
<SelectItem value="grape">Grape</SelectItem>
|
|
903
|
-
<SelectSeparator />
|
|
904
|
-
<SelectItem value="strawberry">Strawberry</SelectItem>
|
|
905
|
-
</SelectContent>
|
|
906
|
-
</Select>
|
|
907
|
-
),
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
export const LongList: Story = {
|
|
911
|
-
render: () => (
|
|
912
|
-
<SelectField
|
|
913
|
-
label="Country Selection"
|
|
914
|
-
placeholder="Select a country"
|
|
915
|
-
helperText="Scroll to see more options"
|
|
916
|
-
decoration="outline"
|
|
917
|
-
fullWidth
|
|
918
|
-
>
|
|
919
|
-
<SelectItem value="us">United States</SelectItem>
|
|
920
|
-
<SelectSeparator />
|
|
921
|
-
<SelectItem value="ca">Canada</SelectItem>
|
|
922
|
-
<SelectSeparator />
|
|
923
|
-
<SelectItem value="uk">United Kingdom</SelectItem>
|
|
924
|
-
<SelectSeparator />
|
|
925
|
-
<SelectItem value="fr">France</SelectItem>
|
|
926
|
-
<SelectSeparator />
|
|
927
|
-
<SelectItem value="de">Germany</SelectItem>
|
|
928
|
-
<SelectSeparator />
|
|
929
|
-
<SelectItem value="it">Italy</SelectItem>
|
|
930
|
-
<SelectSeparator />
|
|
931
|
-
<SelectItem value="es">Spain</SelectItem>
|
|
932
|
-
<SelectSeparator />
|
|
933
|
-
<SelectItem value="au">Australia</SelectItem>
|
|
934
|
-
<SelectSeparator />
|
|
935
|
-
<SelectItem value="jp">Japan</SelectItem>
|
|
936
|
-
<SelectSeparator />
|
|
937
|
-
<SelectItem value="kr">South Korea</SelectItem>
|
|
938
|
-
<SelectSeparator />
|
|
939
|
-
<SelectItem value="in">India</SelectItem>
|
|
940
|
-
<SelectSeparator />
|
|
941
|
-
<SelectItem value="br">Brazil</SelectItem>
|
|
942
|
-
<SelectSeparator />
|
|
943
|
-
<SelectItem value="mx">Mexico</SelectItem>
|
|
944
|
-
<SelectSeparator />
|
|
945
|
-
<SelectItem value="ar">Argentina</SelectItem>
|
|
946
|
-
<SelectSeparator />
|
|
947
|
-
<SelectItem value="cl">Chile</SelectItem>
|
|
948
|
-
<SelectSeparator />
|
|
949
|
-
<SelectItem value="pe">Peru</SelectItem>
|
|
950
|
-
<SelectSeparator />
|
|
951
|
-
<SelectItem value="co">Colombia</SelectItem>
|
|
952
|
-
<SelectSeparator />
|
|
953
|
-
<SelectItem value="ve">Venezuela</SelectItem>
|
|
954
|
-
<SelectSeparator />
|
|
955
|
-
<SelectItem value="ec">Ecuador</SelectItem>
|
|
956
|
-
<SelectSeparator />
|
|
957
|
-
<SelectItem value="uy">Uruguay</SelectItem>
|
|
958
|
-
</SelectField>
|
|
842
|
+
{/* Keyboard navigation info */}
|
|
843
|
+
<div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border p-4">
|
|
844
|
+
<p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm mb-2 font-medium">
|
|
845
|
+
Keyboard Navigation
|
|
846
|
+
</p>
|
|
847
|
+
<ul className="space-y-1">
|
|
848
|
+
{[
|
|
849
|
+
"Space / Enter — opens the dropdown",
|
|
850
|
+
"Arrow Up / Arrow Down — moves between options",
|
|
851
|
+
"Home / End — jumps to first or last option",
|
|
852
|
+
"Escape — closes the dropdown without selection",
|
|
853
|
+
"Tab — moves focus to the next focusable element",
|
|
854
|
+
].map((item) => (
|
|
855
|
+
<li
|
|
856
|
+
key={item}
|
|
857
|
+
className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm"
|
|
858
|
+
>
|
|
859
|
+
{item}
|
|
860
|
+
</li>
|
|
861
|
+
))}
|
|
862
|
+
</ul>
|
|
863
|
+
</div>
|
|
864
|
+
</div>
|
|
959
865
|
),
|
|
960
|
-
parameters: {
|
|
961
|
-
docs: {
|
|
962
|
-
description: {
|
|
963
|
-
story: `
|
|
964
|
-
Select with many options that demonstrates scrolling behavior.
|
|
965
|
-
The content area becomes scrollable when it exceeds the maximum height,
|
|
966
|
-
and scroll buttons appear automatically at the top and bottom.
|
|
967
|
-
`,
|
|
968
|
-
},
|
|
969
|
-
},
|
|
970
|
-
},
|
|
971
866
|
}
|