@xsolla/xui-multi-select 0.156.0 → 0.157.0-pr302.1779365565

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,7 +11,7 @@ npm install @xsolla/xui-multi-select
11
11
  ## Imports
12
12
 
13
13
  ```tsx
14
- import { MultiSelect } from '@xsolla/xui-multi-select';
14
+ import { MultiSelect } from "@xsolla/xui-multi-select";
15
15
  import type {
16
16
  MultiSelectProps,
17
17
  MultiSelectOption,
@@ -19,16 +19,16 @@ import type {
19
19
  MultiSelectVariant,
20
20
  MultiSelectSize,
21
21
  MultiSelectState,
22
- } from '@xsolla/xui-multi-select';
22
+ } from "@xsolla/xui-multi-select";
23
23
  ```
24
24
 
25
25
  ## Quick start
26
26
 
27
27
  ```tsx
28
28
  const options = [
29
- { label: 'React', value: 'react' },
30
- { label: 'Vue', value: 'vue' },
31
- { label: 'Angular', value: 'angular' },
29
+ { label: "React", value: "react" },
30
+ { label: "Vue", value: "vue" },
31
+ { label: "Angular", value: "angular" },
32
32
  ];
33
33
 
34
34
  const [selected, setSelected] = useState<MultiSelectValue>([]);
@@ -47,15 +47,17 @@ const [selected, setSelected] = useState<MultiSelectValue>([]);
47
47
  When the option list is rendered elsewhere (for example [`@xsolla/xui-b2b-group-select`](./b2b-group-select.md)), set **`dropdownMenu={false}`** so the control does not open the built-in list. Wire the same **`value`** / **`onChange`** to both components; use **`onTriggerPress`** to toggle your panel, **`menuOpen`** for chevron/open styling, and **`menuMinWidth`** (default **540**, aligned with `GROUP_SELECT_MIN_PANEL_WIDTH`) so the field matches the panel width.
48
48
 
49
49
  ```tsx
50
- import * as React from 'react';
51
- import { MultiSelect } from '@xsolla/xui-multi-select';
50
+ import * as React from "react";
51
+ import { MultiSelect } from "@xsolla/xui-multi-select";
52
52
  import {
53
53
  GroupSelect,
54
54
  GROUP_SELECT_MIN_PANEL_WIDTH,
55
55
  type GroupSelectGroup,
56
- } from '@xsolla/xui-b2b-group-select';
56
+ } from "@xsolla/xui-b2b-group-select";
57
57
 
58
- const groups: GroupSelectGroup[] = [/* ... */];
58
+ const groups: GroupSelectGroup[] = [
59
+ /* ... */
60
+ ];
59
61
  const flatOptions = groups.flatMap((g) =>
60
62
  g.items.map((it) => ({ value: it.id, label: it.label }))
61
63
  );
