@xsolla/xui-multi-select 0.149.1 → 0.151.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
@@ -1,169 +1,208 @@
1
- # Multi Select
1
+ # MultiSelect
2
2
 
3
- A cross-platform React multi-select component that allows users to select multiple options from a dropdown list with checkboxes.
3
+ A cross-platform multi-select control that lets users pick multiple options from a dropdown list.
4
4
 
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
8
  npm install @xsolla/xui-multi-select
9
- # or
10
- yarn add @xsolla/xui-multi-select
11
9
  ```
12
10
 
13
- ## Demo
14
-
15
- ### Basic Multi Select
11
+ ## Imports
16
12
 
17
13
  ```tsx
18
- import * as React from 'react';
19
14
  import { MultiSelect } from '@xsolla/xui-multi-select';
20
-
21
- export default function BasicMultiSelect() {
22
- const [selected, setSelected] = React.useState<string[]>([]);
23
-
24
- return (
25
- <MultiSelect
26
- options={['React', 'Vue', 'Angular', 'Svelte']}
27
- value={selected}
28
- onChange={setSelected}
29
- placeholder="Select frameworks"
30
- />
31
- );
32
- }
15
+ import type {
16
+ MultiSelectProps,
17
+ MultiSelectOption,
18
+ MultiSelectValue,
19
+ MultiSelectVariant,
20
+ MultiSelectSize,
21
+ MultiSelectState,
22
+ } from '@xsolla/xui-multi-select';
33
23
  ```
34
24
 
35
- ### With Label
25
+ ## Quick start
36
26
 
37
27
  ```tsx
38
- import * as React from 'react';
39
- import { MultiSelect } from '@xsolla/xui-multi-select';
28
+ const options = [
29
+ { label: 'React', value: 'react' },
30
+ { label: 'Vue', value: 'vue' },
31
+ { label: 'Angular', value: 'angular' },
32
+ ];
40
33
 
41
- export default function LabeledMultiSelect() {
42
- const [selected, setSelected] = React.useState<string[]>(['Marketing']);
34
+ const [selected, setSelected] = useState<MultiSelectValue>([]);
43
35
 
44
- return (
45
- <MultiSelect
46
- label="Departments"
47
- options={['Engineering', 'Marketing', 'Sales', 'Design', 'HR']}
48
- value={selected}
49
- onChange={setSelected}
50
- placeholder="Select departments"
51
- />
52
- );
53
- }
36
+ <MultiSelect
37
+ label="Frameworks"
38
+ options={options}
39
+ value={selected}
40
+ onChange={setSelected}
41
+ placeholder="Select frameworks"
42
+ />;
54
43
  ```
55
44
 
56
- ### Multi Select Sizes
45
+ ### External panel (B2B grouped select)
46
+
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.
57
48
 
58
49
  ```tsx
59
50
  import * as React from 'react';
60
51
  import { MultiSelect } from '@xsolla/xui-multi-select';
52
+ import {
53
+ GroupSelect,
54
+ GROUP_SELECT_MIN_PANEL_WIDTH,
55
+ type GroupSelectGroup,
56
+ } from '@xsolla/xui-b2b-group-select';
57
+
58
+ const groups: GroupSelectGroup[] = [/* ... */];
59
+ const flatOptions = groups.flatMap((g) =>
60
+ g.items.map((it) => ({ value: it.id, label: it.label }))
61
+ );
61
62
 
