Live Preview
Actions
Copy ⌘C
Paste ⌘V
Danger Zone
Delete ⌘⌫
Dropdown Menu Component Generation Skill
Skill này hướng dẫn bạn (AI Agent) tạo component Dropdown Menu / Context Menu — danh sách actions hiển thị khi click trigger element.
1. Mục tiêu (Objective)
Tạo component Menu hỗ trợ nested submenus, keyboard shortcuts, grouped items, checkbox/radio items, và tích hợp 100% Design Tokens.
2. AI Context & Intent (Ngữ cảnh cho AI)
Khi nào dùng Dropdown Menu?
- Action list: Click button → hiện list actions (Edit, Delete, Share...)
- More actions: "⋯" button → hiện thêm options
- User menu: Avatar click → Profile, Settings, Logout
- Context menu: Right-click → context actions
Phân biệt với component khác
| Tình huống | Component đúng | Lý do |
|---|---|---|
| Danh sách actions khi click | Dropdown Menu | Action items |
| Chọn giá trị từ list (form) | Select | Form input, selection |
| Interactive content (form, rich) | Popover | Rich content, not just actions |
| Confirm hành động | Modal (alert) | Blocking confirmation |
| Chú thích khi hover | Tooltip | Text-only, non-interactive |
Decision Tree cho AI
text
Cần hiển thị list khi click?
├─ Danh sách actions (Edit, Delete...) → Dropdown Menu
│ ├─ Có sub-categories → Dropdown Menu with submenus
│ ├─ Có toggleable items → Dropdown Menu with checkbox/radio items
│ └─ Right-click triggered → Context Menu (same component)
├─ Chọn value cho form → Select
├─ Rich interactive content → Popover
└─ Info khi hover → Tooltip
3. Ngữ nghĩa & Phân loại (Semantics)
3.1. Item Types
| Type | Mô tả | Visual |
|---|---|---|
item |
Action item thường | Icon + Label + Shortcut |
checkbox |
Toggle on/off | ☑/☐ + Label |
radio |
Select 1 trong group | ◉/○ + Label |
label |
Section header (non-interactive) | Uppercase, muted text |
separator |
Visual divider | Horizontal line |
submenu |
Item mở sub-menu | Label + Arrow → |
3.2. Item Anatomy
text
┌─────────────────────────────────┐
│ [✓] [pencil] Edit Ctrl+E │ ← check + icon + label + shortcut
│ [copy] Copy Ctrl+C │
│ [clipboard] Paste Ctrl+V │
│ ───────────────────────────── │ ← separator
│ GROUP LABEL │ ← label
│ [share] Share ► │ ← submenu arrow
│ ───────────────────────────── │
│ [trash] Delete ⌫ │ ← destructive item (red text)
└─────────────────────────────────┘
Icon: Tất cả icon trong Dropdown Menu PHẢI dùng Lucide SVG từ
assets/icons/. CẤM dùng text emoji. Xemicon.mdmục 10.
3.3. Destructive Items
- Text và icon màu
destructive(đỏ) - Hover background cũng khác (nhẹ đỏ)
- Dùng cho: Delete, Remove, Revoke, Ban
3.5. Slot Map (Figma ↔ Code)
📎 Source:
slot-manifest.json→dropdown-menu· Layer: surface
| Figma Slot | data-slot |
CSS Class | Required | Accepts |
|---|---|---|---|---|
| Root | dropdown-menu |
.dropdown-menu |
✅ | — |
| Trigger | dropdown-menu-trigger |
— | ✅ | button, icon-button |
| Content | dropdown-menu-content |
.dropdown-menu-content |
✅ | dropdown-menu-item-group |
| Item | dropdown-menu-item |
.dropdown-menu-item |
✅ (n×) | menu-item |
| Item Icon | dropdown-menu-item-icon |
— | ❌ | icon |
| Item Label | dropdown-menu-item-label |
— | ✅ | text |
| Shortcut | dropdown-menu-item-shortcut |
— | ❌ | text |
| Separator | dropdown-menu-separator |
— | ❌ | divider |
4. Token Mapping
?? Atomic Mapping: Xem
ATOMIC-MAPPING.md? m?c dropdown-menu. Component token JSON files d� deprecated.Semantic color tokens (qua Mode layer) và state-specific colors được define đầy đủ trong file
.tokens.json.
5. Props & API
typescript
interface DropdownMenuProps {
/** Trigger element */
trigger: ReactNode;
/** Align popup relative to trigger */
align?: 'start' | 'center' | 'end';
/** Side of trigger to show */
side?: 'top' | 'bottom' | 'left' | 'right';
children: ReactNode;
}
interface MenuItemProps {
/** Label */
label: string;
/** Leading icon */
icon?: ReactNode;
/** Keyboard shortcut hint */
shortcut?: string;
/** Disabled */
disabled?: boolean;
/** Destructive styling */
destructive?: boolean;
/** Click handler */
onClick?: () => void;
}
interface MenuCheckboxItemProps {
label: string;
checked?: boolean;
onChange?: (checked: boolean) => void;
}
interface MenuRadioGroupProps {
value?: string;
onChange?: (value: string) => void;
children: ReactNode;
}
interface MenuSubProps {
/** Sub-menu trigger label */
label: string;
icon?: ReactNode;
children: ReactNode;
}
6. Accessibility (a11y)
- Trigger:
aria-haspopup="menu",aria-expanded="true"/"false". - Menu:
role="menu". - Item:
role="menuitem". Checkbox:role="menuitemcheckbox". Radio:role="menuitemradio". - Keyboard:
Enter/Spaceactivate item,Arrow Up/Downnavigate,Arrow Rightopen submenu,Arrow Leftclose submenu,Escapeclose menu. - Disabled:
aria-disabled="true", skip khi arrow-key navigate. - Shortcut: Hiển thị visual +
aria-keyshortcutsattribute. - Focus: Auto-focus first item khi menu opens. Return focus to trigger khi close.
- Typeahead: Gõ ký tự → focus item có label bắt đầu bằng ký tự đó.
7. Best Practices & Rules
- Portal: Render menu qua Portal — tránh overflow/z-index issues.
- Collision detection: Auto-flip position khi không đủ viewport space.
- Close triggers: Click item, click outside, Escape, scroll page.
- Submenu delay: 200ms delay trước khi mở submenu (tránh flash khi di chuột ngang).
- Max items visible: ~8-10 items, scroll nếu nhiều hơn.
- Shortcut alignment: Keyboard shortcuts right-aligned, mono font.
- Destructive cuối cùng: Đặt destructive items ở cuối menu, sau separator.
- Không Hardcode: Mọi giá trị từ Token.
- Icon: CHỈ dùng Lucide icon từ
assets/icons/. CẤM dùng text emoji. Xemicon.mdmục 10.
8. Example Usage
jsx
{/* Basic dropdown */}
<DropdownMenu trigger={<Button variant="ghost" icon={<MoreIcon />} />}>
<MenuItem icon={<EditIcon />} label="Chỉnh sửa" shortcut="Ctrl+E" onClick={edit} />
<MenuItem icon={<CopyIcon />} label="Sao chép" shortcut="Ctrl+C" onClick={copy} />
<MenuSeparator />
<MenuLabel>Chia sẻ</MenuLabel>
<MenuSub label="Chia sẻ qua..." icon={<ShareIcon />}>
<MenuItem label="Email" onClick={shareEmail} />
<MenuItem label="Link" onClick={shareLink} />
</MenuSub>
<MenuSeparator />
<MenuItem icon={<TrashIcon />} label="Xoá" destructive onClick={del} />
</DropdownMenu>
{/* User menu with checkbox */}
<DropdownMenu trigger={<Avatar src="/user.jpg" size="sm" />} align="end">
<MenuLabel>Ngọc Lê</MenuLabel>
<MenuItem label="Hồ sơ" href="/profile" />
<MenuItem label="Cài đặt" href="/settings" />
<MenuSeparator />
<MenuCheckboxItem label="Chế độ tối" checked={isDark} onChange={setIsDark} />
<MenuSeparator />
<MenuItem label="Đăng xuất" onClick={logout} />
</DropdownMenu>