@@ -91,28 +93,29 @@ Add backdrop, click-outside, and Escape handling in your layout as needed (see S
91
93
 
92
94
  ### `<MultiSelect>`
93
95
 
94
- | Prop | Type | Default | Description |
95
- | --- | --- | --- | --- |
96
- | `options` | `MultiSelectOption[]` | — | Available options. |
97
- | `value` | `MultiSelectValue` | `[]` | Selected values. |
98
- | `onChange` | `(values: MultiSelectValue) => void` | | Fired when the selection changes. |
99
- | `placeholder` | `string` | `'Select'` | Placeholder shown when empty. |
100
- | `label` | `string` | | Label rendered above the control. |
101
- | `size` | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` | Control size. |
102
- | `state` | `'default' \| 'hover' \| 'focus' \| 'disable' \| 'error'` | | Forced visual state. |
103
- | `disabled` | `boolean` | `false` | Disable the control. |
104
- | `errorMessage` | `string` | | Error message; also marks the control invalid. |
105
- | `variant` | `'tag' \| 'text'` | `'tag'` | How selected options are displayed. |
106
- | `flexible` | `boolean` | `true` | When `true` the control grows with content; otherwise fixed-height. |
107
- | `removeTagsButtons` | `boolean` | `true` | Show a remove button on each tag. |
108
- | `extraClear` | `boolean` | `false` | Show a clear-all button. |
109
- | `maxHeight` | `number` | `300` | Maximum dropdown height in pixels. |
110
- | `iconLeft` | `ReactNode` | | Icon rendered on the left of the control. |
111
- | `iconRight` | `ReactNode` | — | Icon on the right (overrides the default caret). |
112
- | `dropdownMenu` | `boolean` | `true` | When `false`, hides the built-in list and disables click-to-open; use with an external picker (e.g. `GroupSelect`) wired to the same `value` / `onChange`. |
113
- | `onTriggerPress` | `() => void` | | When `dropdownMenu` is `false`: fired when the user activates the field. Typically toggles the external panel. |
114
- | `menuOpen` | `boolean` | `false` | When `dropdownMenu` is `false`: drives chevron direction and layering like the built-in open state. |
115
- | `menuMinWidth` | `number` | `540` | When `dropdownMenu` is `false`: field `min-width` in px (matches `GroupSelect`). Use `0` for no minimum. |
96
+ | Prop | Type | Default | Description |
97
+ | ------------------- | --------------------------------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
98
+ | `testID` | `string` | — | Test ID for testing frameworks. On web this renders as `data-testid`; on React Native it renders as `testID`. |
99
+ | `options` | `MultiSelectOption[]` | | Available options. |
100
+ | `value` | `MultiSelectValue` | `[]` | Selected values. |
101
+ | `onChange` | `(values: MultiSelectValue) => void` | | Fired when the selection changes. |
102
+ | `placeholder` | `string` | `'Select'` | Placeholder shown when empty. |
103
+ | `label` | `string` | | Label rendered above the control. |
104
+ | `size` | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` | Control size. |
105
+ | `state` | `'default' \| 'hover' \| 'focus' \| 'disable' \| 'error'` | — | Forced visual state. |
106
+ | `disabled` | `boolean` | `false` | Disable the control. |
107
+ | `errorMessage` | `string` | | Error message; also marks the control invalid. |
108
+ | `variant` | `'tag' \| 'text'` | `'tag'` | How selected options are displayed. |
109
+ | `flexible` | `boolean` | `true` | When `true` the control grows with content; otherwise fixed-height. |
110
+ | `removeTagsButtons` | `boolean` | `true` | Show a remove button on each tag. |
111
+ | `extraClear` | `boolean` | `false` | Show a clear-all button. |
112
+ | `maxHeight` | `number` | `300` | Maximum dropdown height in pixels. |
113
+ | `iconLeft` | `ReactNode` | — | Icon rendered on the left of the control. |
114
+ | `iconRight` | `ReactNode` | | Icon on the right (overrides the default caret). |
115
+ | `dropdownMenu` | `boolean` | `true` | When `false`, hides the built-in list and disables click-to-open; use with an external picker (e.g. `GroupSelect`) wired to the same `value` / `onChange`. |
116
+ | `onTriggerPress` | `() => void` | | When `dropdownMenu` is `false`: fired when the user activates the field. Typically toggles the external panel. |
117
+ | `menuOpen` | `boolean` | `false` | When `dropdownMenu` is `false`: drives chevron direction and layering like the built-in open state. |
118
+ | `menuMinWidth` | `number` | `540` | When `dropdownMenu` is `false`: field `min-width` in px (matches `GroupSelect`). Use `0` for no minimum. |
116
119
 
117
120
  Inherits `ThemeOverrideProps` (`themeMode`, `themeProductContext`).
118
121
 
@@ -120,9 +123,9 @@ Inherits `ThemeOverrideProps` (`themeMode`, `themeProductContext`).
120
123
 
121
124
  ```ts
122
125
  type MultiSelectValue = (string | number)[];
123
- type MultiSelectVariant = 'tag' | 'text';
124
- type MultiSelectSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
125
- type MultiSelectState = 'default' | 'hover' | 'focus' | 'disable' | 'error';
126
+ type MultiSelectVariant = "tag" | "text";
127
+ type MultiSelectSize = "xs" | "sm" | "md" | "lg" | "xl";
128
+ type MultiSelectState = "default" | "hover" | "focus" | "disable" | "error";
126
129
 
127
130
  interface MultiSelectOption {
128
131
  label: ReactNode;
@@ -153,9 +156,9 @@ const options = [
153
156
 
154
157
  ```tsx
155
158
  const options = [
156
- { label: 'React', value: 'react' },
157
- { label: 'Vue', value: 'vue' },
158
- { label: 'Angular', value: 'angular' },
159
+ { label: "React", value: "react" },
160
+ { label: "Vue", value: "vue" },
161
+ { label: "Angular", value: "angular" },
159
162
  ];
160
163
 
161
164
  const [selected, setSelected] = useState<MultiSelectValue>([]);
@@ -173,9 +176,9 @@ const [selected, setSelected] = useState<MultiSelectValue>([]);
173
176
 
174
177
  ```tsx
175
178
  const options = [
176
- { label: 'Design', value: 'design' },
177
- { label: 'Engineering', value: 'engineering' },
178
- { label: 'Product', value: 'product' },
179
+ { label: "Design", value: "design" },
180
+ { label: "Engineering", value: "engineering" },
181
+ { label: "Product", value: "product" },
179
182
  ];
180
183
 
181
184
  const [skills, setSkills] = useState<MultiSelectValue>([]);
@@ -193,12 +196,12 @@ const [skills, setSkills] = useState<MultiSelectValue>([]);
193
196
 
194
197
  ```tsx
195
198
  const options = [
196
- { label: 'React', value: 'react' },
197
- { label: 'Vue', value: 'vue' },
198
- { label: 'Angular', value: 'angular' },
199
+ { label: "React", value: "react" },
200
+ { label: "Vue", value: "vue" },
201
+ { label: "Angular", value: "angular" },
199
202
  ];
200
203
 
201
- <MultiSelect options={options} value={['react']} disabled />;
204
+ <MultiSelect options={options} value={["react"]} disabled />;
202
205
  ```
203
206
 
204
207
  ## Accessibility
@@ -106,6 +106,8 @@ interface MultiSelectProps extends ThemeOverrideProps {
106
106
  * no minimum. Ignored when the built-in dropdown is enabled.
107
107
  */
108
108
  menuMinWidth?: number;
109
+ /** Test ID for testing frameworks */
110
+ testID?: string;
109
111
  }
110
112
 
111
113
  declare const MultiSelect: react.ForwardRefExoticComponent<MultiSelectProps & react.RefAttributes<HTMLDivElement>>;
package/native/index.d.ts CHANGED
@@ -106,6 +106,8 @@ interface MultiSelectProps extends ThemeOverrideProps {
106
106
  * no minimum. Ignored when the built-in dropdown is enabled.
107
107
  */
108
108
  menuMinWidth?: number;
109
+ /** Test ID for testing frameworks */
110
+ testID?: string;
109
111
  }
110
112
 
111
113
  declare const MultiSelect: react.ForwardRefExoticComponent<MultiSelectProps & react.RefAttributes<HTMLDivElement>>;
package/native/index.js CHANGED
@@ -238,6 +238,8 @@ var Text = ({
238
238
  numberOfLines,
239
239
  id,
240
240
  role,
241
+ testID,
242
+ "data-testid": dataTestId,
241
243
  style: styleProp,
242
244
  ...props
243
245
  }) => {
@@ -267,7 +269,7 @@ var Text = ({
267
269
  {
268
270
  style: baseStyle,
269
271
  numberOfLines,
270
- testID: id,
272
+ testID: dataTestId || testID || id,
271
273
  accessibilityRole,
272
274
  children
273
275
  }
@@ -278,7 +280,13 @@ var Text = ({
278
280
  var import_react = __toESM(require("react"));
279
281
  var import_react_native3 = require("react-native");
280
282
  var import_jsx_runtime3 = require("react/jsx-runtime");
281
- var Icon = ({ children, color, size }) => {
283
+ var Icon = ({
284
+ children,
285
+ color,
286
+ size,
287
+ testID,
288
+ "data-testid": dataTestId
289
+ }) => {
282
290
  const style = {
283
291
  width: typeof size === "number" ? size : void 0,
284
292
  height: typeof size === "number" ? size : void 0,
@@ -295,7 +303,7 @@ var Icon = ({ children, color, size }) => {
295
303
  }
296
304
  return child;
297
305
  });
298
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style, children: childrenWithProps });
306
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style, testID: dataTestId || testID, children: childrenWithProps });
299
307
  };
300
308
 
301
309
  // src/MultiSelect.tsx
@@ -1050,6 +1058,7 @@ var BaseIcon = ({
1050
1058
  className,
1051
1059
  style,
1052
1060
  "data-testid": testId,
1061
+ testID,
1053
1062
  "aria-label": ariaLabel,
1054
1063
  "aria-hidden": ariaHidden
1055
1064
  }) => {
@@ -1062,7 +1071,7 @@ var BaseIcon = ({
1062
1071
  $color: color,
1063
1072
  className,
1064
1073
  style,
1065
- "data-testid": testId,
1074
+ "data-testid": testId || testID,
1066
1075
  role: ariaLabel ? "img" : void 0,
1067
1076
  "aria-label": ariaLabel,
1068
1077
  "aria-hidden": ariaHidden != null ? ariaHidden : ariaLabel ? void 0 : true,
@@ -1257,8 +1266,13 @@ var StyledIcon2 = (0, import_styled_components2.default)(FilteredDiv)`
1257
1266
  stroke: currentColor;
1258
1267
  }