62
- export default function MultiSelectSizes() {
63
- const options = ['Option 1', 'Option 2', 'Option 3'];
63
+ export default function GroupedFieldShell() {
64
+ const [value, setValue] = React.useState<string[]>([]);
65
+ const [open, setOpen] = React.useState(false);
64
66
 
65
67
  return (
66
- <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
67
- <MultiSelect options={options} size="xs" placeholder="Extra Small" />
68
- <MultiSelect options={options} size="sm" placeholder="Small" />
69
- <MultiSelect options={options} size="md" placeholder="Medium" />
70
- <MultiSelect options={options} size="lg" placeholder="Large" />
71
- <MultiSelect options={options} size="xl" placeholder="Extra Large" />
72
- </div>
68
+ <>
69
+ <MultiSelect
70
+ options={flatOptions}
71
+ value={value}
72
+ onChange={(v) => setValue(v.map(String))}
73
+ placeholder="Select regions"
74
+ size="sm"
75
+ dropdownMenu={false}
76
+ menuOpen={open}
77
+ menuMinWidth={GROUP_SELECT_MIN_PANEL_WIDTH}
78
+ onTriggerPress={() => setOpen((o) => !o)}
79
+ />
80
+ {open && (
81
+ <GroupSelect groups={groups} value={value} onChange={setValue} />
82
+ )}
83
+ </>
73
84
  );
74
85
  }
75
86
  ```
76
87
 
77
- ## Anatomy
88
+ Add backdrop, click-outside, and Escape handling in your layout as needed (see Storybook).
78
89
 
79
- ```jsx
80
- import { MultiSelect } from '@xsolla/xui-multi-select';
90
+ ## API Reference
81
91
 
82
- <MultiSelect
83
- options={['a', 'b', 'c']} // Available options
84
- value={selectedArray} // Currently selected values
85
- onChange={setSelected} // Selection change handler
86
- placeholder="Select..." // Placeholder text
87
- label="Label" // Label above select
88
- size="md" // Size variant
89
- disabled={false} // Disabled state
90
- />
92
+ ### `<MultiSelect>`
93
+
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. |
116
+
117
+ Inherits `ThemeOverrideProps` (`themeMode`, `themeProductContext`).
118
+
119
+ ### Types
120
+
121
+ ```ts
122
+ 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
+
127
+ interface MultiSelectOption {
128
+ label: ReactNode;
129
+ value: string | number;
130
+ disabled?: boolean;
131
+ }
91
132
  ```
92
133
 
93
134
  ## Examples
94
135
 
95
- ### Form Integration
136
+ ### Sizes
96
137
 
97
138
  ```tsx
98
- import * as React from 'react';
99
- import { MultiSelect } from '@xsolla/xui-multi-select';
100
- import { Button } from '@xsolla/xui-button';
139
+ const options = [
140
+ { label: 'React', value: 'react' },
141
+ { label: 'Vue', value: 'vue' },
142
+ { label: 'Angular', value: 'angular' },
143
+ ];
144
+
145
+ <MultiSelect options={options} size="xs" placeholder="Extra small" />
146
+ <MultiSelect options={options} size="sm" placeholder="Small" />
147
+ <MultiSelect options={options} size="md" placeholder="Medium" />
148
+ <MultiSelect options={options} size="lg" placeholder="Large" />
149
+ <MultiSelect options={options} size="xl" placeholder="Extra large" />
150
+ ```
101
151
 
102
- export default function FormMultiSelect() {
103
- const [skills, setSkills] = React.useState<string[]>([]);
152
+ ### Text variant with clear-all
104
153
 
105
- const handleSubmit = () => {
106
- console.log('Selected skills:', skills);
107
- };
154
+ ```tsx
155
+ const options = [
156
+ { label: 'React', value: 'react' },
157
+ { label: 'Vue', value: 'vue' },
158
+ { label: 'Angular', value: 'angular' },
159
+ ];
108
160
 
109
- return (
110
- <div style={{ display: 'flex', flexDirection: 'column', gap: 16, width: 300 }}>
111
- <MultiSelect
112
- label="Skills"
113
- options={['JavaScript', 'TypeScript', 'Python', 'Go', 'Rust', 'Java']}
114
- value={skills}
115
- onChange={setSkills}
116
- placeholder="Select your skills"
117
- />
118
- <Button onPress={handleSubmit}>Submit</Button>
119
- </div>
120
- );
121
- }
161
+ const [selected, setSelected] = useState<MultiSelectValue>([]);
162
+
163
+ <MultiSelect
164
+ options={options}
165
+ value={selected}
166
+ onChange={setSelected}
167
+ variant="text"
168
+ extraClear
169
+ />;
122
170
  ```
123
171
 
124
- ### Disabled State
172
+ ### Error state
125
173
 
126
174
  ```tsx
127
- import * as React from 'react';
128
- import { MultiSelect } from '@xsolla/xui-multi-select';
175
+ const options = [
176
+ { label: 'Design', value: 'design' },
177
+ { label: 'Engineering', value: 'engineering' },
178
+ { label: 'Product', value: 'product' },
179
+ ];
129
180
 
130
- export default function DisabledMultiSelect() {
131
- return (
132
- <MultiSelect
133
- options={['Option 1', 'Option 2', 'Option 3']}
134
- value={['Option 1']}
135
- disabled
136
- placeholder="Disabled select"
137
- />
138
- );
139
- }
140
- ```
181
+ const [skills, setSkills] = useState<MultiSelectValue>([]);
141
182
 
142
- ## API Reference
183
+ <MultiSelect
184
+ label="Skills"
185
+ options={options}
186
+ value={skills}
187
+ onChange={setSkills}
188
+ errorMessage="Please select at least one skill"
189
+ />;
190
+ ```
143
191
 
144
- ### MultiSelect
192
+ ### Disabled
145
193
 
146
- **MultiSelect Props:**
194
+ ```tsx
195
+ const options = [
196
+ { label: 'React', value: 'react' },
197
+ { label: 'Vue', value: 'vue' },
198
+ { label: 'Angular', value: 'angular' },
199
+ ];
147
200
 
148
- | Prop | Type | Default | Description |
149
- | :--- | :--- | :------ | :---------- |
150
- | options | `string[]` | - | **Required.** Available options. |
151
- | value | `string[]` | `[]` | Currently selected values. |
152
- | onChange | `(value: string[]) => void` | - | Selection change handler. |
153
- | placeholder | `string` | - | Placeholder when nothing selected. |
154
- | size | `"xs" \| "sm" \| "md" \| "lg" \| "xl"` | `"md"` | Component size. |
155
- | label | `string` | - | Label above select. |
156
- | disabled | `boolean` | `false` | Disabled state. |
157
-
158
- ## Display Behavior
159
-
160
- - When no items selected: Shows placeholder
161
- - When items selected: Shows "N selected" text
162
- - Dropdown shows checkboxes for each option
163
- - Checked items are visually indicated
201
+ <MultiSelect options={options} value={['react']} disabled />;
202
+ ```
164
203
 
165
204
  ## Accessibility
166
205
 
167
- - Uses checkbox pattern for selections
168
- - Dropdown is keyboard navigable
169
- - Selection state announced to screen readers
206
+ - Selection is rendered as a checkbox list inside the dropdown.
207
+ - The dropdown is keyboard navigable; selection state is announced to assistive technology.
208
+ - An `errorMessage` marks the control as invalid for screen readers.
@@ -83,6 +83,29 @@ interface MultiSelectProps extends ThemeOverrideProps {
83
83
  * @default 300
84
84
  */
85
85
  maxHeight?: number;
86
+ /**
87
+ * When false, the built-in options list and backdrop are not shown and the control
88
+ * does not open on click. Use with an external picker (e.g. grouped select) wired
89
+ * to the same `value` / `onChange`.
90
+ * @default true
91
+ */
92
+ dropdownMenu?: boolean;
93
+ /**
94
+ * When `dropdownMenu` is false: fired when the user activates the field (same gesture
95
+ * that would open the built-in list). Typically toggle an external panel.
96
+ */
97
+ onTriggerPress?: () => void;
98
+ /**
99
+ * When `dropdownMenu` is false: whether an external menu/panel is open — drives
100
+ * chevron direction and control layering like the built-in open state.
101
+ */
102
+ menuOpen?: boolean;
103
+ /**
104
+ * When `dropdownMenu` is false: `min-width` of the field in px so it aligns with
105
+ * a typical grouped panel (default **540**, same as `GroupSelect`). Use `0` for
106
+ * no minimum. Ignored when the built-in dropdown is enabled.
107
+ */
108
+ menuMinWidth?: number;
86
109
  }
87
110
 
88
111
  declare const MultiSelect: react.ForwardRefExoticComponent<MultiSelectProps & react.RefAttributes<HTMLDivElement>>;
package/native/index.d.ts CHANGED
@@ -83,6 +83,29 @@ interface MultiSelectProps extends ThemeOverrideProps {
83
83
  * @default 300
84
84
  */
85
85
  maxHeight?: number;
86
+ /**
87
+ * When false, the built-in options list and backdrop are not shown and the control
88
+ * does not open on click. Use with an external picker (e.g. grouped select) wired
89
+ * to the same `value` / `onChange`.
90
+ * @default true
91
+ */
92
+ dropdownMenu?: boolean;
93
+ /**
94
+ * When `dropdownMenu` is false: fired when the user activates the field (same gesture
95
+ * that would open the built-in list). Typically toggle an external panel.
96
+ */
97
+ onTriggerPress?: () => void;
98
+ /**
99
+ * When `dropdownMenu` is false: whether an external menu/panel is open — drives
100
+ * chevron direction and control layering like the built-in open state.
101
+ */
102
+ menuOpen?: boolean;
103
+ /**
104
+ * When `dropdownMenu` is false: `min-width` of the field in px so it aligns with
105
+ * a typical grouped panel (default **540**, same as `GroupSelect`). Use `0` for
106
+ * no minimum. Ignored when the built-in dropdown is enabled.
107
+ */
108
+ menuMinWidth?: number;
86
109
  }
87
110
 
88
111
  declare const MultiSelect: react.ForwardRefExoticComponent<MultiSelectProps & react.RefAttributes<HTMLDivElement>>;