Skip to content

Commit dd7cd9e

Browse files
committed
feat(CommandPalette): add new component * 5
1 parent 0da8418 commit dd7cd9e

File tree

9 files changed

+528
-47
lines changed

9 files changed

+528
-47
lines changed

src/components/actions/CommandMenu/CommandMenu.docs.mdx

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,119 @@ const [searchValue, setSearchValue] = useState('');
188188
</CommandMenu>
189189
```
190190

191+
### With Selection
192+
193+
```jsx
194+
const [selectedKeys, setSelectedKeys] = useState(['copy']);
195+
196+
<CommandMenu
197+
selectionMode="single"
198+
selectedKeys={selectedKeys}
199+
onSelectionChange={setSelectedKeys}
200+
searchPlaceholder="Select commands..."
201+
>
202+
<Menu.Item key="copy">Copy</Menu.Item>
203+
<Menu.Item key="paste">Paste</Menu.Item>
204+
<Menu.Item key="cut">Cut</Menu.Item>
205+
</CommandMenu>
206+
```
207+
208+
### Multiple Selection
209+
210+
```jsx
211+
const [selectedKeys, setSelectedKeys] = useState(['copy', 'paste']);
212+
213+
<CommandMenu
214+
selectionMode="multiple"
215+
selectedKeys={selectedKeys}
216+
onSelectionChange={setSelectedKeys}
217+
searchPlaceholder="Select multiple commands..."
218+
>
219+
<Menu.Item key="copy">Copy</Menu.Item>
220+
<Menu.Item key="paste">Paste</Menu.Item>
221+
<Menu.Item key="cut">Cut</Menu.Item>
222+
</CommandMenu>
223+
```
224+
225+
### With DialogTrigger
226+
227+
Use CommandMenu inside a Dialog with DialogTrigger for modal command palette functionality:
228+
229+
```jsx
230+
<DialogTrigger>
231+
<Button>Open Command Menu</Button>
232+
<Dialog size="medium" isDismissable={false}>
233+
<CommandMenu width="100%" height="max(40x, 90vh)" size="medium" searchPlaceholder="Search commands...">
234+
<Menu.Item key="copy" description="Copy selected text" hotkeys="Ctrl+C">
235+
Copy
236+
</Menu.Item>
237+
<Menu.Item key="paste" description="Paste from clipboard" hotkeys="Ctrl+V">
238+
Paste
239+
</Menu.Item>
240+
<Menu.Item key="cut" description="Cut selected text" hotkeys="Ctrl+X">
241+
Cut
242+
</Menu.Item>
243+
</CommandMenu>
244+
</Dialog>
245+
</DialogTrigger>
246+
```
247+
248+
### With useDialogContainer Hook
249+
250+
For programmatic control over the command menu dialog:
251+
252+
```jsx
253+
import { useDialogContainer } from '@cube-dev/ui-kit';
254+
255+
function CommandMenuDialogContent({ onClose, ...args }) {
256+
const handleAction = (key) => {
257+
console.log('Action selected:', key);
258+
onClose();
259+
};
260+
261+
return (
262+
<Dialog size="medium" isDismissable={false}>
263+
<CommandMenu
264+
width="100%"
265+
height="max(40x, 90vh)"
266+
size="medium"
267+
onAction={handleAction}
268+
{...args}
269+
>
270+
<Menu.Item key="copy" description="Copy selected text" hotkeys="Ctrl+C">
271+
Copy
272+
</Menu.Item>
273+
<Menu.Item key="paste" description="Paste from clipboard" hotkeys="Ctrl+V">
274+
Paste
275+
</Menu.Item>
276+
<Menu.Item key="cut" description="Cut selected text" hotkeys="Ctrl+X">
277+
Cut
278+
</Menu.Item>
279+
</CommandMenu>
280+
</Dialog>
281+
);
282+
}
283+
284+
function App() {
285+
const dialog = useDialogContainer(CommandMenuDialogContent);
286+
287+
const handleOpenDialog = () => {
288+
dialog.open({
289+
searchPlaceholder: 'Search commands...',
290+
autoFocus: true,
291+
onClose: dialog.close,
292+
});
293+
};
294+
295+
return (
296+
<div>
297+
<Button onPress={handleOpenDialog}>Open Command Menu</Button>
298+
{dialog.rendered}
299+
</div>
300+
);
301+
}
302+
```
303+
191304
## Advanced Features
192305

193306
### Enhanced Search

src/components/actions/CommandMenu/CommandMenu.stories.tsx

Lines changed: 238 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,24 @@
11
import { expect, userEvent, waitFor, within } from '@storybook/test';
2+
import {
3+
IconArrowBack,
4+
IconArrowForward,
5+
IconClipboard,
6+
IconCopy,
7+
IconCut,
8+
IconDeviceFloppy,
9+
IconFile,
10+
IconFileText,
11+
IconFolder,
12+
IconSearch,
13+
IconSettings,
14+
} from '@tabler/icons-react';
215
import React, { useState } from 'react';
316

4-
import { Dialog, DialogTrigger } from '../../overlays/Dialog';
17+
import {
18+
Dialog,
19+
DialogTrigger,
20+
useDialogContainer,
21+
} from '../../overlays/Dialog';
522
import { Button } from '../Button';
623
import { Menu } from '../Menu/Menu';
724

@@ -557,6 +574,41 @@ MultipleSelection.args = {
557574
autoFocus: true,
558575
};
559576

577+
export const SingleSelection: StoryFn<CubeCommandMenuProps<any>> = (args) => {
578+
const [selectedKey, setSelectedKey] = useState<string | null>(null);
579+
580+
return (
581+
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
582+
<div>
583+
<strong>Selected:</strong> {selectedKey || 'None'}
584+
</div>
585+
<CommandMenu
586+
{...args}
587+
selectionMode="single"
588+
selectedKeys={selectedKey ? [selectedKey] : []}
589+
onSelectionChange={(keys) => {
590+
setSelectedKey(keys[0] || null);
591+
}}
592+
>
593+
{basicCommands.map((command) => (
594+
<CommandMenu.Item
595+
key={command.key}
596+
description={command.description}
597+
hotkeys={command.hotkeys}
598+
>
599+
{command.label}
600+
</CommandMenu.Item>
601+
))}
602+
</CommandMenu>
603+
</div>
604+
);
605+
};
606+
607+
SingleSelection.args = {
608+
searchPlaceholder: 'Select a single command...',
609+
autoFocus: true,
610+
};
611+
560612
export const CustomStyling: StoryFn<CubeCommandMenuProps<any>> = (args) => (
561613
<CommandMenu
562614
{...args}
@@ -673,7 +725,7 @@ export const WithDialog: StoryFn<CubeCommandMenuProps<any>> = (args) => (
673725
<DialogTrigger>
674726
<Button>Open Command Menu</Button>
675727
<Dialog size="medium" isDismissable={false}>
676-
<CommandMenu width="100%" height="20x" {...args} size="medium">
728+
<CommandMenu width="100%" height="min(40x, 90vh)" {...args} size="medium">
677729
{basicCommands.map((command) => (
678730
<CommandMenu.Item
679731
key={command.key}
@@ -692,3 +744,187 @@ WithDialog.args = {
692744
searchPlaceholder: 'Search commands...',
693745
autoFocus: true,
694746
};
747+
748+
WithDialog.play = async ({ canvasElement }) => {
749+
const canvas = within(canvasElement);
750+
const button = canvas.getByText('Open Command Menu');
751+
await userEvent.click(button);
752+
753+
// Wait for dialog to open
754+
await waitFor(() => {
755+
canvas.getByPlaceholderText('Search commands...');
756+
});
757+
};
758+
759+
function CommandMenuDialogContent({
760+
onClose,
761+
...args
762+
}: CubeCommandMenuProps<any> & { onClose: () => void }) {
763+
const commandMenuProps = {
764+
...args,
765+
onAction: (key: React.Key) => {
766+
console.log('Action selected:', key);
767+
onClose();
768+
},
769+
};
770+
771+
return (
772+
<Dialog size="medium" isDismissable={false}>
773+
<CommandMenu
774+
width="100%"
775+
height="min(40x, 90vh)"
776+
{...commandMenuProps}
777+
size="medium"
778+
>
779+
{basicCommands.map((command) => (
780+
<CommandMenu.Item
781+
key={command.key}
782+
description={command.description}
783+
hotkeys={command.hotkeys}
784+
>
785+
{command.label}
786+
</CommandMenu.Item>
787+
))}
788+
</CommandMenu>
789+
</Dialog>
790+
);
791+
}
792+
793+
export const WithDialogContainer: StoryFn<CubeCommandMenuProps<any>> = (
794+
args,
795+
) => {
796+
const dialog = useDialogContainer(CommandMenuDialogContent);
797+
798+
const handleOpenDialog = () => {
799+
dialog.open({
800+
...args,
801+
onClose: dialog.close,
802+
});
803+
};
804+
805+
return (
806+
<div>
807+
<Button onPress={handleOpenDialog}>Open Command Menu (Hook)</Button>
808+
{dialog.rendered}
809+
</div>
810+
);
811+
};
812+
813+
WithDialogContainer.args = {
814+
searchPlaceholder: 'Search commands...',
815+
autoFocus: true,
816+
};
817+
818+
WithDialogContainer.play = async ({ canvasElement }) => {
819+
const canvas = within(canvasElement);
820+
const button = canvas.getByText('Open Command Menu (Hook)');
821+
await userEvent.click(button);
822+
823+
// Wait for dialog to open
824+
await waitFor(() => {
825+
canvas.getByPlaceholderText('Search commands...');
826+
});
827+
};
828+
829+
export const WithIcons: StoryFn<CubeCommandMenuProps<any>> = (args) => (
830+
<CommandMenu {...args}>
831+
<Menu.Section title="File Operations">
832+
<CommandMenu.Item
833+
key="new-file"
834+
icon={<IconFile />}
835+
description="Create a new file"
836+
hotkeys="Ctrl+N"
837+
>
838+
New File
839+
</CommandMenu.Item>
840+
<CommandMenu.Item
841+
key="open-file"
842+
icon={<IconFolder />}
843+
description="Open an existing file"
844+
hotkeys="Ctrl+O"
845+
>
846+
Open File
847+
</CommandMenu.Item>
848+
<CommandMenu.Item
849+
key="save-file"
850+
icon={<IconDeviceFloppy />}
851+
description="Save current file"
852+
hotkeys="Ctrl+S"
853+
>
854+
Save File
855+
</CommandMenu.Item>
856+
</Menu.Section>
857+
858+
<Menu.Section title="Edit Operations">
859+
<CommandMenu.Item
860+
key="copy"
861+
icon={<IconCopy />}
862+
description="Copy selected text"
863+
hotkeys="Ctrl+C"
864+
keywords={['duplicate', 'clone']}
865+
>
866+
Copy
867+
</CommandMenu.Item>
868+
<CommandMenu.Item
869+
key="paste"
870+
icon={<IconClipboard />}
871+
description="Paste from clipboard"
872+
hotkeys="Ctrl+V"
873+
keywords={['insert']}
874+
>
875+
Paste
876+
</CommandMenu.Item>
877+
<CommandMenu.Item
878+
key="cut"
879+
icon={<IconCut />}
880+
description="Cut selected text"
881+
hotkeys="Ctrl+X"
882+
>
883+
Cut
884+
</CommandMenu.Item>
885+
<CommandMenu.Item
886+
key="undo"
887+
icon={<IconArrowBack />}
888+
description="Undo last action"
889+
hotkeys="Ctrl+Z"
890+
>
891+
Undo
892+
</CommandMenu.Item>
893+
<CommandMenu.Item
894+
key="redo"
895+
icon={<IconArrowForward />}
896+
description="Redo last action"
897+
hotkeys="Ctrl+Y"
898+
>
899+
Redo
900+
</CommandMenu.Item>
901+
</Menu.Section>
902+
903+
<Menu.Section title="Tools">
904+
<CommandMenu.Item
905+
key="search"
906+
icon={<IconSearch />}
907+
description="Search in files"
908+
hotkeys="Ctrl+F"
909+
>
910+
Search
911+
</CommandMenu.Item>
912+
<CommandMenu.Item
913+
key="settings"
914+
icon={<IconSettings />}
915+
description="Open settings"
916+
hotkeys="Ctrl+."
917+
>
918+
Settings
919+
</CommandMenu.Item>
920+
<CommandMenu.Item key="documents" description="View all documents">
921+
Documents
922+
</CommandMenu.Item>
923+
</Menu.Section>
924+
</CommandMenu>
925+
);
926+
927+
WithIcons.args = {
928+
searchPlaceholder: 'Search commands with icons...',
929+
autoFocus: true,
930+
};

0 commit comments

Comments
 (0)