@thanh-libs/menu 0.0.2 → 0.0.3
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.
- package/README.md +612 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
# @thanh-libs/menu
|
|
2
|
+
|
|
3
|
+
Persistent navigation menu for React — compound-component API with **inline collapsible** and **floating popover** sub-menus, keyboard navigation, typeahead search, customisable color schemes, and active-state indicators. Built with [Emotion](https://emotion.sh/) and [Floating UI](https://floating-ui.com/).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @thanh-libs/menu
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Peer dependencies
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install react react-dom @emotion/react @emotion/styled @floating-ui/react @thanh-libs/theme @thanh-libs/dialog
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import {
|
|
23
|
+
Menu, MenuItem, MenuLabel, MenuDivider,
|
|
24
|
+
MenuGroup, MenuSub, MenuSubTrigger, MenuSubContent,
|
|
25
|
+
} from '@thanh-libs/menu';
|
|
26
|
+
|
|
27
|
+
function Sidebar() {
|
|
28
|
+
const [active, setActive] = useState('home');
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Menu style={{ width: 260 }}>
|
|
32
|
+
<MenuItem
|
|
33
|
+
icon={<HomeIcon />}
|
|
34
|
+
selected={active === 'home'}
|
|
35
|
+
onClick={() => setActive('home')}
|
|
36
|
+
>
|
|
37
|
+
Home
|
|
38
|
+
</MenuItem>
|
|
39
|
+
|
|
40
|
+
<MenuSub>
|
|
41
|
+
<MenuSubTrigger icon={<SettingsIcon />}>Settings</MenuSubTrigger>
|
|
42
|
+
<MenuSubContent>
|
|
43
|
+
<MenuItem
|
|
44
|
+
selected={active === 'general'}
|
|
45
|
+
onClick={() => setActive('general')}
|
|
46
|
+
>
|
|
47
|
+
General
|
|
48
|
+
</MenuItem>
|
|
49
|
+
<MenuItem
|
|
50
|
+
selected={active === 'security'}
|
|
51
|
+
onClick={() => setActive('security')}
|
|
52
|
+
>
|
|
53
|
+
Security
|
|
54
|
+
</MenuItem>
|
|
55
|
+
</MenuSubContent>
|
|
56
|
+
</MenuSub>
|
|
57
|
+
|
|
58
|
+
<MenuDivider />
|
|
59
|
+
<MenuItem danger>Logout</MenuItem>
|
|
60
|
+
</Menu>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Components
|
|
68
|
+
|
|
69
|
+
### Menu
|
|
70
|
+
|
|
71
|
+
Root container. Provides context (mode, density, color scheme, …) to all descendants. Always rendered — this is a **persistent visible list**, not a dropdown/popover.
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
<Menu dense mode="inline" display="default" style={{ width: 260 }}>
|
|
75
|
+
{/* MenuItem, MenuSub, MenuDivider, MenuGroup, MenuLabel … */}
|
|
76
|
+
</Menu>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
#### Props
|
|
80
|
+
|
|
81
|
+
| Prop | Type | Default | Description |
|
|
82
|
+
|------|------|---------|-------------|
|
|
83
|
+
| `children` | `ReactNode` | **(required)** | Menu items and sub-components |
|
|
84
|
+
| `dense` | `boolean` | `false` | Compact mode — smaller padding and font size |
|
|
85
|
+
| `maxHeight` | `number \| string` | — | Fixed max height with overflow scroll |
|
|
86
|
+
| `mode` | `MenuSubMode` | `'inline'` | Default sub-menu mode for all nested `MenuSub` |
|
|
87
|
+
| `display` | `MenuDisplay` | `'default'` | `'icon'` shows only icons (mini-sidebar) |
|
|
88
|
+
| `trigger` | `MenuSubTriggerType` | `'hover'` | Default popover trigger type (`'hover'` or `'click'`) |
|
|
89
|
+
| `floatingSettings` | `MenuFloatingSettings` | — | Global Floating UI settings for popover sub-menus |
|
|
90
|
+
| `colorScheme` | `MenuColorScheme` | — | Custom color scheme (raw CSS color strings) |
|
|
91
|
+
| `activeIndicator` | `MenuActiveIndicator` | `'dot'` | Active indicator on selected items (see below) |
|
|
92
|
+
| `showDot` | `boolean` | `false` | Show inline dot bullets on child items inside `MenuSubContent` |
|
|
93
|
+
|
|
94
|
+
Also accepts all native `<div>` HTML attributes (`className`, `style`, `id`, `onKeyDown`, etc.).
|
|
95
|
+
|
|
96
|
+
#### Active Indicator
|
|
97
|
+
|
|
98
|
+
Controls the visual marker beside selected items:
|
|
99
|
+
|
|
100
|
+
| Value | Description |
|
|
101
|
+
|-------|-------------|
|
|
102
|
+
| `'dot'` (default) | Small filled circle beside the selected item |
|
|
103
|
+
| `'bar'` | Vertical bar beside the selected item |
|
|
104
|
+
| `false` | No indicator |
|
|
105
|
+
| `ReactNode` | Custom indicator element |
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
<Menu activeIndicator="bar">…</Menu>
|
|
109
|
+
<Menu activeIndicator={false}>…</Menu>
|
|
110
|
+
<Menu activeIndicator={<CustomIcon />}>…</Menu>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
### MenuItem
|
|
116
|
+
|
|
117
|
+
Clickable action or navigation item. Supports icon, shortcut, disabled, danger, and selected states.
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
<MenuItem
|
|
121
|
+
icon={<UserIcon />}
|
|
122
|
+
shortcut="⌘K"
|
|
123
|
+
selected
|
|
124
|
+
onClick={() => navigate('/users')}
|
|
125
|
+
>
|
|
126
|
+
Users
|
|
127
|
+
</MenuItem>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
#### Props
|
|
131
|
+
|
|
132
|
+
| Prop | Type | Default | Description |
|
|
133
|
+
|------|------|---------|-------------|
|
|
134
|
+
| `children` | `ReactNode` | **(required)** | Label content |
|
|
135
|
+
| `icon` | `ReactNode` | — | Leading icon (20×20 container, SVGs auto-fill) |
|
|
136
|
+
| `shortcut` | `ReactNode` | — | Trailing keyboard shortcut text |
|
|
137
|
+
| `disabled` | `boolean` | `false` | Disabled state — no click, muted color |
|
|
138
|
+
| `danger` | `boolean` | `false` | Destructive/danger styling (red) |
|
|
139
|
+
| `selected` | `boolean` | `false` | Selected/active state — bold text + background highlight |
|
|
140
|
+
| `onClick` | `() => void` | — | Click handler (blocked when disabled) |
|
|
141
|
+
|
|
142
|
+
Also accepts all native `<div>` HTML attributes except `onClick` (redefined as `() => void`).
|
|
143
|
+
|
|
144
|
+
**Auto-expand behaviour**: When `selected={true}` and the item is inside a `MenuSub`, the parent sub-menu (and all ancestors) automatically expand to reveal the active item on mount.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
### MenuLabel
|
|
149
|
+
|
|
150
|
+
Non-interactive group heading text.
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
<MenuLabel>Navigation</MenuLabel>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
#### Props
|
|
157
|
+
|
|
158
|
+
| Prop | Type | Default | Description |
|
|
159
|
+
|------|------|---------|-------------|
|
|
160
|
+
| `children` | `ReactNode` | **(required)** | Label text |
|
|
161
|
+
| `className` | `string` | — | Additional CSS class |
|
|
162
|
+
| `style` | `CSSProperties` | — | Inline styles |
|
|
163
|
+
| `id` | `string` | — | Element ID |
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
### MenuDivider
|
|
168
|
+
|
|
169
|
+
Visual horizontal separator between menu items.
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
<MenuDivider />
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
#### Props
|
|
176
|
+
|
|
177
|
+
| Prop | Type | Default | Description |
|
|
178
|
+
|------|------|---------|-------------|
|
|
179
|
+
| `className` | `string` | — | Additional CSS class |
|
|
180
|
+
| `style` | `CSSProperties` | — | Inline styles |
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
### MenuGroup
|
|
185
|
+
|
|
186
|
+
Semantic grouping of items with an optional label shorthand.
|
|
187
|
+
|
|
188
|
+
```tsx
|
|
189
|
+
<MenuGroup label="Account">
|
|
190
|
+
<MenuItem>Profile</MenuItem>
|
|
191
|
+
<MenuItem>Settings</MenuItem>
|
|
192
|
+
</MenuGroup>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
#### Props
|
|
196
|
+
|
|
197
|
+
| Prop | Type | Default | Description |
|
|
198
|
+
|------|------|---------|-------------|
|
|
199
|
+
| `children` | `ReactNode` | **(required)** | Menu items inside the group |
|
|
200
|
+
| `label` | `ReactNode` | — | Optional group label (renders a `MenuLabel` internally) |
|
|
201
|
+
| `className` | `string` | — | Additional CSS class |
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
### MenuSub
|
|
206
|
+
|
|
207
|
+
Container for a collapsible or floating sub-menu. Wraps `MenuSubTrigger` + `MenuSubContent`.
|
|
208
|
+
|
|
209
|
+
Supports **two modes**:
|
|
210
|
+
- **`inline`** (default) — Collapsible accordion within the parent list
|
|
211
|
+
- **`popover`** — Floating panel that opens to the side via Floating UI
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
<MenuSub mode="inline" defaultOpen>
|
|
215
|
+
<MenuSubTrigger icon={<SettingsIcon />}>Settings</MenuSubTrigger>
|
|
216
|
+
<MenuSubContent>
|
|
217
|
+
<MenuItem>General</MenuItem>
|
|
218
|
+
<MenuItem>Security</MenuItem>
|
|
219
|
+
</MenuSubContent>
|
|
220
|
+
</MenuSub>
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
#### Props
|
|
224
|
+
|
|
225
|
+
| Prop | Type | Default | Description |
|
|
226
|
+
|------|------|---------|-------------|
|
|
227
|
+
| `children` | `ReactNode` | **(required)** | Must contain `MenuSubTrigger` + `MenuSubContent` |
|
|
228
|
+
| `open` | `boolean` | — | Controlled open state |
|
|
229
|
+
| `defaultOpen` | `boolean` | `false` | Initial open state (uncontrolled) |
|
|
230
|
+
| `onOpenChange` | `(open: boolean) => void` | — | Callback when open state changes |
|
|
231
|
+
| `mode` | `MenuSubMode` | inherited | Override parent sub-menu mode (`'inline'` or `'popover'`) |
|
|
232
|
+
| `trigger` | `MenuSubTriggerType` | inherited | Override popover trigger type (`'hover'` or `'click'`) |
|
|
233
|
+
|
|
234
|
+
**Controlled vs Uncontrolled**:
|
|
235
|
+
- Pass `open` + `onOpenChange` for controlled mode
|
|
236
|
+
- Pass `defaultOpen` for uncontrolled mode (internal state)
|
|
237
|
+
|
|
238
|
+
**Auto-expand**: When any descendant `MenuItem` has `selected={true}`, the sub-menu automatically opens and propagates upward through all ancestor `MenuSub` components.
|
|
239
|
+
|
|
240
|
+
**Soft-select**: When any descendant is selected, the `MenuSubTrigger` renders with a soft-selected background to indicate it contains the active item.
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
### MenuSubTrigger
|
|
245
|
+
|
|
246
|
+
The item that toggles a sub-menu open/closed. Renders a trailing arrow indicator.
|
|
247
|
+
|
|
248
|
+
```tsx
|
|
249
|
+
<MenuSubTrigger icon={<FolderIcon />}>Projects</MenuSubTrigger>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
#### Props
|
|
253
|
+
|
|
254
|
+
| Prop | Type | Default | Description |
|
|
255
|
+
|------|------|---------|-------------|
|
|
256
|
+
| `children` | `ReactNode` | **(required)** | Label content |
|
|
257
|
+
| `icon` | `ReactNode` | — | Leading icon |
|
|
258
|
+
| `disabled` | `boolean` | `false` | Disabled state |
|
|
259
|
+
|
|
260
|
+
Also accepts all native `<div>` HTML attributes except `onClick`.
|
|
261
|
+
|
|
262
|
+
**Arrow indicators**:
|
|
263
|
+
- Inline mode: `▾` (closed) / `▴` (open)
|
|
264
|
+
- Popover mode: `▸` (always)
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
### MenuSubContent
|
|
269
|
+
|
|
270
|
+
Content container for sub-menu items. Automatically renders as inline (animated collapse) or floating popover based on the resolved mode.
|
|
271
|
+
|
|
272
|
+
```tsx
|
|
273
|
+
<MenuSubContent>
|
|
274
|
+
<MenuItem>Child Item 1</MenuItem>
|
|
275
|
+
<MenuItem>Child Item 2</MenuItem>
|
|
276
|
+
</MenuSubContent>
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
#### Props
|
|
280
|
+
|
|
281
|
+
| Prop | Type | Default | Description |
|
|
282
|
+
|------|------|---------|-------------|
|
|
283
|
+
| `children` | `ReactNode` | **(required)** | Sub-menu items |
|
|
284
|
+
|
|
285
|
+
Also accepts all native `<div>` HTML attributes.
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## Types
|
|
290
|
+
|
|
291
|
+
### MenuSubMode
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
type MenuSubMode = 'inline' | 'popover';
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
| Value | Description |
|
|
298
|
+
|-------|-------------|
|
|
299
|
+
| `'inline'` | Collapsible sub-menu rendered inline within parent |
|
|
300
|
+
| `'popover'` | Floating sub-menu using Floating UI positioning |
|
|
301
|
+
|
|
302
|
+
### MenuDisplay
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
type MenuDisplay = 'default' | 'icon';
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
| Value | Description |
|
|
309
|
+
|-------|-------------|
|
|
310
|
+
| `'default'` | Full display — icons, labels, shortcuts visible |
|
|
311
|
+
| `'icon'` | Icon-only mode (mini sidebar) — labels and shortcuts hidden |
|
|
312
|
+
|
|
313
|
+
### MenuSubTriggerType
|
|
314
|
+
|
|
315
|
+
```ts
|
|
316
|
+
type MenuSubTriggerType = 'hover' | 'click';
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Controls how popover sub-menus are triggered. Only applies when `mode="popover"`.
|
|
320
|
+
|
|
321
|
+
### MenuActiveIndicator
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
type MenuActiveIndicator = 'dot' | 'bar' | false | ReactNode;
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### MenuFloatingSettings
|
|
328
|
+
|
|
329
|
+
Global Floating UI configuration for all popover sub-menus.
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
interface MenuFloatingSettings {
|
|
333
|
+
placement?: Placement; // default: 'right-start'
|
|
334
|
+
offset?: number; // gap between trigger and popover (px)
|
|
335
|
+
flip?: boolean; // flip placement if space is limited
|
|
336
|
+
shift?: boolean; // shift to stay in viewport
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### MenuColorScheme
|
|
341
|
+
|
|
342
|
+
Full color customisation via raw CSS color strings. All properties are optional — only override what you need.
|
|
343
|
+
|
|
344
|
+
```ts
|
|
345
|
+
interface MenuColorScheme {
|
|
346
|
+
// Container
|
|
347
|
+
background?: string;
|
|
348
|
+
color?: string;
|
|
349
|
+
|
|
350
|
+
// Hover state
|
|
351
|
+
hoverBg?: string;
|
|
352
|
+
hoverColor?: string;
|
|
353
|
+
|
|
354
|
+
// Active/selected item
|
|
355
|
+
activeBg?: string;
|
|
356
|
+
activeColor?: string;
|
|
357
|
+
|
|
358
|
+
// Soft-selected (parent trigger of active child)
|
|
359
|
+
softSelectedBg?: string;
|
|
360
|
+
|
|
361
|
+
// Danger item
|
|
362
|
+
dangerColor?: string;
|
|
363
|
+
dangerHoverBg?: string;
|
|
364
|
+
|
|
365
|
+
// Disabled text
|
|
366
|
+
disabledColor?: string;
|
|
367
|
+
|
|
368
|
+
// Secondary text (labels, shortcuts, arrows)
|
|
369
|
+
secondaryColor?: string;
|
|
370
|
+
|
|
371
|
+
// Divider line
|
|
372
|
+
dividerColor?: string;
|
|
373
|
+
|
|
374
|
+
// Focus ring
|
|
375
|
+
focusRingColor?: string;
|
|
376
|
+
|
|
377
|
+
// Popover sub-menu
|
|
378
|
+
popoverBg?: string;
|
|
379
|
+
popoverBorderColor?: string;
|
|
380
|
+
|
|
381
|
+
// Inline dot indicator
|
|
382
|
+
dotColor?: string;
|
|
383
|
+
dotActiveColor?: string;
|
|
384
|
+
|
|
385
|
+
// Child item hover (items inside SubContent)
|
|
386
|
+
childHoverColor?: string;
|
|
387
|
+
childHoverBg?: string;
|
|
388
|
+
|
|
389
|
+
// Active indicator icon color
|
|
390
|
+
activeIconColor?: string;
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## Keyboard Navigation
|
|
397
|
+
|
|
398
|
+
Full keyboard support following [WAI-ARIA Menu Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/menu/):
|
|
399
|
+
|
|
400
|
+
| Key | Action |
|
|
401
|
+
|-----|--------|
|
|
402
|
+
| `↓` Arrow Down | Move focus to next visible item (wraps to first) |
|
|
403
|
+
| `↑` Arrow Up | Move focus to previous visible item (wraps to last) |
|
|
404
|
+
| `Home` | Move focus to first item |
|
|
405
|
+
| `End` | Move focus to last item |
|
|
406
|
+
| `Enter` / `Space` | Activate focused item or toggle sub-menu |
|
|
407
|
+
| `→` Arrow Right | Open inline sub-menu (on trigger) |
|
|
408
|
+
| `←` Arrow Left | Close current sub-menu, return focus to trigger |
|
|
409
|
+
| `Escape` | Close current sub-menu |
|
|
410
|
+
| **Typeahead** | Type characters to jump to matching item (500ms buffer) |
|
|
411
|
+
|
|
412
|
+
- Focus management uses roving tabindex — only the focused item has `tabIndex={0}`
|
|
413
|
+
- Collapsed sub-menu items are excluded from keyboard navigation
|
|
414
|
+
- Popover sub-menus have independent keyboard navigation within their floating panel
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
## Accessibility
|
|
419
|
+
|
|
420
|
+
| Feature | Implementation |
|
|
421
|
+
|---------|---------------|
|
|
422
|
+
| Roles | `menu` (container), `menuitem` (items), `separator` (dividers), `group` (groups) |
|
|
423
|
+
| `aria-disabled` | Set on disabled items |
|
|
424
|
+
| `aria-current="page"` | Set on selected items |
|
|
425
|
+
| `aria-haspopup="menu"` | Set on sub-menu triggers |
|
|
426
|
+
| `aria-expanded` | Set on sub-menu triggers (true/false) |
|
|
427
|
+
| `aria-labelledby` | Groups and sub-content reference their labels/triggers |
|
|
428
|
+
| Focus ring | Visible `box-shadow` outline on `:focus-visible` |
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
## Theming
|
|
433
|
+
|
|
434
|
+
### Theme Provider
|
|
435
|
+
|
|
436
|
+
Wrap your app with `ThemeProvider` from `@thanh-libs/theme`. The menu reads `palette`, `spacing` from the theme context.
|
|
437
|
+
|
|
438
|
+
```tsx
|
|
439
|
+
import { ThemeProvider } from '@thanh-libs/theme';
|
|
440
|
+
import { Menu, MenuItem } from '@thanh-libs/menu';
|
|
441
|
+
|
|
442
|
+
<ThemeProvider>
|
|
443
|
+
<Menu style={{ width: 260 }}>
|
|
444
|
+
<MenuItem>Home</MenuItem>
|
|
445
|
+
</Menu>
|
|
446
|
+
</ThemeProvider>
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### Custom Color Scheme
|
|
450
|
+
|
|
451
|
+
For full control without modifying the theme, pass a `colorScheme` prop with raw CSS color strings:
|
|
452
|
+
|
|
453
|
+
```tsx
|
|
454
|
+
<Menu
|
|
455
|
+
colorScheme={{
|
|
456
|
+
background: '#1e1e2e',
|
|
457
|
+
color: '#cdd6f4',
|
|
458
|
+
hoverBg: 'rgba(205,214,244,0.08)',
|
|
459
|
+
activeBg: 'rgba(137,180,250,0.2)',
|
|
460
|
+
activeColor: '#89b4fa',
|
|
461
|
+
softSelectedBg: 'rgba(137,180,250,0.08)',
|
|
462
|
+
secondaryColor: 'rgba(205,214,244,0.5)',
|
|
463
|
+
dividerColor: 'rgba(205,214,244,0.12)',
|
|
464
|
+
focusRingColor: '#89b4fa',
|
|
465
|
+
dangerColor: '#f38ba8',
|
|
466
|
+
dangerHoverBg: 'rgba(243,139,168,0.12)',
|
|
467
|
+
disabledColor: 'rgba(205,214,244,0.3)',
|
|
468
|
+
popoverBg: '#181825',
|
|
469
|
+
popoverBorderColor: 'rgba(205,214,244,0.1)',
|
|
470
|
+
}}
|
|
471
|
+
>
|
|
472
|
+
…
|
|
473
|
+
</Menu>
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## Usage Patterns
|
|
479
|
+
|
|
480
|
+
### Sidebar with Grouped Items
|
|
481
|
+
|
|
482
|
+
```tsx
|
|
483
|
+
<Menu style={{ width: 260 }}>
|
|
484
|
+
<MenuGroup label="Navigation">
|
|
485
|
+
<MenuItem icon={<DashboardIcon />} selected>Dashboard</MenuItem>
|
|
486
|
+
<MenuItem icon={<UsersIcon />}>Users</MenuItem>
|
|
487
|
+
<MenuItem icon={<ChartIcon />}>Analytics</MenuItem>
|
|
488
|
+
</MenuGroup>
|
|
489
|
+
<MenuDivider />
|
|
490
|
+
<MenuGroup label="Account">
|
|
491
|
+
<MenuItem icon={<ProfileIcon />}>Profile</MenuItem>
|
|
492
|
+
<MenuItem icon={<SettingsIcon />}>Settings</MenuItem>
|
|
493
|
+
<MenuItem icon={<LogoutIcon />} danger>Logout</MenuItem>
|
|
494
|
+
</MenuGroup>
|
|
495
|
+
</Menu>
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### Icon-Only Mini Sidebar (with Popover Sub-menus)
|
|
499
|
+
|
|
500
|
+
```tsx
|
|
501
|
+
<Menu display="icon" mode="popover" style={{ width: 'fit-content' }}>
|
|
502
|
+
<MenuItem icon={<HomeIcon />} selected>Home</MenuItem>
|
|
503
|
+
<MenuItem icon={<UserIcon />}>Profile</MenuItem>
|
|
504
|
+
<MenuSub>
|
|
505
|
+
<MenuSubTrigger icon={<SettingsIcon />}>Settings</MenuSubTrigger>
|
|
506
|
+
<MenuSubContent>
|
|
507
|
+
<MenuItem>General</MenuItem>
|
|
508
|
+
<MenuItem>Security</MenuItem>
|
|
509
|
+
</MenuSubContent>
|
|
510
|
+
</MenuSub>
|
|
511
|
+
</Menu>
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### Nested Sub-menus (Multi-level)
|
|
515
|
+
|
|
516
|
+
```tsx
|
|
517
|
+
<Menu style={{ width: 260 }}>
|
|
518
|
+
<MenuItem>Home</MenuItem>
|
|
519
|
+
<MenuSub>
|
|
520
|
+
<MenuSubTrigger>Projects</MenuSubTrigger>
|
|
521
|
+
<MenuSubContent>
|
|
522
|
+
<MenuItem>All Projects</MenuItem>
|
|
523
|
+
<MenuSub>
|
|
524
|
+
<MenuSubTrigger>By Team</MenuSubTrigger>
|
|
525
|
+
<MenuSubContent>
|
|
526
|
+
<MenuItem>Frontend</MenuItem>
|
|
527
|
+
<MenuItem>Backend</MenuItem>
|
|
528
|
+
<MenuItem>DevOps</MenuItem>
|
|
529
|
+
</MenuSubContent>
|
|
530
|
+
</MenuSub>
|
|
531
|
+
</MenuSubContent>
|
|
532
|
+
</MenuSub>
|
|
533
|
+
</Menu>
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### Popover Sub-menus (Floating)
|
|
537
|
+
|
|
538
|
+
```tsx
|
|
539
|
+
<Menu mode="popover" trigger="hover" floatingSettings={{ placement: 'right-start', offset: 4 }}>
|
|
540
|
+
<MenuItem>Dashboard</MenuItem>
|
|
541
|
+
<MenuSub>
|
|
542
|
+
<MenuSubTrigger>Analytics</MenuSubTrigger>
|
|
543
|
+
<MenuSubContent>
|
|
544
|
+
<MenuItem>Overview</MenuItem>
|
|
545
|
+
<MenuItem>Reports</MenuItem>
|
|
546
|
+
</MenuSubContent>
|
|
547
|
+
</MenuSub>
|
|
548
|
+
</Menu>
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### Inline Dot Indicators
|
|
552
|
+
|
|
553
|
+
Show dot bullets beside child items inside expanded sub-menus:
|
|
554
|
+
|
|
555
|
+
```tsx
|
|
556
|
+
<Menu showDot style={{ width: 280 }}>
|
|
557
|
+
<MenuItem icon={<HomeIcon />} selected>Dashboard</MenuItem>
|
|
558
|
+
<MenuSub defaultOpen>
|
|
559
|
+
<MenuSubTrigger icon={<ChartIcon />}>Analytics</MenuSubTrigger>
|
|
560
|
+
<MenuSubContent>
|
|
561
|
+
<MenuItem>Overview</MenuItem>
|
|
562
|
+
<MenuItem selected>Reports</MenuItem>
|
|
563
|
+
<MenuItem>Exports</MenuItem>
|
|
564
|
+
</MenuSubContent>
|
|
565
|
+
</MenuSub>
|
|
566
|
+
</Menu>
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
### Dense Mode
|
|
570
|
+
|
|
571
|
+
```tsx
|
|
572
|
+
<Menu dense style={{ width: 220 }}>
|
|
573
|
+
<MenuItem>Home</MenuItem>
|
|
574
|
+
<MenuItem selected>Users</MenuItem>
|
|
575
|
+
<MenuItem>Analytics</MenuItem>
|
|
576
|
+
</Menu>
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### Scrollable Menu
|
|
580
|
+
|
|
581
|
+
```tsx
|
|
582
|
+
<Menu maxHeight={300} style={{ width: 260 }}>
|
|
583
|
+
{items.map((item) => (
|
|
584
|
+
<MenuItem key={item.id}>{item.label}</MenuItem>
|
|
585
|
+
))}
|
|
586
|
+
</Menu>
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
---
|
|
590
|
+
|
|
591
|
+
## Design Tokens
|
|
592
|
+
|
|
593
|
+
| Token | Value | Description |
|
|
594
|
+
|-------|-------|-------------|
|
|
595
|
+
| Font size (default) | `0.875rem` (14px) | Regular item text |
|
|
596
|
+
| Font size (dense) | `0.8125rem` (13px) | Dense mode item text |
|
|
597
|
+
| Font size (shortcut) | `0.75rem` (12px) | Keyboard shortcut text |
|
|
598
|
+
| Font size (label) | `0.75rem` (12px) | Group label text |
|
|
599
|
+
| Icon size | `20×20px` | Leading icon container |
|
|
600
|
+
| Border radius | `0.375rem` (6px) | Item border radius |
|
|
601
|
+
| Collapse duration | `250ms` | Inline sub-menu expand/collapse |
|
|
602
|
+
| Background transition | `150ms` | Hover/select background change |
|
|
603
|
+
| Typeahead timeout | `500ms` | Character buffer reset delay |
|
|
604
|
+
| Popover min width | `160px` | Minimum floating panel width |
|
|
605
|
+
| Popover z-index | `1300` | Floating panel z-index |
|
|
606
|
+
| Sub close delay | `150ms` | Popover close delay (hover mode) |
|
|
607
|
+
|
|
608
|
+
---
|
|
609
|
+
|
|
610
|
+
## License
|
|
611
|
+
|
|
612
|
+
MIT
|