CommandMenu

Overlay

검색어와 `>` 접두사로 명령어를 함께 찾는 커맨드 팔레트.

Usage

탭·문서 목록 검색과 명령 실행을 하나의 입력창에서 함께 제공할 때

import

import

import { CommandMenu } from '@mildang/design-system/unofficial/CommandMenu';

예제를 복사해 쓸 때 필요한 준비

@mildang/icons 를 따로 설치한다. DS 패키지에 아이콘 컴포넌트가 포함되지 않는다.

API Reference

CommandMenu Props

Prop

Type

Default

ariaLabel
필수

string

지정 안 함

emptyMessage
필수

ReactNode

지정 안 함

inputValue
필수

string

지정 안 함

items
필수

CommandMenuItemData[]

지정 안 함

onInputValueChange
필수

(value: string) => void

지정 안 함

onOpenChange
필수

(open: boolean) => void

지정 안 함

open
필수

boolean

지정 안 함

placeholder
필수

string

지정 안 함

CommandMenuItem

Prop

Type

Default

onSelect
필수

() => void

지정 안 함

title
필수

ReactNode

지정 안 함

active

boolean

지정 안 함

description

ReactNode

지정 안 함

icon

ReactNode

지정 안 함

id

string

지정 안 함

onMouseEnter

() => void

지정 안 함

subtitle

ReactNode

지정 안 함

예제

기본 사용

목록을 검색하다가 `>`를 입력하면 명령어 모드로 전환되는 기본형입니다.

코드

import React from 'react';
import { CommandMenu, type CommandMenuItemData } from '@mildang/design-system/unofficial/CommandMenu';
import { Button } from '@mildang/design-system/Button';
import AssignmentIcon from '@mildang/icons/react/assignment';
import NotesIcon from '@mildang/icons/react/editor/notes';

const sampleTabs: Array<{ id: string; title: string; subtitle?: string; description?: string; icon: React.ReactNode }> = [
  { id: 'tab-1', title: 'CommandMenu.stories.tsx', subtitle: 'packages/design-system/src/ui/CommandMenu', icon: <AssignmentIcon />, description: 'Enter' },
  { id: 'tab-2', title: 'CommandMenu.ts', subtitle: 'packages/design-system/src/preset/recipes', icon: <AssignmentIcon /> },
  { id: 'tab-3', title: '학습 활동', subtitle: '챕터 A / 모듈 1', icon: <NotesIcon /> },
  { id: 'tab-4', title: '학습 활동', subtitle: '챕터 B / 모듈 2', icon: <NotesIcon /> },
];

const sampleCommands: Array<{ id: string; title: string; description?: string }> = [
  { id: 'close-window', title: '창 닫기', description: '⌘ W' },
  { id: 'close-all-windows', title: '모든 창 닫기', description: '⌘ Shift W' },
  { id: 'save', title: '저장', description: '⌘ S' },
  { id: 'save-all', title: '모두 저장', description: '⌘ Shift S' },
  { id: 'save-as', title: '다른 이름으로 저장', description: '⌘ Shift A' },
];

const CommandMenuDefaultExample = function Default() {
    const [open, setOpen] = React.useState(true);
    const [inputValue, setInputValue] = React.useState('');

    const isCommandMode = inputValue.startsWith('>');
    const items: CommandMenuItemData[] = React.useMemo(() => {
      if (isCommandMode) {
        const q = inputValue.slice(1).trim().toLowerCase();
        const filtered = q
          ? sampleCommands.filter((c) => c.title.toLowerCase().includes(q))
          : sampleCommands;
        return filtered.map((c) => ({
          id: c.id,
          title: c.title,
          description: c.description,
          onSelect: () => setOpen(false),
        }));
      }
      const q = inputValue.toLowerCase();
      const filtered = q
        ? sampleTabs.filter((t) => t.title.toLowerCase().includes(q))
        : sampleTabs;
      return filtered.map((t) => ({
        id: t.id,
        icon: t.icon,
        title: t.title,
        subtitle: t.subtitle,
        description: t.description,
        onSelect: () => setOpen(false),
      }));
    }, [inputValue, isCommandMode]);

    return (
      <>
        <Button variant="tertiary" onClick={() => setOpen(true)}>
          Open CommandMenu
        </Button>
        <CommandMenu
          open={open}
          onOpenChange={(next) => {
            setOpen(next);
            if (!next) setInputValue('');
          }}
          inputValue={inputValue}
          onInputValueChange={setInputValue}
          items={items}
          placeholder="목록 검색 ( > 로 명령어)"
          emptyMessage={isCommandMode ? '일치하는 명령어가 없습니다.' : '일치하는 목록이 없습니다.'}
          ariaLabel="커맨드 메뉴"
        />
      </>
    );
  };

export default CommandMenuDefaultExample;