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.
Files changed (174) hide show
  1. package/dist/components/aspect-ratio/AspectRatio.stories.tsx +290 -1228
  2. package/dist/components/avatar/Avatar.stories.tsx +219 -235
  3. package/dist/components/badge/Badge.stories.tsx +379 -116
  4. package/dist/components/banner/Banner.stories.tsx +445 -391
  5. package/dist/components/breadcrumb/Breadcrumb.stories.tsx +453 -199
  6. package/dist/components/button/Button.stories.tsx +585 -230
  7. package/dist/components/card/Card.stories.tsx +619 -301
  8. package/dist/components/char-count/CharCount.stories.tsx +350 -248
  9. package/dist/components/checkbox/Checkbox.stories.tsx +309 -167
  10. package/dist/components/chip/Chip.stories.tsx +362 -168
  11. package/dist/components/circular-loader/CircularLoader.stories.tsx +221 -636
  12. package/dist/components/clamp-lines/ClampLines.stories.tsx +246 -117
  13. package/dist/components/collapsible/Collapsible.stories.tsx +391 -252
  14. package/dist/components/command/Command.stories.tsx +530 -867
  15. package/dist/components/dialog/Dialog.stories.tsx +501 -950
  16. package/dist/components/divider/Divider.stories.tsx +264 -527
  17. package/dist/components/dot-loader/DotLoader.stories.tsx +256 -257
  18. package/dist/components/drawer/Drawer.stories.tsx +659 -1023
  19. package/dist/components/dropdown/Dropdown.stories.tsx +643 -1028
  20. package/dist/components/form/Form.stories.tsx +560 -274
  21. package/dist/components/helper-text/HelperText.stories.tsx +199 -200
  22. package/dist/components/hover-card/HoverCard.stories.tsx +318 -1254
  23. package/dist/components/icon-button/IconButton.stories.tsx +837 -194
  24. package/dist/components/if-else/if-else.stories.tsx +370 -83
  25. package/dist/components/input/Input.stories.tsx +436 -368
  26. package/dist/components/label/Label.stories.tsx +156 -154
  27. package/dist/components/list/List.stories.tsx +484 -835
  28. package/dist/components/marquee/Marquee.stories.tsx +356 -712
  29. package/dist/components/otp-inputs/OtpInputs.stories.tsx +352 -422
  30. package/dist/components/overlay/Overlay.stories.tsx +452 -824
  31. package/dist/components/pagination/Pagination.stories.tsx +721 -210
  32. package/dist/components/popover/Popover.stories.tsx +481 -896
  33. package/dist/components/radio/Radio.stories.tsx +432 -124
  34. package/dist/components/resizable/Resizable.stories.tsx +495 -799
  35. package/dist/components/scroll-area/ScrollArea.stories.tsx +383 -1059
  36. package/dist/components/search/Search.stories.tsx +312 -595
  37. package/dist/components/select/Select.stories.tsx +684 -789
  38. package/dist/components/sheet/Sheet.stories.tsx +671 -950
  39. package/dist/components/skelton/Skelton.stories.tsx +230 -764
  40. package/dist/components/slider/Slider.stories.tsx +383 -760
  41. package/dist/components/stepper/Stepper.stories.tsx +371 -514
  42. package/dist/components/switch/Switch.stories.tsx +461 -208
  43. package/dist/components/switch-case/SwitchCase.stories.tsx +367 -188
  44. package/dist/components/table/Table.stories.tsx +770 -916
  45. package/dist/components/tabs/Tabs.stories.tsx +458 -1455
  46. package/dist/components/tag/Tag.stories.tsx +714 -542
  47. package/dist/components/textarea/TextArea.stories.tsx +621 -562
  48. package/dist/components/thumbnail-tags/ThumbnailTags.stories.tsx +228 -154
  49. package/dist/components/toast/Toast.stories.tsx +452 -1339
  50. package/dist/components/toggle/Toggle.stories.tsx +488 -931
  51. package/dist/components/tooltip/Tooltip.stories.tsx +344 -1388
  52. package/dist/components/typography/Typography.stories.tsx +406 -89
  53. package/dist/hooks/use-change-state/UseChangeState.stories.tsx +309 -606
  54. package/dist/hooks/use-previous/UsePrevious.stories.tsx +367 -917
  55. package/dist/hooks/use-standalone-pagination/UseStandalonePagination.stories.tsx +639 -867
  56. package/dist/icons/Icons.stories.tsx +0 -12
  57. package/dist/icons/ai-avatar-icon/AiAvatarIcon.stories.tsx +223 -1060
  58. package/dist/icons/alert-icon/AlertIcon.stories.tsx +106 -968
  59. package/dist/icons/all-icons.tsx +37 -16
  60. package/dist/icons/angle-down-icon/AngleDownIcon.stories.tsx +137 -1010
  61. package/dist/icons/apple-logo-icon/AppleLogoIcon.stories.tsx +145 -935
  62. package/dist/icons/arrow-box-left-icon/ArrowBoxLeftIcon.stories.tsx +132 -1046
  63. package/dist/icons/arrow-corner-up-left-icon/ArrowCornerUpLeftIcon.stories.tsx +134 -986
  64. package/dist/icons/arrow-corner-up-right-icon/ArrowCornerUpRightIcon.stories.tsx +135 -1028
  65. package/dist/icons/arrow-left-icon/ArrowLeftIcon.stories.tsx +133 -971
  66. package/dist/icons/arrow-right-icon/ArrowRightIcon.stories.tsx +145 -1123
  67. package/dist/icons/arrow-right-up-icon/ArrowRightUpIcon.stories.tsx +143 -1252
  68. package/dist/icons/art-board-icon/ArtBoardIcon.stories.tsx +123 -632
  69. package/dist/icons/audio-bar-icon/AudioBarIcon.stories.tsx +141 -1223
  70. package/dist/icons/backward-ten-seconds-icon/BackwardTenSecondsIcon.stories.tsx +164 -1018
  71. package/dist/icons/bubble-check-icon/BubbleCheckIcon.stories.tsx +121 -1236
  72. package/dist/icons/bubble-crossed-icon/BubbleCrossedIcon.stories.tsx +121 -1213
  73. package/dist/icons/bubble-sparkle-icon/BubbleSparkleIcon.stories.tsx +116 -893
  74. package/dist/icons/camera-icon/CameraIcon.stories.tsx +109 -1254
  75. package/dist/icons/capital-a-letter-icon/CapitalALetterIcon.stories.tsx +114 -975
  76. package/dist/icons/chevron-double-left-icon/ChevronDoubleLeftIcon.stories.tsx +157 -994
  77. package/dist/icons/chevron-double-right-icon/ChevronDoubleRightIcon.stories.tsx +160 -992
  78. package/dist/icons/chevron-down-icon/ChevronDownIcon.stories.tsx +140 -970
  79. package/dist/icons/chevron-left-icon/ChevronLeftIcon.stories.tsx +126 -993
  80. package/dist/icons/chevron-right-icon/ChevronRightIcon.stories.tsx +144 -987
  81. package/dist/icons/chevron-up-icon/ChevronUpIcon.stories.tsx +141 -1007
  82. package/dist/icons/circle-tick-icon/CircleTickIcon.stories.tsx +147 -1187
  83. package/dist/icons/circular-play-icon/CircularPlayIcon.stories.tsx +110 -476
  84. package/dist/icons/coin-icon/CoinIcon.stories.tsx +120 -1364
  85. package/dist/icons/coin-toons-icon/CoinToonsIcon.stories.tsx +113 -1360
  86. package/dist/icons/column-wide-add-icon/ColumnWideAddIcon.stories.tsx +111 -942
  87. package/dist/icons/command-icon/CommandIcon.stories.tsx +124 -1087
  88. package/dist/icons/copy-icon/CopyIcon.stories.tsx +119 -996
  89. package/dist/icons/cross-circle-icon/CrossCircleIcon.stories.tsx +144 -1046
  90. package/dist/icons/cross-icon/CrossIcon.stories.tsx +136 -999
  91. package/dist/icons/download-icon/DownloadIcon.stories.tsx +123 -857
  92. package/dist/icons/edit-big-icon/EditBigIcon.stories.tsx +121 -1080
  93. package/dist/icons/email-icon/EmailIcon.stories.tsx +112 -979
  94. package/dist/icons/expand-icon/ExpandIcon.stories.tsx +109 -1146
  95. package/dist/icons/eye-close-icon/EyeCloseIcon.stories.tsx +141 -1068
  96. package/dist/icons/eye-open-icon/EyeOpenIcon.stories.tsx +140 -1081
  97. package/dist/icons/feature-shine-icon/FeatureShineIcon.stories.tsx +124 -1050
  98. package/dist/icons/file-chart-icon/FileChartIcon.stories.tsx +123 -1091
  99. package/dist/icons/file-text-icon/FileTextIcon.stories.tsx +122 -633
  100. package/dist/icons/filter-bar-row-icon/FilterBarRowIcon.stories.tsx +116 -1087
  101. package/dist/icons/forward-ten-seconds-icon/ForwardTenSecondsIcon.stories.tsx +166 -1020
  102. package/dist/icons/git-branch-icon/GitBranchIcon.stories.tsx +112 -1182
  103. package/dist/icons/git-fork-icon/GitForkIcon.stories.tsx +112 -1155
  104. package/dist/icons/globe-icon/GlobeIcon.stories.tsx +127 -325
  105. package/dist/icons/google-logo-icon/GoogleLogoIcon.stories.tsx +142 -985
  106. package/dist/icons/grip-vertical-icon/GripVerticalIcon.stories.tsx +116 -1217
  107. package/dist/icons/head-icon/HeadIcon.stories.tsx +108 -953
  108. package/dist/icons/heart-icon/HeartIcon.stories.tsx +117 -1060
  109. package/dist/icons/image-avatar-sparkle-icon/ImageAvatarSparkleIcon.stories.tsx +116 -716
  110. package/dist/icons/image-icon/ImageIcon.stories.tsx +102 -1164
  111. package/dist/icons/import-folder-icon/ImportFolderIcon.stories.tsx +108 -1233
  112. package/dist/icons/import-left-arrow-folder-icon/ImportLeftArrowFolderIcon.stories.tsx +133 -1289
  113. package/dist/icons/indian-flag-icon/IndianFlagIcon.stories.tsx +155 -1012
  114. package/dist/icons/instagram-icon/InstagramIcon.stories.tsx +158 -1438
  115. package/dist/icons/layout-column-icon/LayoutColumnIcon.stories.tsx +121 -1011
  116. package/dist/icons/layout-left-icon/LayoutLeftIcon.stories.tsx +116 -981
  117. package/dist/icons/layout-right-icon/LayoutRightIcon.stories.tsx +116 -979
  118. package/dist/icons/light-bulb-simple-icon/LightBulbSimpleIcon.stories.tsx +105 -1252
  119. package/dist/icons/linked-in-icon/LinkedInIcon.stories.tsx +151 -1554
  120. package/dist/icons/magic-book-icon/MagicBookIcon.stories.tsx +107 -1227
  121. package/dist/icons/magic-edit-icon/MagicEditIcon.stories.tsx +116 -707
  122. package/dist/icons/maintenance-icon/MaintenanceIcon.stories.tsx +119 -1226
  123. package/dist/icons/message-icon/MessageIcon.stories.tsx +111 -557
  124. package/dist/icons/minimize-icon/MinimizeIcon.stories.tsx +112 -1198
  125. package/dist/icons/moon-icon/MoonIcon.stories.tsx +117 -557
  126. package/dist/icons/move-horizontal-icon/MoveHorizontalIcon.stories.tsx +106 -1235
  127. package/dist/icons/move-vertical-icon/MoveVerticalIcon.stories.tsx +112 -1185
  128. package/dist/icons/musical-note-icon/MusicalNoteIcon.stories.tsx +116 -1012
  129. package/dist/icons/notepad-icon/NotepadIcon.stories.tsx +108 -1137
  130. package/dist/icons/notes-icon/NotesIcon.stories.tsx +116 -1138
  131. package/dist/icons/page-search-icon/PageSearchIcon.stories.tsx +106 -1146
  132. package/dist/icons/page-text-icon/PageTextIcon.stories.tsx +119 -719
  133. package/dist/icons/paint-roll-icon/PaintRollIcon.stories.tsx +110 -999
  134. package/dist/icons/paper-plane-icon/PaperPlaneIcon.stories.tsx +109 -912
  135. package/dist/icons/pause-icon/PauseIcon.stories.tsx +110 -1041
  136. package/dist/icons/pencil-icon/PencilIcon.stories.tsx +112 -1109
  137. package/dist/icons/phone-icon/PhoneIcon.stories.tsx +112 -1023
  138. package/dist/icons/plus-icon/PlusIcon.stories.tsx +103 -1132
  139. package/dist/icons/pocket-studio-icon/PocketStudioIcon.stories.tsx +104 -870
  140. package/dist/icons/scroll-down-icon/ScrollDownIcon.stories.tsx +99 -476
  141. package/dist/icons/search-icon/SearchIcon.stories.tsx +108 -1161
  142. package/dist/icons/setting-icon/SettingIcon.stories.tsx +104 -1009
  143. package/dist/icons/share-icon/ShareIcon.stories.tsx +117 -1064
  144. package/dist/icons/shield-icon/ShieldIcon.stories.tsx +114 -974
  145. package/dist/icons/site-logo-icon/SiteLogoIcon.stories.tsx +134 -1160
  146. package/dist/icons/skip-backward-icon/SkipBackwardIcon.stories.tsx +169 -1017
  147. package/dist/icons/skip-forward-icon/SkipForwardIcon.stories.tsx +161 -1016
  148. package/dist/icons/sparkles-soft-icon/SparklesSoftIcon.stories.tsx +102 -1001
  149. package/dist/icons/spinner-gradient-icon/SpinnerGradientIcon.stories.tsx +155 -593
  150. package/dist/icons/spinner-solid-icon/SpinnerSolidIcon.stories.tsx +155 -608
  151. package/dist/icons/spinner-solid-neutral-icon/SpinnerSolidINeutralcon.stories.tsx +142 -712
  152. package/dist/icons/star-icon/StarIcon.stories.tsx +120 -946
  153. package/dist/icons/store-coin-icon/StoreCoinIcon.stories.tsx +109 -1013
  154. package/dist/icons/suggestion-icon/SuggestionIcon.stories.tsx +113 -891
  155. package/dist/icons/sun-icon/SunIcon.stories.tsx +117 -864
  156. package/dist/icons/text-color-icon/TextColorIcon.stories.tsx +113 -989
  157. package/dist/icons/text-indicator-icon/TextIndicatorIcon.stories.tsx +120 -1027
  158. package/dist/icons/threads-icon/ThreadsIcon.stories.tsx +153 -1476
  159. package/dist/icons/tick-circle-icon/TickCircleIcon.stories.tsx +143 -1187
  160. package/dist/icons/tick-icon/TickIcon.stories.tsx +142 -1322
  161. package/dist/icons/trash-icon/TrashIcon.stories.tsx +105 -970
  162. package/dist/icons/twitter-x-icon/TwitterXIcon.stories.tsx +154 -1457
  163. package/dist/icons/upload-icon/UploadIcon.stories.tsx +112 -930
  164. package/dist/icons/vertical-menu-icon/VerticalMenuIcon.stories.tsx +115 -1019
  165. package/dist/icons/video-play-list-icon/VideoPlaylistIcon.stories.tsx +122 -1092
  166. package/dist/icons/voice-playing-icon/VoicePlayingIcon.stories.tsx +120 -1401
  167. package/dist/icons/volume-full-icon/VolumeFullIcon.stories.tsx +107 -1212
  168. package/dist/icons/volume-half-icon/VolumeHalfIcon.stories.tsx +109 -1122
  169. package/dist/icons/volume-off-icon/VolumeOffIcon.stories.tsx +112 -1124
  170. package/dist/icons/warning-icon/WarningIcon.stories.tsx +119 -1083
  171. package/dist/icons/youtube-icon/YoutubeIcon.stories.tsx +158 -983
  172. package/dist/index.cjs +1 -1
  173. package/dist/index.js +1 -1
  174. package/package.json +1 -1
