@acusti/dropdown 0.50.1 → 0.51.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 +264 -17
- package/dist/Dropdown.d.ts +2 -1
- package/dist/Dropdown.js +191 -169
- package/dist/Dropdown.js.map +1 -1
- package/dist/helpers.d.ts +14 -20
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -13,9 +13,9 @@ of macOS.
|
|
|
13
13
|
|
|
14
14
|
The three primary design goals for the existence of this component:
|
|
15
15
|
|
|
16
|
-
1. Best-in-class UX (inspired by macOS native menus) with excellent
|
|
16
|
+
1. **Best-in-class UX** (inspired by macOS native menus) with excellent
|
|
17
17
|
keyboard support
|
|
18
|
-
2. Best-in-class DX with the simplest possible API:
|
|
18
|
+
2. **Best-in-class DX** with the simplest possible API:
|
|
19
19
|
1. To create a dropdown with a `<button>` trigger, pass in a single
|
|
20
20
|
child element with the body of the dropdown
|
|
21
21
|
2. To create a dropdown with a custom trigger, pass in exactly two
|
|
@@ -30,7 +30,7 @@ The three primary design goals for the existence of this component:
|
|
|
30
30
|
[collection of CSS custom properties](https://github.com/acusti/uikit/blob/main/packages/dropdown/src/styles.ts#L21-L32)
|
|
31
31
|
used internally to style them if that works best for you, or just
|
|
32
32
|
override the minimal default CSS as appropriate
|
|
33
|
-
3. Lightweight bundle size with the bare minimum of dependencies (see
|
|
33
|
+
3. **Lightweight bundle size** with the bare minimum of dependencies (see
|
|
34
34
|
minzipped size above)
|
|
35
35
|
|
|
36
36
|
See the [storybook docs and demo][] to get a feel for what it can do.
|
|
@@ -38,19 +38,49 @@ See the [storybook docs and demo][] to get a feel for what it can do.
|
|
|
38
38
|
[storybook docs and demo]:
|
|
39
39
|
https://uikit.acusti.ca/?path=/docs/uikit-controls-Dropdown--docs
|
|
40
40
|
|
|
41
|
-
##
|
|
41
|
+
## Installation
|
|
42
42
|
|
|
43
|
-
```
|
|
43
|
+
```bash
|
|
44
44
|
npm install @acusti/dropdown
|
|
45
45
|
# or
|
|
46
46
|
yarn add @acusti/dropdown
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
import Dropdown from '@acusti/dropdown';
|
|
53
|
+
|
|
54
|
+
// Simple dropdown with button trigger
|
|
55
|
+
function SimpleDropdown() {
|
|
56
|
+
return (
|
|
57
|
+
<Dropdown>
|
|
58
|
+
<ul>
|
|
59
|
+
<li>Option 1</li>
|
|
60
|
+
<li>Option 2</li>
|
|
61
|
+
<li>Option 3</li>
|
|
62
|
+
</ul>
|
|
63
|
+
</Dropdown>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
50
66
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
67
|
+
// Custom trigger
|
|
68
|
+
function CustomTrigger() {
|
|
69
|
+
return (
|
|
70
|
+
<Dropdown>
|
|
71
|
+
<button>My Custom Button</button>
|
|
72
|
+
<ul>
|
|
73
|
+
<li>Option 1</li>
|
|
74
|
+
<li>Option 2</li>
|
|
75
|
+
</ul>
|
|
76
|
+
</Dropdown>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## API Reference
|
|
82
|
+
|
|
83
|
+
### Props
|
|
54
84
|
|
|
55
85
|
```ts
|
|
56
86
|
type Props = {
|
|
@@ -66,17 +96,49 @@ type Props = {
|
|
|
66
96
|
allowEmpty?: boolean;
|
|
67
97
|
/**
|
|
68
98
|
* Can take a single React element or exactly two renderable children.
|
|
99
|
+
* - Single child: The dropdown body (trigger will be auto-generated button)
|
|
100
|
+
* - Two children: [trigger, body]
|
|
69
101
|
*/
|
|
102
|
+
children: ReactNode | [ReactNode, ReactNode];
|
|
70
103
|
className?: string;
|
|
71
104
|
disabled?: boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Group identifier string links dropdowns together into a menu
|
|
107
|
+
* (like macOS top menubar).
|
|
108
|
+
*/
|
|
109
|
+
group?: string;
|
|
110
|
+
/**
|
|
111
|
+
* Whether the dropdown contains items that can be selected.
|
|
112
|
+
* Defaults to true if children contain elements with data-ukt-item or data-ukt-value.
|
|
113
|
+
*/
|
|
72
114
|
hasItems?: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Whether the dropdown should be open when first mounted.
|
|
117
|
+
*/
|
|
73
118
|
isOpenOnMount?: boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Whether the dropdown should include a search input for filtering options.
|
|
121
|
+
*/
|
|
74
122
|
isSearchable?: boolean;
|
|
123
|
+
/**
|
|
124
|
+
* Whether the dropdown should remain open after selecting an item.
|
|
125
|
+
* Useful for multi-select scenarios.
|
|
126
|
+
*/
|
|
75
127
|
keepOpenOnSubmit?: boolean;
|
|
128
|
+
/**
|
|
129
|
+
* Label text for the trigger button (when using single child syntax).
|
|
130
|
+
*/
|
|
76
131
|
label?: string;
|
|
77
132
|
/**
|
|
78
|
-
*
|
|
79
|
-
|
|
133
|
+
* Minimum height for the dropdown body in pixels.
|
|
134
|
+
*/
|
|
135
|
+
minHeightBody?: number;
|
|
136
|
+
/**
|
|
137
|
+
* Minimum width for the dropdown body in pixels.
|
|
138
|
+
*/
|
|
139
|
+
minWidthBody?: number;
|
|
140
|
+
/**
|
|
141
|
+
* Name attribute for the search input (requires isSearchable: true).
|
|
80
142
|
*/
|
|
81
143
|
name?: string;
|
|
82
144
|
onClick?: (event: React.MouseEvent<HTMLElement>) => unknown;
|
|
@@ -84,22 +146,207 @@ type Props = {
|
|
|
84
146
|
onMouseDown?: (event: React.MouseEvent<HTMLElement>) => unknown;
|
|
85
147
|
onMouseUp?: (event: React.MouseEvent<HTMLElement>) => unknown;
|
|
86
148
|
onOpen?: () => unknown;
|
|
149
|
+
/**
|
|
150
|
+
* Called when an item is selected. The payload includes:
|
|
151
|
+
* - element: The DOM element that was clicked
|
|
152
|
+
* - event: The click or keyboard event
|
|
153
|
+
* - label: The visible text of the item
|
|
154
|
+
* - value: The value attribute or text content
|
|
155
|
+
*/
|
|
87
156
|
onSubmitItem?: (payload: Item) => void;
|
|
88
157
|
/**
|
|
89
|
-
*
|
|
90
|
-
* Used as search input’s placeholder.
|
|
158
|
+
* Placeholder text for the search input (requires isSearchable: true).
|
|
91
159
|
*/
|
|
92
160
|
placeholder?: string;
|
|
93
161
|
style?: React.CSSProperties;
|
|
94
162
|
/**
|
|
95
|
-
*
|
|
96
|
-
* Used as search input’s tabIndex.
|
|
163
|
+
* Tab index for the search input (requires isSearchable: true).
|
|
97
164
|
*/
|
|
98
165
|
tabIndex?: number;
|
|
99
166
|
/**
|
|
100
|
-
*
|
|
101
|
-
* Used
|
|
167
|
+
* Current value of the search input (requires isSearchable: true).
|
|
168
|
+
* Used for controlled components and change detection.
|
|
102
169
|
*/
|
|
103
170
|
value?: string;
|
|
104
171
|
};
|
|
105
172
|
```
|
|
173
|
+
|
|
174
|
+
### Item Type
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
type Item = {
|
|
178
|
+
element: HTMLElement | null;
|
|
179
|
+
event: Event | React.SyntheticEvent<HTMLElement>;
|
|
180
|
+
label: string;
|
|
181
|
+
value: string;
|
|
182
|
+
};
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Usage Examples
|
|
186
|
+
|
|
187
|
+
### Basic List Dropdown
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
import Dropdown from '@acusti/dropdown';
|
|
191
|
+
|
|
192
|
+
function StatesDropdown() {
|
|
193
|
+
const handleSelection = (item) => {
|
|
194
|
+
console.log('Selected:', item.value);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<Dropdown onSubmitItem={handleSelection}>
|
|
199
|
+
<ul>
|
|
200
|
+
<li>California</li>
|
|
201
|
+
<li>New York</li>
|
|
202
|
+
<li>Texas</li>
|
|
203
|
+
<li>Florida</li>
|
|
204
|
+
</ul>
|
|
205
|
+
</Dropdown>
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Searchable Dropdown
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
function SearchableDropdown() {
|
|
214
|
+
return (
|
|
215
|
+
<Dropdown
|
|
216
|
+
isSearchable
|
|
217
|
+
placeholder="Search states…"
|
|
218
|
+
label="Choose a state"
|
|
219
|
+
>
|
|
220
|
+
<ul>
|
|
221
|
+
<li>Alabama</li>
|
|
222
|
+
<li>Alaska</li>
|
|
223
|
+
<li>Arizona</li>
|
|
224
|
+
{/* ... more states */}
|
|
225
|
+
</ul>
|
|
226
|
+
</Dropdown>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Custom Values with Data Attributes
|
|
232
|
+
|
|
233
|
+
```tsx
|
|
234
|
+
function FontWeightDropdown() {
|
|
235
|
+
return (
|
|
236
|
+
<Dropdown onSubmitItem={(item) => setFontWeight(item.value)}>
|
|
237
|
+
<ul>
|
|
238
|
+
<li data-ukt-value="100">Thin (100)</li>
|
|
239
|
+
<li data-ukt-value="400">Regular (400)</li>
|
|
240
|
+
<li data-ukt-value="700">Bold (700)</li>
|
|
241
|
+
<li data-ukt-value="900">Black (900)</li>
|
|
242
|
+
</ul>
|
|
243
|
+
</Dropdown>
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Allow Creating New Items
|
|
249
|
+
|
|
250
|
+
```tsx
|
|
251
|
+
function TagsDropdown() {
|
|
252
|
+
const [tags, setTags] = useState(['react', 'typescript', 'dropdown']);
|
|
253
|
+
|
|
254
|
+
const handleNewTag = (item) => {
|
|
255
|
+
if (!tags.includes(item.value)) {
|
|
256
|
+
setTags([...tags, item.value]);
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
return (
|
|
261
|
+
<Dropdown
|
|
262
|
+
isSearchable
|
|
263
|
+
allowCreate
|
|
264
|
+
placeholder="Add or select a tag…"
|
|
265
|
+
onSubmitItem={handleNewTag}
|
|
266
|
+
>
|
|
267
|
+
<ul>
|
|
268
|
+
{tags.map((tag) => (
|
|
269
|
+
<li key={tag}>{tag}</li>
|
|
270
|
+
))}
|
|
271
|
+
</ul>
|
|
272
|
+
</Dropdown>
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Multi-Select with Checkboxes
|
|
278
|
+
|
|
279
|
+
```tsx
|
|
280
|
+
function MultiSelectDropdown() {
|
|
281
|
+
return (
|
|
282
|
+
<Dropdown
|
|
283
|
+
keepOpenOnSubmit
|
|
284
|
+
onSubmitItem={({ label }) => {
|
|
285
|
+
console.log('Selected color:', label);
|
|
286
|
+
}}
|
|
287
|
+
>
|
|
288
|
+
<ul>
|
|
289
|
+
<li>
|
|
290
|
+
<label>
|
|
291
|
+
<input type="checkbox" /> Red
|
|
292
|
+
</label>
|
|
293
|
+
</li>
|
|
294
|
+
<li>
|
|
295
|
+
<label>
|
|
296
|
+
<input type="checkbox" /> Blue
|
|
297
|
+
</label>
|
|
298
|
+
</li>
|
|
299
|
+
</ul>
|
|
300
|
+
</Dropdown>
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Dropdown with Interactive Content
|
|
306
|
+
|
|
307
|
+
```tsx
|
|
308
|
+
function InteractiveDropdown() {
|
|
309
|
+
return (
|
|
310
|
+
<Dropdown hasItems={false}>
|
|
311
|
+
<button>Settings</button>
|
|
312
|
+
<div style={{ padding: '16px' }}>
|
|
313
|
+
<label>
|
|
314
|
+
Full name:{' '}
|
|
315
|
+
<input
|
|
316
|
+
defaultValue=""
|
|
317
|
+
onChange={(value) =>
|
|
318
|
+
console.log('Full name:', value)
|
|
319
|
+
}
|
|
320
|
+
placeholder="Sally Ride"
|
|
321
|
+
type="text"
|
|
322
|
+
/>
|
|
323
|
+
</label>
|
|
324
|
+
<label>
|
|
325
|
+
Email:{' '}
|
|
326
|
+
<input
|
|
327
|
+
defaultValue=""
|
|
328
|
+
onChange={(value) => console.log('Email:', value)}
|
|
329
|
+
placeholder="sally@ride.com"
|
|
330
|
+
type="email"
|
|
331
|
+
/>
|
|
332
|
+
</label>
|
|
333
|
+
</div>
|
|
334
|
+
</Dropdown>
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## Keyboard Navigation & Accessibility
|
|
340
|
+
|
|
341
|
+
The dropdown implements full keyboard navigation:
|
|
342
|
+
|
|
343
|
+
- **Enter/Space**: Open dropdown or select highlighted item
|
|
344
|
+
- **Escape**: Close dropdown
|
|
345
|
+
- **Arrow Up/Down**: Navigate between items
|
|
346
|
+
- **Home/End**: Jump to first/last item
|
|
347
|
+
- **Type characters**: Search for items (when searchable)
|
|
348
|
+
|
|
349
|
+
For accessibility, the component focuses on semantic HTML structure and
|
|
350
|
+
keyboard navigation. It works best when you use appropriate HTML elements
|
|
351
|
+
in your dropdown content (like `<ul>` and `<li>` for lists, `<button>`
|
|
352
|
+
elements for actions, etc.).
|
package/dist/Dropdown.d.ts
CHANGED
|
@@ -39,6 +39,7 @@ export type Props = {
|
|
|
39
39
|
* Used as search input’s name.
|
|
40
40
|
*/
|
|
41
41
|
name?: string;
|
|
42
|
+
onActiveItem?: (payload: Item) => void;
|
|
42
43
|
onClick?: (event: ReactMouseEvent<HTMLElement>) => unknown;
|
|
43
44
|
onClose?: () => unknown;
|
|
44
45
|
onMouseDown?: (event: ReactMouseEvent<HTMLElement>) => unknown;
|
|
@@ -64,5 +65,5 @@ export type Props = {
|
|
|
64
65
|
};
|
|
65
66
|
type ChildrenTuple = [ReactNode, ReactNode] | readonly [ReactNode, ReactNode];
|
|
66
67
|
type MaybeHTMLElement = HTMLElement | null;
|
|
67
|
-
export default function Dropdown({ allowCreate, allowEmpty, children, className, disabled, hasItems, isOpenOnMount, isSearchable, keepOpenOnSubmit, label, minHeightBody, minWidthBody, name, onClick, onClose, onMouseDown, onMouseUp, onOpen, onSubmitItem, placeholder, style: styleFromProps, tabIndex, value, }: Props): import("react/jsx-runtime").JSX.Element;
|
|
68
|
+
export default function Dropdown({ allowCreate, allowEmpty, children, className, disabled, hasItems, isOpenOnMount, isSearchable, keepOpenOnSubmit, label, minHeightBody, minWidthBody, name, onActiveItem, onClick, onClose, onMouseDown, onMouseUp, onOpen, onSubmitItem, placeholder, style: styleFromProps, tabIndex, value, }: Props): import("react/jsx-runtime").JSX.Element;
|
|
68
69
|
export {};
|