@xsolla/xui-context-menu 0.64.0-pr81.1768677662

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.
@@ -0,0 +1,1886 @@
1
+ // src/ContextMenu.tsx
2
+ import React10, {
3
+ forwardRef as forwardRef9,
4
+ useState,
5
+ useRef as useRef5,
6
+ useEffect as useEffect5,
7
+ useCallback,
8
+ useMemo
9
+ } from "react";
10
+
11
+ // ../primitives-native/src/Box.tsx
12
+ import {
13
+ View,
14
+ Pressable,
15
+ Image
16
+ } from "react-native";
17
+ import { jsx } from "react/jsx-runtime";
18
+ var Box = ({
19
+ children,
20
+ onPress,
21
+ onLayout,
22
+ onMoveShouldSetResponder,
23
+ onResponderGrant,
24
+ onResponderMove,
25
+ onResponderRelease,
26
+ onResponderTerminate,
27
+ backgroundColor,
28
+ borderColor,
29
+ borderWidth,
30
+ borderBottomWidth,
31
+ borderBottomColor,
32
+ borderTopWidth,
33
+ borderTopColor,
34
+ borderLeftWidth,
35
+ borderLeftColor,
36
+ borderRightWidth,
37
+ borderRightColor,
38
+ borderRadius,
39
+ borderStyle,
40
+ height,
41
+ padding,
42
+ paddingHorizontal,
43
+ paddingVertical,
44
+ margin,
45
+ marginTop,
46
+ marginBottom,
47
+ marginLeft,
48
+ marginRight,
49
+ flexDirection,
50
+ alignItems,
51
+ justifyContent,
52
+ position,
53
+ top,
54
+ bottom,
55
+ left,
56
+ right,
57
+ width,
58
+ flex,
59
+ overflow,
60
+ zIndex,
61
+ hoverStyle,
62
+ pressStyle,
63
+ style,
64
+ "data-testid": dataTestId,
65
+ testID,
66
+ as,
67
+ src,
68
+ alt,
69
+ ...rest
70
+ }) => {
71
+ const getContainerStyle = (pressed) => ({
72
+ backgroundColor: pressed && pressStyle?.backgroundColor ? pressStyle.backgroundColor : backgroundColor,
73
+ borderColor,
74
+ borderWidth,
75
+ borderBottomWidth,
76
+ borderBottomColor,
77
+ borderTopWidth,
78
+ borderTopColor,
79
+ borderLeftWidth,
80
+ borderLeftColor,
81
+ borderRightWidth,
82
+ borderRightColor,
83
+ borderRadius,
84
+ borderStyle,
85
+ overflow,
86
+ zIndex,
87
+ height,
88
+ width,
89
+ padding,
90
+ paddingHorizontal,
91
+ paddingVertical,
92
+ margin,
93
+ marginTop,
94
+ marginBottom,
95
+ marginLeft,
96
+ marginRight,
97
+ flexDirection,
98
+ alignItems,
99
+ justifyContent,
100
+ position,
101
+ top,
102
+ bottom,
103
+ left,
104
+ right,
105
+ flex,
106
+ ...style
107
+ });
108
+ const finalTestID = dataTestId || testID;
109
+ const {
110
+ role,
111
+ tabIndex,
112
+ onKeyDown,
113
+ onKeyUp,
114
+ "aria-label": _ariaLabel,
115
+ "aria-labelledby": _ariaLabelledBy,
116
+ "aria-current": _ariaCurrent,
117
+ "aria-disabled": _ariaDisabled,
118
+ "aria-live": _ariaLive,
119
+ className,
120
+ "data-testid": _dataTestId,
121
+ ...nativeRest
122
+ } = rest;
123
+ if (as === "img" && src) {
124
+ const imageStyle = {
125
+ width,
126
+ height,
127
+ borderRadius,
128
+ position,
129
+ top,
130
+ bottom,
131
+ left,
132
+ right,
133
+ ...style
134
+ };
135
+ return /* @__PURE__ */ jsx(
136
+ Image,
137
+ {
138
+ source: { uri: src },
139
+ style: imageStyle,
140
+ testID: finalTestID,
141
+ resizeMode: "cover",
142
+ ...nativeRest
143
+ }
144
+ );
145
+ }
146
+ if (onPress) {
147
+ return /* @__PURE__ */ jsx(
148
+ Pressable,
149
+ {
150
+ onPress,
151
+ onLayout,
152
+ onMoveShouldSetResponder,
153
+ onResponderGrant,
154
+ onResponderMove,
155
+ onResponderRelease,
156
+ onResponderTerminate,
157
+ style: ({ pressed }) => getContainerStyle(pressed),
158
+ testID: finalTestID,
159
+ ...nativeRest,
160
+ children
161
+ }
162
+ );
163
+ }
164
+ return /* @__PURE__ */ jsx(
165
+ View,
166
+ {
167
+ style: getContainerStyle(),
168
+ testID: finalTestID,
169
+ onLayout,
170
+ onMoveShouldSetResponder,
171
+ onResponderGrant,
172
+ onResponderMove,
173
+ onResponderRelease,
174
+ onResponderTerminate,
175
+ ...nativeRest,
176
+ children
177
+ }
178
+ );
179
+ };
180
+
181
+ // ../primitives-native/src/Text.tsx
182
+ import { Text as RNText } from "react-native";
183
+ import { jsx as jsx2 } from "react/jsx-runtime";
184
+ var roleMap = {
185
+ alert: "alert",
186
+ heading: "header",
187
+ button: "button",
188
+ link: "link",
189
+ text: "text"
190
+ };
191
+ var Text = ({
192
+ children,
193
+ color,
194
+ fontSize,
195
+ fontWeight,
196
+ fontFamily,
197
+ id,
198
+ role,
199
+ ...props
200
+ }) => {
201
+ let resolvedFontFamily = fontFamily ? fontFamily.split(",")[0].replace(/['"]/g, "").trim() : void 0;
202
+ if (resolvedFontFamily === "Pilat Wide Bold") {
203
+ resolvedFontFamily = void 0;
204
+ }
205
+ const style = {
206
+ color,
207
+ fontSize: typeof fontSize === "number" ? fontSize : void 0,
208
+ fontWeight,
209
+ fontFamily: resolvedFontFamily,
210
+ textDecorationLine: props.textDecoration
211
+ };
212
+ const accessibilityRole = role ? roleMap[role] : void 0;
213
+ return /* @__PURE__ */ jsx2(RNText, { style, testID: id, accessibilityRole, children });
214
+ };
215
+
216
+ // ../primitives-native/src/Spinner.tsx
217
+ import { ActivityIndicator, View as View2 } from "react-native";
218
+ import { jsx as jsx3 } from "react/jsx-runtime";
219
+ var Spinner = ({
220
+ color,
221
+ size,
222
+ role,
223
+ "aria-label": ariaLabel,
224
+ "aria-live": ariaLive,
225
+ "aria-describedby": ariaDescribedBy,
226
+ testID
227
+ }) => {
228
+ return /* @__PURE__ */ jsx3(
229
+ View2,
230
+ {
231
+ accessible: true,
232
+ accessibilityRole: role === "status" ? "none" : void 0,
233
+ accessibilityLabel: ariaLabel,
234
+ accessibilityLiveRegion: ariaLive === "polite" ? "polite" : ariaLive === "assertive" ? "assertive" : "none",
235
+ testID,
236
+ children: /* @__PURE__ */ jsx3(
237
+ ActivityIndicator,
238
+ {
239
+ color,
240
+ size: typeof size === "number" ? size : "small"
241
+ }
242
+ )
243
+ }
244
+ );
245
+ };
246
+ Spinner.displayName = "Spinner";
247
+
248
+ // ../primitives-native/src/Icon.tsx
249
+ import React from "react";
250
+ import { View as View3 } from "react-native";
251
+ import { jsx as jsx4 } from "react/jsx-runtime";
252
+ var Icon = ({ children, color, size }) => {
253
+ const style = {
254
+ width: typeof size === "number" ? size : void 0,
255
+ height: typeof size === "number" ? size : void 0,
256
+ alignItems: "center",
257
+ justifyContent: "center"
258
+ };
259
+ const childrenWithProps = React.Children.map(children, (child) => {
260
+ if (React.isValidElement(child)) {
261
+ return React.cloneElement(child, {
262
+ color: child.props.color || color,
263
+ // Also pass size if child seems to be an icon that needs it
264
+ size: child.props.size || size
265
+ });
266
+ }
267
+ return child;
268
+ });
269
+ return /* @__PURE__ */ jsx4(View3, { style, children: childrenWithProps });
270
+ };
271
+
272
+ // ../primitives-native/src/Divider.tsx
273
+ import { View as View4 } from "react-native";
274
+ import { jsx as jsx5 } from "react/jsx-runtime";
275
+
276
+ // ../primitives-native/src/Input.tsx
277
+ import { forwardRef } from "react";
278
+ import { TextInput as RNTextInput } from "react-native";
279
+ import { jsx as jsx6 } from "react/jsx-runtime";
280
+ var keyboardTypeMap = {
281
+ text: "default",
282
+ number: "numeric",
283
+ email: "email-address",
284
+ tel: "phone-pad",
285
+ url: "url",
286
+ decimal: "decimal-pad"
287
+ };
288
+ var inputModeToKeyboardType = {
289
+ none: "default",
290
+ text: "default",
291
+ decimal: "decimal-pad",
292
+ numeric: "number-pad",
293
+ tel: "phone-pad",
294
+ search: "default",
295
+ email: "email-address",
296
+ url: "url"
297
+ };
298
+ var autoCompleteToTextContentType = {
299
+ "one-time-code": "oneTimeCode",
300
+ email: "emailAddress",
301
+ username: "username",
302
+ password: "password",
303
+ "new-password": "newPassword",
304
+ tel: "telephoneNumber",
305
+ "postal-code": "postalCode",
306
+ name: "name"
307
+ };
308
+ var InputPrimitive = forwardRef(
309
+ ({
310
+ value,
311
+ placeholder,
312
+ onChange,
313
+ onChangeText,
314
+ onFocus,
315
+ onBlur,
316
+ onKeyDown,
317
+ disabled,
318
+ secureTextEntry,
319
+ style,
320
+ color,
321
+ fontSize,
322
+ placeholderTextColor,
323
+ maxLength,
324
+ name,
325
+ type,
326
+ inputMode,
327
+ autoComplete,
328
+ id,
329
+ "aria-invalid": ariaInvalid,
330
+ "aria-describedby": ariaDescribedBy,
331
+ "aria-labelledby": ariaLabelledBy,
332
+ "aria-label": ariaLabel,
333
+ "aria-disabled": ariaDisabled,
334
+ "data-testid": dataTestId
335
+ }, ref) => {
336
+ const handleChangeText = (text) => {
337
+ onChangeText?.(text);
338
+ if (onChange) {
339
+ const syntheticEvent = {
340
+ target: { value: text },
341
+ currentTarget: { value: text },
342
+ type: "change",
343
+ nativeEvent: { text },
344
+ preventDefault: () => {
345
+ },
346
+ stopPropagation: () => {
347
+ },
348
+ isTrusted: false
349
+ };
350
+ onChange(syntheticEvent);
351
+ }
352
+ };
353
+ const keyboardType = inputMode ? inputModeToKeyboardType[inputMode] || "default" : type ? keyboardTypeMap[type] || "default" : "default";
354
+ const textContentType = autoComplete ? autoCompleteToTextContentType[autoComplete] : void 0;
355
+ return /* @__PURE__ */ jsx6(
356
+ RNTextInput,
357
+ {
358
+ ref,
359
+ value,
360
+ placeholder,
361
+ onChangeText: handleChangeText,
362
+ onFocus,
363
+ onBlur,
364
+ onKeyPress: (e) => {
365
+ if (onKeyDown) {
366
+ onKeyDown({
367
+ key: e.nativeEvent.key,
368
+ preventDefault: () => {
369
+ }
370
+ });
371
+ }
372
+ },
373
+ editable: !disabled,
374
+ secureTextEntry: secureTextEntry || type === "password",
375
+ keyboardType,
376
+ textContentType,
377
+ style: [
378
+ {
379
+ color,
380
+ fontSize: typeof fontSize === "number" ? fontSize : void 0,
381
+ flex: 1,
382
+ padding: 0,
383
+ textAlign: style?.textAlign || "left"
384
+ },
385
+ style
386
+ ],
387
+ placeholderTextColor,
388
+ maxLength,
389
+ testID: dataTestId || id,
390
+ accessibilityLabel: ariaLabel,
391
+ accessibilityHint: ariaDescribedBy,
392
+ accessibilityState: {
393
+ disabled: disabled || ariaDisabled
394
+ },
395
+ accessible: true
396
+ }
397
+ );
398
+ }
399
+ );
400
+ InputPrimitive.displayName = "InputPrimitive";
401
+
402
+ // ../primitives-native/src/TextArea.tsx
403
+ import { forwardRef as forwardRef2 } from "react";
404
+ import { TextInput as RNTextInput2 } from "react-native";
405
+ import { jsx as jsx7 } from "react/jsx-runtime";
406
+ var TextAreaPrimitive = forwardRef2(
407
+ ({
408
+ value,
409
+ placeholder,
410
+ onChange,
411
+ onChangeText,
412
+ onFocus,
413
+ onBlur,
414
+ onKeyDown,
415
+ disabled,
416
+ style,
417
+ color,
418
+ fontSize,
419
+ placeholderTextColor,
420
+ maxLength,
421
+ rows,
422
+ id,
423
+ "aria-invalid": ariaInvalid,
424
+ "aria-describedby": ariaDescribedBy,
425
+ "aria-labelledby": ariaLabelledBy,
426
+ "aria-label": ariaLabel,
427
+ "aria-disabled": ariaDisabled,
428
+ "data-testid": dataTestId
429
+ }, ref) => {
430
+ const handleChangeText = (text) => {
431
+ onChangeText?.(text);
432
+ if (onChange) {
433
+ const syntheticEvent = {
434
+ target: { value: text },
435
+ currentTarget: { value: text },
436
+ type: "change",
437
+ nativeEvent: { text },
438
+ preventDefault: () => {
439
+ },
440
+ stopPropagation: () => {
441
+ },
442
+ isTrusted: false
443
+ };
444
+ onChange(syntheticEvent);
445
+ }
446
+ };
447
+ return /* @__PURE__ */ jsx7(
448
+ RNTextInput2,
449
+ {
450
+ ref,
451
+ value,
452
+ placeholder,
453
+ onChangeText: handleChangeText,
454
+ onFocus,
455
+ onBlur,
456
+ onKeyPress: (e) => {
457
+ if (onKeyDown) {
458
+ onKeyDown({
459
+ key: e.nativeEvent.key,
460
+ preventDefault: () => {
461
+ }
462
+ });
463
+ }
464
+ },
465
+ editable: !disabled,
466
+ multiline: true,
467
+ numberOfLines: rows || 4,
468
+ style: [
469
+ {
470
+ color,
471
+ fontSize: typeof fontSize === "number" ? fontSize : void 0,
472
+ flex: 1,
473
+ padding: 0,
474
+ textAlignVertical: "top",
475
+ textAlign: style?.textAlign || "left"
476
+ },
477
+ style
478
+ ],
479
+ placeholderTextColor,
480
+ maxLength,
481
+ testID: dataTestId || id,
482
+ accessibilityLabel: ariaLabel,
483
+ accessibilityHint: ariaDescribedBy,
484
+ accessibilityState: {
485
+ disabled: disabled || ariaDisabled
486
+ },
487
+ accessible: true
488
+ }
489
+ );
490
+ }
491
+ );
492
+ TextAreaPrimitive.displayName = "TextAreaPrimitive";
493
+
494
+ // src/ContextMenu.tsx
495
+ import { useDesignSystem as useDesignSystem7 } from "@xsolla/xui-core";
496
+ import { Spinner as Spinner2 } from "@xsolla/xui-spinner";
497
+
498
+ // src/ContextMenuContext.tsx
499
+ import { createContext, useContext } from "react";
500
+ var ContextMenuContext = createContext(void 0);
501
+ var useContextMenu = () => {
502
+ const context = useContext(ContextMenuContext);
503
+ return context;
504
+ };
505
+ var useContextMenuRequired = () => {
506
+ const context = useContext(ContextMenuContext);
507
+ if (!context) {
508
+ throw new Error(
509
+ "useContextMenuRequired must be used within a ContextMenu component"
510
+ );
511
+ }
512
+ return context;
513
+ };
514
+
515
+ // src/ContextMenuItem.tsx
516
+ import { forwardRef as forwardRef3, useEffect, useRef } from "react";
517
+ import { useDesignSystem } from "@xsolla/xui-core";
518
+ import { ArrowRight } from "@xsolla/xui-icons";
519
+ import { jsx as jsx8, jsxs } from "react/jsx-runtime";
520
+ var ContextMenuItem = forwardRef3(
521
+ ({
522
+ children,
523
+ description,
524
+ icon,
525
+ trailing,
526
+ shortcut,
527
+ selected,
528
+ disabled,
529
+ active,
530
+ size: propSize,
531
+ hasSubmenu,
532
+ onPress,
533
+ "data-testid": testId = "context-menu-item"
534
+ }, ref) => {
535
+ const { theme } = useDesignSystem();
536
+ const context = useContextMenu();
537
+ const size = propSize || context?.size || "m";
538
+ const sizeStyles = theme.sizing.contextMenu(size);
539
+ const itemRef = useRef(null);
540
+ const brandColors = theme.colors.control.brand.primary;
541
+ const contentColors = theme.colors.content;
542
+ useEffect(() => {
543
+ if (context && !disabled) {
544
+ const id = typeof children === "string" ? children : String(Math.random());
545
+ context.registerItem(id);
546
+ return () => context.unregisterItem(id);
547
+ }
548
+ }, [context, disabled, children]);
549
+ const handlePress = () => {
550
+ if (disabled) return;
551
+ onPress?.();
552
+ if (context) {
553
+ context.onItemSelect({
554
+ id: typeof children === "string" ? children : "",
555
+ label: typeof children === "string" ? children : "",
556
+ variant: "default",
557
+ selected
558
+ });
559
+ }
560
+ };
561
+ const handleKeyDown = (event) => {
562
+ if (disabled) return;
563
+ if (event.key === "Enter" || event.key === " ") {
564
+ event.preventDefault();
565
+ handlePress();
566
+ }
567
+ };
568
+ const getBackgroundColor = () => {
569
+ if (selected) {
570
+ return brandColors.bg;
571
+ }
572
+ if (active) {
573
+ return theme.colors.control.input.bgHover;
574
+ }
575
+ return "transparent";
576
+ };
577
+ const getTextColor = () => {
578
+ if (disabled) {
579
+ return theme.colors.control.input.textDisable;
580
+ }
581
+ if (selected) {
582
+ return contentColors.on.brand;
583
+ }
584
+ return theme.colors.content.primary;
585
+ };
586
+ const getIconColor = () => {
587
+ if (disabled) {
588
+ return theme.colors.content.tertiary;
589
+ }
590
+ if (selected) {
591
+ return contentColors.on.brand;
592
+ }
593
+ return theme.colors.content.secondary;
594
+ };
595
+ return /* @__PURE__ */ jsxs(
596
+ Box,
597
+ {
598
+ ref: ref || itemRef,
599
+ role: "menuitem",
600
+ "aria-disabled": disabled || void 0,
601
+ "aria-selected": selected || void 0,
602
+ tabIndex: disabled ? -1 : 0,
603
+ flexDirection: "row",
604
+ alignItems: description ? "flex-start" : "center",
605
+ gap: sizeStyles.gap,
606
+ paddingHorizontal: sizeStyles.itemPaddingHorizontal,
607
+ paddingVertical: sizeStyles.itemPaddingVertical,
608
+ backgroundColor: getBackgroundColor(),
609
+ hoverStyle: !disabled && !selected ? {
610
+ backgroundColor: theme.colors.control.input.bgHover
611
+ } : void 0,
612
+ pressStyle: !disabled ? {
613
+ backgroundColor: selected ? brandColors.bgPress : theme.colors.control.input.bgDisable
614
+ } : void 0,
615
+ onPress: handlePress,
616
+ onKeyDown: handleKeyDown,
617
+ style: {
618
+ cursor: disabled ? "not-allowed" : "pointer",
619
+ opacity: disabled ? 0.5 : 1,
620
+ outline: "none"
621
+ },
622
+ "data-testid": testId,
623
+ children: [
624
+ icon && /* @__PURE__ */ jsx8(
625
+ Box,
626
+ {
627
+ width: sizeStyles.iconSize,
628
+ marginTop: description ? 2 : 0,
629
+ alignItems: "center",
630
+ justifyContent: "center",
631
+ children: /* @__PURE__ */ jsx8(Icon, { size: sizeStyles.iconSize, color: getIconColor(), children: icon })
632
+ }
633
+ ),
634
+ /* @__PURE__ */ jsxs(Box, { flex: 1, flexDirection: "column", gap: 2, children: [
635
+ /* @__PURE__ */ jsx8(
636
+ Text,
637
+ {
638
+ color: getTextColor(),
639
+ fontSize: sizeStyles.fontSize,
640
+ fontWeight: "400",
641
+ lineHeight: sizeStyles.fontSize + 2,
642
+ children
643
+ }
644
+ ),
645
+ description && /* @__PURE__ */ jsx8(
646
+ Text,
647
+ {
648
+ color: selected ? contentColors.on.brand : theme.colors.content.tertiary,
649
+ fontSize: sizeStyles.descriptionFontSize,
650
+ lineHeight: sizeStyles.descriptionFontSize + 2,
651
+ children: description
652
+ }
653
+ )
654
+ ] }),
655
+ shortcut && /* @__PURE__ */ jsx8(
656
+ Text,
657
+ {
658
+ color: selected ? contentColors.on.brand : theme.colors.content.tertiary,
659
+ fontSize: sizeStyles.descriptionFontSize,
660
+ children: shortcut
661
+ }
662
+ ),
663
+ trailing,
664
+ hasSubmenu && /* @__PURE__ */ jsx8(
665
+ Icon,
666
+ {
667
+ size: sizeStyles.iconSize - 4,
668
+ color: selected ? contentColors.on.brand : theme.colors.content.secondary,
669
+ children: /* @__PURE__ */ jsx8(ArrowRight, {})
670
+ }
671
+ )
672
+ ]
673
+ }
674
+ );
675
+ }
676
+ );
677
+ ContextMenuItem.displayName = "ContextMenuItem";
678
+
679
+ // src/ContextMenuCheckboxItem.tsx
680
+ import { forwardRef as forwardRef4, useEffect as useEffect2, useRef as useRef2 } from "react";
681
+ import { useDesignSystem as useDesignSystem2 } from "@xsolla/xui-core";
682
+ import { Check, Minus } from "@xsolla/xui-icons";
683
+ import { jsx as jsx9, jsxs as jsxs2 } from "react/jsx-runtime";
684
+ var checkboxSizeMap = {
685
+ s: 14,
686
+ m: 16,
687
+ l: 18,
688
+ xl: 20
689
+ };
690
+ var checkIconSizeMap = {
691
+ s: 10,
692
+ m: 12,
693
+ l: 14,
694
+ xl: 16
695
+ };
696
+ var CheckboxIndicator = ({
697
+ checked,
698
+ indeterminate,
699
+ size,
700
+ disabled
701
+ }) => {
702
+ const { theme } = useDesignSystem2();
703
+ const brandColors = theme.colors.control.brand.primary;
704
+ const faintColors = theme.colors.control.faint;
705
+ const contentColors = theme.colors.content;
706
+ const boxSize = checkboxSizeMap[size];
707
+ const iconSize = checkIconSizeMap[size];
708
+ const isActive = checked || indeterminate;
709
+ const getBgColor = () => {
710
+ if (disabled) {
711
+ return brandColors.bgDisable;
712
+ }
713
+ if (isActive) {
714
+ return brandColors.bg;
715
+ }
716
+ return faintColors.bg;
717
+ };
718
+ const getBorderColor = () => {
719
+ if (isActive && !disabled) {
720
+ return brandColors.bg;
721
+ }
722
+ return faintColors.border;
723
+ };
724
+ return /* @__PURE__ */ jsx9(
725
+ Box,
726
+ {
727
+ width: boxSize,
728
+ height: boxSize,
729
+ borderRadius: 4,
730
+ borderWidth: isActive ? 0 : 1,
731
+ borderColor: getBorderColor(),
732
+ backgroundColor: getBgColor(),
733
+ alignItems: "center",
734
+ justifyContent: "center",
735
+ children: indeterminate ? /* @__PURE__ */ jsx9(
736
+ Icon,
737
+ {
738
+ size: iconSize,
739
+ color: disabled ? contentColors.tertiary : contentColors.on.brand,
740
+ children: /* @__PURE__ */ jsx9(Minus, {})
741
+ }
742
+ ) : checked ? /* @__PURE__ */ jsx9(
743
+ Icon,
744
+ {
745
+ size: iconSize,
746
+ color: disabled ? contentColors.tertiary : contentColors.on.brand,
747
+ children: /* @__PURE__ */ jsx9(Check, {})
748
+ }
749
+ ) : null
750
+ }
751
+ );
752
+ };
753
+ var ContextMenuCheckboxItem = forwardRef4(
754
+ ({
755
+ children,
756
+ description,
757
+ leadingContent,
758
+ trailing,
759
+ checked = false,
760
+ indeterminate = false,
761
+ disabled,
762
+ size: propSize,
763
+ onCheckedChange,
764
+ onPress,
765
+ "data-testid": testId = "context-menu-checkbox-item"
766
+ }, ref) => {
767
+ const { theme } = useDesignSystem2();
768
+ const context = useContextMenu();
769
+ const size = propSize || context?.size || "m";
770
+ const sizeStyles = theme.sizing.contextMenu(size);
771
+ const itemRef = useRef2(null);
772
+ const contentColors = theme.colors.content;
773
+ useEffect2(() => {
774
+ if (context && !disabled) {
775
+ const id = typeof children === "string" ? children : String(Math.random());
776
+ context.registerItem(id);
777
+ return () => context.unregisterItem(id);
778
+ }
779
+ }, [context, disabled, children]);
780
+ const handlePress = () => {
781
+ if (disabled) return;
782
+ const newChecked = !checked;
783
+ onCheckedChange?.(newChecked);
784
+ onPress?.();
785
+ if (context) {
786
+ context.onItemSelect({
787
+ id: typeof children === "string" ? children : "",
788
+ label: typeof children === "string" ? children : "",
789
+ variant: "checkbox",
790
+ checked: newChecked
791
+ });
792
+ }
793
+ };
794
+ const handleKeyDown = (event) => {
795
+ if (disabled) return;
796
+ if (event.key === "Enter" || event.key === " ") {
797
+ event.preventDefault();
798
+ handlePress();
799
+ }
800
+ };
801
+ const getTextColor = () => {
802
+ if (disabled) {
803
+ return theme.colors.control.input.textDisable;
804
+ }
805
+ return theme.colors.control.input.text;
806
+ };
807
+ return /* @__PURE__ */ jsxs2(
808
+ Box,
809
+ {
810
+ ref: ref || itemRef,
811
+ role: "menuitemcheckbox",
812
+ "aria-checked": indeterminate ? "mixed" : checked,
813
+ "aria-disabled": disabled || void 0,
814
+ tabIndex: disabled ? -1 : 0,
815
+ flexDirection: "row",
816
+ alignItems: description ? "flex-start" : "center",
817
+ gap: sizeStyles.gap,
818
+ paddingHorizontal: sizeStyles.itemPaddingHorizontal,
819
+ paddingVertical: sizeStyles.itemPaddingVertical,
820
+ backgroundColor: "transparent",
821
+ hoverStyle: !disabled ? {
822
+ backgroundColor: theme.colors.control.input.bgHover
823
+ } : void 0,
824
+ pressStyle: !disabled ? {
825
+ backgroundColor: theme.colors.control.input.bgDisable
826
+ } : void 0,
827
+ onPress: handlePress,
828
+ onKeyDown: handleKeyDown,
829
+ style: {
830
+ cursor: disabled ? "not-allowed" : "pointer",
831
+ opacity: disabled ? 0.5 : 1,
832
+ outline: "none"
833
+ },
834
+ "data-testid": testId,
835
+ children: [
836
+ /* @__PURE__ */ jsx9(
837
+ Box,
838
+ {
839
+ marginTop: description ? 2 : 0,
840
+ alignItems: "center",
841
+ justifyContent: "center",
842
+ children: /* @__PURE__ */ jsx9(
843
+ CheckboxIndicator,
844
+ {
845
+ checked,
846
+ indeterminate,
847
+ size,
848
+ disabled
849
+ }
850
+ )
851
+ }
852
+ ),
853
+ leadingContent && /* @__PURE__ */ jsx9(Box, { padding: 5, alignItems: "center", justifyContent: "center", children: leadingContent }),
854
+ /* @__PURE__ */ jsxs2(Box, { flex: 1, flexDirection: "column", gap: 2, children: [
855
+ /* @__PURE__ */ jsx9(
856
+ Text,
857
+ {
858
+ color: getTextColor(),
859
+ fontSize: sizeStyles.fontSize,
860
+ fontWeight: "400",
861
+ lineHeight: sizeStyles.fontSize + 2,
862
+ children
863
+ }
864
+ ),
865
+ description && /* @__PURE__ */ jsx9(
866
+ Text,
867
+ {
868
+ color: theme.colors.content.tertiary,
869
+ fontSize: sizeStyles.descriptionFontSize,
870
+ lineHeight: sizeStyles.descriptionFontSize + 2,
871
+ children: description
872
+ }
873
+ )
874
+ ] }),
875
+ trailing
876
+ ]
877
+ }
878
+ );
879
+ }
880
+ );
881
+ ContextMenuCheckboxItem.displayName = "ContextMenuCheckboxItem";
882
+
883
+ // src/ContextMenuRadioGroup.tsx
884
+ import { createContext as createContext2, useContext as useContext2, forwardRef as forwardRef5 } from "react";
885
+ import { jsx as jsx10 } from "react/jsx-runtime";
886
+ var RadioGroupContext = createContext2(null);
887
+ var useRadioGroup = () => useContext2(RadioGroupContext);
888
+ var ContextMenuRadioGroup = forwardRef5(
889
+ ({
890
+ children,
891
+ value,
892
+ onValueChange,
893
+ "data-testid": testId = "context-menu-radio-group"
894
+ }, ref) => {
895
+ return /* @__PURE__ */ jsx10(RadioGroupContext.Provider, { value: { value, onValueChange }, children: /* @__PURE__ */ jsx10(Box, { ref, role: "group", flexDirection: "column", "data-testid": testId, children }) });
896
+ }
897
+ );
898
+ ContextMenuRadioGroup.displayName = "ContextMenuRadioGroup";
899
+
900
+ // src/ContextMenuRadioItem.tsx
901
+ import { forwardRef as forwardRef6, useEffect as useEffect3, useRef as useRef3 } from "react";
902
+ import { useDesignSystem as useDesignSystem3 } from "@xsolla/xui-core";
903
+ import { jsx as jsx11, jsxs as jsxs3 } from "react/jsx-runtime";
904
+ var radioSizeMap = {
905
+ s: 14,
906
+ m: 16,
907
+ l: 18,
908
+ xl: 20
909
+ };
910
+ var radioDotSizeMap = {
911
+ s: 6,
912
+ m: 8,
913
+ l: 8,
914
+ xl: 10
915
+ };
916
+ var RadioIndicator = ({
917
+ checked,
918
+ size,
919
+ disabled
920
+ }) => {
921
+ const { theme } = useDesignSystem3();
922
+ const brandColors = theme.colors.control.brand.primary;
923
+ const faintColors = theme.colors.control.faint;
924
+ const contentColors = theme.colors.content;
925
+ const outerSize = radioSizeMap[size];
926
+ const dotSize = radioDotSizeMap[size];
927
+ const getBgColor = () => {
928
+ if (disabled) {
929
+ return brandColors.bgDisable;
930
+ }
931
+ if (checked) {
932
+ return brandColors.bg;
933
+ }
934
+ return faintColors.bg;
935
+ };
936
+ const getBorderColor = () => {
937
+ if (checked && !disabled) {
938
+ return brandColors.bg;
939
+ }
940
+ return faintColors.border;
941
+ };
942
+ return /* @__PURE__ */ jsx11(
943
+ Box,
944
+ {
945
+ width: outerSize,
946
+ height: outerSize,
947
+ borderRadius: outerSize / 2,
948
+ borderWidth: checked ? 0 : 1,
949
+ borderColor: getBorderColor(),
950
+ backgroundColor: getBgColor(),
951
+ alignItems: "center",
952
+ justifyContent: "center",
953
+ children: checked && /* @__PURE__ */ jsx11(
954
+ Box,
955
+ {
956
+ width: dotSize,
957
+ height: dotSize,
958
+ borderRadius: dotSize / 2,
959
+ backgroundColor: disabled ? contentColors.tertiary : contentColors.on.brand
960
+ }
961
+ )
962
+ }
963
+ );
964
+ };
965
+ var ContextMenuRadioItem = forwardRef6(
966
+ ({
967
+ children,
968
+ description,
969
+ value,
970
+ disabled,
971
+ size: propSize,
972
+ onPress,
973
+ "data-testid": testId = "context-menu-radio-item"
974
+ }, ref) => {
975
+ const { theme } = useDesignSystem3();
976
+ const context = useContextMenu();
977
+ const radioGroup = useRadioGroup();
978
+ const size = propSize || context?.size || "m";
979
+ const sizeStyles = theme.sizing.contextMenu(size);
980
+ const itemRef = useRef3(null);
981
+ const checked = radioGroup?.value === value;
982
+ useEffect3(() => {
983
+ if (context && !disabled) {
984
+ const id = typeof children === "string" ? children : String(Math.random());
985
+ context.registerItem(id);
986
+ return () => context.unregisterItem(id);
987
+ }
988
+ }, [context, disabled, children]);
989
+ const handlePress = () => {
990
+ if (disabled) return;
991
+ radioGroup?.onValueChange(value);
992
+ onPress?.();
993
+ if (context) {
994
+ context.onItemSelect({
995
+ id: typeof children === "string" ? children : "",
996
+ label: typeof children === "string" ? children : "",
997
+ variant: "radio",
998
+ checked: true
999
+ });
1000
+ }
1001
+ };
1002
+ const handleKeyDown = (event) => {
1003
+ if (disabled) return;
1004
+ if (event.key === "Enter" || event.key === " ") {
1005
+ event.preventDefault();
1006
+ handlePress();
1007
+ }
1008
+ };
1009
+ const getTextColor = () => {
1010
+ if (disabled) {
1011
+ return theme.colors.control.input.textDisable;
1012
+ }
1013
+ return theme.colors.control.input.text;
1014
+ };
1015
+ return /* @__PURE__ */ jsxs3(
1016
+ Box,
1017
+ {
1018
+ ref: ref || itemRef,
1019
+ role: "menuitemradio",
1020
+ "aria-checked": checked,
1021
+ "aria-disabled": disabled || void 0,
1022
+ tabIndex: disabled ? -1 : 0,
1023
+ flexDirection: "row",
1024
+ alignItems: description ? "flex-start" : "center",
1025
+ gap: sizeStyles.gap,
1026
+ paddingHorizontal: sizeStyles.itemPaddingHorizontal,
1027
+ paddingVertical: sizeStyles.itemPaddingVertical,
1028
+ backgroundColor: "transparent",
1029
+ hoverStyle: !disabled ? {
1030
+ backgroundColor: theme.colors.control.input.bgHover
1031
+ } : void 0,
1032
+ pressStyle: !disabled ? {
1033
+ backgroundColor: theme.colors.control.input.bgDisable
1034
+ } : void 0,
1035
+ onPress: handlePress,
1036
+ onKeyDown: handleKeyDown,
1037
+ style: {
1038
+ cursor: disabled ? "not-allowed" : "pointer",
1039
+ opacity: disabled ? 0.5 : 1,
1040
+ outline: "none"
1041
+ },
1042
+ "data-testid": testId,
1043
+ children: [
1044
+ /* @__PURE__ */ jsx11(
1045
+ Box,
1046
+ {
1047
+ marginTop: description ? 2 : 0,
1048
+ alignItems: "center",
1049
+ justifyContent: "center",
1050
+ children: /* @__PURE__ */ jsx11(RadioIndicator, { checked, size, disabled })
1051
+ }
1052
+ ),
1053
+ /* @__PURE__ */ jsxs3(Box, { flex: 1, flexDirection: "column", gap: 2, children: [
1054
+ /* @__PURE__ */ jsx11(
1055
+ Text,
1056
+ {
1057
+ color: getTextColor(),
1058
+ fontSize: sizeStyles.fontSize,
1059
+ fontWeight: "400",
1060
+ lineHeight: sizeStyles.fontSize + 2,
1061
+ children
1062
+ }
1063
+ ),
1064
+ description && /* @__PURE__ */ jsx11(
1065
+ Text,
1066
+ {
1067
+ color: theme.colors.content.tertiary,
1068
+ fontSize: sizeStyles.descriptionFontSize,
1069
+ lineHeight: sizeStyles.descriptionFontSize + 2,
1070
+ children: description
1071
+ }
1072
+ )
1073
+ ] })
1074
+ ]
1075
+ }
1076
+ );
1077
+ }
1078
+ );
1079
+ ContextMenuRadioItem.displayName = "ContextMenuRadioItem";
1080
+
1081
+ // src/ContextMenuGroup.tsx
1082
+ import { forwardRef as forwardRef7 } from "react";
1083
+ import { useDesignSystem as useDesignSystem4 } from "@xsolla/xui-core";
1084
+ import { jsx as jsx12, jsxs as jsxs4 } from "react/jsx-runtime";
1085
+ var ContextMenuGroup = forwardRef7(
1086
+ ({
1087
+ label,
1088
+ description,
1089
+ children,
1090
+ size: propSize,
1091
+ "data-testid": testId = "context-menu-group"
1092
+ }, ref) => {
1093
+ const { theme } = useDesignSystem4();
1094
+ const context = useContextMenu();
1095
+ const size = propSize || context?.size || "m";
1096
+ const sizeStyles = theme.sizing.contextMenu(size);
1097
+ return /* @__PURE__ */ jsxs4(Box, { ref, role: "group", "aria-label": label, "data-testid": testId, children: [
1098
+ label && /* @__PURE__ */ jsxs4(
1099
+ Box,
1100
+ {
1101
+ paddingHorizontal: sizeStyles.itemPaddingHorizontal,
1102
+ paddingVertical: sizeStyles.itemPaddingVertical,
1103
+ children: [
1104
+ /* @__PURE__ */ jsx12(
1105
+ Text,
1106
+ {
1107
+ color: theme.colors.content.secondary,
1108
+ fontSize: sizeStyles.descriptionFontSize,
1109
+ fontWeight: "600",
1110
+ style: { textTransform: "uppercase", letterSpacing: 0.5 },
1111
+ children: label
1112
+ }
1113
+ ),
1114
+ description && /* @__PURE__ */ jsx12(
1115
+ Text,
1116
+ {
1117
+ color: theme.colors.content.tertiary,
1118
+ fontSize: sizeStyles.descriptionFontSize - 2,
1119
+ marginTop: 2,
1120
+ children: description
1121
+ }
1122
+ )
1123
+ ]
1124
+ }
1125
+ ),
1126
+ children
1127
+ ] });
1128
+ }
1129
+ );
1130
+ ContextMenuGroup.displayName = "ContextMenuGroup";
1131
+
1132
+ // src/ContextMenuSeparator.tsx
1133
+ import { useDesignSystem as useDesignSystem5 } from "@xsolla/xui-core";
1134
+ import { jsx as jsx13 } from "react/jsx-runtime";
1135
+ var ContextMenuSeparator = ({
1136
+ size: propSize,
1137
+ "data-testid": testId = "context-menu-separator"
1138
+ }) => {
1139
+ const { theme } = useDesignSystem5();
1140
+ const context = useContextMenu();
1141
+ const size = propSize || context?.size || "m";
1142
+ const sizeStyles = theme.sizing.contextMenu(size);
1143
+ return /* @__PURE__ */ jsx13(
1144
+ Box,
1145
+ {
1146
+ role: "separator",
1147
+ marginVertical: sizeStyles.paddingVertical,
1148
+ marginHorizontal: sizeStyles.itemPaddingHorizontal,
1149
+ height: 1,
1150
+ backgroundColor: theme.colors.border.secondary,
1151
+ "data-testid": testId
1152
+ }
1153
+ );
1154
+ };
1155
+ ContextMenuSeparator.displayName = "ContextMenuSeparator";
1156
+
1157
+ // src/ContextMenuSearch.tsx
1158
+ import React9, { forwardRef as forwardRef8, useRef as useRef4, useEffect as useEffect4 } from "react";
1159
+ import { useDesignSystem as useDesignSystem6 } from "@xsolla/xui-core";
1160
+
1161
+ // ../icons-base/dist/web/index.mjs
1162
+ import styled from "styled-components";
1163
+ import { jsx as jsx14 } from "react/jsx-runtime";
1164
+ import { jsx as jsx22 } from "react/jsx-runtime";
1165
+ import { jsx as jsx32 } from "react/jsx-runtime";
1166
+ import { jsx as jsx42 } from "react/jsx-runtime";
1167
+ import { jsx as jsx52 } from "react/jsx-runtime";
1168
+ import { jsx as jsx62 } from "react/jsx-runtime";
1169
+ import { jsx as jsx72 } from "react/jsx-runtime";
1170
+ import { jsx as jsx82 } from "react/jsx-runtime";
1171
+ import { jsx as jsx92 } from "react/jsx-runtime";
1172
+ import { jsx as jsx102 } from "react/jsx-runtime";
1173
+ import { jsx as jsx112 } from "react/jsx-runtime";
1174
+ import { jsx as jsx122 } from "react/jsx-runtime";
1175
+ import { jsx as jsx132 } from "react/jsx-runtime";
1176
+ import { jsx as jsx142 } from "react/jsx-runtime";
1177
+ import { jsx as jsx15 } from "react/jsx-runtime";
1178
+ import { jsx as jsx16 } from "react/jsx-runtime";
1179
+ import { jsx as jsx17 } from "react/jsx-runtime";
1180
+ import { jsx as jsx18 } from "react/jsx-runtime";
1181
+ import { jsx as jsx19 } from "react/jsx-runtime";
1182
+ import { jsx as jsx20 } from "react/jsx-runtime";
1183
+ import { jsx as jsx21 } from "react/jsx-runtime";
1184
+ import { jsx as jsx222 } from "react/jsx-runtime";
1185
+ import { jsx as jsx23 } from "react/jsx-runtime";
1186
+ import { jsx as jsx24 } from "react/jsx-runtime";
1187
+ import { jsx as jsx25 } from "react/jsx-runtime";
1188
+ import { jsx as jsx26 } from "react/jsx-runtime";
1189
+ import { jsx as jsx27 } from "react/jsx-runtime";
1190
+ import { jsx as jsx28 } from "react/jsx-runtime";
1191
+ import { jsx as jsx29 } from "react/jsx-runtime";
1192
+ import { jsx as jsx30 } from "react/jsx-runtime";
1193
+ import { jsx as jsx31 } from "react/jsx-runtime";
1194
+ import { jsx as jsx322 } from "react/jsx-runtime";
1195
+ import { jsx as jsx33 } from "react/jsx-runtime";
1196
+ import { jsx as jsx34 } from "react/jsx-runtime";
1197
+ import { jsx as jsx35 } from "react/jsx-runtime";
1198
+ import { jsx as jsx36 } from "react/jsx-runtime";
1199
+ import { jsx as jsx37 } from "react/jsx-runtime";
1200
+ import { jsx as jsx38 } from "react/jsx-runtime";
1201
+ import { jsx as jsx39 } from "react/jsx-runtime";
1202
+ import { jsx as jsx40 } from "react/jsx-runtime";
1203
+ import { jsx as jsx41 } from "react/jsx-runtime";
1204
+ import { jsx as jsx422 } from "react/jsx-runtime";
1205
+ import { jsx as jsx43 } from "react/jsx-runtime";
1206
+ import { jsx as jsx44 } from "react/jsx-runtime";
1207
+ import { jsx as jsx45 } from "react/jsx-runtime";
1208
+ import { jsx as jsx46 } from "react/jsx-runtime";
1209
+ import { jsx as jsx47 } from "react/jsx-runtime";
1210
+ import { jsx as jsx48 } from "react/jsx-runtime";
1211
+ import { jsx as jsx49 } from "react/jsx-runtime";
1212
+ import { jsx as jsx50 } from "react/jsx-runtime";
1213
+ import { jsx as jsx51 } from "react/jsx-runtime";
1214
+ import { jsx as jsx522 } from "react/jsx-runtime";
1215
+ import { jsx as jsx53 } from "react/jsx-runtime";
1216
+ import { jsx as jsx54 } from "react/jsx-runtime";
1217
+ import { jsx as jsx55 } from "react/jsx-runtime";
1218
+ import { jsx as jsx56 } from "react/jsx-runtime";
1219
+ import { jsx as jsx57 } from "react/jsx-runtime";
1220
+ import { jsx as jsx58 } from "react/jsx-runtime";
1221
+ import { jsx as jsx59 } from "react/jsx-runtime";
1222
+ import { jsx as jsx60 } from "react/jsx-runtime";
1223
+ import { jsx as jsx61 } from "react/jsx-runtime";
1224
+ import { jsx as jsx622 } from "react/jsx-runtime";
1225
+ import { jsx as jsx63 } from "react/jsx-runtime";
1226
+ import { jsx as jsx64 } from "react/jsx-runtime";
1227
+ import { jsx as jsx65 } from "react/jsx-runtime";
1228
+ import { jsx as jsx66 } from "react/jsx-runtime";
1229
+ import { jsx as jsx67 } from "react/jsx-runtime";
1230
+ import { jsx as jsx68 } from "react/jsx-runtime";
1231
+ import { jsx as jsx69 } from "react/jsx-runtime";
1232
+ import { jsx as jsx70 } from "react/jsx-runtime";
1233
+ import { jsx as jsx71 } from "react/jsx-runtime";
1234
+ import { jsx as jsx722 } from "react/jsx-runtime";
1235
+ import { jsx as jsx73 } from "react/jsx-runtime";
1236
+ import { jsx as jsx74 } from "react/jsx-runtime";
1237
+ import { jsx as jsx75 } from "react/jsx-runtime";
1238
+ import { jsx as jsx76 } from "react/jsx-runtime";
1239
+ import { jsx as jsx77 } from "react/jsx-runtime";
1240
+ import { jsx as jsx78 } from "react/jsx-runtime";
1241
+ import { jsx as jsx79 } from "react/jsx-runtime";
1242
+ import { jsx as jsx80 } from "react/jsx-runtime";
1243
+ import { jsx as jsx81 } from "react/jsx-runtime";
1244
+ import { jsx as jsx822 } from "react/jsx-runtime";
1245
+ import { jsx as jsx83 } from "react/jsx-runtime";
1246
+ import { jsx as jsx84 } from "react/jsx-runtime";
1247
+ import { jsx as jsx85 } from "react/jsx-runtime";
1248
+ import { jsx as jsx86 } from "react/jsx-runtime";
1249
+ import { jsx as jsx87 } from "react/jsx-runtime";
1250
+ import { jsx as jsx88 } from "react/jsx-runtime";
1251
+ import { jsx as jsx89 } from "react/jsx-runtime";
1252
+ import { jsx as jsx90 } from "react/jsx-runtime";
1253
+ import { jsx as jsx91 } from "react/jsx-runtime";
1254
+ import { jsx as jsx922 } from "react/jsx-runtime";
1255
+ import { jsx as jsx93 } from "react/jsx-runtime";
1256
+ import { jsx as jsx94 } from "react/jsx-runtime";
1257
+ import { jsx as jsx95 } from "react/jsx-runtime";
1258
+ import { jsx as jsx96 } from "react/jsx-runtime";
1259
+ import { jsx as jsx97 } from "react/jsx-runtime";
1260
+ import { jsx as jsx98 } from "react/jsx-runtime";
1261
+ import { jsx as jsx99 } from "react/jsx-runtime";
1262
+ import { jsx as jsx100 } from "react/jsx-runtime";
1263
+ import { jsx as jsx101 } from "react/jsx-runtime";
1264
+ import { jsx as jsx1022 } from "react/jsx-runtime";
1265
+ import { jsx as jsx103 } from "react/jsx-runtime";
1266
+ import { jsx as jsx104 } from "react/jsx-runtime";
1267
+ import { jsx as jsx105 } from "react/jsx-runtime";
1268
+ import { jsx as jsx106 } from "react/jsx-runtime";
1269
+ import { jsx as jsx107 } from "react/jsx-runtime";
1270
+ import { jsx as jsx108 } from "react/jsx-runtime";
1271
+ import { jsx as jsx109 } from "react/jsx-runtime";
1272
+ import { jsx as jsx110 } from "react/jsx-runtime";
1273
+ import { jsx as jsx111 } from "react/jsx-runtime";
1274
+ import { jsx as jsx1122 } from "react/jsx-runtime";
1275
+ import { jsx as jsx113 } from "react/jsx-runtime";
1276
+ import { jsx as jsx114 } from "react/jsx-runtime";
1277
+ var StyledIcon = styled.div`
1278
+ display: inline-flex;
1279
+ align-items: center;
1280
+ justify-content: center;
1281
+ width: ${(props) => props.$size}px;
1282
+ height: ${(props) => props.$size}px;
1283
+ color: ${(props) => props.$color};
1284
+
1285
+ svg {
1286
+ width: 100%;
1287
+ height: 100%;
1288
+ }
1289
+ `;
1290
+ var BaseIcon = ({
1291
+ variant = "line",
1292
+ size = 24,
1293
+ color = "currentColor",
1294
+ solidContent: solidContent114,
1295
+ lineContent: lineContent114,
1296
+ className,
1297
+ style,
1298
+ "data-testid": testId,
1299
+ "aria-label": ariaLabel,
1300
+ "aria-hidden": ariaHidden
1301
+ }) => {
1302
+ const svgContent = variant === "line" ? lineContent114 : solidContent114;
1303
+ return /* @__PURE__ */ jsx14(
1304
+ StyledIcon,
1305
+ {
1306
+ $size: size,
1307
+ $color: color,
1308
+ className,
1309
+ style,
1310
+ "data-testid": testId,
1311
+ role: ariaLabel ? "img" : void 0,
1312
+ "aria-label": ariaLabel,
1313
+ "aria-hidden": ariaHidden != null ? ariaHidden : ariaLabel ? void 0 : true,
1314
+ dangerouslySetInnerHTML: { __html: svgContent }
1315
+ }
1316
+ );
1317
+ };
1318
+ var solidContent100 = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%"><g id="icon_search--line"><path id="Union" fill-rule="evenodd" clip-rule="evenodd" d="M10 2C14.4183 2 18 5.58172 18 10C18 11.8484 17.3711 13.5488 16.3184 14.9033L21.957 20.543L20.543 21.957L14.9033 16.3174C13.5487 17.3703 11.8486 18 10 18C5.58172 18 2 14.4183 2 10C2 5.58172 5.58172 2 10 2ZM10 4C6.68629 4 4 6.68629 4 10C4 13.3137 6.68629 16 10 16C13.3137 16 16 13.3137 16 10C16 6.68629 13.3137 4 10 4Z" style="fill: currentColor"/></g></svg>`;
1319
+ var lineContent100 = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%"><g id="icon_search--line"><path id="Union" fill-rule="evenodd" clip-rule="evenodd" d="M10 2C14.4183 2 18 5.58172 18 10C18 11.8484 17.3711 13.5488 16.3184 14.9033L21.957 20.543L20.543 21.957L14.9033 16.3174C13.5487 17.3703 11.8486 18 10 18C5.58172 18 2 14.4183 2 10C2 5.58172 5.58172 2 10 2ZM10 4C6.68629 4 4 6.68629 4 10C4 13.3137 6.68629 16 10 16C13.3137 16 16 13.3137 16 10C16 6.68629 13.3137 4 10 4Z" style="fill: currentColor"/></g></svg>`;
1320
+ var Search = (props) => /* @__PURE__ */ jsx101(BaseIcon, { ...props, solidContent: solidContent100, lineContent: lineContent100 });
1321
+
1322
+ // src/ContextMenuSearch.tsx
1323
+ import { Divider } from "@xsolla/xui-divider";
1324
+ import { jsx as jsx115, jsxs as jsxs5 } from "react/jsx-runtime";
1325
+ var ContextMenuSearch = forwardRef8(
1326
+ ({
1327
+ value,
1328
+ onChange,
1329
+ onValueChange,
1330
+ placeholder = "Search",
1331
+ autoFocus = true,
1332
+ size: propSize,
1333
+ "data-testid": testId = "context-menu-search"
1334
+ }, ref) => {
1335
+ const { theme } = useDesignSystem6();
1336
+ const context = useContextMenu();
1337
+ const size = propSize || context?.size || "m";
1338
+ const sizeStyles = theme.sizing.contextMenu(size);
1339
+ const inputRef = useRef4(null);
1340
+ const inputColors = theme.colors.control.input;
1341
+ React9.useImperativeHandle(
1342
+ ref,
1343
+ () => inputRef.current,
1344
+ []
1345
+ );
1346
+ useEffect4(() => {
1347
+ if (autoFocus && inputRef.current) {
1348
+ setTimeout(() => {
1349
+ inputRef.current?.focus();
1350
+ }, 0);
1351
+ }
1352
+ }, [autoFocus]);
1353
+ const handleChange = (e) => {
1354
+ onChange?.(e);
1355
+ onValueChange?.(e.target.value);
1356
+ };
1357
+ const handleKeyDown = (e) => {
1358
+ e.stopPropagation();
1359
+ if (e.key === "Escape") {
1360
+ context?.closeMenu();
1361
+ }
1362
+ };
1363
+ return /* @__PURE__ */ jsxs5(
1364
+ Box,
1365
+ {
1366
+ flexDirection: "column",
1367
+ gap: sizeStyles.searchGap,
1368
+ paddingHorizontal: sizeStyles.searchPaddingHorizontal,
1369
+ paddingVertical: sizeStyles.searchPaddingVertical,
1370
+ "data-testid": testId,
1371
+ children: [
1372
+ /* @__PURE__ */ jsxs5(Box, { flexDirection: "row", alignItems: "center", gap: 8, children: [
1373
+ /* @__PURE__ */ jsx115(
1374
+ Icon,
1375
+ {
1376
+ size: sizeStyles.searchIconSize,
1377
+ color: inputColors.placeholder,
1378
+ children: /* @__PURE__ */ jsx115(Search, {})
1379
+ }
1380
+ ),
1381
+ /* @__PURE__ */ jsx115(Box, { flex: 1, children: /* @__PURE__ */ jsx115(
1382
+ InputPrimitive,
1383
+ {
1384
+ ref: inputRef,
1385
+ value,
1386
+ onChange: handleChange,
1387
+ onKeyDown: handleKeyDown,
1388
+ placeholder,
1389
+ fontSize: sizeStyles.searchFontSize,
1390
+ color: inputColors.text,
1391
+ placeholderTextColor: inputColors.placeholder,
1392
+ "aria-label": placeholder,
1393
+ "data-testid": `${testId}__input`,
1394
+ style: {
1395
+ lineHeight: `${sizeStyles.searchLineHeight}px`
1396
+ }
1397
+ }
1398
+ ) })
1399
+ ] }),
1400
+ /* @__PURE__ */ jsx115(Divider, {})
1401
+ ]
1402
+ }
1403
+ );
1404
+ }
1405
+ );
1406
+ ContextMenuSearch.displayName = "ContextMenuSearch";
1407
+
1408
+ // src/ContextMenu.tsx
1409
+ import { jsx as jsx116, jsxs as jsxs6 } from "react/jsx-runtime";
1410
+ var ContextMenuRoot = forwardRef9(
1411
+ ({
1412
+ children,
1413
+ list,
1414
+ groups,
1415
+ size = "m",
1416
+ isOpen: propIsOpen,
1417
+ onOpenChange,
1418
+ position,
1419
+ trigger,
1420
+ width,
1421
+ maxHeight = 300,
1422
+ onSelect,
1423
+ onCheckedChange,
1424
+ closeOnSelect = true,
1425
+ isLoading,
1426
+ "aria-label": ariaLabel,
1427
+ "data-testid": testId = "context-menu"
1428
+ }, ref) => {
1429
+ const { theme } = useDesignSystem7();
1430
+ const [internalIsOpen, setInternalIsOpen] = useState(false);
1431
+ const [activeIndex, setActiveIndex] = useState(-1);
1432
+ const containerRef = useRef5(null);
1433
+ const menuRef = useRef5(null);
1434
+ const itemsRef = useRef5([]);
1435
+ const isOpen = propIsOpen !== void 0 ? propIsOpen : internalIsOpen;
1436
+ const sizeStyles = theme.sizing.contextMenu(size);
1437
+ const closeMenu = useCallback(() => {
1438
+ if (propIsOpen === void 0) {
1439
+ setInternalIsOpen(false);
1440
+ }
1441
+ onOpenChange?.(false);
1442
+ setActiveIndex(-1);
1443
+ }, [propIsOpen, onOpenChange]);
1444
+ const toggleMenu = useCallback(() => {
1445
+ const nextOpen = !isOpen;
1446
+ if (propIsOpen === void 0) {
1447
+ setInternalIsOpen(nextOpen);
1448
+ }
1449
+ onOpenChange?.(nextOpen);
1450
+ if (!nextOpen) {
1451
+ setActiveIndex(-1);
1452
+ }
1453
+ }, [isOpen, propIsOpen, onOpenChange]);
1454
+ useEffect5(() => {
1455
+ const handleClickOutside = (event) => {
1456
+ if (containerRef.current && !containerRef.current.contains(event.target)) {
1457
+ closeMenu();
1458
+ }
1459
+ };
1460
+ if (isOpen) {
1461
+ document.addEventListener("mousedown", handleClickOutside);
1462
+ }
1463
+ return () => {
1464
+ document.removeEventListener("mousedown", handleClickOutside);
1465
+ };
1466
+ }, [isOpen, closeMenu]);
1467
+ useEffect5(() => {
1468
+ const handleEscape = (event) => {
1469
+ if (event.key === "Escape") {
1470
+ closeMenu();
1471
+ }
1472
+ };
1473
+ if (isOpen) {
1474
+ document.addEventListener("keydown", handleEscape);
1475
+ }
1476
+ return () => {
1477
+ document.removeEventListener("keydown", handleEscape);
1478
+ };
1479
+ }, [isOpen, closeMenu]);
1480
+ useEffect5(() => {
1481
+ if (isOpen && menuRef.current) {
1482
+ menuRef.current.focus();
1483
+ }
1484
+ }, [isOpen]);
1485
+ useEffect5(() => {
1486
+ if (!isOpen) {
1487
+ itemsRef.current = [];
1488
+ }
1489
+ }, [isOpen]);
1490
+ const registerItem = useCallback((id) => {
1491
+ const index = itemsRef.current.indexOf(id);
1492
+ if (index === -1) {
1493
+ itemsRef.current.push(id);
1494
+ return itemsRef.current.length - 1;
1495
+ }
1496
+ return index;
1497
+ }, []);
1498
+ const unregisterItem = useCallback((id) => {
1499
+ const index = itemsRef.current.indexOf(id);
1500
+ if (index !== -1) {
1501
+ itemsRef.current.splice(index, 1);
1502
+ }
1503
+ }, []);
1504
+ const onItemSelect = useCallback(
1505
+ (item) => {
1506
+ onSelect?.(item);
1507
+ if (item.variant === "checkbox") {
1508
+ onCheckedChange?.(item.id, !item.checked);
1509
+ } else if (item.variant === "radio") {
1510
+ onCheckedChange?.(item.id, true);
1511
+ if (closeOnSelect) {
1512
+ closeMenu();
1513
+ }
1514
+ } else if (closeOnSelect) {
1515
+ closeMenu();
1516
+ }
1517
+ },
1518
+ [onSelect, onCheckedChange, closeOnSelect, closeMenu]
1519
+ );
1520
+ const handleKeyDown = useCallback(
1521
+ (event) => {
1522
+ const itemCount = itemsRef.current.length;
1523
+ if (itemCount === 0) return;
1524
+ switch (event.key) {
1525
+ case "ArrowDown":
1526
+ event.preventDefault();
1527
+ setActiveIndex((prev) => prev < itemCount - 1 ? prev + 1 : 0);
1528
+ break;
1529
+ case "ArrowUp":
1530
+ event.preventDefault();
1531
+ setActiveIndex((prev) => prev > 0 ? prev - 1 : itemCount - 1);
1532
+ break;
1533
+ case "Home":
1534
+ event.preventDefault();
1535
+ setActiveIndex(0);
1536
+ break;
1537
+ case "End":
1538
+ event.preventDefault();
1539
+ setActiveIndex(itemCount - 1);
1540
+ break;
1541
+ case "Enter":
1542
+ case " ":
1543
+ event.preventDefault();
1544
+ break;
1545
+ case "Tab":
1546
+ closeMenu();
1547
+ break;
1548
+ }
1549
+ },
1550
+ [closeMenu]
1551
+ );
1552
+ const contextValue = useMemo(
1553
+ () => ({
1554
+ size,
1555
+ closeMenu,
1556
+ onItemSelect,
1557
+ activeIndex,
1558
+ setActiveIndex,
1559
+ registerItem,
1560
+ unregisterItem
1561
+ }),
1562
+ [size, closeMenu, onItemSelect, activeIndex, registerItem, unregisterItem]
1563
+ );
1564
+ const getPositionStyles = () => {
1565
+ if (position) {
1566
+ return {
1567
+ position: "fixed",
1568
+ left: position.x,
1569
+ top: position.y
1570
+ };
1571
+ }
1572
+ return {
1573
+ position: "absolute",
1574
+ top: "100%",
1575
+ left: 0,
1576
+ marginTop: 4
1577
+ };
1578
+ };
1579
+ const renderItems = (itemsData) => {
1580
+ return itemsData.map((item, index) => {
1581
+ const commonProps = {
1582
+ key: item.id || index,
1583
+ disabled: item.disabled,
1584
+ description: item.description,
1585
+ "data-testid": `context-menu-item-${item.id || index}`
1586
+ };
1587
+ if (item.variant === "checkbox") {
1588
+ return /* @__PURE__ */ jsx116(
1589
+ ContextMenuCheckboxItem,
1590
+ {
1591
+ ...commonProps,
1592
+ checked: item.checked,
1593
+ onCheckedChange: (checked) => onCheckedChange?.(item.id, checked),
1594
+ onPress: item.onPress,
1595
+ children: item.label
1596
+ }
1597
+ );
1598
+ }
1599
+ if (item.variant === "radio") {
1600
+ return /* @__PURE__ */ jsx116(
1601
+ ContextMenuRadioItem,
1602
+ {
1603
+ ...commonProps,
1604
+ value: item.id,
1605
+ onPress: item.onPress,
1606
+ children: item.label
1607
+ }
1608
+ );
1609
+ }
1610
+ return /* @__PURE__ */ jsx116(
1611
+ ContextMenuItem,
1612
+ {
1613
+ ...commonProps,
1614
+ icon: item.icon,
1615
+ selected: item.selected,
1616
+ shortcut: item.trailing?.type === "shortcut" ? item.trailing.content : void 0,
1617
+ trailing: item.trailing?.type !== "shortcut" && item.trailing?.type !== "none" ? item.trailing?.content : void 0,
1618
+ hasSubmenu: item.children && item.children.length > 0,
1619
+ onPress: item.onPress,
1620
+ active: activeIndex === index,
1621
+ children: item.label
1622
+ }
1623
+ );
1624
+ });
1625
+ };
1626
+ const renderGroups = (groupsData) => {
1627
+ return groupsData.map((group, groupIndex) => /* @__PURE__ */ jsxs6(React10.Fragment, { children: [
1628
+ groupIndex > 0 && /* @__PURE__ */ jsx116(ContextMenuSeparator, {}),
1629
+ /* @__PURE__ */ jsx116(
1630
+ ContextMenuGroup,
1631
+ {
1632
+ label: group.label,
1633
+ description: group.description,
1634
+ "data-testid": `context-menu-group-${group.id || groupIndex}`,
1635
+ children: renderItems(group.items)
1636
+ }
1637
+ )
1638
+ ] }, group.id || groupIndex));
1639
+ };
1640
+ const renderContent = () => {
1641
+ if (isLoading) {
1642
+ const brandColor = theme.colors.control.brand.primary.bg;
1643
+ return /* @__PURE__ */ jsx116(
1644
+ Box,
1645
+ {
1646
+ padding: 16,
1647
+ alignItems: "center",
1648
+ justifyContent: "center",
1649
+ minHeight: 60,
1650
+ children: /* @__PURE__ */ jsx116(Spinner2, { size: "m", color: brandColor })
1651
+ }
1652
+ );
1653
+ }
1654
+ if (children) {
1655
+ return children;
1656
+ }
1657
+ if (groups && groups.length > 0) {
1658
+ return renderGroups(groups);
1659
+ }
1660
+ if (list && list.length > 0) {
1661
+ return renderItems(list);
1662
+ }
1663
+ return null;
1664
+ };
1665
+ return /* @__PURE__ */ jsx116(ContextMenuContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs6(
1666
+ "div",
1667
+ {
1668
+ ref: containerRef,
1669
+ style: {
1670
+ position: position ? "static" : "relative",
1671
+ width: trigger ? "fit-content" : void 0,
1672
+ display: "inline-block"
1673
+ },
1674
+ "data-testid": testId,
1675
+ children: [
1676
+ trigger && /* @__PURE__ */ jsx116(
1677
+ "div",
1678
+ {
1679
+ onClick: toggleMenu,
1680
+ style: { cursor: "pointer" },
1681
+ "data-testid": `${testId}-trigger`,
1682
+ children: trigger
1683
+ }
1684
+ ),
1685
+ isOpen && /* @__PURE__ */ jsx116(
1686
+ Box,
1687
+ {
1688
+ ref: (node) => {
1689
+ menuRef.current = node;
1690
+ if (typeof ref === "function") {
1691
+ ref(node);
1692
+ } else if (ref) {
1693
+ ref.current = node;
1694
+ }
1695
+ },
1696
+ role: "menu",
1697
+ "aria-label": ariaLabel,
1698
+ backgroundColor: theme.colors.background.secondary,
1699
+ borderColor: theme.colors.border.secondary,
1700
+ borderWidth: 1,
1701
+ borderRadius: theme.radius.button,
1702
+ paddingVertical: sizeStyles.paddingVertical,
1703
+ width,
1704
+ minWidth: sizeStyles.minWidth,
1705
+ style: {
1706
+ ...getPositionStyles(),
1707
+ zIndex: 1e3,
1708
+ boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
1709
+ maxHeight,
1710
+ overflowY: "auto",
1711
+ outline: "none"
1712
+ },
1713
+ tabIndex: -1,
1714
+ onKeyDown: handleKeyDown,
1715
+ "data-testid": `${testId}-content`,
1716
+ children: renderContent()
1717
+ }
1718
+ )
1719
+ ]
1720
+ }
1721
+ ) });
1722
+ }
1723
+ );
1724
+ ContextMenuRoot.displayName = "ContextMenu";
1725
+ var ContextMenu = Object.assign(ContextMenuRoot, {
1726
+ Item: ContextMenuItem,
1727
+ CheckboxItem: ContextMenuCheckboxItem,
1728
+ RadioGroup: ContextMenuRadioGroup,
1729
+ RadioItem: ContextMenuRadioItem,
1730
+ Group: ContextMenuGroup,
1731
+ Separator: ContextMenuSeparator,
1732
+ Search: ContextMenuSearch
1733
+ });
1734
+
1735
+ // src/hooks/useContextMenuPosition.ts
1736
+ import { useState as useState2, useEffect as useEffect6 } from "react";
1737
+ var useContextMenuPosition = ({
1738
+ position,
1739
+ menuRef,
1740
+ isOpen,
1741
+ offset = 8
1742
+ }) => {
1743
+ const [adjustedPosition, setAdjustedPosition] = useState2();
1744
+ useEffect6(() => {
1745
+ if (!isOpen || !position || !menuRef.current) {
1746
+ setAdjustedPosition(void 0);
1747
+ return;
1748
+ }
1749
+ const rafId = requestAnimationFrame(() => {
1750
+ const menu = menuRef.current;
1751
+ if (!menu) return;
1752
+ const rect = menu.getBoundingClientRect();
1753
+ const viewportWidth = window.innerWidth;
1754
+ const viewportHeight = window.innerHeight;
1755
+ let { x, y } = position;
1756
+ let transformOriginX = "left";
1757
+ let transformOriginY = "top";
1758
+ if (x + rect.width > viewportWidth - offset) {
1759
+ x = Math.max(offset, viewportWidth - rect.width - offset);
1760
+ transformOriginX = "right";
1761
+ }
1762
+ if (x < offset) {
1763
+ x = offset;
1764
+ transformOriginX = "left";
1765
+ }
1766
+ if (y + rect.height > viewportHeight - offset) {
1767
+ const aboveY = position.y - rect.height;
1768
+ if (aboveY >= offset) {
1769
+ y = aboveY;
1770
+ transformOriginY = "bottom";
1771
+ } else {
1772
+ y = Math.max(offset, viewportHeight - rect.height - offset);
1773
+ transformOriginY = "bottom";
1774
+ }
1775
+ }
1776
+ if (y < offset) {
1777
+ y = offset;
1778
+ transformOriginY = "top";
1779
+ }
1780
+ setAdjustedPosition({
1781
+ x,
1782
+ y,
1783
+ transformOrigin: `${transformOriginY} ${transformOriginX}`
1784
+ });
1785
+ });
1786
+ return () => cancelAnimationFrame(rafId);
1787
+ }, [isOpen, position, menuRef, offset]);
1788
+ return adjustedPosition;
1789
+ };
1790
+
1791
+ // src/hooks/useKeyboardNavigation.ts
1792
+ import { useCallback as useCallback2, useRef as useRef6, useEffect as useEffect7 } from "react";
1793
+ var useKeyboardNavigation = ({
1794
+ isOpen,
1795
+ itemCount,
1796
+ activeIndex,
1797
+ setActiveIndex,
1798
+ onSelect,
1799
+ onClose,
1800
+ loop = true
1801
+ }) => {
1802
+ const typeaheadBuffer = useRef6("");
1803
+ const typeaheadTimeout = useRef6(null);
1804
+ useEffect7(() => {
1805
+ if (!isOpen) {
1806
+ typeaheadBuffer.current = "";
1807
+ if (typeaheadTimeout.current) {
1808
+ clearTimeout(typeaheadTimeout.current);
1809
+ }
1810
+ }
1811
+ }, [isOpen]);
1812
+ const handleKeyDown = useCallback2(
1813
+ (event) => {
1814
+ if (!isOpen || itemCount === 0) return;
1815
+ switch (event.key) {
1816
+ case "ArrowDown":
1817
+ event.preventDefault();
1818
+ if (loop) {
1819
+ setActiveIndex(activeIndex < itemCount - 1 ? activeIndex + 1 : 0);
1820
+ } else {
1821
+ setActiveIndex(Math.min(activeIndex + 1, itemCount - 1));
1822
+ }
1823
+ break;
1824
+ case "ArrowUp":
1825
+ event.preventDefault();
1826
+ if (loop) {
1827
+ setActiveIndex(activeIndex > 0 ? activeIndex - 1 : itemCount - 1);
1828
+ } else {
1829
+ setActiveIndex(Math.max(activeIndex - 1, 0));
1830
+ }
1831
+ break;
1832
+ case "Home":
1833
+ event.preventDefault();
1834
+ setActiveIndex(0);
1835
+ break;
1836
+ case "End":
1837
+ event.preventDefault();
1838
+ setActiveIndex(itemCount - 1);
1839
+ break;
1840
+ case "Enter":
1841
+ case " ":
1842
+ event.preventDefault();
1843
+ if (activeIndex >= 0 && activeIndex < itemCount) {
1844
+ onSelect(activeIndex);
1845
+ }
1846
+ break;
1847
+ case "Escape":
1848
+ event.preventDefault();
1849
+ onClose();
1850
+ break;
1851
+ case "Tab":
1852
+ onClose();
1853
+ break;
1854
+ default:
1855
+ if (event.key.length === 1 && !event.ctrlKey && !event.metaKey) {
1856
+ typeaheadBuffer.current += event.key.toLowerCase();
1857
+ if (typeaheadTimeout.current) {
1858
+ clearTimeout(typeaheadTimeout.current);
1859
+ }
1860
+ typeaheadTimeout.current = setTimeout(() => {
1861
+ typeaheadBuffer.current = "";
1862
+ }, 500);
1863
+ }
1864
+ break;
1865
+ }
1866
+ },
1867
+ [isOpen, itemCount, activeIndex, setActiveIndex, onSelect, onClose, loop]
1868
+ );
1869
+ return { handleKeyDown };
1870
+ };
1871
+ export {
1872
+ ContextMenu,
1873
+ ContextMenuCheckboxItem,
1874
+ ContextMenuGroup,
1875
+ ContextMenuItem,
1876
+ ContextMenuRadioGroup,
1877
+ ContextMenuRadioItem,
1878
+ ContextMenuSearch,
1879
+ ContextMenuSeparator,
1880
+ useContextMenu,
1881
+ useContextMenuPosition,
1882
+ useContextMenuRequired,
1883
+ useKeyboardNavigation,
1884
+ useRadioGroup
1885
+ };
1886
+ //# sourceMappingURL=index.mjs.map