@@ -1,30 +1,30 @@
1
1
  import React from "react"
2
- import { Badge } from "@components/badge"
3
2
  import { Button } from "@components/button"
4
3
  import {
5
- ArrowRightIcon,
6
- BubbleCheckIcon,
7
- BubbleSparkleIcon,
4
+ AudioBarIcon,
8
5
  ChevronLeftIcon,
9
- ChevronUpIcon,
10
- CommandIcon,
11
- CrossCircleIcon,
6
+ ChevronRightIcon,
7
+ CircleTickIcon,
8
+ DownloadIcon,
12
9
  EditBigIcon,
13
- EyeOpenIcon,
14
- FeatureShineIcon,
15
- FileChartIcon,
10
+ FileTextIcon,
11
+ HeartIcon,
16
12
  ImageIcon,
17
- ImportFolderIcon,
18
- LightBulbSimpleIcon,
19
- MagicBookIcon,
20
13
  MaintenanceIcon,
14
+ MusicalNoteIcon,
21
15
  SearchIcon,
22
- TickIcon,
16
+ ShareIcon,
17
+ SkipForwardIcon,
18
+ SparklesSoftIcon,
19
+ StarIcon,
23
20
  TrashIcon,
24
21
  UploadIcon,
22
+ VerticalMenuIcon,
25
23
  } from "@icons/index"
26
24
  import type { Meta, StoryObj } from "@storybook/react-vite"
27
25
 
