aural-ui 3.0.7 → 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 -1199
- package/dist/components/avatar/Avatar.stories.tsx +235 -237
- 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/button/index.tsx +7 -7
- 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 -620
- 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 +533 -856
- package/dist/components/dialog/Dialog.stories.tsx +505 -949
- package/dist/components/divider/Divider.stories.tsx +265 -502
- package/dist/components/dot-loader/DotLoader.stories.tsx +256 -257
- package/dist/components/drawer/Drawer.stories.tsx +659 -993
- package/dist/components/drawer/index.tsx +3 -3
- package/dist/components/dropdown/Dropdown.stories.tsx +643 -1018
- 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 -1221
- 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 +485 -822
- package/dist/components/marquee/Marquee.stories.tsx +356 -694
- package/dist/components/otp-inputs/OtpInputs.stories.tsx +352 -410
- package/dist/components/overlay/Overlay.stories.tsx +452 -818
- package/dist/components/overlay/index.tsx +4 -4
- package/dist/components/pagination/Pagination.stories.tsx +721 -210
- package/dist/components/popover/Popover.stories.tsx +484 -873
- package/dist/components/radio/Radio.stories.tsx +432 -124
- package/dist/components/resizable/Resizable.stories.tsx +496 -752
- package/dist/components/scroll-area/ScrollArea.stories.tsx +384 -1006
- package/dist/components/search/Search.stories.tsx +314 -575
- package/dist/components/select/Select.stories.tsx +684 -787
- package/dist/components/sheet/Sheet.stories.tsx +671 -936
- package/dist/components/skelton/Skelton.stories.tsx +230 -764
- package/dist/components/slider/Slider.stories.tsx +384 -737
- 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 -914
- package/dist/components/tabs/Tabs.stories.tsx +459 -1400
- 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 -148
- package/dist/components/toast/Toast.stories.tsx +452 -1333
- package/dist/components/toggle/Toggle.stories.tsx +488 -909
- package/dist/components/tooltip/Tooltip.stories.tsx +344 -1372
- 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 +226 -1013
- package/dist/icons/alert-icon/AlertIcon.stories.tsx +109 -929
- package/dist/icons/all-icons.tsx +124 -87
- package/dist/icons/angle-down-icon/AngleDownIcon.stories.tsx +140 -971
- package/dist/icons/apple-logo-icon/AppleLogoIcon.stories.tsx +148 -888
- package/dist/icons/arrow-box-left-icon/ArrowBoxLeftIcon.stories.tsx +135 -1019
- package/dist/icons/arrow-corner-up-left-icon/ArrowCornerUpLeftIcon.stories.tsx +137 -953
- package/dist/icons/arrow-corner-up-right-icon/ArrowCornerUpRightIcon.stories.tsx +138 -997
- package/dist/icons/arrow-left-icon/ArrowLeftIcon.stories.tsx +136 -942
- package/dist/icons/arrow-right-icon/ArrowRightIcon.stories.tsx +148 -1092
- package/dist/icons/arrow-right-up-icon/ArrowRightUpIcon.stories.tsx +146 -1211
- package/dist/icons/art-board-icon/ArtBoardIcon.stories.tsx +126 -615
- package/dist/icons/audio-bar-icon/AudioBarIcon.stories.tsx +144 -1164
- package/dist/icons/backward-ten-seconds-icon/BackwardTenSecondsIcon.stories.tsx +167 -985
- package/dist/icons/bubble-check-icon/BubbleCheckIcon.stories.tsx +122 -1179
- package/dist/icons/bubble-crossed-icon/BubbleCrossedIcon.stories.tsx +124 -1168
- package/dist/icons/bubble-sparkle-icon/BubbleSparkleIcon.stories.tsx +119 -850
- package/dist/icons/camera-icon/CameraIcon.stories.tsx +112 -1213
- package/dist/icons/capital-a-letter-icon/CapitalALetterIcon.stories.tsx +117 -934
- package/dist/icons/chevron-double-left-icon/ChevronDoubleLeftIcon.stories.tsx +160 -961
- package/dist/icons/chevron-double-right-icon/ChevronDoubleRightIcon.stories.tsx +163 -961
- package/dist/icons/chevron-down-icon/ChevronDownIcon.stories.tsx +144 -942
- package/dist/icons/chevron-left-icon/ChevronLeftIcon.stories.tsx +129 -966
- package/dist/icons/chevron-right-icon/ChevronRightIcon.stories.tsx +147 -964
- package/dist/icons/chevron-up-icon/ChevronUpIcon.stories.tsx +145 -975
- package/dist/icons/circle-tick-icon/CircleTickIcon.stories.tsx +150 -1142
- package/dist/icons/circular-play-icon/CircularPlayIcon.stories.tsx +114 -461
- package/dist/icons/coin-icon/CoinIcon.stories.tsx +124 -1322
- package/dist/icons/coin-toons-icon/CoinToonsIcon.stories.tsx +117 -1318
- package/dist/icons/column-wide-add-icon/ColumnWideAddIcon.stories.tsx +114 -903
- package/dist/icons/command-icon/CommandIcon.stories.tsx +127 -1042
- package/dist/icons/copy-icon/CopyIcon.stories.tsx +123 -962
- package/dist/icons/cross-circle-icon/CrossCircleIcon.stories.tsx +147 -999
- package/dist/icons/cross-icon/CrossIcon.stories.tsx +139 -960
- package/dist/icons/download-icon/DownloadIcon.stories.tsx +126 -820
- package/dist/icons/edit-big-icon/EditBigIcon.stories.tsx +124 -1031
- package/dist/icons/email-icon/EmailIcon.stories.tsx +115 -936
- package/dist/icons/expand-icon/ExpandIcon.stories.tsx +112 -1111
- package/dist/icons/eye-close-icon/EyeCloseIcon.stories.tsx +144 -1025
- package/dist/icons/eye-open-icon/EyeOpenIcon.stories.tsx +143 -1036
- package/dist/icons/feature-shine-icon/FeatureShineIcon.stories.tsx +127 -1011
- package/dist/icons/file-chart-icon/FileChartIcon.stories.tsx +126 -1056
- package/dist/icons/file-text-icon/FileTextIcon.stories.tsx +125 -614
- package/dist/icons/filter-bar-row-icon/FilterBarRowIcon.stories.tsx +119 -1050
- package/dist/icons/forward-ten-seconds-icon/ForwardTenSecondsIcon.stories.tsx +169 -989
- package/dist/icons/git-branch-icon/GitBranchIcon.stories.tsx +115 -1145
- package/dist/icons/git-fork-icon/GitForkIcon.stories.tsx +115 -1122
- package/dist/icons/globe-icon/GlobeIcon.stories.tsx +130 -313
- package/dist/icons/google-logo-icon/GoogleLogoIcon.stories.tsx +145 -940
- package/dist/icons/grip-vertical-icon/GripVerticalIcon.stories.tsx +119 -1174
- package/dist/icons/head-icon/HeadIcon.stories.tsx +111 -916
- package/dist/icons/heart-icon/HeartIcon.stories.tsx +120 -1019
- package/dist/icons/image-avatar-sparkle-icon/ImageAvatarSparkleIcon.stories.tsx +119 -683
- package/dist/icons/image-icon/ImageIcon.stories.tsx +105 -1121
- package/dist/icons/import-folder-icon/ImportFolderIcon.stories.tsx +111 -1192
- package/dist/icons/import-left-arrow-folder-icon/ImportLeftArrowFolderIcon.stories.tsx +136 -1256
- package/dist/icons/indian-flag-icon/IndianFlagIcon.stories.tsx +159 -962
- package/dist/icons/instagram-icon/InstagramIcon.stories.tsx +161 -1385
- package/dist/icons/layout-column-icon/LayoutColumnIcon.stories.tsx +124 -972
- package/dist/icons/layout-left-icon/LayoutLeftIcon.stories.tsx +119 -948
- package/dist/icons/layout-right-icon/LayoutRightIcon.stories.tsx +119 -942
- package/dist/icons/light-bulb-simple-icon/LightBulbSimpleIcon.stories.tsx +108 -1215
- package/dist/icons/linked-in-icon/LinkedInIcon.stories.tsx +154 -1517
- package/dist/icons/magic-book-icon/MagicBookIcon.stories.tsx +110 -1188
- package/dist/icons/magic-edit-icon/MagicEditIcon.stories.tsx +119 -678
- package/dist/icons/maintenance-icon/MaintenanceIcon.stories.tsx +123 -1184
- package/dist/icons/message-icon/MessageIcon.stories.tsx +114 -538
- package/dist/icons/minimize-icon/MinimizeIcon.stories.tsx +116 -1158
- package/dist/icons/moon-icon/MoonIcon.stories.tsx +120 -536
- package/dist/icons/move-horizontal-icon/MoveHorizontalIcon.stories.tsx +109 -1184
- package/dist/icons/move-vertical-icon/MoveVerticalIcon.stories.tsx +115 -1134
- package/dist/icons/musical-note-icon/MusicalNoteIcon.stories.tsx +119 -971
- package/dist/icons/notepad-icon/NotepadIcon.stories.tsx +111 -1100
- package/dist/icons/notes-icon/NotesIcon.stories.tsx +119 -1101
- package/dist/icons/page-search-icon/PageSearchIcon.stories.tsx +109 -1111
- package/dist/icons/page-text-icon/PageTextIcon.stories.tsx +122 -684
- package/dist/icons/paint-roll-icon/PaintRollIcon.stories.tsx +113 -954
- package/dist/icons/paper-plane-icon/PaperPlaneIcon.stories.tsx +112 -877
- package/dist/icons/pause-icon/PauseIcon.stories.tsx +113 -1000
- package/dist/icons/pencil-icon/PencilIcon.stories.tsx +115 -1070
- package/dist/icons/phone-icon/PhoneIcon.stories.tsx +115 -978
- package/dist/icons/plus-icon/PlusIcon.stories.tsx +106 -1093
- package/dist/icons/pocket-studio-icon/PocketStudioIcon.stories.tsx +107 -829
- package/dist/icons/scroll-down-icon/ScrollDownIcon.stories.tsx +102 -469
- package/dist/icons/search-icon/SearchIcon.stories.tsx +111 -1124
- package/dist/icons/setting-icon/SettingIcon.stories.tsx +107 -970
- package/dist/icons/share-icon/ShareIcon.stories.tsx +120 -1025
- package/dist/icons/shield-icon/ShieldIcon.stories.tsx +117 -931
- package/dist/icons/site-logo-icon/SiteLogoIcon.stories.tsx +137 -1104
- package/dist/icons/skip-backward-icon/SkipBackwardIcon.stories.tsx +172 -982
- package/dist/icons/skip-forward-icon/SkipForwardIcon.stories.tsx +164 -983
- package/dist/icons/sparkles-soft-icon/SparklesSoftIcon.stories.tsx +105 -958
- package/dist/icons/spinner-gradient-icon/SpinnerGradientIcon.stories.tsx +158 -580
- package/dist/icons/spinner-gradient-icon/index.tsx +6 -1
- package/dist/icons/spinner-solid-icon/SpinnerSolidIcon.stories.tsx +158 -587
- package/dist/icons/spinner-solid-icon/index.tsx +6 -1
- package/dist/icons/spinner-solid-neutral-icon/SpinnerSolidINeutralcon.stories.tsx +146 -682
- package/dist/icons/spinner-solid-neutral-icon/index.tsx +1 -1
- package/dist/icons/star-icon/StarIcon.stories.tsx +124 -904
- package/dist/icons/store-coin-icon/StoreCoinIcon.stories.tsx +112 -964
- package/dist/icons/suggestion-icon/SuggestionIcon.stories.tsx +116 -852
- package/dist/icons/sun-icon/SunIcon.stories.tsx +120 -831
- package/dist/icons/text-color-icon/TextColorIcon.stories.tsx +116 -950
- package/dist/icons/text-indicator-icon/TextIndicatorIcon.stories.tsx +123 -980
- package/dist/icons/threads-icon/ThreadsIcon.stories.tsx +156 -1427
- package/dist/icons/tick-circle-icon/TickCircleIcon.stories.tsx +146 -1142
- package/dist/icons/tick-icon/TickIcon.stories.tsx +145 -1276
- package/dist/icons/trash-icon/TrashIcon.stories.tsx +108 -933
- package/dist/icons/twitter-x-icon/TwitterXIcon.stories.tsx +157 -1402
- package/dist/icons/upload-icon/UploadIcon.stories.tsx +115 -889
- package/dist/icons/vertical-menu-icon/VerticalMenuIcon.stories.tsx +118 -984
- package/dist/icons/video-play-list-icon/VideoPlaylistIcon.stories.tsx +125 -1049
- package/dist/icons/voice-playing-icon/VoicePlayingIcon.stories.tsx +123 -1356
- package/dist/icons/volume-full-icon/VolumeFullIcon.stories.tsx +110 -1171
- package/dist/icons/volume-half-icon/VolumeHalfIcon.stories.tsx +112 -1093
- package/dist/icons/volume-off-icon/VolumeOffIcon.stories.tsx +115 -1087
- package/dist/icons/warning-icon/WarningIcon.stories.tsx +122 -1046
- package/dist/icons/youtube-icon/YoutubeIcon.stories.tsx +161 -936
- package/dist/index.cjs +84 -84
- package/dist/index.js +84 -84
- package/dist/styles/aural-all-theme.css +1222 -0
- package/dist/styles/{aural-theme.css → aural-dark-theme.css} +15 -3
- package/dist/styles/aural-light-theme.css +1047 -0
- 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,755 +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
|
-
|
|
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
|
|
434
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">
|
|
435
272
|
<SelectField
|
|
436
|
-
label="
|
|
437
|
-
placeholder="
|
|
438
|
-
|
|
439
|
-
onValueChange={setValue}
|
|
440
|
-
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"
|
|
441
276
|
decoration="outline"
|
|
442
277
|
fullWidth
|
|
443
278
|
>
|
|
444
|
-
<SelectItem value="
|
|
445
|
-
<SelectItem value="
|
|
446
|
-
<SelectItem value="
|
|
447
|
-
|
|
448
|
-
|
|
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>
|
|
449
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>
|
|
450
288
|
</div>
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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>
|
|
458
325
|
</div>
|
|
459
326
|
</div>
|
|
460
|
-
|
|
461
|
-
|
|
327
|
+
</div>
|
|
328
|
+
),
|
|
462
329
|
}
|
|
463
330
|
|
|
464
|
-
|
|
465
|
-
render: () => (
|
|
466
|
-
<div className="max-w-md space-y-6">
|
|
467
|
-
<SelectField
|
|
468
|
-
label="Disabled Select"
|
|
469
|
-
placeholder="This select is disabled"
|
|
470
|
-
disabled
|
|
471
|
-
helperText="This select cannot be interacted with"
|
|
472
|
-
decoration="outline"
|
|
473
|
-
fullWidth
|
|
474
|
-
>
|
|
475
|
-
<SelectItem value="option1">Option 1</SelectItem>
|
|
476
|
-
<SelectItem value="option2">Option 2</SelectItem>
|
|
477
|
-
</SelectField>
|
|
331
|
+
// ─── 4. States ───────────────────────────────────────────────────────────────
|
|
478
332
|
|
|
479
|
-
|
|
480
|
-
label="Select with Disabled Items"
|
|
481
|
-
placeholder="Some items are disabled"
|
|
482
|
-
helperText="Some options may be unavailable"
|
|
483
|
-
decoration="filled"
|
|
484
|
-
fullWidth
|
|
485
|
-
>
|
|
486
|
-
<SelectItem value="available1">Available Option 1</SelectItem>
|
|
487
|
-
<SelectItem value="disabled1" disabled>
|
|
488
|
-
Disabled Option 1
|
|
489
|
-
</SelectItem>
|
|
490
|
-
<SelectItem value="available2">Available Option 2</SelectItem>
|
|
491
|
-
<SelectItem value="disabled2" disabled>
|
|
492
|
-
Disabled Option 2
|
|
493
|
-
</SelectItem>
|
|
494
|
-
</SelectField>
|
|
495
|
-
</div>
|
|
496
|
-
),
|
|
333
|
+
export const States: Story = {
|
|
497
334
|
parameters: {
|
|
498
335
|
docs: {
|
|
499
336
|
description: {
|
|
500
|
-
story:
|
|
337
|
+
story:
|
|
338
|
+
"All five states side by side: default (idle), disabled, error (validation failure), warning (advisory), and success (validated).",
|
|
501
339
|
},
|
|
502
340
|
},
|
|
503
341
|
},
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
export const WithGroups: Story = {
|
|
507
342
|
render: () => (
|
|
508
|
-
<
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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>
|
|
533
404
|
),
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// ─── 5. Interactive ──────────────────────────────────────────────────────────
|
|
408
|
+
|
|
409
|
+
export const Interactive: Story = {
|
|
534
410
|
parameters: {
|
|
535
411
|
docs: {
|
|
536
412
|
description: {
|
|
537
|
-
story:
|
|
538
|
-
|
|
539
|
-
Useful for organizing related options into categories.
|
|
540
|
-
`,
|
|
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.",
|
|
541
415
|
},
|
|
542
416
|
},
|
|
543
417
|
},
|
|
544
|
-
|
|
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")
|
|
545
424
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
onSubmit={(e) => {
|
|
551
|
-
e.preventDefault()
|
|
552
|
-
const formData = new FormData(e.currentTarget)
|
|
553
|
-
alert(
|
|
554
|
-
`Selected values:\nFruit: ${formData.get("fruit")}\nColor: ${formData.get("color")}`
|
|
555
|
-
)
|
|
556
|
-
}}
|
|
557
|
-
>
|
|
558
|
-
<SelectField
|
|
559
|
-
label="Favorite Fruit"
|
|
560
|
-
placeholder="Select a fruit"
|
|
561
|
-
name="fruit"
|
|
562
|
-
required
|
|
563
|
-
helperText="Required field"
|
|
564
|
-
decoration="outline"
|
|
565
|
-
fullWidth
|
|
566
|
-
>
|
|
567
|
-
<SelectItem value="apple">Apple</SelectItem>
|
|
568
|
-
<SelectItem value="banana">Banana</SelectItem>
|
|
569
|
-
<SelectItem value="orange">Orange</SelectItem>
|
|
570
|
-
</SelectField>
|
|
425
|
+
const countryVariant = country ? "success" : "default"
|
|
426
|
+
const countryHelper = country
|
|
427
|
+
? "Country selected successfully"
|
|
428
|
+
: "Please select your country"
|
|
571
429
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
decoration="filled"
|
|
578
|
-
fullWidth
|
|
579
|
-
>
|
|
580
|
-
<SelectItem value="red">Red</SelectItem>
|
|
581
|
-
<SelectItem value="blue">Blue</SelectItem>
|
|
582
|
-
<SelectItem value="green">Green</SelectItem>
|
|
583
|
-
</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
|
+
]
|
|
584
435
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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 = {
|
|
590
538
|
parameters: {
|
|
591
539
|
docs: {
|
|
592
540
|
description: {
|
|
593
|
-
story:
|
|
594
|
-
Select
|
|
595
|
-
for form validation and submission. The select values will be included in form data.
|
|
596
|
-
`,
|
|
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.",
|
|
597
543
|
},
|
|
598
544
|
},
|
|
599
545
|
},
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
export const AtomicComposition: Story = {
|
|
603
546
|
render: () => {
|
|
604
|
-
const [
|
|
547
|
+
const [atomicValue, setAtomicValue] = React.useState("")
|
|
605
548
|
|
|
606
549
|
return (
|
|
607
|
-
<
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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"
|
|
613
566
|
decoration="outline"
|
|
614
|
-
|
|
615
|
-
aria-describedby="custom-select-helper"
|
|
567
|
+
fullWidth
|
|
616
568
|
>
|
|
617
|
-
<
|
|
618
|
-
|
|
569
|
+
<SelectItem value="react">React</SelectItem>
|
|
570
|
+
<SelectItem value="vue">Vue</SelectItem>
|
|
571
|
+
</SelectField>
|
|
572
|
+
</div>
|
|
573
|
+
</div>
|
|
619
574
|
|
|
620
|
-
|
|
621
|
-
|
|
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>
|
|
622
629
|
<SelectSeparator />
|
|
623
|
-
<SelectItem value="
|
|
630
|
+
<SelectItem value="banana">Banana</SelectItem>
|
|
624
631
|
<SelectSeparator />
|
|
625
|
-
<SelectItem value="
|
|
626
|
-
</
|
|
627
|
-
</
|
|
628
|
-
</
|
|
629
|
-
|
|
630
|
-
<SelectHelperText id="custom-select-helper" variant="default">
|
|
631
|
-
This is built using atomic components for maximum flexibility
|
|
632
|
-
</SelectHelperText>
|
|
633
|
-
</SelectRoot>
|
|
632
|
+
<SelectItem value="cherry">Cherry</SelectItem>
|
|
633
|
+
</SelectField>
|
|
634
|
+
</div>
|
|
635
|
+
</div>
|
|
636
|
+
</div>
|
|
634
637
|
)
|
|
635
638
|
},
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// ─── 7. UseCases ─────────────────────────────────────────────────────────────
|
|
642
|
+
|
|
643
|
+
export const UseCases: Story = {
|
|
636
644
|
parameters: {
|
|
637
645
|
docs: {
|
|
638
646
|
description: {
|
|
639
647
|
story:
|
|
640
|
-
"
|
|
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.",
|
|
641
649
|
},
|
|
642
650
|
},
|
|
643
651
|
},
|
|
644
|
-
|
|
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("")
|
|
645
657
|
|
|
646
|
-
|
|
647
|
-
|
|
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
|
-
<SelectItem value="option1">Option 1</SelectItem>
|
|
694
|
-
<SelectItem value="option2">Option 2</SelectItem>
|
|
695
|
-
</SelectField>
|
|
696
|
-
</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>
|
|
697
704
|
|
|
698
|
-
|
|
699
|
-
|
|
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
|
-
<SelectItem value="option1">Option 1</SelectItem>
|
|
732
|
-
<SelectItem value="option2">Option 2</SelectItem>
|
|
733
|
-
</SelectField>
|
|
734
|
-
<SelectField
|
|
735
|
-
label="Success"
|
|
736
|
-
placeholder="Success outline..."
|
|
737
|
-
variant="success"
|
|
738
|
-
decoration="outline"
|
|
739
|
-
helperText="Great choice!"
|
|
740
|
-
fullWidth
|
|
741
|
-
>
|
|
742
|
-
<SelectItem value="option1">Option 1</SelectItem>
|
|
743
|
-
<SelectItem value="option2">Option 2</SelectItem>
|
|
744
|
-
</SelectField>
|
|
745
|
-
</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>
|
|
746
738
|
|
|
747
|
-
|
|
748
|
-
|
|
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
|
-
|
|
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>
|
|
794
787
|
</div>
|
|
795
|
-
|
|
796
|
-
|
|
788
|
+
)
|
|
789
|
+
},
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// ─── 8. Accessibility ────────────────────────────────────────────────────────
|
|
793
|
+
|
|
794
|
+
export const Accessibility: Story = {
|
|
797
795
|
parameters: {
|
|
798
796
|
docs: {
|
|
799
797
|
description: {
|
|
800
798
|
story:
|
|
801
|
-
"
|
|
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.",
|
|
802
800
|
},
|
|
803
801
|
},
|
|
804
802
|
},
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
return (
|
|
813
|
-
<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>
|
|
814
810
|
<SelectField
|
|
815
811
|
label="Country"
|
|
816
|
-
placeholder="Select your country"
|
|
817
|
-
value={country}
|
|
818
|
-
onValueChange={setCountry}
|
|
812
|
+
placeholder="Select your country…"
|
|
819
813
|
required
|
|
820
|
-
helperText=
|
|
821
|
-
country
|
|
822
|
-
? "Country selected successfully"
|
|
823
|
-
: "Please select your country"
|
|
824
|
-
}
|
|
825
|
-
variant={country ? "success" : "default"}
|
|
814
|
+
helperText="Required — used for regional settings"
|
|
826
815
|
decoration="outline"
|
|
827
816
|
fullWidth
|
|
828
817
|
>
|
|
829
818
|
<SelectItem value="us">United States</SelectItem>
|
|
830
819
|
<SelectItem value="ca">Canada</SelectItem>
|
|
831
820
|
<SelectItem value="uk">United Kingdom</SelectItem>
|
|
832
|
-
<SelectItem value="de">Germany</SelectItem>
|
|
833
|
-
<SelectItem value="fr">France</SelectItem>
|
|
834
821
|
</SelectField>
|
|
822
|
+
</div>
|
|
835
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>
|
|
836
829
|
<SelectField
|
|
837
|
-
label="
|
|
838
|
-
placeholder="
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
decoration="filled"
|
|
830
|
+
label="Plan"
|
|
831
|
+
placeholder="Select a plan…"
|
|
832
|
+
variant="error"
|
|
833
|
+
helperText="Please select a plan to continue"
|
|
834
|
+
decoration="outline"
|
|
843
835
|
fullWidth
|
|
844
836
|
>
|
|
845
|
-
<
|
|
846
|
-
|
|
847
|
-
<SelectItem value="react">React</SelectItem>
|
|
848
|
-
<SelectSeparator />
|
|
849
|
-
<SelectItem value="vue">Vue.js</SelectItem>
|
|
850
|
-
<SelectSeparator />
|
|
851
|
-
<SelectItem value="angular">Angular</SelectItem>
|
|
852
|
-
</SelectGroup>
|
|
853
|
-
<SelectSeparator />
|
|
854
|
-
<SelectGroup>
|
|
855
|
-
<SelectLabel className="px-2 pt-2 pb-1">Others</SelectLabel>
|
|
856
|
-
<SelectItem value="svelte">Svelte</SelectItem>
|
|
857
|
-
<SelectSeparator />
|
|
858
|
-
<SelectItem value="solid">SolidJS</SelectItem>
|
|
859
|
-
</SelectGroup>
|
|
837
|
+
<SelectItem value="starter">Starter</SelectItem>
|
|
838
|
+
<SelectItem value="pro">Pro</SelectItem>
|
|
860
839
|
</SelectField>
|
|
861
840
|
</div>
|
|
862
|
-
)
|
|
863
|
-
},
|
|
864
|
-
parameters: {
|
|
865
|
-
docs: {
|
|
866
|
-
description: {
|
|
867
|
-
story:
|
|
868
|
-
"An example showing multiple SelectField components in a form with different configurations, decorations, and dynamic states based on user input.",
|
|
869
|
-
},
|
|
870
|
-
},
|
|
871
|
-
},
|
|
872
|
-
}
|
|
873
841
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
<SelectSeparator />
|
|
898
|
-
<SelectItem value="orange">Orange</SelectItem>
|
|
899
|
-
<SelectSeparator />
|
|
900
|
-
<SelectItem value="grape">Grape</SelectItem>
|
|
901
|
-
<SelectSeparator />
|
|
902
|
-
<SelectItem value="strawberry">Strawberry</SelectItem>
|
|
903
|
-
</SelectContent>
|
|
904
|
-
</Select>
|
|
905
|
-
),
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
export const LongList: Story = {
|
|
909
|
-
render: () => (
|
|
910
|
-
<SelectField
|
|
911
|
-
label="Country Selection"
|
|
912
|
-
placeholder="Select a country"
|
|
913
|
-
helperText="Scroll to see more options"
|
|
914
|
-
decoration="outline"
|
|
915
|
-
fullWidth
|
|
916
|
-
>
|
|
917
|
-
<SelectItem value="us">United States</SelectItem>
|
|
918
|
-
<SelectSeparator />
|
|
919
|
-
<SelectItem value="ca">Canada</SelectItem>
|
|
920
|
-
<SelectSeparator />
|
|
921
|
-
<SelectItem value="uk">United Kingdom</SelectItem>
|
|
922
|
-
<SelectSeparator />
|
|
923
|
-
<SelectItem value="fr">France</SelectItem>
|
|
924
|
-
<SelectSeparator />
|
|
925
|
-
<SelectItem value="de">Germany</SelectItem>
|
|
926
|
-
<SelectSeparator />
|
|
927
|
-
<SelectItem value="it">Italy</SelectItem>
|
|
928
|
-
<SelectSeparator />
|
|
929
|
-
<SelectItem value="es">Spain</SelectItem>
|
|
930
|
-
<SelectSeparator />
|
|
931
|
-
<SelectItem value="au">Australia</SelectItem>
|
|
932
|
-
<SelectSeparator />
|
|
933
|
-
<SelectItem value="jp">Japan</SelectItem>
|
|
934
|
-
<SelectSeparator />
|
|
935
|
-
<SelectItem value="kr">South Korea</SelectItem>
|
|
936
|
-
<SelectSeparator />
|
|
937
|
-
<SelectItem value="in">India</SelectItem>
|
|
938
|
-
<SelectSeparator />
|
|
939
|
-
<SelectItem value="br">Brazil</SelectItem>
|
|
940
|
-
<SelectSeparator />
|
|
941
|
-
<SelectItem value="mx">Mexico</SelectItem>
|
|
942
|
-
<SelectSeparator />
|
|
943
|
-
<SelectItem value="ar">Argentina</SelectItem>
|
|
944
|
-
<SelectSeparator />
|
|
945
|
-
<SelectItem value="cl">Chile</SelectItem>
|
|
946
|
-
<SelectSeparator />
|
|
947
|
-
<SelectItem value="pe">Peru</SelectItem>
|
|
948
|
-
<SelectSeparator />
|
|
949
|
-
<SelectItem value="co">Colombia</SelectItem>
|
|
950
|
-
<SelectSeparator />
|
|
951
|
-
<SelectItem value="ve">Venezuela</SelectItem>
|
|
952
|
-
<SelectSeparator />
|
|
953
|
-
<SelectItem value="ec">Ecuador</SelectItem>
|
|
954
|
-
<SelectSeparator />
|
|
955
|
-
<SelectItem value="uy">Uruguay</SelectItem>
|
|
956
|
-
</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>
|
|
957
865
|
),
|
|
958
|
-
parameters: {
|
|
959
|
-
docs: {
|
|
960
|
-
description: {
|
|
961
|
-
story: `
|
|
962
|
-
Select with many options that demonstrates scrolling behavior.
|
|
963
|
-
The content area becomes scrollable when it exceeds the maximum height,
|
|
964
|
-
and scroll buttons appear automatically at the top and bottom.
|
|
965
|
-
`,
|
|
966
|
-
},
|
|
967
|
-
},
|
|
968
|
-
},
|
|
969
866
|
}
|