@stackline/react-multiselect-dropdown 17.0.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/LICENSE +21 -0
- package/README.md +374 -0
- package/dist/index.cjs +2022 -0
- package/dist/index.d.cts +113 -0
- package/dist/index.d.ts +113 -0
- package/dist/index.js +2002 -0
- package/package.json +75 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alexandro Marques
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# @stackline/react-multiselect-dropdown
|
|
2
|
+
|
|
3
|
+
> A maintained React multiselect dropdown for React 17 applications, with controlled React state, searchable/grouped options, lazy loading hooks, custom render functions, skins, body-overlay positioning, and ADA-friendly keyboard/ARIA behavior.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@stackline/react-multiselect-dropdown)
|
|
6
|
+
[](https://www.npmjs.com/package/@stackline/react-multiselect-dropdown)
|
|
7
|
+
[](https://www.npmjs.com/package/@stackline/react-multiselect-dropdown)
|
|
8
|
+
[](https://github.com/alexandroit/react-multiselect-dropdown/blob/main/LICENSE)
|
|
9
|
+
[](https://alexandro.net/docs/react/multiselect/react-17/)
|
|
10
|
+
[](https://www.typescriptlang.org)
|
|
11
|
+
[](https://github.com/alexandroit/react-multiselect-dropdown/stargazers)
|
|
12
|
+
|
|
13
|
+
**[Documentation & Live Demos](https://alexandro.net/docs/react/multiselect/)** | **[React 17 Demo](https://alexandro.net/docs/react/multiselect/react-17/)** | **[npm](https://www.npmjs.com/package/@stackline/react-multiselect-dropdown)** | **[Issues](https://github.com/alexandroit/react-multiselect-dropdown/issues)** | **[Repository](https://github.com/alexandroit/react-multiselect-dropdown)**
|
|
14
|
+
|
|
15
|
+
**Latest tested package release:** `17.0.0` for React `17.x`
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
> **Credits:** Current maintenance, React line stewardship, publishing, and documentation by [Alexandro Paixao Marques](https://github.com/alexandroit/react-multiselect-dropdown).
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Why this library?
|
|
24
|
+
|
|
25
|
+
`@stackline/react-multiselect-dropdown` provides a maintained React multiselect component for applications that need predictable selection state, search, grouping, skins, keyboard support, and live tested examples.
|
|
26
|
+
|
|
27
|
+
The package is built around a controlled React API: pass `data`, bind `selectedItems`, receive updates through `onChange`, and customize behavior through a `settings` object. It also supports custom React render functions for option rows and selected badges, lazy loading callbacks, imperative `ref` methods, and body-overlay positioning for dialogs or clipped containers.
|
|
28
|
+
|
|
29
|
+
The current package release is `17.0.0` for React 17.x applications. It was tested in a clean React `17.0.2` application before publication to the local validation registry.
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
| Feature | Supported |
|
|
34
|
+
| :--- | :---: |
|
|
35
|
+
| React 17 tested published release line | Yes |
|
|
36
|
+
| Multi-select and single-select modes | Yes |
|
|
37
|
+
| Controlled and uncontrolled selection | Yes |
|
|
38
|
+
| Search and filter | Yes |
|
|
39
|
+
| Group by field | Yes |
|
|
40
|
+
| Custom item render functions | Yes |
|
|
41
|
+
| Custom badge render functions | Yes |
|
|
42
|
+
| Lazy loading hooks | Yes |
|
|
43
|
+
| Add-new-item from search text | Yes |
|
|
44
|
+
| Ref methods for open, close, focus, select all, and clear | Yes |
|
|
45
|
+
| Built-in `classic`, `material`, `dark`, `custom`, and `brand` skins | Yes |
|
|
46
|
+
| ADA-friendly keyboard navigation, focus states, and ARIA labels | Yes |
|
|
47
|
+
| Dialog and overflow-container support through `appendToBody` / `tagToBody` | Yes |
|
|
48
|
+
| Versioned docs builds per React line | Yes |
|
|
49
|
+
|
|
50
|
+
## Table of Contents
|
|
51
|
+
|
|
52
|
+
1. [React Version Compatibility](#react-version-compatibility)
|
|
53
|
+
2. [Installation](#installation)
|
|
54
|
+
3. [Setup](#setup)
|
|
55
|
+
4. [Styling and Skins](#styling-and-skins)
|
|
56
|
+
5. [Basic Usage](#basic-usage)
|
|
57
|
+
6. [Official React 17 Test Matrix](#official-react-17-test-matrix)
|
|
58
|
+
7. [Custom Render Functions](#custom-render-functions)
|
|
59
|
+
8. [Forms and Controlled State](#forms-and-controlled-state)
|
|
60
|
+
9. [Lazy Loading and Dynamic Data](#lazy-loading-and-dynamic-data)
|
|
61
|
+
10. [Dialogs and Overflow Containers](#dialogs-and-overflow-containers)
|
|
62
|
+
11. [Events](#events)
|
|
63
|
+
12. [Ref Methods](#ref-methods)
|
|
64
|
+
13. [Run Locally](#run-locally)
|
|
65
|
+
14. [License](#license)
|
|
66
|
+
|
|
67
|
+
## React Version Compatibility
|
|
68
|
+
|
|
69
|
+
Each package family installs on its matching React family. Keep the package family aligned with the React major used by your application.
|
|
70
|
+
|
|
71
|
+
| Package family | React family | Peer range | Tested release window | Demo link |
|
|
72
|
+
| :---: | :---: | :---: | :---: | :--- |
|
|
73
|
+
| **17.x** | **React 17 only** | **`>=17.0.0 <18.0.0`** | **17.0.0 -> 17.0.2** | [React 17 family docs](https://alexandro.net/docs/react/multiselect/react-17/) |
|
|
74
|
+
| **18.x** | **React 18 only** | **`>=18.0.0 <19.0.0`** | Planned | [React docs](https://alexandro.net/docs/react/multiselect/) |
|
|
75
|
+
| **19.x** | **React 19 only** | **`>=19.0.0 <20.0.0`** | Planned | [React docs](https://alexandro.net/docs/react/multiselect/) |
|
|
76
|
+
|
|
77
|
+
## Installation
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npm install @stackline/react-multiselect-dropdown@17.0.0 --save-exact
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Install `17.0.0` for React 17.x applications. The package includes its component styles and injects them at runtime, so no extra CSS import is required for the default experience.
|
|
84
|
+
|
|
85
|
+
## Setup
|
|
86
|
+
|
|
87
|
+
### 1. Import the component
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
import { MultiSelectDropdown } from '@stackline/react-multiselect-dropdown';
|
|
91
|
+
import type {
|
|
92
|
+
DropdownSettings,
|
|
93
|
+
MultiSelectDropdownHandle
|
|
94
|
+
} from '@stackline/react-multiselect-dropdown';
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 2. Keep selection in React state
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
const [selectedCountries, setSelectedCountries] = useState<Country[]>([]);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 3. Pass a stable settings object
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
const settings = useMemo<DropdownSettings<Country>>(
|
|
107
|
+
() => ({
|
|
108
|
+
text: 'Select countries',
|
|
109
|
+
enableSearchFilter: true,
|
|
110
|
+
primaryKey: 'id',
|
|
111
|
+
labelKey: 'itemName',
|
|
112
|
+
badgeShowLimit: 3,
|
|
113
|
+
skin: 'classic'
|
|
114
|
+
}),
|
|
115
|
+
[]
|
|
116
|
+
);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Styling and Skins
|
|
120
|
+
|
|
121
|
+
Use `settings.skin` to switch the visual mode:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
setSettings((current) => ({
|
|
125
|
+
...current,
|
|
126
|
+
skin: 'material'
|
|
127
|
+
}));
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Built-in skins:
|
|
131
|
+
|
|
132
|
+
| Skin | Usage |
|
|
133
|
+
| :--- | :--- |
|
|
134
|
+
| `classic` | Compact classic dropdown styling. |
|
|
135
|
+
| `material` | Material-style rounded controls and chips. |
|
|
136
|
+
| `dark` | Dark UI surfaces. |
|
|
137
|
+
| `custom` | CSS-variable starter skin for custom projects. |
|
|
138
|
+
| `brand` | Stackline brand skin. |
|
|
139
|
+
|
|
140
|
+
`settings.theme` is accepted as a legacy alias, but new React usage should configure only `settings.skin`.
|
|
141
|
+
|
|
142
|
+
## Basic Usage
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
import { useMemo, useState } from 'react';
|
|
146
|
+
import {
|
|
147
|
+
MultiSelectDropdown,
|
|
148
|
+
type DropdownSettings
|
|
149
|
+
} from '@stackline/react-multiselect-dropdown';
|
|
150
|
+
|
|
151
|
+
type Country = {
|
|
152
|
+
id: number;
|
|
153
|
+
itemName: string;
|
|
154
|
+
capital: string;
|
|
155
|
+
region: string;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const countries: Country[] = [
|
|
159
|
+
{ id: 1, itemName: 'Brazil', capital: 'Brasilia', region: 'South America' },
|
|
160
|
+
{ id: 2, itemName: 'Canada', capital: 'Ottawa', region: 'North America' },
|
|
161
|
+
{ id: 3, itemName: 'Portugal', capital: 'Lisbon', region: 'Europe' },
|
|
162
|
+
{ id: 4, itemName: 'United States', capital: 'Washington, DC', region: 'North America' },
|
|
163
|
+
{ id: 5, itemName: 'Argentina', capital: 'Buenos Aires', region: 'South America' },
|
|
164
|
+
{ id: 6, itemName: 'Mexico', capital: 'Mexico City', region: 'North America' }
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
export function CountrySelector() {
|
|
168
|
+
const [selectedCountries, setSelectedCountries] = useState<Country[]>([
|
|
169
|
+
countries[1]
|
|
170
|
+
]);
|
|
171
|
+
|
|
172
|
+
const settings = useMemo<DropdownSettings<Country>>(
|
|
173
|
+
() => ({
|
|
174
|
+
singleSelection: false,
|
|
175
|
+
text: 'Select countries',
|
|
176
|
+
selectAllText: 'Select all',
|
|
177
|
+
unSelectAllText: 'Clear all',
|
|
178
|
+
enableSearchFilter: true,
|
|
179
|
+
searchPlaceholderText: 'Search',
|
|
180
|
+
primaryKey: 'id',
|
|
181
|
+
labelKey: 'itemName',
|
|
182
|
+
badgeShowLimit: 4,
|
|
183
|
+
maxHeight: 260,
|
|
184
|
+
showCheckbox: true,
|
|
185
|
+
noDataLabel: 'No data',
|
|
186
|
+
skin: 'classic',
|
|
187
|
+
appendToBody: false
|
|
188
|
+
}),
|
|
189
|
+
[]
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
return (
|
|
193
|
+
<MultiSelectDropdown
|
|
194
|
+
data={countries}
|
|
195
|
+
selectedItems={selectedCountries}
|
|
196
|
+
onChange={setSelectedCountries}
|
|
197
|
+
settings={settings}
|
|
198
|
+
onSelect={(item) => console.log('selected', item)}
|
|
199
|
+
onDeSelect={(item) => console.log('removed', item)}
|
|
200
|
+
onSelectAll={(items) => console.log('selected all', items)}
|
|
201
|
+
onDeSelectAll={(items) => console.log('cleared all', items)}
|
|
202
|
+
/>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Official React 17 Test Matrix
|
|
208
|
+
|
|
209
|
+
The published React 17 release was tested in a clean React `17.0.2` application with `@stackline/react-multiselect-dropdown@17.0.0`. The docs use the same examples from that test app, including keyboard navigation, focus, ARIA behavior, badge counters, responsive action buttons, scrollable lists, and dialog-safe body overlays.
|
|
210
|
+
|
|
211
|
+
The same core scenarios are validated for the visual skins:
|
|
212
|
+
|
|
213
|
+
| # | Scenario | Main settings tested |
|
|
214
|
+
| :---: | :--- | :--- |
|
|
215
|
+
| 01 | Basic multi | `{ enableSearchFilter: true }` |
|
|
216
|
+
| 02 | All selected badges visible | `{ badgeShowLimit: 10 }` |
|
|
217
|
+
| 03 | Single selection | `{ singleSelection: true }` |
|
|
218
|
+
| 04 | Search by fields | `{ searchBy: ['itemName', 'capital'] }` |
|
|
219
|
+
| 05 | Grouped options | `{ groupBy: 'category', selectGroup: true }` |
|
|
220
|
+
| 06 | Selection limit | `{ limitSelection: 2, badgeShowLimit: 2 }` |
|
|
221
|
+
| 07 | Custom rendering | `renderItem` and `renderBadge` |
|
|
222
|
+
| 08 | Search and add item | `{ addNewItemOnFilter: true }` |
|
|
223
|
+
| 09 | Disabled state | `{ disabled: true }` |
|
|
224
|
+
| 10 | Controlled form validation | React state and derived validation |
|
|
225
|
+
| 11 | Long list with keyboard scroll | `{ maxHeight: 140 }` |
|
|
226
|
+
| 12 | Local lazy loading | `{ lazyLoading: true }` |
|
|
227
|
+
| 13 | Dialog and overflow container | `{ appendToBody: true, tagToBody: true }` |
|
|
228
|
+
| 14 | Body overlay auto direction | `{ autoPosition: true, position: 'top' }` |
|
|
229
|
+
| 15 | Ref methods | `openDropdown`, `closeDropdown`, `selectAll`, `clearSelection` |
|
|
230
|
+
|
|
231
|
+
## Custom Render Functions
|
|
232
|
+
|
|
233
|
+
Use `renderItem` for option rows and `renderBadge` for selected chips:
|
|
234
|
+
|
|
235
|
+
```tsx
|
|
236
|
+
<MultiSelectDropdown
|
|
237
|
+
data={countries}
|
|
238
|
+
selectedItems={selectedCountries}
|
|
239
|
+
onChange={setSelectedCountries}
|
|
240
|
+
settings={settings}
|
|
241
|
+
renderItem={(item, context) => (
|
|
242
|
+
<span>
|
|
243
|
+
<strong>{context.label}</strong>
|
|
244
|
+
<small>{item.capital}</small>
|
|
245
|
+
</span>
|
|
246
|
+
)}
|
|
247
|
+
renderBadge={(item) => (
|
|
248
|
+
<span>{item.itemName}</span>
|
|
249
|
+
)}
|
|
250
|
+
/>
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Forms and Controlled State
|
|
254
|
+
|
|
255
|
+
Keep the selected array in React state and derive validity from that state:
|
|
256
|
+
|
|
257
|
+
```tsx
|
|
258
|
+
const [name, setName] = useState('');
|
|
259
|
+
const [email, setEmail] = useState('');
|
|
260
|
+
const [selectedSkills, setSelectedSkills] = useState<Skill[]>([]);
|
|
261
|
+
|
|
262
|
+
const formIsValid = email.trim().length > 0 && selectedSkills.length > 0;
|
|
263
|
+
|
|
264
|
+
return (
|
|
265
|
+
<form onSubmit={(event) => event.preventDefault()}>
|
|
266
|
+
<input value={name} onChange={(event) => setName(event.target.value)} />
|
|
267
|
+
<input value={email} onChange={(event) => setEmail(event.target.value)} />
|
|
268
|
+
|
|
269
|
+
<MultiSelectDropdown
|
|
270
|
+
data={skills}
|
|
271
|
+
selectedItems={selectedSkills}
|
|
272
|
+
onChange={setSelectedSkills}
|
|
273
|
+
settings={skillSettings}
|
|
274
|
+
/>
|
|
275
|
+
|
|
276
|
+
<button type="submit" disabled={!formIsValid}>
|
|
277
|
+
Submit
|
|
278
|
+
</button>
|
|
279
|
+
</form>
|
|
280
|
+
);
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Lazy Loading and Dynamic Data
|
|
284
|
+
|
|
285
|
+
Enable lazy loading through the settings object and append more rows when the list reaches the end:
|
|
286
|
+
|
|
287
|
+
```tsx
|
|
288
|
+
const settings = {
|
|
289
|
+
text: 'Select people',
|
|
290
|
+
enableSearchFilter: true,
|
|
291
|
+
lazyLoading: true,
|
|
292
|
+
labelKey: 'name',
|
|
293
|
+
primaryKey: 'id',
|
|
294
|
+
maxHeight: 140
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
<MultiSelectDropdown
|
|
298
|
+
data={people}
|
|
299
|
+
selectedItems={selectedPeople}
|
|
300
|
+
onChange={setSelectedPeople}
|
|
301
|
+
settings={settings}
|
|
302
|
+
onScrollToEnd={() => {
|
|
303
|
+
setPeople((current) => current.concat(loadMorePeople()));
|
|
304
|
+
}}
|
|
305
|
+
/>
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Dialogs and Overflow Containers
|
|
309
|
+
|
|
310
|
+
Use `appendToBody: true` or `tagToBody: true` when the dropdown is inside dialogs, modals, drawers, or containers that set `overflow: hidden` or `overflow: auto`.
|
|
311
|
+
|
|
312
|
+
```tsx
|
|
313
|
+
const settings = {
|
|
314
|
+
text: 'Dialog dropdown',
|
|
315
|
+
enableSearchFilter: true,
|
|
316
|
+
skin: 'material',
|
|
317
|
+
appendToBody: true,
|
|
318
|
+
tagToBody: true,
|
|
319
|
+
autoPosition: true
|
|
320
|
+
};
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
With body overlay enabled, the open panel is rendered against `document.body`, aligned to the original trigger, sized to the trigger, recalculated on open, scroll, resize, and content changes, and cleaned up when the dropdown closes or the component unmounts.
|
|
324
|
+
|
|
325
|
+
`autoPosition: true` treats `position` as a preferred direction. The menu opens upward only when there is meaningfully less room below and enough room above; otherwise it opens below and shrinks the scrollable list height to stay visible without covering the trigger.
|
|
326
|
+
|
|
327
|
+
## Events
|
|
328
|
+
|
|
329
|
+
Available callbacks:
|
|
330
|
+
|
|
331
|
+
- `onChange`
|
|
332
|
+
- `onSelect`
|
|
333
|
+
- `onDeSelect`
|
|
334
|
+
- `onSelectAll`
|
|
335
|
+
- `onDeSelectAll`
|
|
336
|
+
- `onOpen`
|
|
337
|
+
- `onClose`
|
|
338
|
+
- `onScrollToEnd`
|
|
339
|
+
- `onAddFilterNewItem`
|
|
340
|
+
- `onGroupSelect`
|
|
341
|
+
- `onGroupDeSelect`
|
|
342
|
+
|
|
343
|
+
## Ref Methods
|
|
344
|
+
|
|
345
|
+
```tsx
|
|
346
|
+
const dropdownRef = useRef<MultiSelectDropdownHandle<Country>>(null);
|
|
347
|
+
|
|
348
|
+
dropdownRef.current?.openDropdown();
|
|
349
|
+
dropdownRef.current?.closeDropdown();
|
|
350
|
+
dropdownRef.current?.focusSearch();
|
|
351
|
+
dropdownRef.current?.selectAll();
|
|
352
|
+
dropdownRef.current?.deSelectAll();
|
|
353
|
+
dropdownRef.current?.clearSelection();
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
## Run Locally
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
npm install
|
|
360
|
+
npm run build
|
|
361
|
+
npm test
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
React 17 docs:
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
cd docs-src/react-17
|
|
368
|
+
npm install
|
|
369
|
+
npm run build
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## License
|
|
373
|
+
|
|
374
|
+
MIT
|