1259
1268
  `;
1260
- var Icon3 = ({ children, ...props }) => {
1261
- return /* @__PURE__ */ (0, import_jsx_runtime725.jsx)(StyledIcon2, { ...props, children });
1269
+ var Icon3 = ({
1270
+ children,
1271
+ testID,
1272
+ "data-testid": dataTestId,
1273
+ ...props
1274
+ }) => {
1275
+ return /* @__PURE__ */ (0, import_jsx_runtime725.jsx)(StyledIcon2, { "data-testid": dataTestId || testID, ...props, children });
1262
1276
  };
1263
1277
  var X2 = (props) => /* @__PURE__ */ (0, import_jsx_runtime726.jsx)(Icon3, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime726.jsx)(X, { size: "100%" }) });
1264
1278
 
@@ -1461,7 +1475,8 @@ var Box2 = import_react5.default.forwardRef(
1461
1475
  top: typeof props.top === "number" ? `${props.top}px` : props.top,
1462
1476
  left: typeof props.left === "number" ? `${props.left}px` : props.left,
1463
1477
  right: typeof props.right === "number" ? `${props.right}px` : props.right,
1464
- bottom: typeof props.bottom === "number" ? `${props.bottom}px` : props.bottom
1478
+ bottom: typeof props.bottom === "number" ? `${props.bottom}px` : props.bottom,
1479
+ ...props.style
1465
1480
  }
1466
1481
  }
1467
1482
  );
@@ -1514,6 +1529,8 @@ var Text2 = ({
1514
1529
  className,
1515
1530
  id,
1516
1531
  role,
1532
+ testID,
1533
+ "data-testid": dataTestId,
1517
1534
  numberOfLines: _numberOfLines,
1518
1535
  ...props
1519
1536
  }) => {
@@ -1524,7 +1541,8 @@ var Text2 = ({
1524
1541
  style,
1525
1542
  className,
1526
1543
  id,
1527
- role
1544
+ role,
1545
+ "data-testid": dataTestId || testID
1528
1546
  }
1529
1547
  );
1530
1548
  };
@@ -1544,8 +1562,13 @@ var StyledIcon3 = (0, import_styled_components5.default)(FilteredDiv22)`
1544
1562
  stroke: currentColor;
