@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 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
- ## Usage
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
- ### Props
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
- This is the type signature for the props you can pass to `Dropdown`. The
52
- unique features provided by the component are called out and explained
53
- above the corresponding prop via JSDoc comments:
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
- * Only usable in conjunction with {isSearchable: true}.
79
- * Used as search input’s name.
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
- * Only usable in conjunction with {isSearchable: true}.
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
- * Only usable in conjunction with {isSearchable: true}.
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
- * Used as search input’s value if props.isSearchable === true
101
- * Used to determine if value has changed to avoid triggering onSubmitItem if not
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.).
@@ -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 {};