@xsolla/xui-dropdown 0.93.0 → 0.94.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.
@@ -5,7 +5,14 @@ interface DropdownProps {
5
5
  children: React.ReactNode;
6
6
  isOpen?: boolean;
7
7
  onOpenChange?: (open: boolean) => void;
8
+ /** Width of the dropdown menu (does not affect trigger width) */
8
9
  width?: string | number;
10
+ /** Horizontal alignment of dropdown menu relative to trigger. Default: "start" */
11
+ align?: "start" | "end";
12
+ /** Accessible label for the dropdown menu */
13
+ "aria-label"?: string;
14
+ /** Test ID for testing frameworks */
15
+ testID?: string;
9
16
  }
10
17
  declare const Dropdown: React.FC<DropdownProps>;
11
18
  interface DropdownItemProps {
@@ -16,6 +23,8 @@ interface DropdownItemProps {
16
23
  selected?: boolean;
17
24
  disabled?: boolean;
18
25
  icon?: React.ReactNode;
26
+ /** Test ID for testing frameworks */
27
+ testID?: string;
19
28
  }
20
29
  declare const DropdownItem: React.FC<DropdownItemProps>;
21
30
 
package/native/index.d.ts CHANGED
@@ -5,7 +5,14 @@ interface DropdownProps {
5
5
  children: React.ReactNode;
6
6
  isOpen?: boolean;
7
7
  onOpenChange?: (open: boolean) => void;
8
+ /** Width of the dropdown menu (does not affect trigger width) */
8
9
  width?: string | number;
10
+ /** Horizontal alignment of dropdown menu relative to trigger. Default: "start" */
11
+ align?: "start" | "end";
12
+ /** Accessible label for the dropdown menu */
13
+ "aria-label"?: string;
14
+ /** Test ID for testing frameworks */
15
+ testID?: string;
9
16
  }
10
17
  declare const Dropdown: React.FC<DropdownProps>;
11
18
  interface DropdownItemProps {
@@ -16,6 +23,8 @@ interface DropdownItemProps {
16
23
  selected?: boolean;
17
24
  disabled?: boolean;
18
25
  icon?: React.ReactNode;
26
+ /** Test ID for testing frameworks */
27
+ testID?: string;
19
28
  }
20
29
  declare const DropdownItem: React.FC<DropdownItemProps>;
21
30
 
package/native/index.js CHANGED
@@ -237,63 +237,126 @@ var Dropdown = ({
237
237
  children,
238
238
  isOpen: propIsOpen,
239
239
  onOpenChange,
240
- width = "auto"
240
+ width = "auto",
241
+ align = "start",
242
+ "aria-label": ariaLabel,
243
+ testID
241
244
  }) => {
242
245
  const [internalIsOpen, setInternalIsOpen] = (0, import_react.useState)(false);
243
246
  const isOpen = propIsOpen !== void 0 ? propIsOpen : internalIsOpen;
244
247
  const containerRef = (0, import_react.useRef)(null);
248
+ const triggerRef = (0, import_react.useRef)(null);
249
+ const menuRef = (0, import_react.useRef)(null);
245
250
  const { theme } = (0, import_xui_core.useDesignSystem)();
246
- const toggleOpen = () => {
251
+ const closeMenu = (0, import_react.useCallback)(() => {
252
+ if (propIsOpen === void 0) {
253
+ setInternalIsOpen(false);
254
+ }
255
+ if (onOpenChange) onOpenChange(false);
256
+ }, [propIsOpen, onOpenChange]);
257
+ const toggleOpen = (0, import_react.useCallback)(() => {
247
258
  const nextOpen = !isOpen;
248
259
  if (propIsOpen === void 0) {
249
260
  setInternalIsOpen(nextOpen);
250
261
  }
251
262
  if (onOpenChange) onOpenChange(nextOpen);
252
- };
263
+ }, [isOpen, propIsOpen, onOpenChange]);
264
+ const focusTrigger = (0, import_react.useCallback)(() => {
265
+ if (typeof document !== "undefined" && triggerRef.current) {
266
+ const focusable = triggerRef.current.querySelector(
267
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
268
+ );
269
+ if (focusable) {
270
+ focusable.focus();
271
+ }
272
+ }
273
+ }, []);
274
+ const handleKeyDown = (0, import_react.useCallback)(
275
+ (event) => {
276
+ if (event.key === "Escape" && isOpen) {
277
+ event.preventDefault();
278
+ closeMenu();
279
+ focusTrigger();
280
+ } else if (event.key === "Tab" && isOpen) {
281
+ closeMenu();
282
+ focusTrigger();
283
+ }
284
+ },
285
+ [isOpen, closeMenu, focusTrigger]
286
+ );
287
+ const handleTriggerKeyDown = (0, import_react.useCallback)(
288
+ (event) => {
289
+ if (event.key === "Enter" || event.key === " ") {
290
+ event.preventDefault();
291
+ toggleOpen();
292
+ } else if (event.key === "ArrowDown" && !isOpen) {
293
+ event.preventDefault();
294
+ if (propIsOpen === void 0) {
295
+ setInternalIsOpen(true);
296
+ }
297
+ if (onOpenChange) onOpenChange(true);
298
+ }
299
+ },
300
+ [isOpen, toggleOpen, propIsOpen, onOpenChange]
301
+ );
253
302
  (0, import_react.useEffect)(() => {
254
303
  const handleClickOutside = (event) => {
255
304
  if (containerRef.current && !containerRef.current.contains(event.target)) {
256
- if (propIsOpen === void 0) {
257
- setInternalIsOpen(false);
258
- }
259
- if (onOpenChange) onOpenChange(false);
305
+ closeMenu();
260
306
  }
261
307
  };
262
- if (isOpen) {
308
+ if (isOpen && typeof document !== "undefined") {
263
309
  document.addEventListener("mousedown", handleClickOutside);
264
310
  }
265
311
  return () => {
266
- document.removeEventListener("mousedown", handleClickOutside);
312
+ if (typeof document !== "undefined") {
313
+ document.removeEventListener("mousedown", handleClickOutside);
314
+ }
267
315
  };
268
- }, [isOpen, propIsOpen, onOpenChange]);
316
+ }, [isOpen, closeMenu]);
269
317
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
270
318
  "div",
271
319
  {
272
320
  ref: containerRef,
273
321
  style: {
274
322
  position: "relative",
275
- width: width === "auto" ? "fit-content" : width,
323
+ width: "fit-content",
276
324
  alignSelf: "flex-start",
277
325
  height: "fit-content"
278
326
  },
327
+ onKeyDown: handleKeyDown,
328
+ "data-testid": testID,
279
329
  children: [
280
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { onClick: toggleOpen, style: { cursor: "pointer" }, children: trigger }),
330
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
331
+ "div",
332
+ {
333
+ ref: triggerRef,
334
+ onClick: toggleOpen,
335
+ onKeyDown: handleTriggerKeyDown,
336
+ "aria-haspopup": "menu",
337
+ "aria-expanded": isOpen,
338
+ children: trigger
339
+ }
340
+ ),
281
341
  isOpen && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
282
342
  Box,
283
343
  {
344
+ ref: menuRef,
284
345
  position: "absolute",
285
346
  top: "100%",
286
- left: 0,
347
+ ...align === "end" ? { right: 0 } : { left: 0 },
287
348
  marginTop: 4,
288
349
  backgroundColor: theme.colors.background.secondary,
289
350
  borderColor: theme.colors.border.secondary,
290
351
  borderWidth: 1,
291
352
  borderRadius: theme.radius.button,
292
353
  paddingVertical: 4,
354
+ role: "menu",
355
+ ...ariaLabel ? { "aria-label": ariaLabel } : {},
293
356
  style: {
294
357
  zIndex: 1e3,
295
358
  boxShadow: "0 4px 12px rgba(0,0,0,0.1)",
296
- minWidth: "100%"
359
+ ...width === "auto" ? { minWidth: "100%" } : { width }
297
360
  },
298
361
  children
299
362
  }
@@ -308,7 +371,8 @@ var DropdownItem = ({
308
371
  active,
309
372
  selected,
310
373
  disabled,
311
- icon
374
+ icon,
375
+ testID
312
376
  }) => {
313
377
  const { theme } = (0, import_xui_core.useDesignSystem)();
314
378
  const brandColors = theme.colors.control.brand.primary;
@@ -328,6 +392,12 @@ var DropdownItem = ({
328
392
  }
329
393
  return theme.colors.content.secondary;
330
394
  };
395
+ const handleKeyDown = (event) => {
396
+ if ((event.key === "Enter" || event.key === " ") && !disabled && onPress) {
397
+ event.preventDefault();
398
+ onPress();
399
+ }
400
+ };
331
401
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
332
402
  Box,
333
403
  {
@@ -340,6 +410,11 @@ var DropdownItem = ({
340
410
  hoverStyle: !disabled && !selected ? {
341
411
  backgroundColor: theme.colors.control.input.bgHover
342
412
  } : void 0,
413
+ role: "menuitem",
414
+ tabIndex: disabled ? -1 : 0,
415
+ "aria-disabled": disabled,
416
+ onKeyDown: handleKeyDown,
417
+ testID,
343
418
  style: {
344
419
  opacity: disabled ? 0.5 : 1,
345
420
  cursor: disabled ? "not-allowed" : "pointer"
@@ -351,6 +426,8 @@ var DropdownItem = ({
351
426
  }
352
427
  );
353
428
  };
429
+ Dropdown.displayName = "Dropdown";
430
+ DropdownItem.displayName = "DropdownItem";
354
431
  // Annotate the CommonJS export names for ESM import in node:
355
432
  0 && (module.exports = {
356
433
  Dropdown,
@@ -11,7 +11,26 @@ declare interface DropdownProps {
11
11
  children: React.ReactNode;
12
12
  isOpen?: boolean;
13
13
  onOpenChange?: (open: boolean) => void;
14
+
15
+ /**
16
+ * Width of the dropdown menu (does not affect trigger width)
17
+ */
14
18
  width?: string | number;
19
+
20
+ /**
21
+ * Horizontal alignment of dropdown menu relative to trigger. Default: "start"
22
+ */
23
+ align?: "start" | "end";
24
+
25
+ /**
26
+ * Accessible label for the dropdown menu
27
+ */
28
+ "aria-label"?: string;
29
+
30
+ /**
31
+ * Test ID for testing frameworks
32
+ */
33
+ testID?: string;
15
34
  }
16
35
  declare var Dropdown: React.FC<DropdownProps>;
17
36
  declare interface DropdownItemProps {
@@ -25,6 +44,11 @@ declare interface DropdownItemProps {
25
44
  selected?: boolean;
26
45
  disabled?: boolean;
27
46
  icon?: React.ReactNode;
47
+
48
+ /**
49
+ * Test ID for testing frameworks
50
+ */
51
+ testID?: string;
28
52
  }
29
53
  declare var DropdownItem: React.FC<DropdownItemProps>;
30
54
  export type { DropdownItemProps, DropdownProps };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/index.tsx","../../src/Dropdown.tsx","../../../primitives-native/src/Box.tsx","../../../primitives-native/src/Text.tsx"],"sourcesContent":["export * from \"./Dropdown\";\n","import React, { useState, useRef, useEffect } from \"react\";\n// @ts-expect-error - this will be resolved at build time\nimport { Box, Text } from \"@xsolla/xui-primitives\";\nimport { useDesignSystem } from \"@xsolla/xui-core\";\n\nexport interface DropdownProps {\n trigger: React.ReactNode;\n children: React.ReactNode;\n isOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n width?: string | number;\n}\n\nexport const Dropdown: React.FC<DropdownProps> = ({\n trigger,\n children,\n isOpen: propIsOpen,\n onOpenChange,\n width = \"auto\",\n}) => {\n const [internalIsOpen, setInternalIsOpen] = useState(false);\n const isOpen = propIsOpen !== undefined ? propIsOpen : internalIsOpen;\n const containerRef = useRef<any>(null);\n const { theme } = useDesignSystem();\n\n const toggleOpen = () => {\n const nextOpen = !isOpen;\n if (propIsOpen === undefined) {\n setInternalIsOpen(nextOpen);\n }\n if (onOpenChange) onOpenChange(nextOpen);\n };\n\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent) => {\n if (\n containerRef.current &&\n !containerRef.current.contains(event.target)\n ) {\n if (propIsOpen === undefined) {\n setInternalIsOpen(false);\n }\n if (onOpenChange) onOpenChange(false);\n }\n };\n\n if (isOpen) {\n document.addEventListener(\"mousedown\", handleClickOutside);\n }\n return () => {\n document.removeEventListener(\"mousedown\", handleClickOutside);\n };\n }, [isOpen, propIsOpen, onOpenChange]);\n\n return (\n <div\n ref={containerRef}\n style={{\n position: \"relative\",\n width: width === \"auto\" ? \"fit-content\" : width,\n alignSelf: \"flex-start\",\n height: \"fit-content\",\n }}\n >\n <div onClick={toggleOpen} style={{ cursor: \"pointer\" }}>\n {trigger}\n </div>\n {isOpen && (\n <Box\n position=\"absolute\"\n top=\"100%\"\n left={0}\n marginTop={4}\n backgroundColor={theme.colors.background.secondary}\n borderColor={theme.colors.border.secondary}\n borderWidth={1}\n borderRadius={theme.radius.button}\n paddingVertical={4}\n style={{\n zIndex: 1000,\n boxShadow: \"0 4px 12px rgba(0,0,0,0.1)\",\n minWidth: \"100%\",\n }}\n >\n {children}\n </Box>\n )}\n </div>\n );\n};\n\nexport interface DropdownItemProps {\n children: React.ReactNode;\n onPress?: () => void;\n active?: boolean;\n /** Whether this item is selected (shows trailing checkmark with control/check/bg color) */\n selected?: boolean;\n disabled?: boolean;\n icon?: React.ReactNode;\n}\n\nexport const DropdownItem: React.FC<DropdownItemProps> = ({\n children,\n onPress,\n active,\n selected,\n disabled,\n icon,\n}) => {\n const { theme } = useDesignSystem();\n const brandColors = theme.colors.control.brand.primary;\n const contentColors = theme.colors.content;\n\n // Determine background color\n const getBackgroundColor = () => {\n if (selected) {\n return brandColors?.bg || theme.colors.control.input.bg; // Cyan background for selected items\n }\n if (active) {\n return theme.colors.control.input.bgHover;\n }\n return \"transparent\";\n };\n\n // Determine text/icon color\n const getContentColor = () => {\n if (selected) {\n return contentColors?.on?.brand || theme.colors.content.primary; // Black text on cyan background\n }\n return theme.colors.content.secondary;\n };\n\n return (\n <Box\n onPress={!disabled ? onPress : undefined}\n paddingHorizontal={16}\n paddingVertical={8}\n flexDirection=\"row\"\n alignItems=\"center\"\n backgroundColor={getBackgroundColor()}\n hoverStyle={\n !disabled && !selected\n ? {\n backgroundColor: theme.colors.control.input.bgHover,\n }\n : undefined\n }\n style={{\n opacity: disabled ? 0.5 : 1,\n cursor: disabled ? \"not-allowed\" : \"pointer\",\n }}\n >\n {icon && (\n <Box marginRight={12} alignItems=\"center\" justifyContent=\"center\">\n {icon}\n </Box>\n )}\n <Box flex={1}>\n <Text color={getContentColor()} fontSize={14} fontWeight=\"400\">\n {children}\n </Text>\n </Box>\n </Box>\n );\n};\n","import React from \"react\";\nimport {\n View,\n Pressable,\n Image,\n ViewStyle,\n ImageStyle,\n DimensionValue,\n AnimatableNumericValue,\n} from \"react-native\";\nimport { BoxProps } from \"@xsolla/xui-primitives-core\";\n\nexport const Box: React.FC<BoxProps> = ({\n children,\n onPress,\n onLayout,\n onMoveShouldSetResponder,\n onResponderGrant,\n onResponderMove,\n onResponderRelease,\n onResponderTerminate,\n backgroundColor,\n borderColor,\n borderWidth,\n borderBottomWidth,\n borderBottomColor,\n borderTopWidth,\n borderTopColor,\n borderLeftWidth,\n borderLeftColor,\n borderRightWidth,\n borderRightColor,\n borderRadius,\n borderStyle,\n height,\n padding,\n paddingHorizontal,\n paddingVertical,\n margin,\n marginTop,\n marginBottom,\n marginLeft,\n marginRight,\n flexDirection,\n alignItems,\n justifyContent,\n position,\n top,\n bottom,\n left,\n right,\n width,\n flex,\n overflow,\n zIndex,\n hoverStyle,\n pressStyle,\n style,\n \"data-testid\": dataTestId,\n testID,\n as,\n src,\n alt,\n ...rest\n}) => {\n const getContainerStyle = (pressed?: boolean): ViewStyle => ({\n backgroundColor:\n pressed && pressStyle?.backgroundColor\n ? pressStyle.backgroundColor\n : backgroundColor,\n borderColor,\n borderWidth,\n borderBottomWidth,\n borderBottomColor,\n borderTopWidth,\n borderTopColor,\n borderLeftWidth,\n borderLeftColor,\n borderRightWidth,\n borderRightColor,\n borderRadius: borderRadius as AnimatableNumericValue,\n borderStyle: borderStyle as ViewStyle[\"borderStyle\"],\n overflow,\n zIndex,\n height: height as DimensionValue,\n width: width as DimensionValue,\n padding: padding as DimensionValue,\n paddingHorizontal: paddingHorizontal as DimensionValue,\n paddingVertical: paddingVertical as DimensionValue,\n margin: margin as DimensionValue,\n marginTop: marginTop as DimensionValue,\n marginBottom: marginBottom as DimensionValue,\n marginLeft: marginLeft as DimensionValue,\n marginRight: marginRight as DimensionValue,\n flexDirection,\n alignItems,\n justifyContent,\n position: position as ViewStyle[\"position\"],\n top: top as DimensionValue,\n bottom: bottom as DimensionValue,\n left: left as DimensionValue,\n right: right as DimensionValue,\n flex,\n ...(style as ViewStyle),\n });\n\n const finalTestID = dataTestId || testID;\n\n // Destructure and drop web-only props from rest before passing to RN components\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const {\n role,\n tabIndex,\n onKeyDown,\n onKeyUp,\n \"aria-label\": _ariaLabel,\n \"aria-labelledby\": _ariaLabelledBy,\n \"aria-current\": _ariaCurrent,\n \"aria-disabled\": _ariaDisabled,\n \"aria-live\": _ariaLive,\n className,\n \"data-testid\": _dataTestId,\n ...nativeRest\n } = rest as Record<string, unknown>;\n\n // Handle as=\"img\" for React Native\n if (as === \"img\" && src) {\n const imageStyle: ImageStyle = {\n width: width as DimensionValue,\n height: height as DimensionValue,\n borderRadius: borderRadius as number,\n position: position as ImageStyle[\"position\"],\n top: top as DimensionValue,\n bottom: bottom as DimensionValue,\n left: left as DimensionValue,\n right: right as DimensionValue,\n ...(style as ImageStyle),\n };\n\n return (\n <Image\n source={{ uri: src }}\n style={imageStyle}\n testID={finalTestID}\n resizeMode=\"cover\"\n {...nativeRest}\n />\n );\n }\n\n if (onPress) {\n return (\n <Pressable\n onPress={onPress}\n onLayout={onLayout}\n onMoveShouldSetResponder={onMoveShouldSetResponder}\n onResponderGrant={onResponderGrant}\n onResponderMove={onResponderMove}\n onResponderRelease={onResponderRelease}\n onResponderTerminate={onResponderTerminate}\n style={({ pressed }) => getContainerStyle(pressed)}\n testID={finalTestID}\n {...nativeRest}\n >\n {children}\n </Pressable>\n );\n }\n\n return (\n <View\n style={getContainerStyle()}\n testID={finalTestID}\n onLayout={onLayout}\n onMoveShouldSetResponder={onMoveShouldSetResponder}\n onResponderGrant={onResponderGrant}\n onResponderMove={onResponderMove}\n onResponderRelease={onResponderRelease}\n onResponderTerminate={onResponderTerminate}\n {...nativeRest}\n >\n {children}\n </View>\n );\n};\n","import React from \"react\";\nimport { Text as RNText, TextStyle, AccessibilityRole } from \"react-native\";\nimport { TextProps } from \"@xsolla/xui-primitives-core\";\n\n// Map web roles to React Native accessibility roles\nconst roleMap: Record<string, AccessibilityRole> = {\n alert: \"alert\",\n heading: \"header\",\n button: \"button\",\n link: \"link\",\n text: \"text\",\n};\n\nexport const Text: React.FC<TextProps> = ({\n children,\n color,\n fontSize,\n fontWeight,\n fontFamily,\n id,\n role,\n ...props\n}) => {\n // Extract the first font name from a comma-separated list (e.g. for web-style font stacks)\n let resolvedFontFamily = fontFamily\n ? fontFamily.split(\",\")[0].replace(/['\"]/g, \"\").trim()\n : undefined;\n\n // On native, if we don't have the custom font loaded, it's better to use the system font\n // to avoid rendering issues or missing text.\n if (resolvedFontFamily === \"Pilat Wide Bold\") {\n resolvedFontFamily = undefined;\n }\n\n const style: TextStyle = {\n color,\n fontSize: typeof fontSize === \"number\" ? fontSize : undefined,\n fontWeight: fontWeight as TextStyle[\"fontWeight\"],\n fontFamily: resolvedFontFamily,\n textDecorationLine: props.textDecoration as TextStyle[\"textDecorationLine\"],\n };\n\n // Map role to React Native accessibilityRole\n const accessibilityRole = role ? roleMap[role] : undefined;\n\n return (\n <RNText style={style} testID={id} accessibilityRole={accessibilityRole}>\n {children}\n </RNText>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAAmD;;;ACCnD,0BAQO;AAmID;AAhIC,IAAM,MAA0B,CAAC;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,eAAe;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,MAAM;AACJ,QAAM,oBAAoB,CAAC,aAAkC;AAAA,IAC3D,iBACE,WAAW,YAAY,kBACnB,WAAW,kBACX;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAI;AAAA,EACN;AAEA,QAAM,cAAc,cAAc;AAIlC,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd,mBAAmB;AAAA,IACnB,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,aAAa;AAAA,IACb;AAAA,IACA,eAAe;AAAA,IACf,GAAG;AAAA,EACL,IAAI;AAGJ,MAAI,OAAO,SAAS,KAAK;AACvB,UAAM,aAAyB;AAAA,MAC7B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAI;AAAA,IACN;AAEA,WACE;AAAA,MAAC;AAAA;AAAA,QACC,QAAQ,EAAE,KAAK,IAAI;AAAA,QACnB,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,YAAW;AAAA,QACV,GAAG;AAAA;AAAA,IACN;AAAA,EAEJ;AAEA,MAAI,SAAS;AACX,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,OAAO,CAAC,EAAE,QAAQ,MAAM,kBAAkB,OAAO;AAAA,QACjD,QAAQ;AAAA,QACP,GAAG;AAAA,QAEH;AAAA;AAAA,IACH;AAAA,EAEJ;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,kBAAkB;AAAA,MACzB,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ;;;ACvLA,IAAAA,uBAA6D;AA6CzD,IAAAC,sBAAA;AAzCJ,IAAM,UAA6C;AAAA,EACjD,OAAO;AAAA,EACP,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,MAAM;AACR;AAEO,IAAM,OAA4B,CAAC;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,MAAM;AAEJ,MAAI,qBAAqB,aACrB,WAAW,MAAM,GAAG,EAAE,CAAC,EAAE,QAAQ,SAAS,EAAE,EAAE,KAAK,IACnD;AAIJ,MAAI,uBAAuB,mBAAmB;AAC5C,yBAAqB;AAAA,EACvB;AAEA,QAAM,QAAmB;AAAA,IACvB;AAAA,IACA,UAAU,OAAO,aAAa,WAAW,WAAW;AAAA,IACpD;AAAA,IACA,YAAY;AAAA,IACZ,oBAAoB,MAAM;AAAA,EAC5B;AAGA,QAAM,oBAAoB,OAAO,QAAQ,IAAI,IAAI;AAEjD,SACE,6CAAC,qBAAAC,MAAA,EAAO,OAAc,QAAQ,IAAI,mBAC/B,UACH;AAEJ;;;AF/CA,sBAAgC;AAoD5B,IAAAC,sBAAA;AA1CG,IAAM,WAAoC,CAAC;AAAA,EAChD;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA,QAAQ;AACV,MAAM;AACJ,QAAM,CAAC,gBAAgB,iBAAiB,QAAI,uBAAS,KAAK;AAC1D,QAAM,SAAS,eAAe,SAAY,aAAa;AACvD,QAAM,mBAAe,qBAAY,IAAI;AACrC,QAAM,EAAE,MAAM,QAAI,iCAAgB;AAElC,QAAM,aAAa,MAAM;AACvB,UAAM,WAAW,CAAC;AAClB,QAAI,eAAe,QAAW;AAC5B,wBAAkB,QAAQ;AAAA,IAC5B;AACA,QAAI,aAAc,cAAa,QAAQ;AAAA,EACzC;AAEA,8BAAU,MAAM;AACd,UAAM,qBAAqB,CAAC,UAAsB;AAChD,UACE,aAAa,WACb,CAAC,aAAa,QAAQ,SAAS,MAAM,MAAM,GAC3C;AACA,YAAI,eAAe,QAAW;AAC5B,4BAAkB,KAAK;AAAA,QACzB;AACA,YAAI,aAAc,cAAa,KAAK;AAAA,MACtC;AAAA,IACF;AAEA,QAAI,QAAQ;AACV,eAAS,iBAAiB,aAAa,kBAAkB;AAAA,IAC3D;AACA,WAAO,MAAM;AACX,eAAS,oBAAoB,aAAa,kBAAkB;AAAA,IAC9D;AAAA,EACF,GAAG,CAAC,QAAQ,YAAY,YAAY,CAAC;AAErC,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,OAAO;AAAA,QACL,UAAU;AAAA,QACV,OAAO,UAAU,SAAS,gBAAgB;AAAA,QAC1C,WAAW;AAAA,QACX,QAAQ;AAAA,MACV;AAAA,MAEA;AAAA,qDAAC,SAAI,SAAS,YAAY,OAAO,EAAE,QAAQ,UAAU,GAClD,mBACH;AAAA,QACC,UACC;AAAA,UAAC;AAAA;AAAA,YACC,UAAS;AAAA,YACT,KAAI;AAAA,YACJ,MAAM;AAAA,YACN,WAAW;AAAA,YACX,iBAAiB,MAAM,OAAO,WAAW;AAAA,YACzC,aAAa,MAAM,OAAO,OAAO;AAAA,YACjC,aAAa;AAAA,YACb,cAAc,MAAM,OAAO;AAAA,YAC3B,iBAAiB;AAAA,YACjB,OAAO;AAAA,cACL,QAAQ;AAAA,cACR,WAAW;AAAA,cACX,UAAU;AAAA,YACZ;AAAA,YAEC;AAAA;AAAA,QACH;AAAA;AAAA;AAAA,EAEJ;AAEJ;AAYO,IAAM,eAA4C,CAAC;AAAA,EACxD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,EAAE,MAAM,QAAI,iCAAgB;AAClC,QAAM,cAAc,MAAM,OAAO,QAAQ,MAAM;AAC/C,QAAM,gBAAgB,MAAM,OAAO;AAGnC,QAAM,qBAAqB,MAAM;AAC/B,QAAI,UAAU;AACZ,aAAO,aAAa,MAAM,MAAM,OAAO,QAAQ,MAAM;AAAA,IACvD;AACA,QAAI,QAAQ;AACV,aAAO,MAAM,OAAO,QAAQ,MAAM;AAAA,IACpC;AACA,WAAO;AAAA,EACT;AAGA,QAAM,kBAAkB,MAAM;AAC5B,QAAI,UAAU;AACZ,aAAO,eAAe,IAAI,SAAS,MAAM,OAAO,QAAQ;AAAA,IAC1D;AACA,WAAO,MAAM,OAAO,QAAQ;AAAA,EAC9B;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,SAAS,CAAC,WAAW,UAAU;AAAA,MAC/B,mBAAmB;AAAA,MACnB,iBAAiB;AAAA,MACjB,eAAc;AAAA,MACd,YAAW;AAAA,MACX,iBAAiB,mBAAmB;AAAA,MACpC,YACE,CAAC,YAAY,CAAC,WACV;AAAA,QACE,iBAAiB,MAAM,OAAO,QAAQ,MAAM;AAAA,MAC9C,IACA;AAAA,MAEN,OAAO;AAAA,QACL,SAAS,WAAW,MAAM;AAAA,QAC1B,QAAQ,WAAW,gBAAgB;AAAA,MACrC;AAAA,MAEC;AAAA,gBACC,6CAAC,OAAI,aAAa,IAAI,YAAW,UAAS,gBAAe,UACtD,gBACH;AAAA,QAEF,6CAAC,OAAI,MAAM,GACT,uDAAC,QAAK,OAAO,gBAAgB,GAAG,UAAU,IAAI,YAAW,OACtD,UACH,GACF;AAAA;AAAA;AAAA,EACF;AAEJ;","names":["import_react_native","import_jsx_runtime","RNText","import_jsx_runtime"]}
1
+ {"version":3,"sources":["../../src/index.tsx","../../src/Dropdown.tsx","../../../primitives-native/src/Box.tsx","../../../primitives-native/src/Text.tsx"],"sourcesContent":["export * from \"./Dropdown\";\n","import React, { useState, useRef, useEffect, useCallback } from \"react\";\n// @ts-expect-error - this will be resolved at build time\nimport { Box, Text } from \"@xsolla/xui-primitives\";\nimport { useDesignSystem } from \"@xsolla/xui-core\";\n\nexport interface DropdownProps {\n trigger: React.ReactNode;\n children: React.ReactNode;\n isOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n /** Width of the dropdown menu (does not affect trigger width) */\n width?: string | number;\n /** Horizontal alignment of dropdown menu relative to trigger. Default: \"start\" */\n align?: \"start\" | \"end\";\n /** Accessible label for the dropdown menu */\n \"aria-label\"?: string;\n /** Test ID for testing frameworks */\n testID?: string;\n}\n\nexport const Dropdown: React.FC<DropdownProps> = ({\n trigger,\n children,\n isOpen: propIsOpen,\n onOpenChange,\n width = \"auto\",\n align = \"start\",\n \"aria-label\": ariaLabel,\n testID,\n}) => {\n const [internalIsOpen, setInternalIsOpen] = useState(false);\n const isOpen = propIsOpen !== undefined ? propIsOpen : internalIsOpen;\n const containerRef = useRef<HTMLDivElement>(null);\n const triggerRef = useRef<HTMLDivElement>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n const { theme } = useDesignSystem();\n\n const closeMenu = useCallback(() => {\n if (propIsOpen === undefined) {\n setInternalIsOpen(false);\n }\n if (onOpenChange) onOpenChange(false);\n }, [propIsOpen, onOpenChange]);\n\n const toggleOpen = useCallback(() => {\n const nextOpen = !isOpen;\n if (propIsOpen === undefined) {\n setInternalIsOpen(nextOpen);\n }\n if (onOpenChange) onOpenChange(nextOpen);\n }, [isOpen, propIsOpen, onOpenChange]);\n\n // Safe focus helper for cross-platform compatibility\n const focusTrigger = useCallback(() => {\n if (typeof document !== \"undefined\" && triggerRef.current) {\n // Find the first focusable element within the trigger wrapper\n const focusable = triggerRef.current.querySelector<HTMLElement>(\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n );\n if (focusable) {\n focusable.focus();\n }\n }\n }, []);\n\n // Handle keyboard navigation\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n if (event.key === \"Escape\" && isOpen) {\n event.preventDefault();\n closeMenu();\n focusTrigger();\n } else if (event.key === \"Tab\" && isOpen) {\n closeMenu();\n // Also restore focus on Tab for consistent keyboard navigation\n focusTrigger();\n }\n },\n [isOpen, closeMenu, focusTrigger]\n );\n\n // Handle trigger keyboard events\n const handleTriggerKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n toggleOpen();\n } else if (event.key === \"ArrowDown\" && !isOpen) {\n event.preventDefault();\n if (propIsOpen === undefined) {\n setInternalIsOpen(true);\n }\n if (onOpenChange) onOpenChange(true);\n }\n },\n [isOpen, toggleOpen, propIsOpen, onOpenChange]\n );\n\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent) => {\n if (\n containerRef.current &&\n !containerRef.current.contains(event.target as Node)\n ) {\n closeMenu();\n }\n };\n\n if (isOpen && typeof document !== \"undefined\") {\n document.addEventListener(\"mousedown\", handleClickOutside);\n }\n return () => {\n if (typeof document !== \"undefined\") {\n document.removeEventListener(\"mousedown\", handleClickOutside);\n }\n };\n }, [isOpen, closeMenu]);\n\n return (\n <div\n ref={containerRef}\n style={{\n position: \"relative\",\n width: \"fit-content\",\n alignSelf: \"flex-start\",\n height: \"fit-content\",\n }}\n onKeyDown={handleKeyDown}\n data-testid={testID}\n >\n <div\n ref={triggerRef}\n onClick={toggleOpen}\n onKeyDown={handleTriggerKeyDown}\n aria-haspopup=\"menu\"\n aria-expanded={isOpen}\n >\n {trigger}\n </div>\n {isOpen && (\n <Box\n ref={menuRef}\n position=\"absolute\"\n top=\"100%\"\n {...(align === \"end\" ? { right: 0 } : { left: 0 })}\n marginTop={4}\n backgroundColor={theme.colors.background.secondary}\n borderColor={theme.colors.border.secondary}\n borderWidth={1}\n borderRadius={theme.radius.button}\n paddingVertical={4}\n role=\"menu\"\n {...(ariaLabel ? { \"aria-label\": ariaLabel } : {})}\n style={{\n zIndex: 1000,\n boxShadow: \"0 4px 12px rgba(0,0,0,0.1)\",\n ...(width === \"auto\" ? { minWidth: \"100%\" } : { width }),\n }}\n >\n {children}\n </Box>\n )}\n </div>\n );\n};\n\nexport interface DropdownItemProps {\n children: React.ReactNode;\n onPress?: () => void;\n active?: boolean;\n /** Whether this item is selected (shows trailing checkmark with control/check/bg color) */\n selected?: boolean;\n disabled?: boolean;\n icon?: React.ReactNode;\n /** Test ID for testing frameworks */\n testID?: string;\n}\n\nexport const DropdownItem: React.FC<DropdownItemProps> = ({\n children,\n onPress,\n active,\n selected,\n disabled,\n icon,\n testID,\n}) => {\n const { theme } = useDesignSystem();\n const brandColors = theme.colors.control.brand.primary;\n const contentColors = theme.colors.content;\n\n // Determine background color\n const getBackgroundColor = () => {\n if (selected) {\n return brandColors?.bg || theme.colors.control.input.bg; // Cyan background for selected items\n }\n if (active) {\n return theme.colors.control.input.bgHover;\n }\n return \"transparent\";\n };\n\n // Determine text/icon color\n const getContentColor = () => {\n if (selected) {\n return contentColors?.on?.brand || theme.colors.content.primary; // Black text on cyan background\n }\n return theme.colors.content.secondary;\n };\n\n // Handle keyboard activation\n const handleKeyDown = (event: React.KeyboardEvent) => {\n if ((event.key === \"Enter\" || event.key === \" \") && !disabled && onPress) {\n event.preventDefault();\n onPress();\n }\n };\n\n return (\n <Box\n onPress={!disabled ? onPress : undefined}\n paddingHorizontal={16}\n paddingVertical={8}\n flexDirection=\"row\"\n alignItems=\"center\"\n backgroundColor={getBackgroundColor()}\n hoverStyle={\n !disabled && !selected\n ? {\n backgroundColor: theme.colors.control.input.bgHover,\n }\n : undefined\n }\n role=\"menuitem\"\n tabIndex={disabled ? -1 : 0}\n aria-disabled={disabled}\n onKeyDown={handleKeyDown}\n testID={testID}\n style={{\n opacity: disabled ? 0.5 : 1,\n cursor: disabled ? \"not-allowed\" : \"pointer\",\n }}\n >\n {icon && (\n <Box marginRight={12} alignItems=\"center\" justifyContent=\"center\">\n {icon}\n </Box>\n )}\n <Box flex={1}>\n <Text color={getContentColor()} fontSize={14} fontWeight=\"400\">\n {children}\n </Text>\n </Box>\n </Box>\n );\n};\n\nDropdown.displayName = \"Dropdown\";\nDropdownItem.displayName = \"DropdownItem\";\n","import React from \"react\";\nimport {\n View,\n Pressable,\n Image,\n ViewStyle,\n ImageStyle,\n DimensionValue,\n AnimatableNumericValue,\n} from \"react-native\";\nimport { BoxProps } from \"@xsolla/xui-primitives-core\";\n\nexport const Box: React.FC<BoxProps> = ({\n children,\n onPress,\n onLayout,\n onMoveShouldSetResponder,\n onResponderGrant,\n onResponderMove,\n onResponderRelease,\n onResponderTerminate,\n backgroundColor,\n borderColor,\n borderWidth,\n borderBottomWidth,\n borderBottomColor,\n borderTopWidth,\n borderTopColor,\n borderLeftWidth,\n borderLeftColor,\n borderRightWidth,\n borderRightColor,\n borderRadius,\n borderStyle,\n height,\n padding,\n paddingHorizontal,\n paddingVertical,\n margin,\n marginTop,\n marginBottom,\n marginLeft,\n marginRight,\n flexDirection,\n alignItems,\n justifyContent,\n position,\n top,\n bottom,\n left,\n right,\n width,\n flex,\n overflow,\n zIndex,\n hoverStyle,\n pressStyle,\n style,\n \"data-testid\": dataTestId,\n testID,\n as,\n src,\n alt,\n ...rest\n}) => {\n const getContainerStyle = (pressed?: boolean): ViewStyle => ({\n backgroundColor:\n pressed && pressStyle?.backgroundColor\n ? pressStyle.backgroundColor\n : backgroundColor,\n borderColor,\n borderWidth,\n borderBottomWidth,\n borderBottomColor,\n borderTopWidth,\n borderTopColor,\n borderLeftWidth,\n borderLeftColor,\n borderRightWidth,\n borderRightColor,\n borderRadius: borderRadius as AnimatableNumericValue,\n borderStyle: borderStyle as ViewStyle[\"borderStyle\"],\n overflow,\n zIndex,\n height: height as DimensionValue,\n width: width as DimensionValue,\n padding: padding as DimensionValue,\n paddingHorizontal: paddingHorizontal as DimensionValue,\n paddingVertical: paddingVertical as DimensionValue,\n margin: margin as DimensionValue,\n marginTop: marginTop as DimensionValue,\n marginBottom: marginBottom as DimensionValue,\n marginLeft: marginLeft as DimensionValue,\n marginRight: marginRight as DimensionValue,\n flexDirection,\n alignItems,\n justifyContent,\n position: position as ViewStyle[\"position\"],\n top: top as DimensionValue,\n bottom: bottom as DimensionValue,\n left: left as DimensionValue,\n right: right as DimensionValue,\n flex,\n ...(style as ViewStyle),\n });\n\n const finalTestID = dataTestId || testID;\n\n // Destructure and drop web-only props from rest before passing to RN components\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const {\n role,\n tabIndex,\n onKeyDown,\n onKeyUp,\n \"aria-label\": _ariaLabel,\n \"aria-labelledby\": _ariaLabelledBy,\n \"aria-current\": _ariaCurrent,\n \"aria-disabled\": _ariaDisabled,\n \"aria-live\": _ariaLive,\n className,\n \"data-testid\": _dataTestId,\n ...nativeRest\n } = rest as Record<string, unknown>;\n\n // Handle as=\"img\" for React Native\n if (as === \"img\" && src) {\n const imageStyle: ImageStyle = {\n width: width as DimensionValue,\n height: height as DimensionValue,\n borderRadius: borderRadius as number,\n position: position as ImageStyle[\"position\"],\n top: top as DimensionValue,\n bottom: bottom as DimensionValue,\n left: left as DimensionValue,\n right: right as DimensionValue,\n ...(style as ImageStyle),\n };\n\n return (\n <Image\n source={{ uri: src }}\n style={imageStyle}\n testID={finalTestID}\n resizeMode=\"cover\"\n {...nativeRest}\n />\n );\n }\n\n if (onPress) {\n return (\n <Pressable\n onPress={onPress}\n onLayout={onLayout}\n onMoveShouldSetResponder={onMoveShouldSetResponder}\n onResponderGrant={onResponderGrant}\n onResponderMove={onResponderMove}\n onResponderRelease={onResponderRelease}\n onResponderTerminate={onResponderTerminate}\n style={({ pressed }) => getContainerStyle(pressed)}\n testID={finalTestID}\n {...nativeRest}\n >\n {children}\n </Pressable>\n );\n }\n\n return (\n <View\n style={getContainerStyle()}\n testID={finalTestID}\n onLayout={onLayout}\n onMoveShouldSetResponder={onMoveShouldSetResponder}\n onResponderGrant={onResponderGrant}\n onResponderMove={onResponderMove}\n onResponderRelease={onResponderRelease}\n onResponderTerminate={onResponderTerminate}\n {...nativeRest}\n >\n {children}\n </View>\n );\n};\n","import React from \"react\";\nimport { Text as RNText, TextStyle, AccessibilityRole } from \"react-native\";\nimport { TextProps } from \"@xsolla/xui-primitives-core\";\n\n// Map web roles to React Native accessibility roles\nconst roleMap: Record<string, AccessibilityRole> = {\n alert: \"alert\",\n heading: \"header\",\n button: \"button\",\n link: \"link\",\n text: \"text\",\n};\n\nexport const Text: React.FC<TextProps> = ({\n children,\n color,\n fontSize,\n fontWeight,\n fontFamily,\n id,\n role,\n ...props\n}) => {\n // Extract the first font name from a comma-separated list (e.g. for web-style font stacks)\n let resolvedFontFamily = fontFamily\n ? fontFamily.split(\",\")[0].replace(/['\"]/g, \"\").trim()\n : undefined;\n\n // On native, if we don't have the custom font loaded, it's better to use the system font\n // to avoid rendering issues or missing text.\n if (resolvedFontFamily === \"Pilat Wide Bold\") {\n resolvedFontFamily = undefined;\n }\n\n const style: TextStyle = {\n color,\n fontSize: typeof fontSize === \"number\" ? fontSize : undefined,\n fontWeight: fontWeight as TextStyle[\"fontWeight\"],\n fontFamily: resolvedFontFamily,\n textDecorationLine: props.textDecoration as TextStyle[\"textDecorationLine\"],\n };\n\n // Map role to React Native accessibilityRole\n const accessibilityRole = role ? roleMap[role] : undefined;\n\n return (\n <RNText style={style} testID={id} accessibilityRole={accessibilityRole}>\n {children}\n </RNText>\n );\n};\n"],"mappings":"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","names":["import_react_native","import_jsx_runtime","RNText","import_jsx_runtime"]}
package/native/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/Dropdown.tsx
2
- import { useState, useRef, useEffect } from "react";
2
+ import { useState, useRef, useEffect, useCallback } from "react";
3
3
 
4
4
  // ../primitives-native/src/Box.tsx
5
5
  import {
@@ -214,63 +214,126 @@ var Dropdown = ({
214
214
  children,
215
215
  isOpen: propIsOpen,
216
216
  onOpenChange,
217
- width = "auto"
217
+ width = "auto",
218
+ align = "start",
219
+ "aria-label": ariaLabel,
220
+ testID
218
221
  }) => {
219
222
  const [internalIsOpen, setInternalIsOpen] = useState(false);
220
223
  const isOpen = propIsOpen !== void 0 ? propIsOpen : internalIsOpen;
221
224
  const containerRef = useRef(null);
225
+ const triggerRef = useRef(null);
226
+ const menuRef = useRef(null);
222
227
  const { theme } = useDesignSystem();
223
- const toggleOpen = () => {
228
+ const closeMenu = useCallback(() => {
229
+ if (propIsOpen === void 0) {
230
+ setInternalIsOpen(false);
231
+ }
232
+ if (onOpenChange) onOpenChange(false);
233
+ }, [propIsOpen, onOpenChange]);
234
+ const toggleOpen = useCallback(() => {
224
235
  const nextOpen = !isOpen;
225
236
  if (propIsOpen === void 0) {
226
237
  setInternalIsOpen(nextOpen);
227
238
  }
228
239
  if (onOpenChange) onOpenChange(nextOpen);
229
- };
240
+ }, [isOpen, propIsOpen, onOpenChange]);
241
+ const focusTrigger = useCallback(() => {
242
+ if (typeof document !== "undefined" && triggerRef.current) {
243
+ const focusable = triggerRef.current.querySelector(
244
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
245
+ );
246
+ if (focusable) {
247
+ focusable.focus();
248
+ }
249
+ }
250
+ }, []);
251
+ const handleKeyDown = useCallback(
252
+ (event) => {
253
+ if (event.key === "Escape" && isOpen) {
254
+ event.preventDefault();
255
+ closeMenu();
256
+ focusTrigger();
257
+ } else if (event.key === "Tab" && isOpen) {
258
+ closeMenu();
259
+ focusTrigger();
260
+ }
261
+ },
262
+ [isOpen, closeMenu, focusTrigger]
263
+ );
264
+ const handleTriggerKeyDown = useCallback(
265
+ (event) => {
266
+ if (event.key === "Enter" || event.key === " ") {
267
+ event.preventDefault();
268
+ toggleOpen();
269
+ } else if (event.key === "ArrowDown" && !isOpen) {
270
+ event.preventDefault();
271
+ if (propIsOpen === void 0) {
272
+ setInternalIsOpen(true);
273
+ }
274
+ if (onOpenChange) onOpenChange(true);
275
+ }
276
+ },
277
+ [isOpen, toggleOpen, propIsOpen, onOpenChange]
278
+ );
230
279
  useEffect(() => {
231
280
  const handleClickOutside = (event) => {
232
281
  if (containerRef.current && !containerRef.current.contains(event.target)) {
233
- if (propIsOpen === void 0) {
234
- setInternalIsOpen(false);
235
- }
236
- if (onOpenChange) onOpenChange(false);
282
+ closeMenu();
237
283
  }
238
284
  };
239
- if (isOpen) {
285
+ if (isOpen && typeof document !== "undefined") {
240
286
  document.addEventListener("mousedown", handleClickOutside);
241
287
  }
242
288
  return () => {
243
- document.removeEventListener("mousedown", handleClickOutside);
289
+ if (typeof document !== "undefined") {
290
+ document.removeEventListener("mousedown", handleClickOutside);
291
+ }
244
292
  };
245
- }, [isOpen, propIsOpen, onOpenChange]);
293
+ }, [isOpen, closeMenu]);
246
294
  return /* @__PURE__ */ jsxs(
247
295
  "div",
248
296
  {
249
297
  ref: containerRef,
250
298
  style: {
251
299
  position: "relative",
252
- width: width === "auto" ? "fit-content" : width,
300
+ width: "fit-content",
253
301
  alignSelf: "flex-start",
254
302
  height: "fit-content"
255
303
  },
304
+ onKeyDown: handleKeyDown,
305
+ "data-testid": testID,
256
306
  children: [
257
- /* @__PURE__ */ jsx3("div", { onClick: toggleOpen, style: { cursor: "pointer" }, children: trigger }),
307
+ /* @__PURE__ */ jsx3(
308
+ "div",
309
+ {
310
+ ref: triggerRef,
311
+ onClick: toggleOpen,
312
+ onKeyDown: handleTriggerKeyDown,
313
+ "aria-haspopup": "menu",
314
+ "aria-expanded": isOpen,
315
+ children: trigger
316
+ }
317
+ ),
258
318
  isOpen && /* @__PURE__ */ jsx3(
259
319
  Box,
260
320
  {
321
+ ref: menuRef,
261
322
  position: "absolute",
262
323
  top: "100%",
263
- left: 0,
324
+ ...align === "end" ? { right: 0 } : { left: 0 },
264
325
  marginTop: 4,
265
326
  backgroundColor: theme.colors.background.secondary,
266
327
  borderColor: theme.colors.border.secondary,
267
328
  borderWidth: 1,
268
329
  borderRadius: theme.radius.button,
269
330
  paddingVertical: 4,
331
+ role: "menu",
332
+ ...ariaLabel ? { "aria-label": ariaLabel } : {},
270
333
  style: {
271
334
  zIndex: 1e3,
272
335
  boxShadow: "0 4px 12px rgba(0,0,0,0.1)",
273
- minWidth: "100%"
336
+ ...width === "auto" ? { minWidth: "100%" } : { width }
274
337
  },
275
338
  children
276
339
  }
@@ -285,7 +348,8 @@ var DropdownItem = ({
285
348
  active,
286
349
  selected,
287
350
  disabled,
288
- icon
351
+ icon,
352
+ testID
289
353
  }) => {
290
354
  const { theme } = useDesignSystem();
291
355
  const brandColors = theme.colors.control.brand.primary;
@@ -305,6 +369,12 @@ var DropdownItem = ({
305
369
  }
306
370
  return theme.colors.content.secondary;
307
371
  };
372
+ const handleKeyDown = (event) => {
373
+ if ((event.key === "Enter" || event.key === " ") && !disabled && onPress) {
374
+ event.preventDefault();
375
+ onPress();
376
+ }
377
+ };
308
378
  return /* @__PURE__ */ jsxs(
309
379
  Box,
310
380
  {
@@ -317,6 +387,11 @@ var DropdownItem = ({
317
387
  hoverStyle: !disabled && !selected ? {
318
388
  backgroundColor: theme.colors.control.input.bgHover
319
389
  } : void 0,
390
+ role: "menuitem",
391
+ tabIndex: disabled ? -1 : 0,
392
+ "aria-disabled": disabled,
393
+ onKeyDown: handleKeyDown,
394
+ testID,
320
395
  style: {
321
396
  opacity: disabled ? 0.5 : 1,
322
397
  cursor: disabled ? "not-allowed" : "pointer"
@@ -328,6 +403,8 @@ var DropdownItem = ({
328
403
  }
329
404
  );
330
405
  };
406
+ Dropdown.displayName = "Dropdown";
407
+ DropdownItem.displayName = "DropdownItem";
331
408
  export {
332
409
  Dropdown,
333
410
  DropdownItem
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/Dropdown.tsx","../../../primitives-native/src/Box.tsx","../../../primitives-native/src/Text.tsx"],"sourcesContent":["import React, { useState, useRef, useEffect } from \"react\";\n// @ts-expect-error - this will be resolved at build time\nimport { Box, Text } from \"@xsolla/xui-primitives\";\nimport { useDesignSystem } from \"@xsolla/xui-core\";\n\nexport interface DropdownProps {\n trigger: React.ReactNode;\n children: React.ReactNode;\n isOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n width?: string | number;\n}\n\nexport const Dropdown: React.FC<DropdownProps> = ({\n trigger,\n children,\n isOpen: propIsOpen,\n onOpenChange,\n width = \"auto\",\n}) => {\n const [internalIsOpen, setInternalIsOpen] = useState(false);\n const isOpen = propIsOpen !== undefined ? propIsOpen : internalIsOpen;\n const containerRef = useRef<any>(null);\n const { theme } = useDesignSystem();\n\n const toggleOpen = () => {\n const nextOpen = !isOpen;\n if (propIsOpen === undefined) {\n setInternalIsOpen(nextOpen);\n }\n if (onOpenChange) onOpenChange(nextOpen);\n };\n\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent) => {\n if (\n containerRef.current &&\n !containerRef.current.contains(event.target)\n ) {\n if (propIsOpen === undefined) {\n setInternalIsOpen(false);\n }\n if (onOpenChange) onOpenChange(false);\n }\n };\n\n if (isOpen) {\n document.addEventListener(\"mousedown\", handleClickOutside);\n }\n return () => {\n document.removeEventListener(\"mousedown\", handleClickOutside);\n };\n }, [isOpen, propIsOpen, onOpenChange]);\n\n return (\n <div\n ref={containerRef}\n style={{\n position: \"relative\",\n width: width === \"auto\" ? \"fit-content\" : width,\n alignSelf: \"flex-start\",\n height: \"fit-content\",\n }}\n >\n <div onClick={toggleOpen} style={{ cursor: \"pointer\" }}>\n {trigger}\n </div>\n {isOpen && (\n <Box\n position=\"absolute\"\n top=\"100%\"\n left={0}\n marginTop={4}\n backgroundColor={theme.colors.background.secondary}\n borderColor={theme.colors.border.secondary}\n borderWidth={1}\n borderRadius={theme.radius.button}\n paddingVertical={4}\n style={{\n zIndex: 1000,\n boxShadow: \"0 4px 12px rgba(0,0,0,0.1)\",\n minWidth: \"100%\",\n }}\n >\n {children}\n </Box>\n )}\n </div>\n );\n};\n\nexport interface DropdownItemProps {\n children: React.ReactNode;\n onPress?: () => void;\n active?: boolean;\n /** Whether this item is selected (shows trailing checkmark with control/check/bg color) */\n selected?: boolean;\n disabled?: boolean;\n icon?: React.ReactNode;\n}\n\nexport const DropdownItem: React.FC<DropdownItemProps> = ({\n children,\n onPress,\n active,\n selected,\n disabled,\n icon,\n}) => {\n const { theme } = useDesignSystem();\n const brandColors = theme.colors.control.brand.primary;\n const contentColors = theme.colors.content;\n\n // Determine background color\n const getBackgroundColor = () => {\n if (selected) {\n return brandColors?.bg || theme.colors.control.input.bg; // Cyan background for selected items\n }\n if (active) {\n return theme.colors.control.input.bgHover;\n }\n return \"transparent\";\n };\n\n // Determine text/icon color\n const getContentColor = () => {\n if (selected) {\n return contentColors?.on?.brand || theme.colors.content.primary; // Black text on cyan background\n }\n return theme.colors.content.secondary;\n };\n\n return (\n <Box\n onPress={!disabled ? onPress : undefined}\n paddingHorizontal={16}\n paddingVertical={8}\n flexDirection=\"row\"\n alignItems=\"center\"\n backgroundColor={getBackgroundColor()}\n hoverStyle={\n !disabled && !selected\n ? {\n backgroundColor: theme.colors.control.input.bgHover,\n }\n : undefined\n }\n style={{\n opacity: disabled ? 0.5 : 1,\n cursor: disabled ? \"not-allowed\" : \"pointer\",\n }}\n >\n {icon && (\n <Box marginRight={12} alignItems=\"center\" justifyContent=\"center\">\n {icon}\n </Box>\n )}\n <Box flex={1}>\n <Text color={getContentColor()} fontSize={14} fontWeight=\"400\">\n {children}\n </Text>\n </Box>\n </Box>\n );\n};\n","import React from \"react\";\nimport {\n View,\n Pressable,\n Image,\n ViewStyle,\n ImageStyle,\n DimensionValue,\n AnimatableNumericValue,\n} from \"react-native\";\nimport { BoxProps } from \"@xsolla/xui-primitives-core\";\n\nexport const Box: React.FC<BoxProps> = ({\n children,\n onPress,\n onLayout,\n onMoveShouldSetResponder,\n onResponderGrant,\n onResponderMove,\n onResponderRelease,\n onResponderTerminate,\n backgroundColor,\n borderColor,\n borderWidth,\n borderBottomWidth,\n borderBottomColor,\n borderTopWidth,\n borderTopColor,\n borderLeftWidth,\n borderLeftColor,\n borderRightWidth,\n borderRightColor,\n borderRadius,\n borderStyle,\n height,\n padding,\n paddingHorizontal,\n paddingVertical,\n margin,\n marginTop,\n marginBottom,\n marginLeft,\n marginRight,\n flexDirection,\n alignItems,\n justifyContent,\n position,\n top,\n bottom,\n left,\n right,\n width,\n flex,\n overflow,\n zIndex,\n hoverStyle,\n pressStyle,\n style,\n \"data-testid\": dataTestId,\n testID,\n as,\n src,\n alt,\n ...rest\n}) => {\n const getContainerStyle = (pressed?: boolean): ViewStyle => ({\n backgroundColor:\n pressed && pressStyle?.backgroundColor\n ? pressStyle.backgroundColor\n : backgroundColor,\n borderColor,\n borderWidth,\n borderBottomWidth,\n borderBottomColor,\n borderTopWidth,\n borderTopColor,\n borderLeftWidth,\n borderLeftColor,\n borderRightWidth,\n borderRightColor,\n borderRadius: borderRadius as AnimatableNumericValue,\n borderStyle: borderStyle as ViewStyle[\"borderStyle\"],\n overflow,\n zIndex,\n height: height as DimensionValue,\n width: width as DimensionValue,\n padding: padding as DimensionValue,\n paddingHorizontal: paddingHorizontal as DimensionValue,\n paddingVertical: paddingVertical as DimensionValue,\n margin: margin as DimensionValue,\n marginTop: marginTop as DimensionValue,\n marginBottom: marginBottom as DimensionValue,\n marginLeft: marginLeft as DimensionValue,\n marginRight: marginRight as DimensionValue,\n flexDirection,\n alignItems,\n justifyContent,\n position: position as ViewStyle[\"position\"],\n top: top as DimensionValue,\n bottom: bottom as DimensionValue,\n left: left as DimensionValue,\n right: right as DimensionValue,\n flex,\n ...(style as ViewStyle),\n });\n\n const finalTestID = dataTestId || testID;\n\n // Destructure and drop web-only props from rest before passing to RN components\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const {\n role,\n tabIndex,\n onKeyDown,\n onKeyUp,\n \"aria-label\": _ariaLabel,\n \"aria-labelledby\": _ariaLabelledBy,\n \"aria-current\": _ariaCurrent,\n \"aria-disabled\": _ariaDisabled,\n \"aria-live\": _ariaLive,\n className,\n \"data-testid\": _dataTestId,\n ...nativeRest\n } = rest as Record<string, unknown>;\n\n // Handle as=\"img\" for React Native\n if (as === \"img\" && src) {\n const imageStyle: ImageStyle = {\n width: width as DimensionValue,\n height: height as DimensionValue,\n borderRadius: borderRadius as number,\n position: position as ImageStyle[\"position\"],\n top: top as DimensionValue,\n bottom: bottom as DimensionValue,\n left: left as DimensionValue,\n right: right as DimensionValue,\n ...(style as ImageStyle),\n };\n\n return (\n <Image\n source={{ uri: src }}\n style={imageStyle}\n testID={finalTestID}\n resizeMode=\"cover\"\n {...nativeRest}\n />\n );\n }\n\n if (onPress) {\n return (\n <Pressable\n onPress={onPress}\n onLayout={onLayout}\n onMoveShouldSetResponder={onMoveShouldSetResponder}\n onResponderGrant={onResponderGrant}\n onResponderMove={onResponderMove}\n onResponderRelease={onResponderRelease}\n onResponderTerminate={onResponderTerminate}\n style={({ pressed }) => getContainerStyle(pressed)}\n testID={finalTestID}\n {...nativeRest}\n >\n {children}\n </Pressable>\n );\n }\n\n return (\n <View\n style={getContainerStyle()}\n testID={finalTestID}\n onLayout={onLayout}\n onMoveShouldSetResponder={onMoveShouldSetResponder}\n onResponderGrant={onResponderGrant}\n onResponderMove={onResponderMove}\n onResponderRelease={onResponderRelease}\n onResponderTerminate={onResponderTerminate}\n {...nativeRest}\n >\n {children}\n </View>\n );\n};\n","import React from \"react\";\nimport { Text as RNText, TextStyle, AccessibilityRole } from \"react-native\";\nimport { TextProps } from \"@xsolla/xui-primitives-core\";\n\n// Map web roles to React Native accessibility roles\nconst roleMap: Record<string, AccessibilityRole> = {\n alert: \"alert\",\n heading: \"header\",\n button: \"button\",\n link: \"link\",\n text: \"text\",\n};\n\nexport const Text: React.FC<TextProps> = ({\n children,\n color,\n fontSize,\n fontWeight,\n fontFamily,\n id,\n role,\n ...props\n}) => {\n // Extract the first font name from a comma-separated list (e.g. for web-style font stacks)\n let resolvedFontFamily = fontFamily\n ? fontFamily.split(\",\")[0].replace(/['\"]/g, \"\").trim()\n : undefined;\n\n // On native, if we don't have the custom font loaded, it's better to use the system font\n // to avoid rendering issues or missing text.\n if (resolvedFontFamily === \"Pilat Wide Bold\") {\n resolvedFontFamily = undefined;\n }\n\n const style: TextStyle = {\n color,\n fontSize: typeof fontSize === \"number\" ? fontSize : undefined,\n fontWeight: fontWeight as TextStyle[\"fontWeight\"],\n fontFamily: resolvedFontFamily,\n textDecorationLine: props.textDecoration as TextStyle[\"textDecorationLine\"],\n };\n\n // Map role to React Native accessibilityRole\n const accessibilityRole = role ? roleMap[role] : undefined;\n\n return (\n <RNText style={style} testID={id} accessibilityRole={accessibilityRole}>\n {children}\n </RNText>\n );\n};\n"],"mappings":"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","names":["jsx","jsx"]}
1
+ {"version":3,"sources":["../../src/Dropdown.tsx","../../../primitives-native/src/Box.tsx","../../../primitives-native/src/Text.tsx"],"sourcesContent":["import React, { useState, useRef, useEffect, useCallback } from \"react\";\n// @ts-expect-error - this will be resolved at build time\nimport { Box, Text } from \"@xsolla/xui-primitives\";\nimport { useDesignSystem } from \"@xsolla/xui-core\";\n\nexport interface DropdownProps {\n trigger: React.ReactNode;\n children: React.ReactNode;\n isOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n /** Width of the dropdown menu (does not affect trigger width) */\n width?: string | number;\n /** Horizontal alignment of dropdown menu relative to trigger. Default: \"start\" */\n align?: \"start\" | \"end\";\n /** Accessible label for the dropdown menu */\n \"aria-label\"?: string;\n /** Test ID for testing frameworks */\n testID?: string;\n}\n\nexport const Dropdown: React.FC<DropdownProps> = ({\n trigger,\n children,\n isOpen: propIsOpen,\n onOpenChange,\n width = \"auto\",\n align = \"start\",\n \"aria-label\": ariaLabel,\n testID,\n}) => {\n const [internalIsOpen, setInternalIsOpen] = useState(false);\n const isOpen = propIsOpen !== undefined ? propIsOpen : internalIsOpen;\n const containerRef = useRef<HTMLDivElement>(null);\n const triggerRef = useRef<HTMLDivElement>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n const { theme } = useDesignSystem();\n\n const closeMenu = useCallback(() => {\n if (propIsOpen === undefined) {\n setInternalIsOpen(false);\n }\n if (onOpenChange) onOpenChange(false);\n }, [propIsOpen, onOpenChange]);\n\n const toggleOpen = useCallback(() => {\n const nextOpen = !isOpen;\n if (propIsOpen === undefined) {\n setInternalIsOpen(nextOpen);\n }\n if (onOpenChange) onOpenChange(nextOpen);\n }, [isOpen, propIsOpen, onOpenChange]);\n\n // Safe focus helper for cross-platform compatibility\n const focusTrigger = useCallback(() => {\n if (typeof document !== \"undefined\" && triggerRef.current) {\n // Find the first focusable element within the trigger wrapper\n const focusable = triggerRef.current.querySelector<HTMLElement>(\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n );\n if (focusable) {\n focusable.focus();\n }\n }\n }, []);\n\n // Handle keyboard navigation\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n if (event.key === \"Escape\" && isOpen) {\n event.preventDefault();\n closeMenu();\n focusTrigger();\n } else if (event.key === \"Tab\" && isOpen) {\n closeMenu();\n // Also restore focus on Tab for consistent keyboard navigation\n focusTrigger();\n }\n },\n [isOpen, closeMenu, focusTrigger]\n );\n\n // Handle trigger keyboard events\n const handleTriggerKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n toggleOpen();\n } else if (event.key === \"ArrowDown\" && !isOpen) {\n event.preventDefault();\n if (propIsOpen === undefined) {\n setInternalIsOpen(true);\n }\n if (onOpenChange) onOpenChange(true);\n }\n },\n [isOpen, toggleOpen, propIsOpen, onOpenChange]\n );\n\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent) => {\n if (\n containerRef.current &&\n !containerRef.current.contains(event.target as Node)\n ) {\n closeMenu();\n }\n };\n\n if (isOpen && typeof document !== \"undefined\") {\n document.addEventListener(\"mousedown\", handleClickOutside);\n }\n return () => {\n if (typeof document !== \"undefined\") {\n document.removeEventListener(\"mousedown\", handleClickOutside);\n }\n };\n }, [isOpen, closeMenu]);\n\n return (\n <div\n ref={containerRef}\n style={{\n position: \"relative\",\n width: \"fit-content\",\n alignSelf: \"flex-start\",\n height: \"fit-content\",\n }}\n onKeyDown={handleKeyDown}\n data-testid={testID}\n >\n <div\n ref={triggerRef}\n onClick={toggleOpen}\n onKeyDown={handleTriggerKeyDown}\n aria-haspopup=\"menu\"\n aria-expanded={isOpen}\n >\n {trigger}\n </div>\n {isOpen && (\n <Box\n ref={menuRef}\n position=\"absolute\"\n top=\"100%\"\n {...(align === \"end\" ? { right: 0 } : { left: 0 })}\n marginTop={4}\n backgroundColor={theme.colors.background.secondary}\n borderColor={theme.colors.border.secondary}\n borderWidth={1}\n borderRadius={theme.radius.button}\n paddingVertical={4}\n role=\"menu\"\n {...(ariaLabel ? { \"aria-label\": ariaLabel } : {})}\n style={{\n zIndex: 1000,\n boxShadow: \"0 4px 12px rgba(0,0,0,0.1)\",\n ...(width === \"auto\" ? { minWidth: \"100%\" } : { width }),\n }}\n >\n {children}\n </Box>\n )}\n </div>\n );\n};\n\nexport interface DropdownItemProps {\n children: React.ReactNode;\n onPress?: () => void;\n active?: boolean;\n /** Whether this item is selected (shows trailing checkmark with control/check/bg color) */\n selected?: boolean;\n disabled?: boolean;\n icon?: React.ReactNode;\n /** Test ID for testing frameworks */\n testID?: string;\n}\n\nexport const DropdownItem: React.FC<DropdownItemProps> = ({\n children,\n onPress,\n active,\n selected,\n disabled,\n icon,\n testID,\n}) => {\n const { theme } = useDesignSystem();\n const brandColors = theme.colors.control.brand.primary;\n const contentColors = theme.colors.content;\n\n // Determine background color\n const getBackgroundColor = () => {\n if (selected) {\n return brandColors?.bg || theme.colors.control.input.bg; // Cyan background for selected items\n }\n if (active) {\n return theme.colors.control.input.bgHover;\n }\n return \"transparent\";\n };\n\n // Determine text/icon color\n const getContentColor = () => {\n if (selected) {\n return contentColors?.on?.brand || theme.colors.content.primary; // Black text on cyan background\n }\n return theme.colors.content.secondary;\n };\n\n // Handle keyboard activation\n const handleKeyDown = (event: React.KeyboardEvent) => {\n if ((event.key === \"Enter\" || event.key === \" \") && !disabled && onPress) {\n event.preventDefault();\n onPress();\n }\n };\n\n return (\n <Box\n onPress={!disabled ? onPress : undefined}\n paddingHorizontal={16}\n paddingVertical={8}\n flexDirection=\"row\"\n alignItems=\"center\"\n backgroundColor={getBackgroundColor()}\n hoverStyle={\n !disabled && !selected\n ? {\n backgroundColor: theme.colors.control.input.bgHover,\n }\n : undefined\n }\n role=\"menuitem\"\n tabIndex={disabled ? -1 : 0}\n aria-disabled={disabled}\n onKeyDown={handleKeyDown}\n testID={testID}\n style={{\n opacity: disabled ? 0.5 : 1,\n cursor: disabled ? \"not-allowed\" : \"pointer\",\n }}\n >\n {icon && (\n <Box marginRight={12} alignItems=\"center\" justifyContent=\"center\">\n {icon}\n </Box>\n )}\n <Box flex={1}>\n <Text color={getContentColor()} fontSize={14} fontWeight=\"400\">\n {children}\n </Text>\n </Box>\n </Box>\n );\n};\n\nDropdown.displayName = \"Dropdown\";\nDropdownItem.displayName = \"DropdownItem\";\n","import React from \"react\";\nimport {\n View,\n Pressable,\n Image,\n ViewStyle,\n ImageStyle,\n DimensionValue,\n AnimatableNumericValue,\n} from \"react-native\";\nimport { BoxProps } from \"@xsolla/xui-primitives-core\";\n\nexport const Box: React.FC<BoxProps> = ({\n children,\n onPress,\n onLayout,\n onMoveShouldSetResponder,\n onResponderGrant,\n onResponderMove,\n onResponderRelease,\n onResponderTerminate,\n backgroundColor,\n borderColor,\n borderWidth,\n borderBottomWidth,\n borderBottomColor,\n borderTopWidth,\n borderTopColor,\n borderLeftWidth,\n borderLeftColor,\n borderRightWidth,\n borderRightColor,\n borderRadius,\n borderStyle,\n height,\n padding,\n paddingHorizontal,\n paddingVertical,\n margin,\n marginTop,\n marginBottom,\n marginLeft,\n marginRight,\n flexDirection,\n alignItems,\n justifyContent,\n position,\n top,\n bottom,\n left,\n right,\n width,\n flex,\n overflow,\n zIndex,\n hoverStyle,\n pressStyle,\n style,\n \"data-testid\": dataTestId,\n testID,\n as,\n src,\n alt,\n ...rest\n}) => {\n const getContainerStyle = (pressed?: boolean): ViewStyle => ({\n backgroundColor:\n pressed && pressStyle?.backgroundColor\n ? pressStyle.backgroundColor\n : backgroundColor,\n borderColor,\n borderWidth,\n borderBottomWidth,\n borderBottomColor,\n borderTopWidth,\n borderTopColor,\n borderLeftWidth,\n borderLeftColor,\n borderRightWidth,\n borderRightColor,\n borderRadius: borderRadius as AnimatableNumericValue,\n borderStyle: borderStyle as ViewStyle[\"borderStyle\"],\n overflow,\n zIndex,\n height: height as DimensionValue,\n width: width as DimensionValue,\n padding: padding as DimensionValue,\n paddingHorizontal: paddingHorizontal as DimensionValue,\n paddingVertical: paddingVertical as DimensionValue,\n margin: margin as DimensionValue,\n marginTop: marginTop as DimensionValue,\n marginBottom: marginBottom as DimensionValue,\n marginLeft: marginLeft as DimensionValue,\n marginRight: marginRight as DimensionValue,\n flexDirection,\n alignItems,\n justifyContent,\n position: position as ViewStyle[\"position\"],\n top: top as DimensionValue,\n bottom: bottom as DimensionValue,\n left: left as DimensionValue,\n right: right as DimensionValue,\n flex,\n ...(style as ViewStyle),\n });\n\n const finalTestID = dataTestId || testID;\n\n // Destructure and drop web-only props from rest before passing to RN components\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const {\n role,\n tabIndex,\n onKeyDown,\n onKeyUp,\n \"aria-label\": _ariaLabel,\n \"aria-labelledby\": _ariaLabelledBy,\n \"aria-current\": _ariaCurrent,\n \"aria-disabled\": _ariaDisabled,\n \"aria-live\": _ariaLive,\n className,\n \"data-testid\": _dataTestId,\n ...nativeRest\n } = rest as Record<string, unknown>;\n\n // Handle as=\"img\" for React Native\n if (as === \"img\" && src) {\n const imageStyle: ImageStyle = {\n width: width as DimensionValue,\n height: height as DimensionValue,\n borderRadius: borderRadius as number,\n position: position as ImageStyle[\"position\"],\n top: top as DimensionValue,\n bottom: bottom as DimensionValue,\n left: left as DimensionValue,\n right: right as DimensionValue,\n ...(style as ImageStyle),\n };\n\n return (\n <Image\n source={{ uri: src }}\n style={imageStyle}\n testID={finalTestID}\n resizeMode=\"cover\"\n {...nativeRest}\n />\n );\n }\n\n if (onPress) {\n return (\n <Pressable\n onPress={onPress}\n onLayout={onLayout}\n onMoveShouldSetResponder={onMoveShouldSetResponder}\n onResponderGrant={onResponderGrant}\n onResponderMove={onResponderMove}\n onResponderRelease={onResponderRelease}\n onResponderTerminate={onResponderTerminate}\n style={({ pressed }) => getContainerStyle(pressed)}\n testID={finalTestID}\n {...nativeRest}\n >\n {children}\n </Pressable>\n );\n }\n\n return (\n <View\n style={getContainerStyle()}\n testID={finalTestID}\n onLayout={onLayout}\n onMoveShouldSetResponder={onMoveShouldSetResponder}\n onResponderGrant={onResponderGrant}\n onResponderMove={onResponderMove}\n onResponderRelease={onResponderRelease}\n onResponderTerminate={onResponderTerminate}\n {...nativeRest}\n >\n {children}\n </View>\n );\n};\n","import React from \"react\";\nimport { Text as RNText, TextStyle, AccessibilityRole } from \"react-native\";\nimport { TextProps } from \"@xsolla/xui-primitives-core\";\n\n// Map web roles to React Native accessibility roles\nconst roleMap: Record<string, AccessibilityRole> = {\n alert: \"alert\",\n heading: \"header\",\n button: \"button\",\n link: \"link\",\n text: \"text\",\n};\n\nexport const Text: React.FC<TextProps> = ({\n children,\n color,\n fontSize,\n fontWeight,\n fontFamily,\n id,\n role,\n ...props\n}) => {\n // Extract the first font name from a comma-separated list (e.g. for web-style font stacks)\n let resolvedFontFamily = fontFamily\n ? fontFamily.split(\",\")[0].replace(/['\"]/g, \"\").trim()\n : undefined;\n\n // On native, if we don't have the custom font loaded, it's better to use the system font\n // to avoid rendering issues or missing text.\n if (resolvedFontFamily === \"Pilat Wide Bold\") {\n resolvedFontFamily = undefined;\n }\n\n const style: TextStyle = {\n color,\n fontSize: typeof fontSize === \"number\" ? fontSize : undefined,\n fontWeight: fontWeight as TextStyle[\"fontWeight\"],\n fontFamily: resolvedFontFamily,\n textDecorationLine: props.textDecoration as TextStyle[\"textDecorationLine\"],\n };\n\n // Map role to React Native accessibilityRole\n const accessibilityRole = role ? roleMap[role] : undefined;\n\n return (\n <RNText style={style} testID={id} accessibilityRole={accessibilityRole}>\n {children}\n </RNText>\n );\n};\n"],"mappings":"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","names":["jsx","jsx"]}