26
+ import { AuralComponentDocsPage } from "src/ui/story-spec/components/component-story-docs-page"
27
+
28
28
  import {
29
29
  Command,
30
30
  CommandDialog,
@@ -43,81 +43,29 @@ const meta: Meta<typeof Command> = {
43
43
  component: Command,
44
44
  parameters: {
45
45
  layout: "fullscreen",
46
- backgrounds: {
47
- default: "dark",
48
- values: [
49
- { name: "dark", value: "#0a0a0a" },
50
- { name: "light", value: "#ffffff" },
51
- ],
52
- },
53
46
  docs: {
54
47
  description: {
55
- component: `
56
- # Command Component
57
-
58
- A fast, unstyled command menu component built on top of CMDK with integration to our design system's List components. Perfect for creating command palettes, search interfaces, and quick action menus.
59
-
60
- ## Features
61
-
62
- - **Fast Fuzzy Search**: Built-in fuzzy search with instant results
63
- - **Keyboard Navigation**: Full keyboard support with arrow keys and Enter
64
- - **Grouping**: Organize commands into logical groups with labels
65
- - **Shortcuts**: Display keyboard shortcuts for commands
66
- - **Icon Support**: Rich icon support for visual command identification
67
- - **Customizable**: Extensive theming and styling options
68
- - **Accessible**: ARIA compliant with screen reader support
69
- - **Dialog Mode**: Can be used as a modal command palette
70
- - **Empty States**: Customizable empty state when no results found
71
-
72
- ## Usage Examples
73
-
74
- ### Basic Command Menu
75
- \`\`\`tsx
76
- <Command>
77
- <CommandInput placeholder="Type a command..." />
78
- <CommandList>
79
- <CommandEmpty>No results found.</CommandEmpty>
80
- <CommandGroup>
81
- <CommandLabel>Suggestions</CommandLabel>
82
- <CommandItem>
83
- <FileChartIcon />
84
- New File
85
- <CommandShortcut>⌘N</CommandShortcut>
86
- </CommandItem>
87
- </CommandGroup>
88
- </CommandList>
89
- </Command>
90
- \`\`\`
91
-
92
- ### Command Dialog
93
- \`\`\`tsx
94
- <CommandDialog open={open} onOpenChange={setOpen}>
95
- <CommandInput placeholder="Search commands..." />
96
- <CommandList>
97
- <CommandEmpty>No commands found.</CommandEmpty>
98
- <CommandGroup>
99
- <CommandLabel>Actions</CommandLabel>
100
- <CommandItem>Save File</CommandItem>
101
- <CommandItem>Export</CommandItem>
102
- </CommandGroup>
103
- </CommandList>
104
- </CommandDialog>
105
- \`\`\`
106
-
107
- ### With Custom Styling
108
- \`\`\`tsx
109
- <Command
110
- listProps={{ variant: "elevated", size: "lg" }}
111
- classes={{ list: "custom-command-list" }}
112
- >
113
- <CommandInput />
114
- <CommandList>
115
- <CommandItem variant="destructive">Delete</CommandItem>
116
- </CommandList>
117
- </Command>
118
- \`\`\`
119
- `,
48
+ component:
49
+ "A fast command palette component built on CMDK, integrated with the design system's List components. Supports inline and modal (CommandDialog) modes, grouped items, live fuzzy search, keyboard navigation, icon support, keyboard shortcuts, and a destructive variant for dangerous actions. Use it for global search, quick actions, and context-sensitive command menus in any audio-focused or general-purpose application.",
120
50
  },
51
+ page: () => (
52
+ <AuralComponentDocsPage
53
+ features={[
54
+ {
55
+ title: "Inline & Modal Modes",
56
+ description: "Palette or dialog overlay",
57
+ },
58
+ {
59
+ title: "Fuzzy Search",
60
+ description: "Live filtering built in",
61
+ },
62
+ {
63
+ title: "Keyboard Navigation",
64
+ description: "Arrow keys and shortcuts",
65
+ },
66
+ ]}
67
+ />
68
+ ),
121
69
  },
122
70
  },
123
71
  tags: ["autodocs"],
@@ -126,172 +74,145 @@ A fast, unstyled command menu component built on top of CMDK with integration to
126
74
  export default meta
127
75
  type Story = StoryObj<typeof Command>
128
76
 
129
- // 1. Basic Command
130
- export const BasicCommand: Story = {
131
- render: () => (
132
- <div className="mx-auto max-w-lg p-8">
133
- <h3 className="text-fm-primary mb-4 text-lg font-medium">
134
- Basic Command Menu
135
- </h3>
136
- <Command className="border-fm-divider-secondary rounded-lg border">
137
- <CommandInput placeholder="Type a command or search..." />
138
- <CommandList>
139
- <CommandEmpty>No results found.</CommandEmpty>
140
- <CommandGroup>
141
- <CommandLabel>Suggestions</CommandLabel>
142
- <CommandItem>
143
- <SearchIcon />
144
- Search Files
145
- </CommandItem>
146
- <CommandItem>
147
- <ImageIcon />
148
- View Images
149
- </CommandItem>
150
- <CommandItem>
151
- <FileChartIcon />
152
- Open Reports
153
- </CommandItem>
154
- </CommandGroup>
155
- <CommandSeparator />
156
- <CommandGroup>
157
- <CommandLabel>Settings</CommandLabel>
158
- <CommandItem shortcut="⌘P">
159
- <EyeOpenIcon />
160
- View Profile
161
- </CommandItem>
162
- <CommandItem shortcut="⌘,">
163
- <MaintenanceIcon />
164
- Settings
165
- </CommandItem>
166
- </CommandGroup>
167
- </CommandList>
168
- </Command>
169
- </div>
170
- ),
171
- parameters: {
172
- docs: {
173
- description: {
174
- story:
175
- "A basic command menu with search input, grouped items, icons, and keyboard shortcuts.",
176
- },
177
- },
178
- },
179
- }
77
+ // ─── Configurations ──────────────────────────────────────────────────────────
180
78
 
181
- // 2. Command Dialog
182
- export const CommandDialogExample: Story = {
79
+ export const Configurations: Story = {
183
80
  render: () => {
184
- const [open, setOpen] = React.useState(false)
81
+ const [dialogOpen, setDialogOpen] = React.useState(false)
185
82
 
186
83
  React.useEffect(() => {
187
84
  const down = (e: KeyboardEvent) => {
188
85
  if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
189
86
  e.preventDefault()
190
- setOpen((open) => !open)
87
+ setDialogOpen((v) => !v)
191
88
  }
192
89
  }
193
-
194
90
  document.addEventListener("keydown", down)
195
91
  return () => document.removeEventListener("keydown", down)
196
92
  }, [])
197
93
 
198
94
  return (
199
95
  <div className="space-y-8 p-8">
200
- <div className="text-center">
201
- <h3 className="text-fm-primary mb-2 text-lg font-medium">
202
- Command Dialog
203
- </h3>
204
- <p className="text-fm-secondary text-sm">
205
- Press{" "}
206
- <kbd className="bg-muted text-muted-foreground pointer-events-none inline-flex h-5 items-center gap-1 rounded border px-1.5 font-mono text-[10px] font-medium opacity-100 select-none">
207
- <span className="text-xs">⌘</span>K
208
- </kbd>{" "}
209
- to open the command dialog
210
- </p>
96
+ {/* Inline palette */}
97
+ <div className="space-y-3">
98
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
99
+ Inline Command Palette
100
+ </h4>
101
+ <div className="mx-auto max-w-md">
102
+ <Command className="border-fm-divider-secondary rounded-lg border">
103
+ <CommandInput placeholder="Search tracks, artists, playlists…" />
104
+ <CommandList>
105
+ <CommandEmpty>No results found.</CommandEmpty>
106
+ <CommandGroup>
107
+ <CommandLabel>Library</CommandLabel>
108
+ <CommandItem>
109
+ <MusicalNoteIcon />
110
+ Browse Tracks
111
+ <CommandShortcut>⌘T</CommandShortcut>
112
+ </CommandItem>
113
+ <CommandItem>
114
+ <StarIcon />
115
+ Favourites
116
+ <CommandShortcut>⌘F</CommandShortcut>
117
+ </CommandItem>
118
+ <CommandItem>
119
+ <DownloadIcon />
120
+ Downloaded
121
+ <CommandShortcut>⌘D</CommandShortcut>
122
+ </CommandItem>
123
+ </CommandGroup>
124
+ <CommandSeparator />
125
+ <CommandGroup>
126
+ <CommandLabel>Actions</CommandLabel>
127
+ <CommandItem>
128
+ <UploadIcon />
129
+ Upload Track
130
+ <CommandShortcut>⌘U</CommandShortcut>
131
+ </CommandItem>
132
+ <CommandItem variant="destructive">
133
+ <TrashIcon />
134
+ Delete Selected
135
+ <CommandShortcut>⌘⌫</CommandShortcut>
136
+ </CommandItem>
137
+ </CommandGroup>
138
+ </CommandList>
139
+ </Command>
140
+ </div>
211
141
  </div>
212
142
 
213
- <div className="flex justify-center">
214
- <Button
215
- variant="outline"
216
- onClick={() => setOpen(true)}
217
- className="relative w-80"
218
- innerClassName="flex items-center justify-between gap-2"
143
+ {/* Dialog / modal palette */}
144
+ <div className="space-y-3">
145
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
146
+ Modal Command Dialog (⌘K)
147
+ </h4>
148
+ <div className="flex justify-center">
149
+ <Button
150
+ variant="outline"
151
+ onClick={() => setDialogOpen(true)}
152
+ innerClassName="flex items-center gap-2"
153
+ >
154
+ <SearchIcon className="size-4" />
155
+ Search commands…
156
+ <span className="font-fm-brand text-fm-secondary ml-2 text-xs tracking-widest opacity-60">
157
+ ⌘K
158
+ </span>
159
+ </Button>
160
+ </div>
161
+
162
+ <CommandDialog
163
+ open={dialogOpen}
164
+ onOpenChange={setDialogOpen}
165
+ title="Command Palette"
166
+ description="Search tracks, artists, and app actions"
219
167
  >
220
- <span className="flex items-center gap-2">
221
- <SearchIcon className="mr-2 h-4 w-4" />
222
- Search commands...
223
- </span>
224
- <kbd className="font-fm-brand text-fm-xs pointer-events-none hidden h-5 items-center gap-1 rounded border px-1.5 font-medium opacity-100 select-none sm:flex">
225
- <span className="text-xs">⌘</span>K
226
- </kbd>
227
- </Button>
168
+ <CommandInput placeholder="Search tracks, artists, actions…" />
169
+ <CommandList>
170
+ <CommandEmpty>No results found.</CommandEmpty>
171
+ <CommandGroup>
172
+ <CommandLabel>Quick Actions</CommandLabel>
173
+ <CommandItem onSelect={() => setDialogOpen(false)}>
174
+ <MusicalNoteIcon />
175
+ Play Next
176
+ <CommandShortcut>⌘→</CommandShortcut>
177
+ </CommandItem>
178
+ <CommandItem onSelect={() => setDialogOpen(false)}>
179
+ <HeartIcon />
180
+ Like Current Track
181
+ <CommandShortcut>⌘L</CommandShortcut>
182
+ </CommandItem>
183
+ <CommandItem onSelect={() => setDialogOpen(false)}>
184
+ <ShareIcon />
185
+ Share Track
186
+ <CommandShortcut>⌘⇧S</CommandShortcut>
187
+ </CommandItem>
188
+ </CommandGroup>
189
+ <CommandSeparator />
190
+ <CommandGroup>
191
+ <CommandLabel>Navigation</CommandLabel>
192
+ <CommandItem onSelect={() => setDialogOpen(false)}>
193
+ <ChevronRightIcon />
194
+ Library
195
+ </CommandItem>
196
+ <CommandItem onSelect={() => setDialogOpen(false)}>
197
+ <ChevronLeftIcon />
198
+ Go Back
199
+ </CommandItem>
200
+ </CommandGroup>
201
+ <CommandSeparator />
202
+ <CommandGroup>
203
+ <CommandLabel>Danger Zone</CommandLabel>
204
+ <CommandItem
205
+ variant="destructive"
206
+ onSelect={() => setDialogOpen(false)}
207
+ >
208
+ <TrashIcon />
209
+ Remove from Library
210
+ <CommandShortcut>⌘⌫</CommandShortcut>
211
+ </CommandItem>
212
+ </CommandGroup>
213
+ </CommandList>
214
+ </CommandDialog>
228
215
  </div>
229
-
230
- <CommandDialog
231
- open={open}
232
- onOpenChange={setOpen}
233
- title="Command Palette"
234
- description="Search for commands and actions"
235
- >
236
- <CommandInput placeholder="Type a command or search..." />
237
- <CommandList>
238
- <CommandEmpty>No results found.</CommandEmpty>
239
- <CommandGroup>
240
- <CommandLabel>Quick Actions</CommandLabel>
241
- <CommandItem onSelect={() => setOpen(false)}>
242
- <FileChartIcon />
243
- New File
244
- <CommandShortcut>⌘N</CommandShortcut>
245
- </CommandItem>
246
- <CommandItem onSelect={() => setOpen(false)}>
247
- <ImportFolderIcon />
248
- Import Folder
249
- <CommandShortcut>⌘⇧N</CommandShortcut>
250
- </CommandItem>
251
- <CommandItem onSelect={() => setOpen(false)}>
252
- <UploadIcon />
253
- Upload
254
- <CommandShortcut>⌘U</CommandShortcut>
255
- </CommandItem>
256
- <CommandItem onSelect={() => setOpen(false)}>
257
- <ImageIcon />
258
- Add Image
259
- <CommandShortcut>⌘I</CommandShortcut>
260
- </CommandItem>
261
- </CommandGroup>
262
- <CommandSeparator />
263
- <CommandGroup>
264
- <CommandLabel>Navigation</CommandLabel>
265
- <CommandItem onSelect={() => setOpen(false)}>
266
- <ArrowRightIcon />
267
- Go Forward
268
- <CommandShortcut>⌘→</CommandShortcut>
269
- </CommandItem>
270
- <CommandItem onSelect={() => setOpen(false)}>
271
- <ChevronLeftIcon />
272
- Go Back
273
- <CommandShortcut>⌘←</CommandShortcut>
274
- </CommandItem>
275
- <CommandItem onSelect={() => setOpen(false)}>
276
- <ChevronUpIcon />
277
- Go Up
278
- <CommandShortcut>⌘↑</CommandShortcut>
279
- </CommandItem>
280
- </CommandGroup>
281
- <CommandSeparator />
282
- <CommandGroup>
283
- <CommandLabel>Dangerous Actions</CommandLabel>
284
- <CommandItem
285
- variant="destructive"
286
- onSelect={() => setOpen(false)}
287
- >
288
- <TrashIcon />
289
- Delete
290
- <CommandShortcut>⌘⌫</CommandShortcut>
291
- </CommandItem>
292
- </CommandGroup>
293
- </CommandList>
294
- </CommandDialog>
295
216
  </div>
296
217
  )
297
218
  },
@@ -299,258 +220,239 @@ export const CommandDialogExample: Story = {
299
220
  docs: {
300
221
  description: {
301
222
  story:
302
- "A command dialog that can be opened with ⌘K. Includes multiple command groups and keyboard shortcuts.",
223
+ "Two configuration modes side by side: an inline Command palette always visible on the page, and a CommandDialog that opens as a modal overlay triggered by a button or ⌘K. Both share the same internal anatomy — input, list, groups, separator, shortcuts, and the destructive variant.",
303
224
  },
304
225
  },
305
226
  },
306
227
  }
307
228
 
308
- // 3. File Management Commands
309
- export const FileManagementCommands: Story = {
310
- render: () => (
311
- <div className="mx-auto max-w-lg space-y-8 p-8">
312
- <h3 className="text-fm-primary text-lg font-medium">File Management</h3>
313
- <Command className="border-fm-divider-secondary rounded-lg border">
314
- <CommandInput placeholder="Search files and actions..." />
315
- <CommandList>
316
- <CommandEmpty>
317
- <div className="py-6 text-center">
318
- <SearchIcon className="text-fm-tertiary mx-auto h-8 w-8" />
319
- <p className="text-fm-secondary mt-2 text-sm">
320
- No files or actions found.
321
- </p>
322
- </div>
323
- </CommandEmpty>
324
-
325
- <CommandGroup>
326
- <CommandLabel>Create</CommandLabel>
327
- <CommandItem>
328
- <FileChartIcon />
329
- New Document
330
- <CommandShortcut>⌘N</CommandShortcut>
331
- </CommandItem>
332
- <CommandItem>
333
- <ImportFolderIcon />
334
- New Folder
335
- <CommandShortcut>⌘⇧N</CommandShortcut>
336
- </CommandItem>
337
- <CommandItem>
338
- <EditBigIcon />
339
- New Template
340
- <CommandShortcut>⌘T</CommandShortcut>
341
- </CommandItem>
342
- </CommandGroup>
343
-
344
- <CommandSeparator />
345
-
346
- <CommandGroup>
347
- <CommandLabel>Actions</CommandLabel>
348
- <CommandItem>
349
- <BubbleCheckIcon />
350
- Approve
351
- <CommandShortcut>⌘A</CommandShortcut>
352
- </CommandItem>
353
- <CommandItem>
354
- <EditBigIcon />
355
- Edit
356
- <CommandShortcut>⌘E</CommandShortcut>
357
- </CommandItem>
358
- <CommandItem>
359
- <BubbleSparkleIcon />
360
- Share
361
- <CommandShortcut>⌘⇧S</CommandShortcut>
362
- </CommandItem>
363
- <CommandItem>
364
- <TickIcon />
365
- Mark Complete
366
- <CommandShortcut>⌘⇧F</CommandShortcut>
367
- </CommandItem>
368
- </CommandGroup>
369
-
370
- <CommandSeparator />
371
-
372
- <CommandGroup>
373
- <CommandLabel>Import/Export</CommandLabel>
374
- <CommandItem>
375
- <UploadIcon />
376
- Import Files
377
- <CommandShortcut>⌘I</CommandShortcut>
378
- </CommandItem>
379
- <CommandItem>
380
- <ArrowRightIcon />
381
- Export Selection
382
- <CommandShortcut>⌘E</CommandShortcut>
383
- </CommandItem>
384
- <CommandItem>
385
- <MaintenanceIcon />
386
- Sync
387
- <CommandShortcut>⌘R</CommandShortcut>
388
- </CommandItem>
389
- </CommandGroup>
390
-
391
- <CommandSeparator />
392
-
393
- <CommandGroup>
394
- <CommandLabel>Danger Zone</CommandLabel>
395
- <CommandItem variant="destructive">
396
- <TrashIcon />
397
- Move to Trash
398
- <CommandShortcut>⌘⌫</CommandShortcut>
399
- </CommandItem>
400
- </CommandGroup>
401
- </CommandList>
402
- </Command>
403
- </div>
404
- ),
405
- parameters: {
406
- docs: {
407
- description: {
408
- story:
409
- "File management command palette with create, action, import/export, and destructive commands organized in groups.",
410
- },
411
- },
412
- },
413
- }
229
+ // ─── Interactive ──────────────────────────────────────────────────────────────
414
230
 
415
- // 4. Search and Filter Commands
416
- export const SearchAndFilterCommands: Story = {
231
+ export const Interactive: Story = {
417
232
  render: () => {
418
- const [searchTerm, setSearchTerm] = React.useState("")
233
+ const [search, setSearch] = React.useState("")
234
+ const [lastSelected, setLastSelected] = React.useState<string | null>(null)
419
235
 
420
- const allItems = [
421
- {
422
- id: "recent",
423
- label: "Recent Files",
424
- icon: <FileChartIcon />,
425
- group: "Quick Access",
426
- },
427
- {
428
- id: "images",
429
- label: "Images",
430
- icon: <ImageIcon />,
431
- group: "Quick Access",
432
- },
433
- {
434
- id: "uploads",
435
- label: "Uploads",
436
- icon: <UploadIcon />,
437
- group: "Quick Access",
438
- },
439
- {
440
- id: "magic",
441
- label: "Magic Book",
442
- icon: <MagicBookIcon />,
443
- group: "Quick Access",
444
- },
445
- {
446
- id: "charts",
447
- label: "Charts",
448
- icon: <FileChartIcon />,
449
- group: "Apps",
450
- },
236
+ const tracks = [
451
237
  {
452
- id: "search",
453
- label: "Search",
454
- icon: <SearchIcon />,
455
- group: "Apps",
238
+ id: "t1",
239
+ label: "Midnight Echoes",
240
+ artist: "Luna Vex",
241
+ group: "Tracks",
456
242
  },
457
243
  {
458
- id: "maintenance",
459
- label: "Maintenance",
460
- icon: <MaintenanceIcon />,
461
- group: "Apps",
462
- },
463
- {
464
- id: "profile",
465
- label: "Profile",
466
- icon: <EyeOpenIcon />,
467
- group: "Account",
244
+ id: "t2",
245
+ label: "Solar Drift",
246
+ artist: "The Velvet Faders",
247
+ group: "Tracks",
468
248
  },
249
+ { id: "t3", label: "Neon Requiem", artist: "Axiom", group: "Tracks" },
250
+ ]
251
+ const artists = [
252
+ { id: "a1", label: "Luna Vex", genre: "Electronic", group: "Artists" },
469
253
  {
470
- id: "features",
471
- label: "Features",
472
- icon: <FeatureShineIcon />,
473
- group: "Account",
254
+ id: "a2",
255
+ label: "The Velvet Faders",
256
+ genre: "Indie",
257
+ group: "Artists",
474
258
  },
475
259
  ]
260
+ const playlists = [
261
+ { id: "p1", label: "Late Night Drive", count: 18, group: "Playlists" },
262
+ { id: "p2", label: "Focus Mode", count: 34, group: "Playlists" },
263
+ { id: "p3", label: "Morning Energy", count: 22, group: "Playlists" },
264
+ ]
476
265
 
477
- const filteredItems = searchTerm
478
- ? allItems.filter((item) =>
479
- item.label.toLowerCase().includes(searchTerm.toLowerCase())
480
- )
481
- : allItems
266
+ const filterItems = <T extends { label: string }>(items: T[]) =>
267
+ search
268
+ ? items.filter((i) =>
269
+ i.label.toLowerCase().includes(search.toLowerCase())
270
+ )
271
+ : items
482
272
 
483
- const groupedItems = filteredItems.reduce(
484
- (acc, item) => {
485
- if (!acc[item.group]) {
486
- acc[item.group] = []
487
- }
488
- acc[item.group].push(item)
489
- return acc
490
- },
491
- {} as Record<string, typeof allItems>
492
- )
273
+ const filteredTracks = filterItems(tracks)
274
+ const filteredArtists = filterItems(artists)
275
+ const filteredPlaylists = filterItems(playlists)
276
+
277
+ const totalResults =
278
+ filteredTracks.length + filteredArtists.length + filteredPlaylists.length
493
279
 
494
280
  return (
495
- <div className="mx-auto max-w-lg space-y-8 p-8">
496
- <div className="space-y-2">
497
- <h3 className="text-fm-primary text-lg font-medium">
498
- Search and Filter
499
- </h3>
500
- <p className="text-fm-secondary text-sm">
501
- Dynamic filtering based on search input
502
- </p>
503
- </div>
281
+ <div className="w-full p-8">
282
+ <div className="mx-auto max-w-3xl space-y-6">
283
+ <div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
284
+ {/* Controls panel */}
285
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary space-y-5 rounded-xl border p-5">
286
+ <p className="text-fm-primary font-fm-brand text-fm-sm leading-fm-sm font-semibold tracking-widest uppercase">
287
+ Search State
288
+ </p>
504
289
 
505
- <Command className="border-fm-divider-secondary rounded-lg border">
506
- <CommandInput
507
- placeholder="Search apps, files, and more..."
508
- value={searchTerm}
509
- onValueChange={setSearchTerm}
510
- />
511
- <CommandList>
512
- <CommandEmpty>
513
- <div className="py-6 text-center">
514
- <SearchIcon className="text-fm-tertiary mx-auto h-8 w-8" />
515
- <p className="text-fm-secondary mt-2 text-sm">
516
- No results for "{searchTerm}"
290
+ <div className="space-y-2">
291
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
292
+ Query
517
293
  </p>
518
- <p className="text-fm-tertiary text-xs">
519
- Try searching for something else
294
+ <p className="text-fm-primary font-fm-text text-fm-md leading-fm-md truncate font-medium">
295
+ {search || "—"}
520
296
  </p>
521
297
  </div>
522
- </CommandEmpty>
523
298
 
524
- {Object.entries(groupedItems).map(([group, items]) => (
525
- <React.Fragment key={group}>
526
- <CommandGroup>
527
- <CommandLabel>{group}</CommandLabel>
528
- {items.map((item) => (
529
- <CommandItem key={item.id}>
530
- {item.icon}
531
- {item.label}
532
- </CommandItem>
533
- ))}
534
- </CommandGroup>
535
- {Object.keys(groupedItems).indexOf(group) <
536
- Object.keys(groupedItems).length - 1 && <CommandSeparator />}
537
- </React.Fragment>
538
- ))}
539
- </CommandList>
540
- </Command>
541
-
542
- {searchTerm && (
543
- <div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border p-4">
544
- <h4 className="text-fm-primary text-sm font-medium">
545
- Search Stats
546
- </h4>
547
- <div className="text-fm-secondary mt-2 space-y-1 text-xs">
548
- <p>Search term: "{searchTerm}"</p>
549
- <p>Results found: {filteredItems.length}</p>
550
- <p>Groups: {Object.keys(groupedItems).length}</p>
299
+ <div className="border-fm-divider-secondary border-t pt-4" />
300
+
301
+ <div className="space-y-2">
302
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
303
+ Results
304
+ </p>
305
+ <div className="space-y-1">
306
+ <div className="flex justify-between">
307
+ <span className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
308
+ Tracks
309
+ </span>
310
+ <span className="text-fm-primary font-fm-text text-fm-sm leading-fm-sm font-medium">
311
+ {filteredTracks.length}
312
+ </span>
313
+ </div>
314
+ <div className="flex justify-between">
315
+ <span className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
316
+ Artists
317
+ </span>
318
+ <span className="text-fm-primary font-fm-text text-fm-sm leading-fm-sm font-medium">
319
+ {filteredArtists.length}
320
+ </span>
321
+ </div>
322
+ <div className="flex justify-between">
323
+ <span className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
324
+ Playlists
325
+ </span>
326
+ <span className="text-fm-primary font-fm-text text-fm-sm leading-fm-sm font-medium">
327
+ {filteredPlaylists.length}
328
+ </span>
329
+ </div>
330
+ </div>
331
+ </div>
332
+
333
+ <div className="border-fm-divider-secondary border-t pt-4" />
334
+
335
+ <div className="space-y-2">
336
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
337
+ Last Selected
338
+ </p>
339
+ <p className="text-fm-primary font-fm-text text-fm-md leading-fm-md truncate font-medium">
340
+ {lastSelected ?? "—"}
341
+ </p>
342
+ </div>
343
+
344
+ {search && (
345
+ <>
346
+ <div className="border-fm-divider-secondary border-t pt-4" />
347
+ <Button
348
+ variant="outline"
349
+ size="sm"
350
+ onClick={() => setSearch("")}
351
+ className="w-full"
352
+ >
353
+ Clear search
354
+ </Button>
355
+ </>
356
+ )}
357
+ </div>
358
+
359
+ {/* Preview stage */}
360
+ <div className="flex flex-col gap-3 lg:col-span-2">
361
+ <Command className="border-fm-divider-secondary rounded-lg border">
362
+ <CommandInput
363
+ placeholder="Search tracks, artists, playlists…"
364
+ value={search}
365
+ onValueChange={setSearch}
366
+ />
367
+ <CommandList>
368
+ <CommandEmpty>
369
+ <div className="py-6 text-center">
370
+ <SearchIcon className="text-fm-tertiary mx-auto size-8" />
371
+ <p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm mt-2">
372
+ No results for &quot;{search}&quot;
373
+ </p>
374
+ </div>
375
+ </CommandEmpty>
376
+
377
+ {filteredTracks.length > 0 && (
378
+ <CommandGroup>
379
+ <CommandLabel>Tracks</CommandLabel>
380
+ {filteredTracks.map((t) => (
381
+ <CommandItem
382
+ key={t.id}
383
+ onSelect={() => setLastSelected(t.label)}
384
+ >
385
+ <MusicalNoteIcon />
386
+ <span className="flex flex-col">
387
+ <span>{t.label}</span>
388
+ <span className="text-fm-tertiary text-xs">
389
+ {t.artist}
390
+ </span>
391
+ </span>
392
+ <CommandShortcut>↵</CommandShortcut>
393
+ </CommandItem>
394
+ ))}
395
+ </CommandGroup>
396
+ )}
397
+
398
+ {filteredTracks.length > 0 && filteredArtists.length > 0 && (
399
+ <CommandSeparator />
400
+ )}
401
+
402
+ {filteredArtists.length > 0 && (
403
+ <CommandGroup>
404
+ <CommandLabel>Artists</CommandLabel>
405
+ {filteredArtists.map((a) => (
406
+ <CommandItem
407
+ key={a.id}
408
+ onSelect={() => setLastSelected(a.label)}
409
+ >
410
+ <StarIcon />
411
+ <span className="flex flex-col">
412
+ <span>{a.label}</span>
413
+ <span className="text-fm-tertiary text-xs">
414
+ {a.genre}
415
+ </span>
416
+ </span>
417
+ </CommandItem>
418
+ ))}
419
+ </CommandGroup>
420
+ )}
421
+
422
+ {filteredArtists.length > 0 &&
423
+ filteredPlaylists.length > 0 && <CommandSeparator />}
424
+
425
+ {filteredPlaylists.length > 0 && (
426
+ <CommandGroup>
427
+ <CommandLabel>Playlists</CommandLabel>
428
+ {filteredPlaylists.map((p) => (
429
+ <CommandItem
430
+ key={p.id}
431
+ onSelect={() => setLastSelected(p.label)}
432
+ >
433
+ <AudioBarIcon />
434
+ <span className="flex flex-col">
435
+ <span>{p.label}</span>
436
+ <span className="text-fm-tertiary text-xs">
437
+ {p.count} tracks
438
+ </span>
439
+ </span>
440
+ </CommandItem>
441
+ ))}
442
+ </CommandGroup>
443
+ )}
444
+ </CommandList>
445
+ </Command>
446
+
447
+ <div className="border-fm-divider-secondary bg-fm-surface-secondary rounded-lg border px-4 py-3">
448
+ <code className="text-fm-secondary text-fm-md leading-fm-md font-(--font-fm-mono)">
449
+ {totalResults} result{totalResults !== 1 ? "s" : ""} —
450
+ navigate with ↑↓ and confirm with ↵
451
+ </code>
452
+ </div>
551
453
  </div>
552
454
  </div>
553
- )}
455
+ </div>
554
456
  </div>
555
457
  )
556
458
  },
@@ -558,442 +460,203 @@ export const SearchAndFilterCommands: Story = {
558
460
  docs: {
559
461
  description: {
560
462
  story:
561
- "Dynamic command menu that filters results based on search input with real-time statistics.",
463
+ "Live search filtering across tracks, artists, and playlists in an audio app context. The left panel shows real-time result counts and the last selected item. Use the keyboard arrow keys to navigate and Enter to select — demonstrating CMDK's built-in keyboard navigation.",
562
464
  },
563
465
  },
564
466
  },
565
467
  }
566
468
 
567
- // 5. Custom Styling Variants
568
- export const CustomStylingVariants: Story = {
569
- render: () => (
570
- <div className="space-y-8 p-8">
571
- <h3 className="text-fm-primary text-center text-lg font-medium">
572
- Custom Styling Variants
573
- </h3>
574
-
575
- <div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
576
- {/* Elevated Command */}
577
- <div className="space-y-4">
578
- <h4 className="text-fm-secondary text-sm font-medium">
579
- Elevated Style
469
+ // ─── UseCases ─────────────────────────────────────────────────────────────────
470
+
471
+ export const UseCases: Story = {
472
+ render: () => {
473
+ const [globalOpen, setGlobalOpen] = React.useState(false)
474
+
475
+ React.useEffect(() => {
476
+ const down = (e: KeyboardEvent) => {
477
+ if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
478
+ e.preventDefault()
479
+ setGlobalOpen((v) => !v)
480
+ }
481
+ }
482
+ document.addEventListener("keydown", down)
483
+ return () => document.removeEventListener("keydown", down)
484
+ }, [])
485
+
486
+ return (
487
+ <div className="mx-auto max-w-3xl space-y-8 p-8">
488
+ {/* 1 — Global app search */}
489
+ <div className="space-y-3">
490
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
491
+ Global App Search (⌘K)
580
492
  </h4>
581
- <Command
582
- className="border-fm-divider-secondary rounded-lg border"
583
- listProps={{ variant: "elevated", size: "lg" }}
493
+ <div className="flex justify-center">
494
+ <Button
495
+ variant="outline"
496
+ onClick={() => setGlobalOpen(true)}
497
+ innerClassName="flex items-center gap-2"
498
+ >
499
+ <SearchIcon className="size-4" />
500
+ Search everything…
501
+ <span className="font-fm-brand text-fm-secondary ml-2 text-xs tracking-widest opacity-60">
502
+ ⌘K
503
+ </span>
504
+ </Button>
505
+ </div>
506
+ <CommandDialog
507
+ open={globalOpen}
508
+ onOpenChange={setGlobalOpen}
509
+ title="Global Search"
510
+ description="Search tracks, artists, playlists, and settings"
584
511
  >
585
- <CommandInput placeholder="Elevated command menu..." />
512
+ <CommandInput placeholder="Search everything…" />
586
513
  <CommandList>
587
514
  <CommandEmpty>No results found.</CommandEmpty>
588
515
  <CommandGroup>
589
- <CommandLabel>Premium Actions</CommandLabel>
590
- <CommandItem>
591
- <FeatureShineIcon />
592
- Premium Feature
593
- <Badge color="positive" className="ml-auto">
594
- Pro
595
- </Badge>
516
+ <CommandLabel>Tracks</CommandLabel>
517
+ <CommandItem onSelect={() => setGlobalOpen(false)}>
518
+ <MusicalNoteIcon />
519
+ Midnight Echoes — Luna Vex
596
520
  </CommandItem>
597
- <CommandItem>
598
- <BubbleSparkleIcon />
599
- Favorite
521
+ <CommandItem onSelect={() => setGlobalOpen(false)}>
522
+ <MusicalNoteIcon />
523
+ Solar Drift — The Velvet Faders
600
524
  </CommandItem>
601
525
  </CommandGroup>
602
- </CommandList>
603
- </Command>
604
- </div>
605
-
606
- {/* Compact Command */}
607
- <div className="space-y-4">
608
- <h4 className="text-fm-secondary text-sm font-medium">
609
- Compact Style
610
- </h4>
611
- <Command
612
- className="border-fm-divider-secondary rounded-lg border"
613
- listProps={{ size: "sm" }}
614
- >
615
- <CommandInput
616
- placeholder="Compact menu..."
617
- classes={{ input: "text-xs" }}
618
- />
619
- <CommandList>
620
- <CommandEmpty>No results found.</CommandEmpty>
526
+ <CommandSeparator />
621
527
  <CommandGroup>
622
- <CommandLabel>Quick Actions</CommandLabel>
623
- <CommandItem>
624
- <FileChartIcon />
625
- New
626
- <CommandShortcut>⌘N</CommandShortcut>
627
- </CommandItem>
628
- <CommandItem>
629
- <SearchIcon />
630
- Find
631
- <CommandShortcut>⌘F</CommandShortcut>
528
+ <CommandLabel>Playlists</CommandLabel>
529
+ <CommandItem onSelect={() => setGlobalOpen(false)}>
530
+ <AudioBarIcon />
531
+ Late Night Drive
632
532
  </CommandItem>
633
- <CommandItem>
634
- <MaintenanceIcon />
635
- Refresh
636
- <CommandShortcut>⌘R</CommandShortcut>
533
+ <CommandItem onSelect={() => setGlobalOpen(false)}>
534
+ <AudioBarIcon />
535
+ Focus Mode
637
536
  </CommandItem>
638
537
  </CommandGroup>
639
- </CommandList>
640
- </Command>
641
- </div>
642
-
643
- {/* Flat Style */}
644
- <div className="space-y-4">
645
- <h4 className="text-fm-secondary text-sm font-medium">Flat Style</h4>
646
- <Command
647
- className="border-fm-divider-secondary rounded-lg border"
648
- listProps={{ variant: "flat", rounded: "lg" }}
649
- >
650
- <CommandInput placeholder="Flat design menu..." />
651
- <CommandList>
652
- <CommandEmpty>No results found.</CommandEmpty>
538
+ <CommandSeparator />
653
539
  <CommandGroup>
654
- <CommandLabel>Interface</CommandLabel>
655
- <CommandItem>
540
+ <CommandLabel>Settings</CommandLabel>
541
+ <CommandItem onSelect={() => setGlobalOpen(false)}>
656
542
  <MaintenanceIcon />
657
- Settings
658
- </CommandItem>
659
- <CommandItem>
660
- <EyeOpenIcon />
661
- Account
543
+ Audio Quality
544
+ <CommandShortcut>⌘,</CommandShortcut>
662
545
  </CommandItem>
663
546
  </CommandGroup>
664
547
  </CommandList>
665
- </Command>
548
+ </CommandDialog>
666
549
  </div>
667
550
 
668
- {/* Custom Classes */}
669
- <div className="space-y-4">
670
- <h4 className="text-fm-secondary text-sm font-medium">
671
- Custom Classes
551
+ {/* 2 File management commands */}
552
+ <div className="space-y-3">
553
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
554
+ File Management Commands
672
555
  </h4>
673
- <Command
674
- className="rounded-lg border border-purple-500/30 bg-purple-900/10"
675
- classes={{
676
- list: "bg-purple-900/20",
677
- }}
678
- >
679
- <CommandInput
680
- placeholder="Purple themed menu..."
681
- classes={{
682
- wrapper: "border-purple-500/30",
683
- icon: "text-purple-400",
684
- }}
685
- />
686
- <CommandList>
687
- <CommandEmpty>No results found.</CommandEmpty>
688
- <CommandGroup>
689
- <CommandLabel>Custom Theme</CommandLabel>
690
- <CommandItem classes={{ root: "hover:bg-purple-500/20" }}>
691
- <FeatureShineIcon />
692
- Special Action
693
- </CommandItem>
694
- <CommandItem classes={{ root: "hover:bg-purple-500/20" }}>
695
- <BubbleSparkleIcon />
696
- Another Action
697
- </CommandItem>
698
- </CommandGroup>
699
- </CommandList>
700
- </Command>
701
- </div>
702
- </div>
703
- </div>
704
- ),
705
- parameters: {
706
- docs: {
707
- description: {
708
- story:
709
- "Various styling options including elevated, compact, flat styles and custom theming with purple color scheme.",
710
- },
711
- },
712
- },
713
- }
714
-
715
- // 6. Complex Command Structure
716
- export const ComplexCommandStructure: Story = {
717
- render: () => (
718
- <div className="mx-auto max-w-lg space-y-8 p-8">
719
- <h3 className="text-fm-primary text-lg font-medium">
720
- Complex Command Structure
721
- </h3>
722
-
723
- <Command className="border-fm-divider-secondary rounded-lg border">
724
- <CommandInput placeholder="Search all commands..." />
725
- <CommandList>
726
- <CommandEmpty>
727
- <div className="py-8 text-center">
728
- <SearchIcon className="text-fm-tertiary mx-auto h-12 w-12" />
729
- <h4 className="text-fm-primary mt-2 text-sm font-medium">
730
- No commands found
731
- </h4>
732
- <p className="text-fm-secondary mt-1 text-xs">
733
- Try adjusting your search to find what you're looking for.
734
- </p>
735
- </div>
736
- </CommandEmpty>
737
-
738
- <CommandGroup>
739
- <CommandLabel>
740
- <FileChartIcon className="mr-2" />
741
- File Operations
742
- </CommandLabel>
743
- <CommandItem>
744
- <FileChartIcon />
745
- New Document
746
- <Badge color="info" className="ml-auto">
747
- Ctrl+N
748
- </Badge>
749
- </CommandItem>
750
- <CommandItem>
751
- <ImportFolderIcon />
752
- Open Folder
753
- <CommandShortcut>⌘O</CommandShortcut>
754
- </CommandItem>
755
- <CommandItem>
756
- <EditBigIcon />
757
- Recent Files
758
- <Badge color="neutral" className="ml-auto">
759
- 5
760
- </Badge>
761
- </CommandItem>
762
- </CommandGroup>
763
-
764
- <CommandSeparator />
765
-
766
- <CommandGroup>
767
- <CommandLabel>
768
- <EyeOpenIcon className="mr-2" />
769
- User Management
770
- </CommandLabel>
771
- <CommandItem>
772
- <EyeOpenIcon />
773
- View Profile
774
- <CommandShortcut>⌘P</CommandShortcut>
775
- </CommandItem>
776
- <CommandItem>
777
- <MaintenanceIcon />
778
- User Settings
779
- <CommandShortcut>⌘,</CommandShortcut>
780
- </CommandItem>
781
- <CommandItem>
782
- <BubbleSparkleIcon />
783
- Share Profile
784
- <Badge color="warning" className="ml-auto">
785
- Beta
786
- </Badge>
787
- </CommandItem>
788
- </CommandGroup>
789
-
790
- <CommandSeparator />
791
-
792
- <CommandGroup>
793
- <CommandLabel>
794
- <UploadIcon className="mr-2" />
795
- Data Management
796
- </CommandLabel>
797
- <CommandItem>
798
- <ArrowRightIcon />
799
- Export Data
800
- <CommandShortcut>⌘E</CommandShortcut>
801
- </CommandItem>
802
- <CommandItem>
803
- <UploadIcon />
804
- Import Data
805
- <CommandShortcut>⌘I</CommandShortcut>
806
- </CommandItem>
807
- <CommandItem>
808
- <MaintenanceIcon />
809
- Sync Data
810
- <Badge color="positive" className="ml-auto">
811
- Auto
812
- </Badge>
813
- </CommandItem>
814
- </CommandGroup>
815
-
816
- <CommandSeparator />
817
-
818
- <CommandGroup>
819
- <CommandLabel>
820
- <FeatureShineIcon className="mr-2" />
821
- Features
822
- </CommandLabel>
823
- <CommandItem>
824
- <MagicBookIcon />
825
- Magic Features
826
- <Badge color="neutral" className="ml-auto">
827
- 12
828
- </Badge>
829
- </CommandItem>
830
- <CommandItem>
831
- <LightBulbSimpleIcon />
832
- Ideas
833
- <Badge color="positive" className="ml-auto">
834
- New
835
- </Badge>
836
- </CommandItem>
837
- <CommandItem>
838
- <FeatureShineIcon />
839
- Special Features
840
- <CommandShortcut>⌘⇧S</CommandShortcut>
841
- </CommandItem>
842
- </CommandGroup>
843
-
844
- <CommandSeparator />
845
-
846
- <CommandGroup>
847
- <CommandLabel>
848
- <TrashIcon className="mr-2" />
849
- Dangerous Actions
850
- </CommandLabel>
851
- <CommandItem variant="destructive">
852
- <TrashIcon />
853
- Delete Account
854
- <Badge color="negative" className="ml-auto">
855
- !
856
- </Badge>
857
- </CommandItem>
858
- <CommandItem variant="destructive">
859
- <CrossCircleIcon />
860
- Reset All Data
861
- <CommandShortcut>⌘⇧R</CommandShortcut>
862
- </CommandItem>
863
- </CommandGroup>
864
- </CommandList>
865
- </Command>
866
- </div>
867
- ),
868
- parameters: {
869
- docs: {
870
- description: {
871
- story:
872
- "Complex command structure with multiple groups, icons in labels, badges for additional context, and both regular and destructive actions.",
873
- },
874
- },
875
- },
876
- }
877
-
878
- // 7. Performance Demo
879
- export const PerformanceDemo: Story = {
880
- render: () => {
881
- const [itemCount, setItemCount] = React.useState(100)
882
-
883
- const generateItems = (count: number) => {
884
- const categories = ["Files", "Actions", "Settings", "Images", "Tools"]
885
- const icons = [
886
- FileChartIcon,
887
- EditBigIcon,
888
- MaintenanceIcon,
889
- ImageIcon,
890
- CommandIcon,
891
- ]
892
- const items = []
893
-
894
- for (let i = 0; i < count; i++) {
895
- const categoryIndex = i % categories.length
896
- items.push({
897
- id: i,
898
- label: `${categories[categoryIndex]} Item ${i + 1}`,
899
- icon: icons[categoryIndex],
900
- category: categories[categoryIndex],
901
- shortcut: i % 10 === 0 ? `⌘${i / 10}` : undefined,
902
- })
903
- }
904
-
905
- return items
906
- }
907
-
908
- const items = React.useMemo(() => generateItems(itemCount), [itemCount])
909
- const groupedItems = React.useMemo(() => {
910
- return items.reduce(
911
- (acc, item) => {
912
- if (!acc[item.category]) {
913
- acc[item.category] = []
914
- }
915
- acc[item.category].push(item)
916
- return acc
917
- },
918
- {} as Record<string, typeof items>
919
- )
920
- }, [items])
921
-
922
- return (
923
- <div className="space-y-8 p-8">
924
- <div className="space-y-4 text-center">
925
- <h3 className="text-fm-primary text-lg font-medium">
926
- Performance Demo
927
- </h3>
928
- <p className="text-fm-secondary text-sm">
929
- Test command menu performance with large datasets
930
- </p>
931
-
932
- <div className="flex items-center justify-center gap-4">
933
- <label className="text-fm-secondary text-sm">Items:</label>
934
- <select
935
- value={itemCount}
936
- onChange={(e) => setItemCount(Number(e.target.value))}
937
- className="border-fm-divider-secondary bg-fm-surface-secondary text-fm-primary rounded border px-3 py-1"
938
- >
939
- <option value={50}>50</option>
940
- <option value={100}>100</option>
941
- <option value={500}>500</option>
942
- <option value={1000}>1000</option>
943
- </select>
944
- <Badge color="info">{itemCount} items</Badge>
556
+ <div className="mx-auto max-w-md">
557
+ <Command className="border-fm-divider-secondary rounded-lg border">
558
+ <CommandInput placeholder="Search file actions…" />
559
+ <CommandList>
560
+ <CommandEmpty>No file actions found.</CommandEmpty>
561
+ <CommandGroup>
562
+ <CommandLabel>Create</CommandLabel>
563
+ <CommandItem>
564
+ <FileTextIcon />
565
+ New Playlist
566
+ <CommandShortcut>⌘N</CommandShortcut>
567
+ </CommandItem>
568
+ <CommandItem>
569
+ <UploadIcon />
570
+ Upload Track
571
+ <CommandShortcut>⌘U</CommandShortcut>
572
+ </CommandItem>
573
+ </CommandGroup>
574
+ <CommandSeparator />
575
+ <CommandGroup>
576
+ <CommandLabel>Manage</CommandLabel>
577
+ <CommandItem>
578
+ <EditBigIcon />
579
+ Edit Metadata
580
+ <CommandShortcut>⌘E</CommandShortcut>
581
+ </CommandItem>
582
+ <CommandItem>
583
+ <DownloadIcon />
584
+ Download Offline
585
+ <CommandShortcut>⌘D</CommandShortcut>
586
+ </CommandItem>
587
+ <CommandItem>
588
+ <ShareIcon />
589
+ Share
590
+ <CommandShortcut>⌘⇧S</CommandShortcut>
591
+ </CommandItem>
592
+ </CommandGroup>
593
+ <CommandSeparator />
594
+ <CommandGroup>
595
+ <CommandLabel>Danger Zone</CommandLabel>
596
+ <CommandItem variant="destructive">
597
+ <TrashIcon />
598
+ Remove from Library
599
+ <CommandShortcut>⌘⌫</CommandShortcut>
600
+ </CommandItem>
601
+ </CommandGroup>
602
+ </CommandList>
603
+ </Command>
945
604
  </div>
946
605
  </div>
947
606
 
948
- <div className="mx-auto max-w-lg">
949
- <Command className="border-fm-divider-secondary rounded-lg border">
950
- <CommandInput placeholder={`Search ${itemCount} items...`} />
951
- <CommandList>
952
- <CommandEmpty>
953
- No results found in {itemCount} items.
954
- </CommandEmpty>
955
-
956
- {Object.entries(groupedItems).map(([category, categoryItems]) => (
957
- <React.Fragment key={category}>
958
- <CommandGroup>
959
- <CommandLabel>
960
- {category} ({categoryItems.length})
961
- </CommandLabel>
962
- {categoryItems.map((item) => {
963
- const IconComponent = item.icon
964
- return (
965
- <CommandItem key={item.id}>
966
- <IconComponent />
967
- {item.label}
968
- {item.shortcut && (
969
- <CommandShortcut>{item.shortcut}</CommandShortcut>
970
- )}
971
- </CommandItem>
972
- )
973
- })}
974
- </CommandGroup>
975
- {Object.keys(groupedItems).indexOf(category) <
976
- Object.keys(groupedItems).length - 1 && (
977
- <CommandSeparator />
978
- )}
979
- </React.Fragment>
980
- ))}
981
- </CommandList>
982
- </Command>
983
- </div>
984
-
985
- <div className="border-fm-divider-secondary bg-fm-surface-secondary mx-auto max-w-lg rounded-lg border p-4">
986
- <h4 className="text-fm-primary text-sm font-medium">
987
- Performance Info
607
+ {/* 3 — Action shortcuts */}
608
+ <div className="space-y-3">
609
+ <h4 className="text-fm-secondary font-fm-text text-fm-md leading-fm-md font-medium">
610
+ Playback Action Shortcuts
988
611
  </h4>
989
- <div className="text-fm-secondary mt-2 grid grid-cols-2 gap-4 text-xs">
990
- <div>Total Items: {itemCount}</div>
991
- <div>Categories: {Object.keys(groupedItems).length}</div>
992
- <div>
993
- Avg per Category:{" "}
994
- {Math.round(itemCount / Object.keys(groupedItems).length)}
995
- </div>
996
- <div>With Shortcuts: {items.filter((i) => i.shortcut).length}</div>
612
+ <div className="mx-auto max-w-md">
613
+ <Command className="border-fm-divider-secondary rounded-lg border">
614
+ <CommandInput placeholder="Search playback actions…" />
615
+ <CommandList>
616
+ <CommandEmpty>No playback actions found.</CommandEmpty>
617
+ <CommandGroup>
618
+ <CommandLabel>Now Playing</CommandLabel>
619
+ <CommandItem>
620
+ <HeartIcon />
621
+ Like Track
622
+ <CommandShortcut>⌘L</CommandShortcut>
623
+ </CommandItem>
624
+ <CommandItem>
625
+ <SkipForwardIcon />
626
+ Skip to Next
627
+ <CommandShortcut>⌘→</CommandShortcut>
628
+ </CommandItem>
629
+ <CommandItem>
630
+ <AudioBarIcon />
631
+ Add to Queue
632
+ <CommandShortcut>⌘Q</CommandShortcut>
633
+ </CommandItem>
634
+ <CommandItem>
635
+ <SparklesSoftIcon />
636
+ Start Radio
637
+ <CommandShortcut>⌘R</CommandShortcut>
638
+ </CommandItem>
639
+ <CommandItem>
640
+ <CircleTickIcon />
641
+ Mark as Listened
642
+ </CommandItem>
643
+ </CommandGroup>
644
+ <CommandSeparator />
645
+ <CommandGroup>
646
+ <CommandLabel>View</CommandLabel>
647
+ <CommandItem>
648
+ <ImageIcon />
649
+ Show Album Art
650
+ <CommandShortcut>⌘⇧A</CommandShortcut>
651
+ </CommandItem>
652
+ <CommandItem>
653
+ <VerticalMenuIcon />
654
+ Track Info
655
+ <CommandShortcut>⌘I</CommandShortcut>
656
+ </CommandItem>
657
+ </CommandGroup>
658
+ </CommandList>
659
+ </Command>
997
660
  </div>
998
661
  </div>
999
662
  </div>
@@ -1003,7 +666,7 @@ export const PerformanceDemo: Story = {
1003
666
  docs: {
1004
667
  description: {
1005
668
  story:
1006
- "Performance demonstration with configurable item counts to test search and rendering performance with large datasets.",
669
+ "Three real-world use cases in a single export: (1) a global app search dialog triggered by ⌘K covering tracks, playlists, and settings; (2) a file-management command palette for create/manage/delete operations; (3) a playback action shortcut palette for controlling the currently playing track without leaving the keyboard.",
1007
670
  },
1008
671
  },
1009
672
  },