1545
1563
  }
1546
1564
  `;
1547
- var Icon4 = ({ children, ...props }) => {
1548
- return /* @__PURE__ */ (0, import_jsx_runtime729.jsx)(StyledIcon3, { ...props, children });
1565
+ var Icon4 = ({
1566
+ children,
1567
+ testID,
1568
+ "data-testid": dataTestId,
1569
+ ...props
1570
+ }) => {
1571
+ return /* @__PURE__ */ (0, import_jsx_runtime729.jsx)(StyledIcon3, { "data-testid": dataTestId || testID, ...props, children });
1549
1572
  };
1550
1573
  var Tag = ({
1551
1574
  size = "md",
@@ -1555,6 +1578,7 @@ var Tag = ({
1555
1578
  iconLeft,
1556
1579
  iconRight,
1557
1580
  onRemove,
1581
+ testID,
1558
1582
  themeMode,
1559
1583
  themeProductContext
1560
1584
  }) => {
@@ -1618,6 +1642,7 @@ var Tag = ({
1618
1642
  return /* @__PURE__ */ (0, import_jsx_runtime730.jsxs)(
1619
1643
  Box2,
1620
1644
  {
1645
+ testID,
1621
1646
  backgroundColor: bg,
1622
1647
  borderRadius: sizeStyles.radius,
1623
1648
  height: sizeStyles.height,
@@ -2174,6 +2199,7 @@ var MultiSelect = (0, import_react10.forwardRef)(
2174
2199
  onTriggerPress,
2175
2200
  menuOpen = false,
2176
2201
  menuMinWidth,
2202
+ testID,
2177
2203
  themeMode,
2178
2204
  themeProductContext
2179
2205
  }, ref) => {
@@ -2231,6 +2257,7 @@ var MultiSelect = (0, import_react10.forwardRef)(
2231
2257
  return /* @__PURE__ */ (0, import_jsx_runtime733.jsxs)(
2232
2258
  Box,
2233
2259
  {
2260
+ testID,
2234
2261
  flexDirection: "column",
2235
2262
  gap: sizeStyles.fieldGap,
2236
2263
  style: externalFieldLayout,
@@ -2321,6 +2348,7 @@ var MultiSelect = (0, import_react10.forwardRef)(
2321
2348
  return /* @__PURE__ */ (0, import_jsx_runtime733.jsx)(
2322
2349
  Box,
2323
2350
  {
2351
+ testID,
2324
2352
  paddingHorizontal: sizeStyles.paddingHorizontal,
2325
2353
  paddingVertical: 8,
2326
2354
  onPress: () => {