@xsolla/xui-multi-select 0.158.0 → 0.159.0-pr290.1779453807

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
 
@@ -1515,6 +1529,8 @@ var Text2 = ({
1515
1529
  className,
1516
1530
  id,
1517
1531
  role,
1532
+ testID,
1533
+ "data-testid": dataTestId,
1518
1534
  numberOfLines: _numberOfLines,
1519
1535
  ...props
1520
1536
  }) => {
@@ -1525,7 +1541,8 @@ var Text2 = ({
1525
1541
  style,
1526
1542
  className,
1527
1543
  id,
1528
- role
1544
+ role,
1545
+ "data-testid": dataTestId || testID
1529
1546
  }
1530
1547
  );
1531
1548
  };
@@ -1545,8 +1562,13 @@ var StyledIcon3 = (0, import_styled_components5.default)(FilteredDiv22)`
1545
1562
  stroke: currentColor;
1546
1563
  }
1547
1564
  `;
1548
- var Icon4 = ({ children, ...props }) => {
1549
- 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 });
1550
1572
  };
1551
1573
  var Tag = ({
1552
1574
  size = "md",
@@ -1556,6 +1578,7 @@ var Tag = ({
1556
1578
  iconLeft,
1557
1579
  iconRight,
1558
1580
  onRemove,
1581
+ testID,
1559
1582
  themeMode,
1560
1583
  themeProductContext
1561
1584
  }) => {
@@ -1619,6 +1642,7 @@ var Tag = ({
1619
1642
  return /* @__PURE__ */ (0, import_jsx_runtime730.jsxs)(
1620
1643
  Box2,
1621
1644
  {
1645
+ testID,
1622
1646
  backgroundColor: bg,
1623
1647
  borderRadius: sizeStyles.radius,
1624
1648
  height: sizeStyles.height,
@@ -2175,6 +2199,7 @@ var MultiSelect = (0, import_react10.forwardRef)(
2175
2199
  onTriggerPress,
2176
2200
  menuOpen = false,
2177
2201
  menuMinWidth,
2202
+ testID,
2178
2203
  themeMode,
2179
2204
  themeProductContext
2180
2205
  }, ref) => {
@@ -2232,6 +2257,7 @@ var MultiSelect = (0, import_react10.forwardRef)(
2232
2257
  return /* @__PURE__ */ (0, import_jsx_runtime733.jsxs)(
2233
2258
  Box,
2234
2259
  {
2260
+ testID,
2235
2261
  flexDirection: "column",
2236
2262
  gap: sizeStyles.fieldGap,
2237
2263
  style: externalFieldLayout,
@@ -2302,7 +2328,7 @@ var MultiSelect = (0, import_react10.forwardRef)(
2302
2328
  backgroundColor: theme.colors.background.secondary,
2303
2329
  borderColor: theme.colors.border.secondary,
2304
2330
  borderWidth: 1,
2305
- borderRadius: theme.radius.button,
2331
+ borderRadius: theme.shape.input[size].borderRadius,
2306
2332
  paddingVertical: 4,
2307
2333
  style: {
2308
2334
  position: "absolute",
@@ -2322,6 +2348,7 @@ var MultiSelect = (0, import_react10.forwardRef)(
2322
2348
  return /* @__PURE__ */ (0, import_jsx_runtime733.jsx)(
2323
2349
  Box,
2324
2350
  {
2351
+ testID,
2325
2352
  paddingHorizontal: sizeStyles.paddingHorizontal,
2326
2353
  paddingVertical: 8,
2327
2354
  onPress: () => {