@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 +155 -116
- package/native/index.d.mts +23 -0
- package/native/index.d.ts +23 -0
- package/native/index.js +159 -135
- package/native/index.js.map +1 -1
- package/native/index.mjs +159 -135
- package/native/index.mjs.map +1 -1
- package/package.json +5 -5
- package/web/index.d.mts +23 -0
- package/web/index.d.ts +23 -0
- package/web/index.js +159 -135
- package/web/index.js.map +1 -1
- package/web/index.mjs +159 -135
- package/web/index.mjs.map +1 -1
package/README.md
CHANGED
|
@@ -1,169 +1,208 @@
|
|
|
1
|
-
#
|
|
1
|
+
# MultiSelect
|
|
2
2
|
|
|
3
|
-
A cross-platform
|
|
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
|
-
##
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
25
|
+
## Quick start
|
|
36
26
|
|
|
37
27
|
```tsx
|
|
38
|
-
|
|
39
|
-
|
|
28
|
+
const options = [
|
|
29
|
+
{ label: 'React', value: 'react' },
|
|
30
|
+
{ label: 'Vue', value: 'vue' },
|
|
31
|
+
{ label: 'Angular', value: 'angular' },
|
|
32
|
+
];
|
|
40
33
|
|
|
41
|
-
|
|
42
|
-
const [selected, setSelected] = React.useState<string[]>(['Marketing']);
|
|
34
|
+
const [selected, setSelected] = useState<MultiSelectValue>([]);
|
|
43
35
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
###
|
|
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
|
|
63
|
-
const
|
|
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
|
-
|
|
67
|
-
<MultiSelect
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
88
|
+
Add backdrop, click-outside, and Escape handling in your layout as needed (see Storybook).
|
|
78
89
|
|
|
79
|
-
|
|
80
|
-
import { MultiSelect } from '@xsolla/xui-multi-select';
|
|
90
|
+
## API Reference
|
|
81
91
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
###
|
|
136
|
+
### Sizes
|
|
96
137
|
|
|
97
138
|
```tsx
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
103
|
-
const [skills, setSkills] = React.useState<string[]>([]);
|
|
152
|
+
### Text variant with clear-all
|
|
104
153
|
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
###
|
|
172
|
+
### Error state
|
|
125
173
|
|
|
126
174
|
```tsx
|
|
127
|
-
|
|
128
|
-
|
|
175
|
+
const options = [
|
|
176
|
+
{ label: 'Design', value: 'design' },
|
|
177
|
+
{ label: 'Engineering', value: 'engineering' },
|
|
178
|
+
{ label: 'Product', value: 'product' },
|
|
179
|
+
];
|
|
129
180
|
|
|
130
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
192
|
+
### Disabled
|
|
145
193
|
|
|
146
|
-
|
|
194
|
+
```tsx
|
|
195
|
+
const options = [
|
|
196
|
+
{ label: 'React', value: 'react' },
|
|
197
|
+
{ label: 'Vue', value: 'vue' },
|
|
198
|
+
{ label: 'Angular', value: 'angular' },
|
|
199
|
+
];
|
|
147
200
|
|
|
148
|
-
|
|
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
|
-
-
|
|
168
|
-
-
|
|
169
|
-
-
|
|
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.
|
package/native/index.d.mts
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>>;
|
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>>;
|