@xsolla/xui-context-menu 0.157.0 → 0.158.0-pr306.1779437575
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 +150 -309
- package/native/index.d.mts +101 -218
- package/native/index.d.ts +101 -218
- package/native/index.js +29593 -2319
- package/native/index.js.map +1 -1
- package/native/index.mjs +29620 -2317
- package/native/index.mjs.map +1 -1
- package/package.json +9 -8
- package/web/index.d.mts +101 -218
- package/web/index.d.ts +101 -218
- package/web/index.js +29593 -2346
- package/web/index.js.map +1 -1
- package/web/index.mjs +29617 -2334
- package/web/index.mjs.map +1 -1
package/README.md
CHANGED
|
@@ -1,334 +1,175 @@
|
|
|
1
1
|
# Context Menu
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Overview
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install @xsolla/xui-context-menu
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Demo
|
|
12
|
-
|
|
13
|
-
### Basic Context Menu
|
|
14
|
-
|
|
15
|
-
```tsx
|
|
16
|
-
import * as React from 'react';
|
|
17
|
-
import { ContextMenu } from '@xsolla/xui-context-menu';
|
|
18
|
-
import { Button } from '@xsolla/xui-button';
|
|
19
|
-
|
|
20
|
-
export default function BasicContextMenu() {
|
|
21
|
-
return (
|
|
22
|
-
<ContextMenu
|
|
23
|
-
trigger={<Button>Open Menu</Button>}
|
|
24
|
-
list={[
|
|
25
|
-
{ id: 'edit', label: 'Edit' },
|
|
26
|
-
{ id: 'duplicate', label: 'Duplicate' },
|
|
27
|
-
{ id: 'delete', label: 'Delete' },
|
|
28
|
-
]}
|
|
29
|
-
onSelect={(item) => console.log('Selected:', item.id)}
|
|
30
|
-
/>
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
### Compound Component API
|
|
5
|
+
`ContextMenu` is an anchored panel of selectable cells. The component is built around two primitives: a single `ContextMenuItem` whose `type` prop switches between cell variants (`option`, `search`, `heading`, `divider`), and a panel that supports a preset shorthand (`type="list" | "phone" | "checkbox" | "radio" | "status" | "brandLogo" | "avatar" | "loading"`) plus a fully custom composition path via `children`.
|
|
36
6
|
|
|
37
|
-
|
|
38
|
-
import * as React from 'react';
|
|
39
|
-
import { ContextMenu } from '@xsolla/xui-context-menu';
|
|
40
|
-
import { Button } from '@xsolla/xui-button';
|
|
41
|
-
|
|
42
|
-
export default function CompoundAPI() {
|
|
43
|
-
return (
|
|
44
|
-
<ContextMenu trigger={<Button>Actions</Button>}>
|
|
45
|
-
<ContextMenu.Item onPress={() => console.log('Edit')}>Edit</ContextMenu.Item>
|
|
46
|
-
<ContextMenu.Item onPress={() => console.log('Copy')}>Copy</ContextMenu.Item>
|
|
47
|
-
<ContextMenu.Separator />
|
|
48
|
-
<ContextMenu.Item onPress={() => console.log('Delete')}>Delete</ContextMenu.Item>
|
|
49
|
-
</ContextMenu>
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
```
|
|
7
|
+
## When to use
|
|
53
8
|
|
|
54
|
-
|
|
9
|
+
- A primary action menu attached to a trigger (e.g. a button's overflow actions).
|
|
10
|
+
- A selection control with checkbox or radio cells (multi-select or single-select).
|
|
11
|
+
- A lightweight picker for status, country, or brand-logo lists.
|
|
55
12
|
|
|
56
|
-
|
|
57
|
-
import * as React from 'react';
|
|
58
|
-
import { ContextMenu } from '@xsolla/xui-context-menu';
|
|
59
|
-
import { Button } from '@xsolla/xui-button';
|
|
60
|
-
|
|
61
|
-
export default function WithGroups() {
|
|
62
|
-
return (
|
|
63
|
-
<ContextMenu
|
|
64
|
-
trigger={<Button>Menu with Groups</Button>}
|
|
65
|
-
groups={[
|
|
66
|
-
{
|
|
67
|
-
id: 'file',
|
|
68
|
-
label: 'File',
|
|
69
|
-
items: [
|
|
70
|
-
{ id: 'new', label: 'New' },
|
|
71
|
-
{ id: 'open', label: 'Open' },
|
|
72
|
-
{ id: 'save', label: 'Save' },
|
|
73
|
-
],
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
id: 'edit',
|
|
77
|
-
label: 'Edit',
|
|
78
|
-
items: [
|
|
79
|
-
{ id: 'cut', label: 'Cut' },
|
|
80
|
-
{ id: 'copy', label: 'Copy' },
|
|
81
|
-
{ id: 'paste', label: 'Paste' },
|
|
82
|
-
],
|
|
83
|
-
},
|
|
84
|
-
]}
|
|
85
|
-
/>
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
```
|
|
13
|
+
## When not to use
|
|
89
14
|
|
|
90
|
-
|
|
15
|
+
- For form fields driven by validation — use `Select`, `Autocomplete` or a plain `Input` instead.
|
|
16
|
+
- For navigational menus or app-level navigation — use the appropriate navigation primitives.
|
|
17
|
+
- For a single destructive confirmation — use a `Dialog`.
|
|
91
18
|
|
|
92
|
-
|
|
93
|
-
import { ContextMenu } from '@xsolla/xui-context-menu';
|
|
19
|
+
## Installation
|
|
94
20
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
isOpen={isOpen} // Controlled open state
|
|
98
|
-
onOpenChange={setIsOpen} // Open state callback
|
|
99
|
-
list={items} // Data-driven items
|
|
100
|
-
groups={groups} // Grouped items
|
|
101
|
-
size="md" // Size variant
|
|
102
|
-
width={200} // Menu width
|
|
103
|
-
maxHeight={300} // Max height with scroll
|
|
104
|
-
closeOnSelect={true} // Close after selection
|
|
105
|
-
onSelect={handleSelect} // Selection callback
|
|
106
|
-
onCheckedChange={handleChecked} // Checkbox/radio callback
|
|
107
|
-
/>
|
|
21
|
+
```bash
|
|
22
|
+
yarn add @xsolla/xui-context-menu
|
|
108
23
|
```
|
|
109
24
|
|
|
110
|
-
##
|
|
111
|
-
|
|
112
|
-
### Checkbox Items
|
|
25
|
+
## Two API paths
|
|
113
26
|
|
|
114
|
-
|
|
115
|
-
import * as React from 'react';
|
|
116
|
-
import { ContextMenu } from '@xsolla/xui-context-menu';
|
|
117
|
-
import { Button } from '@xsolla/xui-button';
|
|
118
|
-
|
|
119
|
-
export default function CheckboxItems() {
|
|
120
|
-
const [settings, setSettings] = React.useState({
|
|
121
|
-
notifications: true,
|
|
122
|
-
sound: false,
|
|
123
|
-
autoSave: true,
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
return (
|
|
127
|
-
<ContextMenu
|
|
128
|
-
trigger={<Button>Settings</Button>}
|
|
129
|
-
list={[
|
|
130
|
-
{ id: 'notifications', label: 'Notifications', variant: 'checkbox', checked: settings.notifications },
|
|
131
|
-
{ id: 'sound', label: 'Sound', variant: 'checkbox', checked: settings.sound },
|
|
132
|
-
{ id: 'autoSave', label: 'Auto Save', variant: 'checkbox', checked: settings.autoSave },
|
|
133
|
-
]}
|
|
134
|
-
onCheckedChange={(id, checked) => {
|
|
135
|
-
setSettings((prev) => ({ ...prev, [id]: checked }));
|
|
136
|
-
}}
|
|
137
|
-
/>
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
```
|
|
27
|
+
### Preset path
|
|
141
28
|
|
|
142
|
-
|
|
29
|
+
Pass `type` and `items`. The panel renders the preset's chrome and composes each option with the right control or slot.
|
|
143
30
|
|
|
144
31
|
```tsx
|
|
145
|
-
import
|
|
146
|
-
import { ContextMenu } from '@xsolla/xui-context-menu';
|
|
147
|
-
import { Button } from '@xsolla/xui-button';
|
|
148
|
-
|
|
149
|
-
export default function RadioGroupMenu() {
|
|
150
|
-
const [theme, setTheme] = React.useState('light');
|
|
151
|
-
|
|
152
|
-
return (
|
|
153
|
-
<ContextMenu trigger={<Button>Theme: {theme}</Button>}>
|
|
154
|
-
<ContextMenu.Group label="Theme">
|
|
155
|
-
<ContextMenu.RadioGroup value={theme} onValueChange={setTheme}>
|
|
156
|
-
<ContextMenu.RadioItem value="light">Light</ContextMenu.RadioItem>
|
|
157
|
-
<ContextMenu.RadioItem value="dark">Dark</ContextMenu.RadioItem>
|
|
158
|
-
<ContextMenu.RadioItem value="system">System</ContextMenu.RadioItem>
|
|
159
|
-
</ContextMenu.RadioGroup>
|
|
160
|
-
</ContextMenu.Group>
|
|
161
|
-
</ContextMenu>
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
### With Search
|
|
32
|
+
import { ContextMenu } from "@xsolla/xui-context-menu";
|
|
167
33
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
{ id: 'apple', label: 'Apple' },
|
|
178
|
-
{ id: 'banana', label: 'Banana' },
|
|
179
|
-
{ id: 'cherry', label: 'Cherry' },
|
|
180
|
-
{ id: 'date', label: 'Date' },
|
|
181
|
-
{ id: 'elderberry', label: 'Elderberry' },
|
|
182
|
-
];
|
|
183
|
-
|
|
184
|
-
const filteredItems = allItems.filter((item) =>
|
|
185
|
-
item.label.toLowerCase().includes(search.toLowerCase())
|
|
186
|
-
);
|
|
187
|
-
|
|
188
|
-
return (
|
|
189
|
-
<ContextMenu trigger={<Button>Select Fruit</Button>}>
|
|
190
|
-
<ContextMenu.Search
|
|
191
|
-
value={search}
|
|
192
|
-
onValueChange={setSearch}
|
|
193
|
-
placeholder="Search fruits..."
|
|
194
|
-
/>
|
|
195
|
-
{filteredItems.map((item) => (
|
|
196
|
-
<ContextMenu.Item key={item.id}>{item.label}</ContextMenu.Item>
|
|
197
|
-
))}
|
|
198
|
-
</ContextMenu>
|
|
199
|
-
);
|
|
200
|
-
}
|
|
34
|
+
<ContextMenu
|
|
35
|
+
type="list"
|
|
36
|
+
trigger={<Button>Open</Button>}
|
|
37
|
+
items={[
|
|
38
|
+
{ type: "option", label: "Edit" },
|
|
39
|
+
{ type: "option", label: "Duplicate" },
|
|
40
|
+
{ type: "option", label: "Delete", destructive: true },
|
|
41
|
+
]}
|
|
42
|
+
/>;
|
|
201
43
|
```
|
|
202
44
|
|
|
203
|
-
###
|
|
45
|
+
### Custom path
|
|
204
46
|
|
|
205
|
-
|
|
206
|
-
import * as React from 'react';
|
|
207
|
-
import { ContextMenu } from '@xsolla/xui-context-menu';
|
|
208
|
-
import { Button } from '@xsolla/xui-button';
|
|
209
|
-
import { Copy, Scissors, Clipboard } from '@xsolla/xui-icons-base';
|
|
210
|
-
|
|
211
|
-
export default function WithIconsAndShortcuts() {
|
|
212
|
-
return (
|
|
213
|
-
<ContextMenu
|
|
214
|
-
trigger={<Button>Edit</Button>}
|
|
215
|
-
list={[
|
|
216
|
-
{ id: 'cut', label: 'Cut', icon: <Scissors />, trailing: { type: 'shortcut', content: 'Cmd+X' } },
|
|
217
|
-
{ id: 'copy', label: 'Copy', icon: <Copy />, trailing: { type: 'shortcut', content: 'Cmd+C' } },
|
|
218
|
-
{ id: 'paste', label: 'Paste', icon: <Clipboard />, trailing: { type: 'shortcut', content: 'Cmd+V' } },
|
|
219
|
-
]}
|
|
220
|
-
/>
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
### Right-Click Context Menu
|
|
47
|
+
Compose cells as children when you need full control of the layout, want to mix headings and dividers freely, or render a slot the preset path doesn't cover.
|
|
226
48
|
|
|
227
49
|
```tsx
|
|
228
|
-
import
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
};
|
|
238
|
-
|
|
239
|
-
return (
|
|
240
|
-
<div
|
|
241
|
-
onContextMenu={handleContextMenu}
|
|
242
|
-
style={{ width: 300, height: 200, background: '#f0f0f0', padding: 16 }}
|
|
243
|
-
>
|
|
244
|
-
Right-click anywhere in this area
|
|
245
|
-
|
|
246
|
-
{position && (
|
|
247
|
-
<ContextMenu
|
|
248
|
-
isOpen={!!position}
|
|
249
|
-
onOpenChange={(open) => !open && setPosition(null)}
|
|
250
|
-
position={position}
|
|
251
|
-
list={[
|
|
252
|
-
{ id: 'inspect', label: 'Inspect' },
|
|
253
|
-
{ id: 'refresh', label: 'Refresh' },
|
|
254
|
-
]}
|
|
255
|
-
/>
|
|
256
|
-
)}
|
|
257
|
-
</div>
|
|
258
|
-
);
|
|
259
|
-
}
|
|
50
|
+
import { ContextMenu, ContextMenuItem } from "@xsolla/xui-context-menu";
|
|
51
|
+
|
|
52
|
+
<ContextMenu trigger={<Button>Open</Button>} aria-label="Actions">
|
|
53
|
+
<ContextMenuItem type="heading" label="Workspace" />
|
|
54
|
+
<ContextMenuItem type="option" label="Personal" />
|
|
55
|
+
<ContextMenuItem type="option" label="Acme Inc." />
|
|
56
|
+
<ContextMenuItem type="divider" />
|
|
57
|
+
<ContextMenuItem type="option" label="Sign out" destructive />
|
|
58
|
+
</ContextMenu>;
|
|
260
59
|
```
|
|
261
60
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
|
271
|
-
|
|
|
272
|
-
|
|
|
273
|
-
|
|
|
274
|
-
|
|
|
275
|
-
|
|
|
276
|
-
|
|
|
277
|
-
|
|
|
278
|
-
|
|
|
279
|
-
|
|
|
280
|
-
|
|
|
281
|
-
|
|
|
282
|
-
|
|
|
283
|
-
|
|
|
284
|
-
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
|
307
|
-
|
|
|
308
|
-
| `
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
61
|
+
Choose the preset path for typical menus where the data is uniform; choose the custom path when cells differ structurally or when you need to drop in bespoke nodes between cells.
|
|
62
|
+
|
|
63
|
+
## `ContextMenuItem` reference
|
|
64
|
+
|
|
65
|
+
`ContextMenuItem` is a discriminated union on `type`. All cell types accept `size`, `data-testid` and theme-override props.
|
|
66
|
+
|
|
67
|
+
### `type="option"`
|
|
68
|
+
|
|
69
|
+
| Prop | Type | Purpose |
|
|
70
|
+
| --- | --- | --- |
|
|
71
|
+
| `label` | `ReactNode` | Primary cell text (required). |
|
|
72
|
+
| `description` | `ReactNode` | Secondary line beneath the label. |
|
|
73
|
+
| `leadingControl` | `"checkbox" \| "radio"` | Renders a `Checkbox` or `Radio` at the start. |
|
|
74
|
+
| `leadingIcon` | `ReactNode` | Icon node before the label group. |
|
|
75
|
+
| `status` | `ReactNode` | Status indicator slot (e.g. `<Status>`). |
|
|
76
|
+
| `iconWrapper` | `ReactNode` | Wrapped icon / avatar slot. |
|
|
77
|
+
| `slot` / `slotContent` | `ReactNode` | Generic slot before the label. |
|
|
78
|
+
| `value` | `ReactNode` | Right-side primary text (e.g. shortcut value, dial code). |
|
|
79
|
+
| `hint` | `ReactNode` | Right-side secondary text below `value`. |
|
|
80
|
+
| `trailingIcon` | `ReactNode` | Trailing icon at the end of the cell. |
|
|
81
|
+
| `keyboardShortcut` | `string` | Display-only shortcut rendered as `<kbd>` and exposed via `aria-keyshortcuts`. |
|
|
82
|
+
| `hasSubmenu` | `boolean` | Marks the cell as a submenu trigger and renders a chevron. |
|
|
83
|
+
| `submenu` | `ReactNode` | A nested `<ContextMenu>` opened on hover/`ArrowRight`/`Enter`. |
|
|
84
|
+
| `checked` | `boolean` | Fully controlled checked state. |
|
|
85
|
+
| `disabled` | `boolean` | Disables interaction and applies the disabled style. |
|
|
86
|
+
| `destructive` | `boolean` | Applies the destructive content colour. |
|
|
87
|
+
| `onSelect` | `() => void` | Fires on activation (click, `Enter`, `Space`). |
|
|
88
|
+
| `onCheckedChange` | `(checked: boolean) => void` | Optional change callback for controls. |
|
|
89
|
+
|
|
90
|
+
Render order (left → right): `leadingControl`, `leadingIcon`, `status`, `iconWrapper`, `slotContent`, `label` (with optional `description` below), `value` (with optional `hint` below), `keyboardShortcut`, submenu chevron, `trailingIcon`.
|
|
91
|
+
|
|
92
|
+
### `type="search"`
|
|
93
|
+
|
|
94
|
+
| Prop | Type | Purpose |
|
|
95
|
+
| --- | --- | --- |
|
|
96
|
+
| `value` | `string` | Controlled value (required). |
|
|
97
|
+
| `onValueChange` | `(value: string) => void` | Change callback (required). |
|
|
98
|
+
| `placeholder` | `string` | Defaults to `"Search"`. |
|
|
99
|
+
| `autoFocus` | `boolean` | Focuses the input on mount. |
|
|
100
|
+
| `aria-label` | `string` | Defaults to `"Search options"`. |
|
|
101
|
+
|
|
102
|
+
### `type="heading"`
|
|
103
|
+
|
|
104
|
+
| Prop | Type | Purpose |
|
|
105
|
+
| --- | --- | --- |
|
|
106
|
+
| `label` | `ReactNode` | Section title (uppercase styling). |
|
|
107
|
+
| `description` | `ReactNode` | Optional helper line beneath. |
|
|
108
|
+
|
|
109
|
+
### `type="divider"`
|
|
110
|
+
|
|
111
|
+
A horizontal rule with `role="separator"`. No content props.
|
|
112
|
+
|
|
113
|
+
## `ContextMenu` reference
|
|
114
|
+
|
|
115
|
+
| Prop | Type | Purpose |
|
|
116
|
+
| --- | --- | --- |
|
|
117
|
+
| `type` | `"list" \| "loading" \| "phone" \| "checkbox" \| "status" \| "brandLogo" \| "radio" \| "avatar"` | Panel preset; works with `items`. |
|
|
118
|
+
| `items` | `ReadonlyArray<Option \| Heading \| Divider>` | Data-driven cells for the preset path. |
|
|
119
|
+
| `children` | `ReactNode` | Custom-composition cells (alternative to `items`). |
|
|
120
|
+
| `size` | `"sm" \| "md" \| "lg" \| "xl"` | Controls cell sizing across the panel. Default `md`. |
|
|
121
|
+
| `searchable` | `boolean` | Auto-renders a sticky search cell and filters options. |
|
|
122
|
+
| `loading` | `boolean` | Renders a centred spinner instead of the cell list. |
|
|
123
|
+
| `emptyMessage` | `string` | Custom message for the default empty state. |
|
|
124
|
+
| `empty` | `ReactNode` | Replace the empty state entirely. |
|
|
125
|
+
| `trigger` | `ReactNode` | Element that toggles the panel; receives `aria-haspopup` / `aria-expanded`. |
|
|
126
|
+
| `placement` | `"bottom-start" \| "top-start" \| "bottom-end" \| "top-end"` | Initial placement. Auto-flips when clipped. |
|
|
127
|
+
| `isOpen` | `boolean` | Controlled open state. |
|
|
128
|
+
| `onOpenChange` | `(open: boolean) => void` | Open-state callback. |
|
|
129
|
+
| `closeOnSelect` | `boolean` | Override the per-preset default. |
|
|
130
|
+
| `width` | `number` | Forces panel width (px). |
|
|
131
|
+
| `maxHeight` | `number` | Caps panel height (px); body scrolls and search stays sticky. |
|
|
132
|
+
| `onSelect` | `(item: ContextMenuOptionItemProps) => void` | Fires for the preset path on option activation. |
|
|
133
|
+
| `aria-label` | `string` | Accessible name for the menu container. |
|
|
134
|
+
| `data-testid` | `string` | Testing handle. |
|
|
135
|
+
|
|
136
|
+
## Behaviour & accessibility
|
|
137
|
+
|
|
138
|
+
- The panel root is `role="menu"` (or hosts `role="menuitemcheckbox"` / `role="menuitemradio"` cells when `checked` is provided). Headings render as `role="presentation"`, dividers as `role="separator"`, and the search cell as `role="searchbox"`.
|
|
139
|
+
- `closeOnSelect` defaults to `true` for every preset except `checkbox`, where multi-select keeps the panel open.
|
|
140
|
+
- On open, focus moves to the search input when present, otherwise to the first option.
|
|
141
|
+
- On close, focus returns to the trigger.
|
|
142
|
+
- The trigger element receives `aria-haspopup="menu"` and `aria-expanded` synced to the open state.
|
|
143
|
+
|
|
144
|
+
## Keyboard reference
|
|
317
145
|
|
|
318
146
|
| Key | Action |
|
|
319
|
-
|
|
|
320
|
-
|
|
|
321
|
-
| `
|
|
322
|
-
| `
|
|
323
|
-
| `
|
|
324
|
-
| `
|
|
325
|
-
| `
|
|
326
|
-
| `
|
|
327
|
-
|
|
328
|
-
##
|
|
329
|
-
|
|
330
|
-
-
|
|
331
|
-
-
|
|
332
|
-
-
|
|
333
|
-
-
|
|
334
|
-
-
|
|
147
|
+
| --- | --- |
|
|
148
|
+
| `↑` / `↓` | Move active option up/down (skips heading/divider). |
|
|
149
|
+
| `Enter` / `Space` | Activate the focused option. |
|
|
150
|
+
| `Esc` | Close the menu and return focus to the trigger. |
|
|
151
|
+
| `Tab` | Close the menu and continue natural focus order. |
|
|
152
|
+
| `Home` / `End` | Jump to the first/last option. |
|
|
153
|
+
| `→` / `Enter` | Open a submenu when on a `hasSubmenu` option. |
|
|
154
|
+
| `←` / `Esc` | Close the submenu and return focus to its parent option. |
|
|
155
|
+
|
|
156
|
+
## Content guidelines
|
|
157
|
+
|
|
158
|
+
- Use short, imperative labels ("Edit", "Duplicate", "Sign out").
|
|
159
|
+
- Use sentence case for option labels.
|
|
160
|
+
- Use uppercase for headings — the heading cell already applies the visual treatment.
|
|
161
|
+
- Place destructive options at the bottom of the list, ideally separated by a divider.
|
|
162
|
+
- Prefer specific empty messages ("No countries match") over the generic default.
|
|
163
|
+
- Aim for seven options or fewer per panel; group with headings or split into submenus when longer.
|
|
164
|
+
|
|
165
|
+
## Migration from prior API
|
|
166
|
+
|
|
167
|
+
| Old | New |
|
|
168
|
+
| --- | --- |
|
|
169
|
+
| `ContextMenuCheckboxItem` | `<ContextMenuItem type="option" leadingControl="checkbox" />` |
|
|
170
|
+
| `ContextMenuRadioItem` | `<ContextMenuItem type="option" leadingControl="radio" />` |
|
|
171
|
+
| `ContextMenuRadioGroup` | `type="radio"` panel preset with shared selected state |
|
|
172
|
+
| `ContextMenuGroup` | `<ContextMenuItem type="heading" />` |
|
|
173
|
+
| `ContextMenuSeparator` | `<ContextMenuItem type="divider" />` |
|
|
174
|
+
| `ContextMenuSearch` | `<ContextMenuItem type="search" />` (or set `searchable: true` on the panel for auto-render) |
|
|
175
|
+
| Size scale `s` / `m` / `l` / `xl` | `sm` / `md` / `lg` / `xl` |
|