@xsolla/xui-context-menu 0.140.0-pr246.1776914902 → 0.141.0

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 ADDED
@@ -0,0 +1,336 @@
1
+ # Context Menu
2
+
3
+ A cross-platform React context menu component that can be triggered by a button or right-click, supporting various item types including checkboxes and radio buttons.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @xsolla/xui-context-menu
9
+ # or
10
+ yarn add @xsolla/xui-context-menu
11
+ ```
12
+
13
+ ## Demo
14
+
15
+ ### Basic Context Menu
16
+
17
+ ```tsx
18
+ import * as React from 'react';
19
+ import { ContextMenu } from '@xsolla/xui-context-menu';
20
+ import { Button } from '@xsolla/xui-button';
21
+
22
+ export default function BasicContextMenu() {
23
+ return (
24
+ <ContextMenu
25
+ trigger={<Button>Open Menu</Button>}
26
+ list={[
27
+ { id: 'edit', label: 'Edit' },
28
+ { id: 'duplicate', label: 'Duplicate' },
29
+ { id: 'delete', label: 'Delete' },
30
+ ]}
31
+ onSelect={(item) => console.log('Selected:', item.id)}
32
+ />
33
+ );
34
+ }
35
+ ```
36
+
37
+ ### Compound Component API
38
+
39
+ ```tsx
40
+ import * as React from 'react';
41
+ import { ContextMenu } from '@xsolla/xui-context-menu';
42
+ import { Button } from '@xsolla/xui-button';
43
+
44
+ export default function CompoundAPI() {
45
+ return (
46
+ <ContextMenu trigger={<Button>Actions</Button>}>
47
+ <ContextMenu.Item onPress={() => console.log('Edit')}>Edit</ContextMenu.Item>
48
+ <ContextMenu.Item onPress={() => console.log('Copy')}>Copy</ContextMenu.Item>
49
+ <ContextMenu.Separator />
50
+ <ContextMenu.Item onPress={() => console.log('Delete')}>Delete</ContextMenu.Item>
51
+ </ContextMenu>
52
+ );
53
+ }
54
+ ```
55
+
56
+ ### With Groups
57
+
58
+ ```tsx
59
+ import * as React from 'react';
60
+ import { ContextMenu } from '@xsolla/xui-context-menu';
61
+ import { Button } from '@xsolla/xui-button';
62
+
63
+ export default function WithGroups() {
64
+ return (
65
+ <ContextMenu
66
+ trigger={<Button>Menu with Groups</Button>}
67
+ groups={[
68
+ {
69
+ id: 'file',
70
+ label: 'File',
71
+ items: [
72
+ { id: 'new', label: 'New' },
73
+ { id: 'open', label: 'Open' },
74
+ { id: 'save', label: 'Save' },
75
+ ],
76
+ },
77
+ {
78
+ id: 'edit',
79
+ label: 'Edit',
80
+ items: [
81
+ { id: 'cut', label: 'Cut' },
82
+ { id: 'copy', label: 'Copy' },
83
+ { id: 'paste', label: 'Paste' },
84
+ ],
85
+ },
86
+ ]}
87
+ />
88
+ );
89
+ }
90
+ ```
91
+
92
+ ## Anatomy
93
+
94
+ ```jsx
95
+ import { ContextMenu } from '@xsolla/xui-context-menu';
96
+
97
+ <ContextMenu
98
+ trigger={<Button>Menu</Button>} // Trigger element
99
+ isOpen={isOpen} // Controlled open state
100
+ onOpenChange={setIsOpen} // Open state callback
101
+ list={items} // Data-driven items
102
+ groups={groups} // Grouped items
103
+ size="md" // Size variant
104
+ width={200} // Menu width
105
+ maxHeight={300} // Max height with scroll
106
+ closeOnSelect={true} // Close after selection
107
+ onSelect={handleSelect} // Selection callback
108
+ onCheckedChange={handleChecked} // Checkbox/radio callback
109
+ />
110
+ ```
111
+
112
+ ## Examples
113
+
114
+ ### Checkbox Items
115
+
116
+ ```tsx
117
+ import * as React from 'react';
118
+ import { ContextMenu } from '@xsolla/xui-context-menu';
119
+ import { Button } from '@xsolla/xui-button';
120
+
121
+ export default function CheckboxItems() {
122
+ const [settings, setSettings] = React.useState({
123
+ notifications: true,
124
+ sound: false,
125
+ autoSave: true,
126
+ });
127
+
128
+ return (
129
+ <ContextMenu
130
+ trigger={<Button>Settings</Button>}
131
+ list={[
132
+ { id: 'notifications', label: 'Notifications', variant: 'checkbox', checked: settings.notifications },
133
+ { id: 'sound', label: 'Sound', variant: 'checkbox', checked: settings.sound },
134
+ { id: 'autoSave', label: 'Auto Save', variant: 'checkbox', checked: settings.autoSave },
135
+ ]}
136
+ onCheckedChange={(id, checked) => {
137
+ setSettings((prev) => ({ ...prev, [id]: checked }));
138
+ }}
139
+ />
140
+ );
141
+ }
142
+ ```
143
+
144
+ ### Radio Group
145
+
146
+ ```tsx
147
+ import * as React from 'react';
148
+ import { ContextMenu } from '@xsolla/xui-context-menu';
149
+ import { Button } from '@xsolla/xui-button';
150
+
151
+ export default function RadioGroupMenu() {
152
+ const [theme, setTheme] = React.useState('light');
153
+
154
+ return (
155
+ <ContextMenu trigger={<Button>Theme: {theme}</Button>}>
156
+ <ContextMenu.Group label="Theme">
157
+ <ContextMenu.RadioGroup value={theme} onValueChange={setTheme}>
158
+ <ContextMenu.RadioItem value="light">Light</ContextMenu.RadioItem>
159
+ <ContextMenu.RadioItem value="dark">Dark</ContextMenu.RadioItem>
160
+ <ContextMenu.RadioItem value="system">System</ContextMenu.RadioItem>
161
+ </ContextMenu.RadioGroup>
162
+ </ContextMenu.Group>
163
+ </ContextMenu>
164
+ );
165
+ }
166
+ ```
167
+
168
+ ### With Search
169
+
170
+ ```tsx
171
+ import * as React from 'react';
172
+ import { ContextMenu } from '@xsolla/xui-context-menu';
173
+ import { Button } from '@xsolla/xui-button';
174
+
175
+ export default function WithSearch() {
176
+ const [search, setSearch] = React.useState('');
177
+
178
+ const allItems = [
179
+ { id: 'apple', label: 'Apple' },
180
+ { id: 'banana', label: 'Banana' },
181
+ { id: 'cherry', label: 'Cherry' },
182
+ { id: 'date', label: 'Date' },
183
+ { id: 'elderberry', label: 'Elderberry' },
184
+ ];
185
+
186
+ const filteredItems = allItems.filter((item) =>
187
+ item.label.toLowerCase().includes(search.toLowerCase())
188
+ );
189
+
190
+ return (
191
+ <ContextMenu trigger={<Button>Select Fruit</Button>}>
192
+ <ContextMenu.Search
193
+ value={search}
194
+ onValueChange={setSearch}
195
+ placeholder="Search fruits..."
196
+ />
197
+ {filteredItems.map((item) => (
198
+ <ContextMenu.Item key={item.id}>{item.label}</ContextMenu.Item>
199
+ ))}
200
+ </ContextMenu>
201
+ );
202
+ }
203
+ ```
204
+
205
+ ### With Icons and Shortcuts
206
+
207
+ ```tsx
208
+ import * as React from 'react';
209
+ import { ContextMenu } from '@xsolla/xui-context-menu';
210
+ import { Button } from '@xsolla/xui-button';
211
+ import { Copy, Scissors, Clipboard } from '@xsolla/xui-icons-base';
212
+
213
+ export default function WithIconsAndShortcuts() {
214
+ return (
215
+ <ContextMenu
216
+ trigger={<Button>Edit</Button>}
217
+ list={[
218
+ { id: 'cut', label: 'Cut', icon: <Scissors />, trailing: { type: 'shortcut', content: 'Cmd+X' } },
219
+ { id: 'copy', label: 'Copy', icon: <Copy />, trailing: { type: 'shortcut', content: 'Cmd+C' } },
220
+ { id: 'paste', label: 'Paste', icon: <Clipboard />, trailing: { type: 'shortcut', content: 'Cmd+V' } },
221
+ ]}
222
+ />
223
+ );
224
+ }
225
+ ```
226
+
227
+ ### Right-Click Context Menu
228
+
229
+ ```tsx
230
+ import * as React from 'react';
231
+ import { ContextMenu } from '@xsolla/xui-context-menu';
232
+
233
+ export default function RightClickMenu() {
234
+ const [position, setPosition] = React.useState<{ x: number; y: number } | null>(null);
235
+
236
+ const handleContextMenu = (e: React.MouseEvent) => {
237
+ e.preventDefault();
238
+ setPosition({ x: e.clientX, y: e.clientY });
239
+ };
240
+
241
+ return (
242
+ <div
243
+ onContextMenu={handleContextMenu}
244
+ style={{ width: 300, height: 200, background: '#f0f0f0', padding: 16 }}
245
+ >
246
+ Right-click anywhere in this area
247
+
248
+ {position && (
249
+ <ContextMenu
250
+ isOpen={!!position}
251
+ onOpenChange={(open) => !open && setPosition(null)}
252
+ position={position}
253
+ list={[
254
+ { id: 'inspect', label: 'Inspect' },
255
+ { id: 'refresh', label: 'Refresh' },
256
+ ]}
257
+ />
258
+ )}
259
+ </div>
260
+ );
261
+ }
262
+ ```
263
+
264
+ ## API Reference
265
+
266
+ ### ContextMenu
267
+
268
+ **ContextMenuProps:**
269
+
270
+ | Prop | Type | Default | Description |
271
+ | :--- | :--- | :------ | :---------- |
272
+ | children | `ReactNode` | - | Compound component children. |
273
+ | trigger | `ReactNode` | - | Element that triggers the menu. |
274
+ | list | `ContextMenuItemData[]` | - | Data-driven item list. |
275
+ | groups | `ContextMenuGroupData[]` | - | Grouped items with labels. |
276
+ | isOpen | `boolean` | - | Controlled open state. |
277
+ | onOpenChange | `(open: boolean) => void` | - | Open state change callback. |
278
+ | position | `{ x: number; y: number }` | - | Fixed position for right-click menus. |
279
+ | size | `"sm" \| "md" \| "lg"` | `"md"` | Menu size variant. |
280
+ | width | `number` | - | Menu width in pixels. |
281
+ | maxHeight | `number` | `300` | Max height before scrolling. |
282
+ | closeOnSelect | `boolean` | `true` | Close menu after item selection. |
283
+ | isLoading | `boolean` | `false` | Show loading spinner. |
284
+ | onSelect | `(item: ContextMenuItemData) => void` | - | Item selection callback. |
285
+ | onCheckedChange | `(id: string, checked: boolean) => void` | - | Checkbox/radio change callback. |
286
+ | aria-label | `string` | - | Accessible menu label. |
287
+
288
+ **ContextMenuItemData:**
289
+
290
+ ```typescript
291
+ interface ContextMenuItemData {
292
+ id: string;
293
+ label: string;
294
+ icon?: ReactNode;
295
+ description?: string;
296
+ disabled?: boolean;
297
+ selected?: boolean;
298
+ checked?: boolean;
299
+ variant?: 'default' | 'checkbox' | 'radio';
300
+ trailing?: { type: 'shortcut' | 'content' | 'none'; content?: string | ReactNode };
301
+ children?: ContextMenuItemData[];
302
+ onPress?: () => void;
303
+ }
304
+ ```
305
+
306
+ ### Compound Components
307
+
308
+ | Component | Description |
309
+ | :-------- | :---------- |
310
+ | `ContextMenu.Item` | Standard menu item. |
311
+ | `ContextMenu.CheckboxItem` | Item with checkbox. |
312
+ | `ContextMenu.RadioGroup` | Container for radio items. |
313
+ | `ContextMenu.RadioItem` | Radio button item. |
314
+ | `ContextMenu.Group` | Group with optional label. |
315
+ | `ContextMenu.Separator` | Visual separator line. |
316
+ | `ContextMenu.Search` | Search input for filtering. |
317
+
318
+ ## Keyboard Navigation
319
+
320
+ | Key | Action |
321
+ | :-- | :----- |
322
+ | `ArrowDown` | Move to next item |
323
+ | `ArrowUp` | Move to previous item |
324
+ | `Home` | Move to first item |
325
+ | `End` | Move to last item |
326
+ | `Enter` / `Space` | Select current item |
327
+ | `Escape` | Close menu |
328
+ | `Tab` | Close menu |
329
+
330
+ ## Accessibility
331
+
332
+ - Menu has `role="menu"` with proper ARIA attributes
333
+ - Items have `role="menuitem"`, checkboxes have `role="menuitemcheckbox"`
334
+ - Keyboard navigation follows WAI-ARIA menu pattern
335
+ - Focus is trapped within menu when open
336
+ - Escape key closes menu and returns focus to trigger
package/native/index.js CHANGED
@@ -49,7 +49,7 @@ module.exports = __toCommonJS(index_exports);
49
49
  // src/ContextMenu.tsx
50
50
  var import_react10 = __toESM(require("react"));
51
51
 
52
- // ../../foundation/primitives-native/src/Box.tsx
52
+ // ../primitives-native/src/Box.tsx
53
53
  var import_react_native = require("react-native");
54
54
  var import_jsx_runtime = require("react/jsx-runtime");
55
55
  var Box = ({
@@ -223,7 +223,7 @@ var Box = ({
223
223
  );
224
224
  };
225
225
 
226
- // ../../foundation/primitives-native/src/Text.tsx
226
+ // ../primitives-native/src/Text.tsx
227
227
  var import_react_native2 = require("react-native");
228
228
  var import_jsx_runtime2 = require("react/jsx-runtime");
229
229
  var roleMap = {
@@ -286,7 +286,7 @@ var Text = ({
286
286
  );
287
287
  };
288
288
 
289
- // ../../foundation/primitives-native/src/Icon.tsx
289
+ // ../primitives-native/src/Icon.tsx
290
290
  var import_react = __toESM(require("react"));
291
291
  var import_react_native3 = require("react-native");
292
292
  var import_jsx_runtime3 = require("react/jsx-runtime");
@@ -310,7 +310,7 @@ var Icon = ({ children, color, size }) => {
310
310
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style, children: childrenWithProps });
311
311
  };
312
312
 
313
- // ../../foundation/primitives-native/src/Input.tsx
313
+ // ../primitives-native/src/Input.tsx
314
314
  var import_react2 = require("react");
315
315
  var import_react_native4 = require("react-native");
316
316
  var import_jsx_runtime4 = require("react/jsx-runtime");
@@ -356,6 +356,7 @@ var InputPrimitive = (0, import_react2.forwardRef)(
356
356
  style,
357
357
  color,
358
358
  fontSize,
359
+ fontFamily,
359
360
  placeholderTextColor,
360
361
  maxLength,
361
362
  type,
@@ -386,6 +387,10 @@ var InputPrimitive = (0, import_react2.forwardRef)(
386
387
  };
387
388
  const keyboardType = inputMode ? inputModeToKeyboardType[inputMode] || "default" : type ? keyboardTypeMap[type] || "default" : "default";
388
389
  const textContentType = autoComplete ? autoCompleteToTextContentType[autoComplete] : void 0;
390
+ let resolvedFontFamily = fontFamily ? fontFamily.split(",")[0].replace(/['"]/g, "").trim() : void 0;
391
+ if (resolvedFontFamily === "Pilat Wide" || resolvedFontFamily === "Pilat Wide Bold" || resolvedFontFamily === "Aktiv Grotesk") {
392
+ resolvedFontFamily = void 0;
393
+ }
389
394
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
390
395
  import_react_native4.TextInput,
391
396
  {
@@ -412,6 +417,7 @@ var InputPrimitive = (0, import_react2.forwardRef)(
412
417
  {
413
418
  color,
414
419
  fontSize: typeof fontSize === "number" ? fontSize : void 0,
420
+ fontFamily: resolvedFontFamily,
415
421
  flex: 1,
416
422
  padding: 0,
417
423
  textAlign: style?.textAlign || "left"
@@ -1124,7 +1130,7 @@ ContextMenuSeparator.displayName = "ContextMenuSeparator";
1124
1130
  var import_react9 = __toESM(require("react"));
1125
1131
  var import_xui_core6 = require("@xsolla/xui-core");
1126
1132
 
1127
- // ../../foundation/icons-base/dist/web/index.mjs
1133
+ // ../icons-base/dist/web/index.mjs
1128
1134
  var import_styled_components = __toESM(require("styled-components"), 1);
1129
1135
  var import_jsx_runtime11 = require("react/jsx-runtime");
1130
1136
  var import_jsx_runtime12 = require("react/jsx-runtime");