aural-ui 4.0.1 → 4.2.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 (175) hide show
  1. package/README.md +8 -1
  2. package/dist/components/aspect-ratio/AspectRatio.stories.tsx +290 -1228
  3. package/dist/components/avatar/Avatar.stories.tsx +219 -235
  4. package/dist/components/badge/Badge.stories.tsx +379 -116
  5. package/dist/components/banner/Banner.stories.tsx +445 -391
  6. package/dist/components/breadcrumb/Breadcrumb.stories.tsx +453 -199
  7. package/dist/components/button/Button.stories.tsx +585 -230
  8. package/dist/components/card/Card.stories.tsx +619 -301
  9. package/dist/components/char-count/CharCount.stories.tsx +350 -248
  10. package/dist/components/checkbox/Checkbox.stories.tsx +309 -167
  11. package/dist/components/chip/Chip.stories.tsx +362 -168
  12. package/dist/components/circular-loader/CircularLoader.stories.tsx +221 -636
  13. package/dist/components/clamp-lines/ClampLines.stories.tsx +246 -117
  14. package/dist/components/collapsible/Collapsible.stories.tsx +391 -252
  15. package/dist/components/command/Command.stories.tsx +530 -867
  16. package/dist/components/dialog/Dialog.stories.tsx +501 -950
  17. package/dist/components/divider/Divider.stories.tsx +264 -527
  18. package/dist/components/dot-loader/DotLoader.stories.tsx +256 -257
  19. package/dist/components/drawer/Drawer.stories.tsx +659 -1023
  20. package/dist/components/dropdown/Dropdown.stories.tsx +643 -1028
  21. package/dist/components/form/Form.stories.tsx +560 -274
  22. package/dist/components/helper-text/HelperText.stories.tsx +199 -200
  23. package/dist/components/hover-card/HoverCard.stories.tsx +318 -1254
  24. package/dist/components/icon-button/IconButton.stories.tsx +837 -194
  25. package/dist/components/if-else/if-else.stories.tsx +370 -83
  26. package/dist/components/input/Input.stories.tsx +436 -368
  27. package/dist/components/label/Label.stories.tsx +156 -154
  28. package/dist/components/list/List.stories.tsx +484 -835
  29. package/dist/components/marquee/Marquee.stories.tsx +356 -712
  30. package/dist/components/otp-inputs/OtpInputs.stories.tsx +352 -422
  31. package/dist/components/overlay/Overlay.stories.tsx +452 -824
  32. package/dist/components/pagination/Pagination.stories.tsx +721 -210
  33. package/dist/components/popover/Popover.stories.tsx +481 -896
  34. package/dist/components/radio/Radio.stories.tsx +432 -124
  35. package/dist/components/resizable/Resizable.stories.tsx +495 -799
  36. package/dist/components/scroll-area/ScrollArea.stories.tsx +383 -1059
  37. package/dist/components/search/Search.stories.tsx +312 -595
  38. package/dist/components/select/Select.stories.tsx +684 -789
  39. package/dist/components/sheet/Sheet.stories.tsx +671 -950
  40. package/dist/components/skelton/Skelton.stories.tsx +230 -764
  41. package/dist/components/slider/Slider.stories.tsx +383 -760
  42. package/dist/components/stepper/Stepper.stories.tsx +371 -514
  43. package/dist/components/switch/Switch.stories.tsx +461 -208
  44. package/dist/components/switch-case/SwitchCase.stories.tsx +367 -188
  45. package/dist/components/table/Table.stories.tsx +770 -916
  46. package/dist/components/tabs/Tabs.stories.tsx +458 -1455
  47. package/dist/components/tag/Tag.stories.tsx +714 -542
  48. package/dist/components/textarea/TextArea.stories.tsx +621 -562
  49. package/dist/components/thumbnail-tags/ThumbnailTags.stories.tsx +228 -154
  50. package/dist/components/toast/Toast.stories.tsx +452 -1339
  51. package/dist/components/toggle/Toggle.stories.tsx +488 -931
  52. package/dist/components/tooltip/Tooltip.stories.tsx +344 -1388
  53. package/dist/components/typography/Typography.stories.tsx +406 -89
  54. package/dist/hooks/use-change-state/UseChangeState.stories.tsx +309 -606
  55. package/dist/hooks/use-previous/UsePrevious.stories.tsx +367 -917
  56. package/dist/hooks/use-standalone-pagination/UseStandalonePagination.stories.tsx +639 -867
  57. package/dist/icons/Icons.stories.tsx +0 -12
  58. package/dist/icons/ai-avatar-icon/AiAvatarIcon.stories.tsx +223 -1060
  59. package/dist/icons/alert-icon/AlertIcon.stories.tsx +106 -968
  60. package/dist/icons/all-icons.tsx +37 -16
  61. package/dist/icons/angle-down-icon/AngleDownIcon.stories.tsx +137 -1010
  62. package/dist/icons/apple-logo-icon/AppleLogoIcon.stories.tsx +145 -935
  63. package/dist/icons/arrow-box-left-icon/ArrowBoxLeftIcon.stories.tsx +132 -1046
  64. package/dist/icons/arrow-corner-up-left-icon/ArrowCornerUpLeftIcon.stories.tsx +134 -986
  65. package/dist/icons/arrow-corner-up-right-icon/ArrowCornerUpRightIcon.stories.tsx +135 -1028
  66. package/dist/icons/arrow-left-icon/ArrowLeftIcon.stories.tsx +133 -971
  67. package/dist/icons/arrow-right-icon/ArrowRightIcon.stories.tsx +145 -1123
  68. package/dist/icons/arrow-right-up-icon/ArrowRightUpIcon.stories.tsx +143 -1252
  69. package/dist/icons/art-board-icon/ArtBoardIcon.stories.tsx +123 -632
  70. package/dist/icons/audio-bar-icon/AudioBarIcon.stories.tsx +141 -1223
  71. package/dist/icons/backward-ten-seconds-icon/BackwardTenSecondsIcon.stories.tsx +164 -1018
  72. package/dist/icons/bubble-check-icon/BubbleCheckIcon.stories.tsx +121 -1236
  73. package/dist/icons/bubble-crossed-icon/BubbleCrossedIcon.stories.tsx +121 -1213
  74. package/dist/icons/bubble-sparkle-icon/BubbleSparkleIcon.stories.tsx +116 -893
  75. package/dist/icons/camera-icon/CameraIcon.stories.tsx +109 -1254
  76. package/dist/icons/capital-a-letter-icon/CapitalALetterIcon.stories.tsx +114 -975
  77. package/dist/icons/chevron-double-left-icon/ChevronDoubleLeftIcon.stories.tsx +157 -994
  78. package/dist/icons/chevron-double-right-icon/ChevronDoubleRightIcon.stories.tsx +160 -992
  79. package/dist/icons/chevron-down-icon/ChevronDownIcon.stories.tsx +140 -970
  80. package/dist/icons/chevron-left-icon/ChevronLeftIcon.stories.tsx +126 -993
  81. package/dist/icons/chevron-right-icon/ChevronRightIcon.stories.tsx +144 -987
  82. package/dist/icons/chevron-up-icon/ChevronUpIcon.stories.tsx +141 -1007
  83. package/dist/icons/circle-tick-icon/CircleTickIcon.stories.tsx +147 -1187
  84. package/dist/icons/circular-play-icon/CircularPlayIcon.stories.tsx +110 -476
  85. package/dist/icons/coin-icon/CoinIcon.stories.tsx +120 -1364
  86. package/dist/icons/coin-toons-icon/CoinToonsIcon.stories.tsx +113 -1360
  87. package/dist/icons/column-wide-add-icon/ColumnWideAddIcon.stories.tsx +111 -942
  88. package/dist/icons/command-icon/CommandIcon.stories.tsx +124 -1087
  89. package/dist/icons/copy-icon/CopyIcon.stories.tsx +119 -996
  90. package/dist/icons/cross-circle-icon/CrossCircleIcon.stories.tsx +144 -1046
  91. package/dist/icons/cross-icon/CrossIcon.stories.tsx +136 -999
  92. package/dist/icons/download-icon/DownloadIcon.stories.tsx +123 -857
  93. package/dist/icons/edit-big-icon/EditBigIcon.stories.tsx +121 -1080
  94. package/dist/icons/email-icon/EmailIcon.stories.tsx +112 -979
  95. package/dist/icons/expand-icon/ExpandIcon.stories.tsx +109 -1146
  96. package/dist/icons/eye-close-icon/EyeCloseIcon.stories.tsx +141 -1068
  97. package/dist/icons/eye-open-icon/EyeOpenIcon.stories.tsx +140 -1081
  98. package/dist/icons/feature-shine-icon/FeatureShineIcon.stories.tsx +124 -1050
  99. package/dist/icons/file-chart-icon/FileChartIcon.stories.tsx +123 -1091
  100. package/dist/icons/file-text-icon/FileTextIcon.stories.tsx +122 -633
  101. package/dist/icons/filter-bar-row-icon/FilterBarRowIcon.stories.tsx +116 -1087
  102. package/dist/icons/forward-ten-seconds-icon/ForwardTenSecondsIcon.stories.tsx +166 -1020
  103. package/dist/icons/git-branch-icon/GitBranchIcon.stories.tsx +112 -1182
  104. package/dist/icons/git-fork-icon/GitForkIcon.stories.tsx +112 -1155
  105. package/dist/icons/globe-icon/GlobeIcon.stories.tsx +127 -325
  106. package/dist/icons/google-logo-icon/GoogleLogoIcon.stories.tsx +142 -985
  107. package/dist/icons/grip-vertical-icon/GripVerticalIcon.stories.tsx +116 -1217
  108. package/dist/icons/head-icon/HeadIcon.stories.tsx +108 -953
  109. package/dist/icons/heart-icon/HeartIcon.stories.tsx +117 -1060
  110. package/dist/icons/image-avatar-sparkle-icon/ImageAvatarSparkleIcon.stories.tsx +116 -716
  111. package/dist/icons/image-icon/ImageIcon.stories.tsx +102 -1164
  112. package/dist/icons/import-folder-icon/ImportFolderIcon.stories.tsx +108 -1233
  113. package/dist/icons/import-left-arrow-folder-icon/ImportLeftArrowFolderIcon.stories.tsx +133 -1289
  114. package/dist/icons/indian-flag-icon/IndianFlagIcon.stories.tsx +155 -1012
  115. package/dist/icons/instagram-icon/InstagramIcon.stories.tsx +158 -1438
  116. package/dist/icons/layout-column-icon/LayoutColumnIcon.stories.tsx +121 -1011
  117. package/dist/icons/layout-left-icon/LayoutLeftIcon.stories.tsx +116 -981
  118. package/dist/icons/layout-right-icon/LayoutRightIcon.stories.tsx +116 -979
  119. package/dist/icons/light-bulb-simple-icon/LightBulbSimpleIcon.stories.tsx +105 -1252
  120. package/dist/icons/linked-in-icon/LinkedInIcon.stories.tsx +151 -1554
  121. package/dist/icons/magic-book-icon/MagicBookIcon.stories.tsx +107 -1227
  122. package/dist/icons/magic-edit-icon/MagicEditIcon.stories.tsx +116 -707
  123. package/dist/icons/maintenance-icon/MaintenanceIcon.stories.tsx +119 -1226
  124. package/dist/icons/message-icon/MessageIcon.stories.tsx +111 -557
  125. package/dist/icons/minimize-icon/MinimizeIcon.stories.tsx +112 -1198
  126. package/dist/icons/moon-icon/MoonIcon.stories.tsx +117 -557
  127. package/dist/icons/move-horizontal-icon/MoveHorizontalIcon.stories.tsx +106 -1235
  128. package/dist/icons/move-vertical-icon/MoveVerticalIcon.stories.tsx +112 -1185
  129. package/dist/icons/musical-note-icon/MusicalNoteIcon.stories.tsx +116 -1012
  130. package/dist/icons/notepad-icon/NotepadIcon.stories.tsx +108 -1137
  131. package/dist/icons/notes-icon/NotesIcon.stories.tsx +116 -1138
  132. package/dist/icons/page-search-icon/PageSearchIcon.stories.tsx +106 -1146
  133. package/dist/icons/page-text-icon/PageTextIcon.stories.tsx +119 -719
  134. package/dist/icons/paint-roll-icon/PaintRollIcon.stories.tsx +110 -999
  135. package/dist/icons/paper-plane-icon/PaperPlaneIcon.stories.tsx +109 -912
  136. package/dist/icons/pause-icon/PauseIcon.stories.tsx +110 -1041
  137. package/dist/icons/pencil-icon/PencilIcon.stories.tsx +112 -1109
  138. package/dist/icons/phone-icon/PhoneIcon.stories.tsx +112 -1023
  139. package/dist/icons/plus-icon/PlusIcon.stories.tsx +103 -1132
  140. package/dist/icons/pocket-studio-icon/PocketStudioIcon.stories.tsx +104 -870
  141. package/dist/icons/scroll-down-icon/ScrollDownIcon.stories.tsx +99 -476
  142. package/dist/icons/search-icon/SearchIcon.stories.tsx +108 -1161
  143. package/dist/icons/setting-icon/SettingIcon.stories.tsx +104 -1009
  144. package/dist/icons/share-icon/ShareIcon.stories.tsx +117 -1064
  145. package/dist/icons/shield-icon/ShieldIcon.stories.tsx +114 -974
  146. package/dist/icons/site-logo-icon/SiteLogoIcon.stories.tsx +134 -1160
  147. package/dist/icons/skip-backward-icon/SkipBackwardIcon.stories.tsx +169 -1017
  148. package/dist/icons/skip-forward-icon/SkipForwardIcon.stories.tsx +161 -1016
  149. package/dist/icons/sparkles-soft-icon/SparklesSoftIcon.stories.tsx +102 -1001
  150. package/dist/icons/spinner-gradient-icon/SpinnerGradientIcon.stories.tsx +155 -593
  151. package/dist/icons/spinner-solid-icon/SpinnerSolidIcon.stories.tsx +155 -608
  152. package/dist/icons/spinner-solid-neutral-icon/SpinnerSolidINeutralcon.stories.tsx +142 -712
  153. package/dist/icons/star-icon/StarIcon.stories.tsx +120 -946
  154. package/dist/icons/store-coin-icon/StoreCoinIcon.stories.tsx +109 -1013
  155. package/dist/icons/suggestion-icon/SuggestionIcon.stories.tsx +113 -891
  156. package/dist/icons/sun-icon/SunIcon.stories.tsx +117 -864
  157. package/dist/icons/text-color-icon/TextColorIcon.stories.tsx +113 -989
  158. package/dist/icons/text-indicator-icon/TextIndicatorIcon.stories.tsx +120 -1027
  159. package/dist/icons/threads-icon/ThreadsIcon.stories.tsx +153 -1476
  160. package/dist/icons/tick-circle-icon/TickCircleIcon.stories.tsx +143 -1187
  161. package/dist/icons/tick-icon/TickIcon.stories.tsx +142 -1322
  162. package/dist/icons/trash-icon/TrashIcon.stories.tsx +105 -970
  163. package/dist/icons/twitter-x-icon/TwitterXIcon.stories.tsx +154 -1457
  164. package/dist/icons/upload-icon/UploadIcon.stories.tsx +112 -930
  165. package/dist/icons/vertical-menu-icon/VerticalMenuIcon.stories.tsx +115 -1019
  166. package/dist/icons/video-play-list-icon/VideoPlaylistIcon.stories.tsx +122 -1092
  167. package/dist/icons/voice-playing-icon/VoicePlayingIcon.stories.tsx +120 -1401
  168. package/dist/icons/volume-full-icon/VolumeFullIcon.stories.tsx +107 -1212
  169. package/dist/icons/volume-half-icon/VolumeHalfIcon.stories.tsx +109 -1122
  170. package/dist/icons/volume-off-icon/VolumeOffIcon.stories.tsx +112 -1124
  171. package/dist/icons/warning-icon/WarningIcon.stories.tsx +119 -1083
  172. package/dist/icons/youtube-icon/YoutubeIcon.stories.tsx +158 -983
  173. package/dist/index.cjs +90 -90
  174. package/dist/index.js +90 -90
  175. package/package.json +8 -3
@@ -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
  },