# CmdPalette

> Command palette modal kiểu Linear/Notion — search + keyboard navigate qua list action, grouped theo category. Trigger thường là `Cmd+K`.

**Archetype mapping:** B (Tool) · A (Dashboard) — chuẩn cho admin/SaaS có nhiều shortcut.

## API

| Prop | Type | Default | Description |
|---|---|---|---|
| `open` | `boolean` | required | Controlled — caller toggle theo `Cmd+K` global listener |
| `onClose` | `() => void` | required | Callback đóng palette |
| `commands` | `CmdCommand[]` | required | Danh sách action có thể search |
| `placeholder` | `string` | `'Type a command or search...'` | Placeholder input |
| `className` | `string` | — | Override dialog |

### `CmdCommand`

| Field | Type | Description |
|---|---|---|
| `label` | `string` | Text hiển thị + match search |
| `group` | `string?` | Nhóm command (vd "Navigation", "Settings") — default `'Commands'` |
| `kbd` | `string?` | Hint phím tắt (vd `'⌘N'`) hiển thị bên phải |
| `onSelect` | `() => void` | Callback khi user chọn — palette tự close sau khi chạy |

## Variants

Không có variant — single modal layout.

## Composition

Standalone — không có sub-component. Caller pass full danh sách `commands` flat, palette tự group theo `command.group`.

## States

- **Closed**: component return `null`, body overflow restored
- **Open**: overlay + dialog mounted, body scroll lock
- **Empty results**: "No results." nếu query không match
- **Active item**: bg-primary-soft + text-primary-deep (arrow keys hoặc hover)

## Usage

```tsx
import { CmdPalette } from '@cowork/ui';
import { useState, useEffect } from 'react';

const [open, setOpen] = useState(false);

// Cmd+K listener
useEffect(() => {
  const onKey = (e: KeyboardEvent) => {
    if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
      e.preventDefault();
      setOpen(true);
    }
  };
  window.addEventListener('keydown', onKey);
  return () => window.removeEventListener('keydown', onKey);
}, []);

<CmdPalette
  open={open}
  onClose={() => setOpen(false)}
  commands={[
    { group: 'Navigation', label: 'Đi tới Dashboard', kbd: 'G D', onSelect: () => router.push('/dashboard') },
    { group: 'Navigation', label: 'Đi tới Đơn hàng', kbd: 'G O', onSelect: () => router.push('/orders') },
    { group: 'Actions', label: 'Tạo đơn mới', kbd: '⌘N', onSelect: () => createOrder() },
    { group: 'Actions', label: 'Đăng xuất', onSelect: () => logout() },
  ]}
/>
```

## Real-world example

- An Nhien admin: Cmd+K mở palette với commands `Tạo đơn mới · Tìm khách · Đi tới báo cáo · Xuất Excel`. Power-user dùng thay vì sidebar.
- KHI CRM: palette filter nhanh members theo lifecycle, push action `Chuyển sang Active · Gửi tin nhắn Zalo · Mở hồ sơ`.
- Internal AI tools dashboard (`tools.dang.pm`): commands switch project, refresh data, mở plan file gần nhất.

## Accessibility

- Dialog có `role="dialog"` + `aria-modal="true"`
- Input có `role="combobox"` + `aria-expanded="true"` + `aria-controls="cmd-listbox"`
- List có `role="listbox"`, mỗi item có `role="option"` + `aria-selected`
- **Focus trap**: input auto-focus khi mở (chưa trap full — Tab có thể escape ra ngoài, cần thêm sentinel nếu cần strict)
- **Scroll lock**: `document.body.style.overflow = 'hidden'` khi mở
- **Escape**: đóng palette
- **Arrow keys**: ↑↓ di chuyển active, `Enter` chạy command
- Backdrop click đóng palette

## Responsive behavior

Dialog `max-w-xl` (576px) căn giữa horizontal, top `15vh`. Mobile full-width có padding `p-4`. List scrollable `max-h-80` (320px) khi nhiều command.

## Related

- [[Modal]] — base dialog primitive, CmdPalette là specialization có search + keyboard nav
- [[SearchBar]] — chỉ input search, không có result list + keyboard nav
- [[Dropdown]] — menu nhỏ trigger từ button, không phải global Cmd+K

## Source

`react/src/organisms/CmdPalette.tsx`
