@xsolla/xui-select 0.158.0 → 0.159.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,17 +13,17 @@ npm install @xsolla/xui-select
13
13
  ### Basic Select
14
14
 
15
15
  ```tsx
16
- import * as React from 'react';
17
- import { Select } from '@xsolla/xui-select';
16
+ import * as React from "react";
17
+ import { Select } from "@xsolla/xui-select";
18
18
 
19
19
  export default function BasicSelect() {
20
- const [value, setValue] = React.useState('');
20
+ const [value, setValue] = React.useState("");
21
21
 
22
22
  return (
23
23
  <Select
24
24
  value={value}
25
25
  onChange={setValue}
26
- options={['Option 1', 'Option 2', 'Option 3']}
26
+ options={["Option 1", "Option 2", "Option 3"]}
27
27
  placeholder="Select an option"
28
28
  />
29
29
  );
@@ -33,18 +33,24 @@ export default function BasicSelect() {
33
33
  ### Select with Label
34
34
 
35
35
  ```tsx
36
- import * as React from 'react';
37
- import { Select } from '@xsolla/xui-select';
36
+ import * as React from "react";
37
+ import { Select } from "@xsolla/xui-select";
38
38
 
39
39
  export default function LabeledSelect() {
40
- const [country, setCountry] = React.useState('');
40
+ const [country, setCountry] = React.useState("");
41
41
 
42
42
  return (
43
43
  <Select
44
44
  label="Country"
45
45
  value={country}
46
46
  onChange={setCountry}
47
- options={['United States', 'Canada', 'United Kingdom', 'Germany', 'France']}
47
+ options={[
48
+ "United States",
49
+ "Canada",
50
+ "United Kingdom",
51
+ "Germany",
52
+ "France",
53
+ ]}
48
54
  placeholder="Select a country"
49
55
  />
50
56
  );
@@ -54,16 +60,16 @@ export default function LabeledSelect() {
54
60
  ### Select with Object Options
55
61
 
56
62
  ```tsx
57
- import * as React from 'react';
58
- import { Select } from '@xsolla/xui-select';
63
+ import * as React from "react";
64
+ import { Select } from "@xsolla/xui-select";
59
65
 
60
66
  export default function ObjectOptionsSelect() {
61
- const [status, setStatus] = React.useState('');
67
+ const [status, setStatus] = React.useState("");
62
68
 
63
69
  const options = [
64
- { label: 'Active', value: 'active' },
65
- { label: 'Pending', value: 'pending' },
66
- { label: 'Inactive', value: 'inactive' },
70
+ { label: "Active", value: "active" },
71
+ { label: "Pending", value: "pending" },
72
+ { label: "Inactive", value: "inactive" },
67
73
  ];
68
74
 
69
75
  return (
@@ -81,14 +87,14 @@ export default function ObjectOptionsSelect() {
81
87
  ### Select Sizes
82
88
 
83
89
  ```tsx
84
- import * as React from 'react';
85
- import { Select } from '@xsolla/xui-select';
90
+ import * as React from "react";
91
+ import { Select } from "@xsolla/xui-select";
86
92
 
87
93
  export default function SelectSizes() {
88
- const options = ['Small', 'Medium', 'Large'];
94
+ const options = ["Small", "Medium", "Large"];
89
95
 
90
96
  return (
91
- <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
97
+ <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
92
98
  <Select size="xs" options={options} placeholder="Extra Small" />
93
99
  <Select size="sm" options={options} placeholder="Small" />
94
100
  <Select size="md" options={options} placeholder="Medium (default)" />
@@ -102,15 +108,15 @@ export default function SelectSizes() {
102
108
  ### Select with Icons
103
109
 
104
110
  ```tsx
105
- import * as React from 'react';
106
- import { Select } from '@xsolla/xui-select';
107
- import { Globe } from '@xsolla/xui-icons-base';
111
+ import * as React from "react";
112
+ import { Select } from "@xsolla/xui-select";
113
+ import { Globe } from "@xsolla/xui-icons-base";
108
114
 
109
115
  export default function SelectWithIcons() {
110
116
  return (
111
117
  <Select
112
118
  iconLeft={<Globe />}
113
- options={['English', 'Spanish', 'French', 'German']}
119
+ options={["English", "Spanish", "French", "German"]}
114
120
  placeholder="Select language"
115
121
  />
116
122
  );
@@ -122,20 +128,20 @@ export default function SelectWithIcons() {
122
128
  Import the component and use it directly:
123
129
 
124
130
  ```jsx
125
- import { Select } from '@xsolla/xui-select';
131
+ import { Select } from "@xsolla/xui-select";
126
132
 
127
133
  <Select
128
- label="Field Label" // Optional label above select
129
- value={value} // Controlled selected value
130
- onChange={setValue} // Value change handler
131
- options={['A', 'B', 'C']} // Array of options (strings or objects)
132
- placeholder="Select..." // Placeholder text
133
- iconLeft={<Icon />} // Optional left icon
134
- iconRight={<Icon />} // Optional right icon (default: ChevronDown)
135
- filled // Whether to show filled background
136
- state="error" // State: "default" | "disable" | "error"
137
- errorMessage="Error message" // Error message (shown when state="error")
138
- />
134
+ label="Field Label" // Optional label above select
135
+ value={value} // Controlled selected value
136
+ onChange={setValue} // Value change handler
137
+ options={["A", "B", "C"]} // Array of options (strings or objects)
138
+ placeholder="Select..." // Placeholder text
139
+ iconLeft={<Icon />} // Optional left icon
140
+ iconRight={<Icon />} // Optional right icon (default: ChevronDown)
141
+ filled // Whether to show filled background
142
+ state="error" // State: "default" | "disable" | "error"
143
+ errorMessage="Error message" // Error message (shown when state="error")
144
+ />;
139
145
  ```
140
146
 
141
147
  ## Examples
@@ -143,8 +149,8 @@ import { Select } from '@xsolla/xui-select';
143
149
  ### Error State
144
150
 
145
151
  ```tsx
146
- import * as React from 'react';
147
- import { Select } from '@xsolla/xui-select';
152
+ import * as React from "react";
153
+ import { Select } from "@xsolla/xui-select";
148
154
 
149
155
  export default function ErrorSelect() {
150
156
  return (
@@ -152,7 +158,7 @@ export default function ErrorSelect() {
152
158
  label="Required Field"
153
159
  state="error"
154
160
  errorMessage="Please select an option"
155
- options={['Option 1', 'Option 2', 'Option 3']}
161
+ options={["Option 1", "Option 2", "Option 3"]}
156
162
  placeholder="Select an option"
157
163
  />
158
164
  );
@@ -162,8 +168,8 @@ export default function ErrorSelect() {
162
168
  ### Disabled Select
163
169
 
164
170
  ```tsx
165
- import * as React from 'react';
166
- import { Select } from '@xsolla/xui-select';
171
+ import * as React from "react";
172
+ import { Select } from "@xsolla/xui-select";
167
173
 
168
174
  export default function DisabledSelect() {
169
175
  return (
@@ -171,7 +177,7 @@ export default function DisabledSelect() {
171
177
  label="Disabled Field"
172
178
  value="Option 1"
173
179
  state="disable"
174
- options={['Option 1', 'Option 2', 'Option 3']}
180
+ options={["Option 1", "Option 2", "Option 3"]}
175
181
  />
176
182
  );
177
183
  }
@@ -180,14 +186,14 @@ export default function DisabledSelect() {
180
186
  ### Unfilled/Transparent Background
181
187
 
182
188
  ```tsx
183
- import * as React from 'react';
184
- import { Select } from '@xsolla/xui-select';
189
+ import * as React from "react";
190
+ import { Select } from "@xsolla/xui-select";
185
191
 
186
192
  export default function UnfilledSelect() {
187
193
  return (
188
194
  <Select
189
195
  filled={false}
190
- options={['Option 1', 'Option 2', 'Option 3']}
196
+ options={["Option 1", "Option 2", "Option 3"]}
191
197
  placeholder="Transparent background"
192
198
  />
193
199
  );
@@ -197,59 +203,85 @@ export default function UnfilledSelect() {
197
203
  ### Icon Only Select
198
204
 
199
205
  ```tsx
200
- import * as React from 'react';
201
- import { Select } from '@xsolla/xui-select';
202
- import { Settings } from '@xsolla/xui-icons';
206
+ import * as React from "react";
207
+ import { Select } from "@xsolla/xui-select";
208
+ import { Settings } from "@xsolla/xui-icons";
203
209
 
204
210
  export default function IconOnlySelect() {
205
211
  return (
206
212
  <Select
207
213
  iconOnly
208
214
  iconLeft={<Settings />}
209
- options={['Settings', 'Preferences', 'Advanced']}
215
+ options={["Settings", "Preferences", "Advanced"]}
210
216
  />
211
217
  );
212
218
  }
213
219
  ```
214
220
 
221
+ ### Full Width Select
222
+
223
+ ```tsx
224
+ import * as React from "react";
225
+ import { Select } from "@xsolla/xui-select";
226
+
227
+ export default function FullWidthSelect() {
228
+ return (
229
+ <div style={{ display: "flex", gap: 16 }}>
230
+ <Select
231
+ fullWidth
232
+ options={["Option 1", "Option 2", "Option 3"]}
233
+ placeholder="Stretches to fill container"
234
+ />
235
+ <Select
236
+ fullWidth={false}
237
+ options={["Option 1", "Option 2", "Option 3"]}
238
+ placeholder="Intrinsic width"
239
+ />
240
+ </div>
241
+ );
242
+ }
243
+ ```
244
+
215
245
  ### Form with Select
216
246
 
217
247
  ```tsx
218
- import * as React from 'react';
219
- import { Select } from '@xsolla/xui-select';
220
- import { Input } from '@xsolla/xui-input';
221
- import { Button } from '@xsolla/xui-button';
248
+ import * as React from "react";
249
+ import { Select } from "@xsolla/xui-select";
250
+ import { Input } from "@xsolla/xui-input";
251
+ import { Button } from "@xsolla/xui-button";
222
252
 
223
253
  export default function FormWithSelect() {
224
254
  const [form, setForm] = React.useState({
225
- name: '',
226
- category: '',
227
- priority: '',
255
+ name: "",
256
+ category: "",
257
+ priority: "",
228
258
  });
229
259
 
230
260
  return (
231
- <div style={{ display: 'flex', flexDirection: 'column', gap: 16, width: 300 }}>
261
+ <div
262
+ style={{ display: "flex", flexDirection: "column", gap: 16, width: 300 }}
263
+ >
232
264
  <Input
233
265
  label="Task Name"
234
266
  value={form.name}
235
- onChangeText={(name) => setForm(prev => ({ ...prev, name }))}
267
+ onChangeText={(name) => setForm((prev) => ({ ...prev, name }))}
236
268
  placeholder="Enter task name"
237
269
  />
238
270
  <Select
239
271
  label="Category"
240
272
  value={form.category}
241
- onChange={(category) => setForm(prev => ({ ...prev, category }))}
242
- options={['Development', 'Design', 'Marketing', 'Sales']}
273
+ onChange={(category) => setForm((prev) => ({ ...prev, category }))}
274
+ options={["Development", "Design", "Marketing", "Sales"]}
243
275
  placeholder="Select category"
244
276
  />
245
277
  <Select
246
278
  label="Priority"
247
279
  value={form.priority}
248
- onChange={(priority) => setForm(prev => ({ ...prev, priority }))}
280
+ onChange={(priority) => setForm((prev) => ({ ...prev, priority }))}
249
281
  options={[
250
- { label: 'High', value: 'high' },
251
- { label: 'Medium', value: 'medium' },
252
- { label: 'Low', value: 'low' },
282
+ { label: "High", value: "high" },
283
+ { label: "Medium", value: "medium" },
284
+ { label: "Low", value: "low" },
253
285
  ]}
254
286
  placeholder="Select priority"
255
287
  />
@@ -267,33 +299,35 @@ The main select component. Renders a button trigger with dropdown menu.
267
299
 
268
300
  **Select Props:**
269
301
 
270
- | Prop | Type | Default | Description |
271
- | :--- | :--- | :------ | :---------- |
272
- | value | `string` | - | The controlled selected value. |
273
- | placeholder | `string` | `"Select"` | Placeholder text when no value selected. |
274
- | onPress | `() => void` | - | Callback when select trigger is pressed. |
275
- | size | `"xl" \| "lg" \| "md" \| "sm" \| "xs"` | `"md"` | The size of the select. |
276
- | state | `"default" \| "hover" \| "focus" \| "disable" \| "error"` | `"default"` | The visual state of the select. |
277
- | label | `string` | - | Label text displayed above the select. |
278
- | disabled | `boolean` | `false` | Whether the select is disabled. Also triggered when `state="disable"`. |
279
- | errorMessage | `string` | - | Error message displayed below the select (also sets error styling). |
280
- | iconLeft | `ReactNode` | - | Icon displayed on the left side. |
281
- | iconRight | `ReactNode` | `<ChevronDown />` | Icon displayed on the right side. |
282
- | filled | `boolean` | `true` | Whether to show filled background. |
283
- | iconOnly | `boolean` | `false` | Whether to show only icon without text. |
284
- | options | `(string \| SelectOption)[]` | `[]` | Array of options to display. |
285
- | onChange | `(value: string) => void` | - | Callback when value changes. |
286
- | searchable | `boolean` | `false` | Whether to show a search input in the dropdown. |
287
- | searchPlaceholder | `string` | `"Search"` | Placeholder text for the search input. |
288
- | noOptionsMessage | `string` | `"No results"` | Message shown when no options match the search. |
289
- | maxHeight | `number` | `300` | Maximum height of the dropdown in pixels. |
302
+ | Prop | Type | Default | Description |
303
+ | :---------------- | :-------------------------------------------------------- | :---------------- | :------------------------------------------------------------------------------------------------------------ |
304
+ | `testID` | `string` | | Test ID for testing frameworks. On web this renders as `data-testid`; on React Native it renders as `testID`. |
305
+ | value | `string` | - | The controlled selected value. |
306
+ | placeholder | `string` | `"Select"` | Placeholder text when no value selected. |
307
+ | onPress | `() => void` | - | Callback when select trigger is pressed. |
308
+ | size | `"xl" \| "lg" \| "md" \| "sm" \| "xs"` | `"md"` | The size of the select. |
309
+ | state | `"default" \| "hover" \| "focus" \| "disable" \| "error"` | `"default"` | The visual state of the select. |
310
+ | label | `string` | - | Label text displayed above the select. |
311
+ | disabled | `boolean` | `false` | Whether the select is disabled. Also triggered when `state="disable"`. |
312
+ | errorMessage | `string` | - | Error message displayed below the select (also sets error styling). |
313
+ | iconLeft | `ReactNode` | - | Icon displayed on the left side. |
314
+ | iconRight | `ReactNode` | `<ChevronDown />` | Icon displayed on the right side. |
315
+ | filled | `boolean` | `true` | Whether to show filled background. |
316
+ | iconOnly | `boolean` | `false` | Whether to show only icon without text. |
317
+ | options | `(string \| SelectOption)[]` | `[]` | Array of options to display. |
318
+ | onChange | `(value: string) => void` | - | Callback when value changes. |
319
+ | searchable | `boolean` | `false` | Whether to show a search input in the dropdown. |
320
+ | searchPlaceholder | `string` | `"Search"` | Placeholder text for the search input. |
321
+ | noOptionsMessage | `string` | `"No results"` | Message shown when no options match the search. |
322
+ | maxHeight | `number` | `300` | Maximum height of the dropdown in pixels. |
323
+ | fullWidth | `boolean` | `true` | Whether the select should stretch to fill the full width of its container. |
290
324
 
291
325
  **SelectOption Type:**
292
326
 
293
327
  ```typescript
294
328
  interface SelectOption {
295
- label: string; // Display text
296
- value: any; // Value to be selected
329
+ label: string; // Display text
330
+ value: any; // Value to be selected
297
331
  disabled?: boolean; // Whether this option is disabled
298
332
  }
299
333
  ```
@@ -302,13 +336,13 @@ interface SelectOption {
302
336
 
303
337
  The Select component works on React Native with the following differences:
304
338
 
305
- | Feature | Web | React Native |
306
- |---------|-----|-------------|
307
- | Searchable dropdown | Supported (`searchable` prop) | **Not available** — prop is ignored |
308
- | Dropdown shadow | CSS `box-shadow` | Android `elevation: 4` |
309
- | Scroll behavior | `overflowY: auto` | Native `ScrollView` |
310
- | Cursor styles | `pointer` / `not-allowed` | Ignored |
311
- | Click-outside dismiss | `document.addEventListener` | Not active (guarded by `isNative`) |
339
+ | Feature | Web | React Native |
340
+ | --------------------- | ----------------------------- | ----------------------------------- |
341
+ | Searchable dropdown | Supported (`searchable` prop) | **Not available** — prop is ignored |
342
+ | Dropdown shadow | CSS `box-shadow` | Android `elevation: 4` |
343
+ | Scroll behavior | `overflowY: auto` | Native `ScrollView` |
344
+ | Cursor styles | `pointer` / `not-allowed` | Ignored |
345
+ | Click-outside dismiss | `document.addEventListener` | Not active (guarded by `isNative`) |
312
346
 
313
347
  ## Accessibility
314
348
 
@@ -25,6 +25,10 @@ interface SelectProps extends ThemeOverrideProps {
25
25
  searchPlaceholder?: string;
26
26
  noOptionsMessage?: string;
27
27
  maxHeight?: number;
28
+ /** Whether the select should stretch to fill the full width of its container */
29
+ fullWidth?: boolean;
30
+ /** Test ID for testing frameworks */
31
+ testID?: string;
28
32
  }
29
33
  declare const Select: React.FC<SelectProps>;
30
34
 
package/native/index.d.ts CHANGED
@@ -25,6 +25,10 @@ interface SelectProps extends ThemeOverrideProps {
25
25
  searchPlaceholder?: string;
26
26
  noOptionsMessage?: string;
27
27
  maxHeight?: number;
28
+ /** Whether the select should stretch to fill the full width of its container */
29
+ fullWidth?: boolean;
30
+ /** Test ID for testing frameworks */
31
+ testID?: string;
28
32
  }
29
33
  declare const Select: React.FC<SelectProps>;
30
34
 
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
  // ../../foundation/primitives-native/src/index.tsx
@@ -325,6 +333,8 @@ var Select = ({
325
333
  searchPlaceholder = "Search",
326
334
  noOptionsMessage = "No results",
327
335
  maxHeight = 300,
336
+ fullWidth = true,
337
+ testID,
328
338
  themeMode,
329
339
  themeProductContext
330
340
  }) => {
@@ -432,10 +442,11 @@ var Select = ({
432
442
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
433
443
  Box,
434
444
  {
445
+ testID,
435
446
  ref: containerRef,
436
447
  flexDirection: "column",
437
448
  gap: sizeStyles.fieldGap,
438
- width: "100%",
449
+ width: fullWidth ? "100%" : void 0,
439
450
  position: "relative",
440
451
  children: [
441
452
  label && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -591,6 +602,7 @@ var Select = ({
591
602
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
592
603
  Box,
593
604
  {
605
+ testID,
594
606
  ref: isSelected ? selectedItemRef : void 0,
595
607
  paddingVertical: sizeStyles.paddingVertical,
596
608
  paddingHorizontal: sizeStyles.paddingHorizontal,