@ceed/ads 1.20.0 → 1.20.1-next.1

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.
Files changed (30) hide show
  1. package/dist/components/ProfileMenu/ProfileMenu.d.ts +1 -1
  2. package/dist/components/data-display/Markdown.md +832 -0
  3. package/dist/components/feedback/Dialog.md +605 -3
  4. package/dist/components/feedback/Modal.md +656 -24
  5. package/dist/components/feedback/llms.txt +1 -1
  6. package/dist/components/inputs/Autocomplete.md +734 -2
  7. package/dist/components/inputs/Calendar.md +655 -1
  8. package/dist/components/inputs/DatePicker.md +699 -3
  9. package/dist/components/inputs/DateRangePicker.md +815 -1
  10. package/dist/components/inputs/MonthPicker.md +626 -4
  11. package/dist/components/inputs/MonthRangePicker.md +682 -4
  12. package/dist/components/inputs/Select.md +600 -0
  13. package/dist/components/layout/Container.md +507 -0
  14. package/dist/components/navigation/Breadcrumbs.md +582 -0
  15. package/dist/components/navigation/IconMenuButton.md +693 -0
  16. package/dist/components/navigation/InsetDrawer.md +1150 -3
  17. package/dist/components/navigation/Link.md +526 -0
  18. package/dist/components/navigation/MenuButton.md +632 -0
  19. package/dist/components/navigation/NavigationGroup.md +401 -1
  20. package/dist/components/navigation/NavigationItem.md +311 -0
  21. package/dist/components/navigation/Navigator.md +373 -0
  22. package/dist/components/navigation/Pagination.md +521 -0
  23. package/dist/components/navigation/ProfileMenu.md +605 -0
  24. package/dist/components/navigation/Tabs.md +609 -7
  25. package/dist/components/surfaces/Accordions.md +947 -3
  26. package/dist/index.cjs +3 -1
  27. package/dist/index.js +3 -1
  28. package/dist/llms.txt +1 -1
  29. package/framer/index.js +1 -1
  30. package/package.json +3 -2
@@ -2,6 +2,8 @@
2
2
 
3
3
  ## Introduction
4
4
 
5
+ MenuButton is a compound component that combines a button with a dropdown menu, allowing users to trigger a list of actions or navigation options from a single interactive element. It displays a button with text and an optional dropdown indicator icon that, when clicked, reveals a menu of selectable items. MenuButton is commonly used for action menus, navigation shortcuts, and grouped operations in toolbars and headers.
6
+
5
7
  ```tsx
6
8
  <MenuButton
7
9
  buttonText="Dashboard..."
@@ -32,8 +34,638 @@
32
34
  | color | — | "primary" |
33
35
  | size | — | — |
34
36
 
37
+ > ⚠️ **Usage Warning** ⚠️
38
+ >
39
+ > Choose the right component for your use case:
40
+ >
41
+ > - **MenuButton**: For action menus triggered by a labeled button
42
+ > - **IconMenuButton**: For action menus triggered by an icon-only button
43
+ > - **Dropdown**: For more complex menu structures with custom triggers
44
+ > - **Select**: For form inputs where user selects a value (not actions)
45
+
35
46
  ## Usage
36
47
 
37
48
  ```tsx
38
49
  import { MenuButton } from '@ceed/ads';
50
+
51
+ function UserMenu() {
52
+ return (
53
+ <MenuButton
54
+ buttonText="Account"
55
+ items={[
56
+ { text: 'Profile', onClick: () => navigate('/profile') },
57
+ { text: 'Settings', onClick: () => navigate('/settings') },
58
+ { text: 'Logout', onClick: handleLogout },
59
+ ]}
60
+ />
61
+ );
62
+ }
63
+ ```
64
+
65
+ ## Examples
66
+
67
+ ### Playground
68
+
69
+ Interactive example with all controls.
70
+
71
+ ```tsx
72
+ <MenuButton
73
+ buttonText="Dashboard..."
74
+ items={[{
75
+ text: 'Profile'
76
+ }, {
77
+ text: 'My account'
78
+ }, {
79
+ text: 'Logout'
80
+ }]}
81
+ showIcon
82
+ variant="solid"
83
+ color="primary"
84
+ />
85
+ ```
86
+
87
+ ### Standalone (Without Icon)
88
+
89
+ Use as a simple navigation link without the dropdown icon.
90
+
91
+ ```tsx
92
+ <MenuButton
93
+ buttonText="Dashboard"
94
+ items={[{
95
+ text: 'Profile'
96
+ }, {
97
+ text: 'My account'
98
+ }, {
99
+ text: 'Logout'
100
+ }]}
101
+ showIcon={false}
102
+ variant="solid"
103
+ color="primary"
104
+ buttonComponent={Link}
105
+ buttonComponentProps={{
106
+ to: '/'
107
+ }}
108
+ />
109
+ ```
110
+
111
+ ### With End Decorator
112
+
113
+ Add badges, chips, or icons at the end of the button.
114
+
115
+ ```tsx
116
+ <MenuButton
117
+ buttonText="Dashboard"
118
+ items={[{
119
+ text: 'Profile'
120
+ }, {
121
+ text: 'My account'
122
+ }, {
123
+ text: 'Logout'
124
+ }]}
125
+ showIcon
126
+ variant="solid"
127
+ color="primary"
128
+ endDecorator={<Chip color="primary">New</Chip>}
129
+ />
39
130
  ```
131
+
132
+ ### Placement: Bottom Start
133
+
134
+ Menu aligns to the left edge of the button.
135
+
136
+ ```tsx
137
+ <MenuButton
138
+ buttonText="Hi"
139
+ items={[{
140
+ text: 'Profile'
141
+ }, {
142
+ text: 'My account'
143
+ }, {
144
+ text: 'Logout'
145
+ }]}
146
+ showIcon
147
+ variant="solid"
148
+ color="primary"
149
+ placement="bottom-start"
150
+ />
151
+ ```
152
+
153
+ ### Placement: Bottom
154
+
155
+ Menu centers below the button.
156
+
157
+ ```tsx
158
+ <MenuButton
159
+ buttonText="Hi"
160
+ items={[{
161
+ text: 'Profile'
162
+ }, {
163
+ text: 'My account'
164
+ }, {
165
+ text: 'Logout'
166
+ }]}
167
+ showIcon
168
+ variant="solid"
169
+ color="primary"
170
+ placement="bottom"
171
+ />
172
+ ```
173
+
174
+ ### Placement: Bottom End
175
+
176
+ Menu aligns to the right edge of the button.
177
+
178
+ ```tsx
179
+ <MenuButton
180
+ buttonText="Hi"
181
+ items={[{
182
+ text: 'Profile'
183
+ }, {
184
+ text: 'My account'
185
+ }, {
186
+ text: 'Logout'
187
+ }]}
188
+ showIcon
189
+ variant="solid"
190
+ color="primary"
191
+ placement="bottom-end"
192
+ />
193
+ ```
194
+
195
+ ## When to Use
196
+
197
+ ### ✅ Good Use Cases
198
+
199
+ - **User account menus**: Profile, settings, logout actions
200
+ - **Action grouping**: Related actions in a toolbar
201
+ - **Navigation shortcuts**: Quick access to related pages
202
+ - **Overflow menus**: When space is limited
203
+ - **Context actions**: Page or section-specific actions
204
+ - **Export options**: Download, export, or share actions
205
+
206
+ ### ❌ When Not to Use
207
+
208
+ - **Form value selection**: Use Select for picking values in forms
209
+ - **Single action**: Use Button for single actions
210
+ - **Icon-only trigger**: Use IconMenuButton for minimal space
211
+ - **Complex menus**: Use Dropdown for nested menus or custom content
212
+ - **Primary navigation**: Use Navigator or Tabs for main navigation
213
+
214
+ ## Common Use Cases
215
+
216
+ ### User Account Menu
217
+
218
+ ```tsx
219
+ function AccountMenu({ user, onLogout }) {
220
+ return (
221
+ <MenuButton
222
+ buttonText={user.name}
223
+ startDecorator={<Avatar src={user.avatar} size="sm" />}
224
+ variant="plain"
225
+ color="neutral"
226
+ items={[
227
+ { text: 'Profile', onClick: () => navigate('/profile') },
228
+ { text: 'Account Settings', onClick: () => navigate('/settings') },
229
+ { text: 'Billing', onClick: () => navigate('/billing') },
230
+ { text: 'Logout', onClick: onLogout },
231
+ ]}
232
+ />
233
+ );
234
+ }
235
+ ```
236
+
237
+ ### Export Options Menu
238
+
239
+ ```tsx
240
+ function ExportMenu({ data, onExport }) {
241
+ return (
242
+ <MenuButton
243
+ buttonText="Export"
244
+ variant="outlined"
245
+ startDecorator={<DownloadIcon />}
246
+ items={[
247
+ { text: 'Export as CSV', onClick: () => onExport('csv') },
248
+ { text: 'Export as Excel', onClick: () => onExport('xlsx') },
249
+ { text: 'Export as PDF', onClick: () => onExport('pdf') },
250
+ { text: 'Print', onClick: () => window.print() },
251
+ ]}
252
+ />
253
+ );
254
+ }
255
+ ```
256
+
257
+ ### Actions Menu in Toolbar
258
+
259
+ ```tsx
260
+ function ToolbarActions({ selectedItems, onAction }) {
261
+ const hasSelection = selectedItems.length > 0;
262
+
263
+ return (
264
+ <Stack direction="row" gap={1}>
265
+ <Button onClick={() => onAction('create')}>Create New</Button>
266
+ <MenuButton
267
+ buttonText="Actions"
268
+ variant="outlined"
269
+ color="neutral"
270
+ disabled={!hasSelection}
271
+ items={[
272
+ { text: `Edit (${selectedItems.length})`, onClick: () => onAction('edit') },
273
+ { text: 'Duplicate', onClick: () => onAction('duplicate') },
274
+ { text: 'Move to Folder', onClick: () => onAction('move') },
275
+ { text: 'Archive', onClick: () => onAction('archive') },
276
+ { text: 'Delete', onClick: () => onAction('delete') },
277
+ ]}
278
+ />
279
+ </Stack>
280
+ );
281
+ }
282
+ ```
283
+
284
+ ### Navigation with Dropdown
285
+
286
+ ```tsx
287
+ function ProductsMenu() {
288
+ return (
289
+ <MenuButton
290
+ buttonText="Products"
291
+ variant="plain"
292
+ showIcon={true}
293
+ items={[
294
+ { text: 'All Products', onClick: () => navigate('/products') },
295
+ { text: 'Categories', onClick: () => navigate('/products/categories') },
296
+ { text: 'Inventory', onClick: () => navigate('/products/inventory') },
297
+ { text: 'Price Lists', onClick: () => navigate('/products/prices') },
298
+ ]}
299
+ />
300
+ );
301
+ }
302
+ ```
303
+
304
+ ### Status Change Menu
305
+
306
+ ```tsx
307
+ function StatusMenu({ currentStatus, onStatusChange }) {
308
+ const statuses = [
309
+ { value: 'draft', label: 'Draft', color: 'neutral' },
310
+ { value: 'pending', label: 'Pending Review', color: 'warning' },
311
+ { value: 'approved', label: 'Approved', color: 'success' },
312
+ { value: 'rejected', label: 'Rejected', color: 'danger' },
313
+ ];
314
+
315
+ const current = statuses.find((s) => s.value === currentStatus);
316
+
317
+ return (
318
+ <MenuButton
319
+ buttonText={current?.label || 'Set Status'}
320
+ variant="soft"
321
+ color={current?.color || 'neutral'}
322
+ items={statuses.map((status) => ({
323
+ text: status.label,
324
+ onClick: () => onStatusChange(status.value),
325
+ }))}
326
+ />
327
+ );
328
+ }
329
+ ```
330
+
331
+ ### Menu with Decorators
332
+
333
+ ```tsx
334
+ function NotificationsMenu({ notifications }) {
335
+ const unreadCount = notifications.filter((n) => !n.read).length;
336
+
337
+ return (
338
+ <MenuButton
339
+ buttonText="Notifications"
340
+ startDecorator={<NotificationsIcon />}
341
+ endDecorator={
342
+ unreadCount > 0 && (
343
+ <Chip size="sm" color="danger">
344
+ {unreadCount}
345
+ </Chip>
346
+ )
347
+ }
348
+ variant="soft"
349
+ items={notifications.slice(0, 5).map((n) => ({
350
+ text: n.title,
351
+ onClick: () => markAsRead(n.id),
352
+ }))}
353
+ />
354
+ );
355
+ }
356
+ ```
357
+
358
+ ### Quick Settings Menu
359
+
360
+ ```tsx
361
+ function QuickSettings({ settings, onSettingChange }) {
362
+ return (
363
+ <MenuButton
364
+ buttonText="Settings"
365
+ startDecorator={<SettingsIcon />}
366
+ variant="plain"
367
+ color="neutral"
368
+ items={[
369
+ {
370
+ text: settings.darkMode ? '☀️ Light Mode' : '🌙 Dark Mode',
371
+ onClick: () => onSettingChange('darkMode', !settings.darkMode),
372
+ },
373
+ {
374
+ text: settings.compactView ? '📐 Normal View' : '📏 Compact View',
375
+ onClick: () => onSettingChange('compactView', !settings.compactView),
376
+ },
377
+ {
378
+ text: 'Language: ' + settings.language.toUpperCase(),
379
+ onClick: () => openLanguageModal(),
380
+ },
381
+ ]}
382
+ />
383
+ );
384
+ }
385
+ ```
386
+
387
+ ## Props and Customization
388
+
389
+ ### Key Props
390
+
391
+ | Prop | Type | Default | Description |
392
+ | ---------------------- | -------------------------------------------------------------- | ---------------- | --------------------------------- |
393
+ | `buttonText` | `string` | - | Text displayed on the button |
394
+ | `items` | `Array<{ text: string; onClick?: () => void }>` | - | Menu items to display |
395
+ | `showIcon` | `boolean` | `true` | Show dropdown indicator icon |
396
+ | `variant` | `'solid' \| 'soft' \| 'outlined' \| 'plain'` | `'solid'` | Button style variant |
397
+ | `color` | `'primary' \| 'neutral' \| 'danger' \| 'success' \| 'warning'` | `'primary'` | Button color |
398
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Button size |
399
+ | `placement` | `'bottom-start' \| 'bottom' \| 'bottom-end'` | `'bottom-start'` | Menu position |
400
+ | `startDecorator` | `ReactNode` | - | Content before button text |
401
+ | `endDecorator` | `ReactNode` | - | Content after button text |
402
+ | `buttonComponent` | `React.ElementType` | `Button` | Custom button component |
403
+ | `buttonComponentProps` | `object` | - | Props for custom button component |
404
+
405
+ ### Menu Item Structure
406
+
407
+ ```tsx
408
+ interface MenuItem {
409
+ text: string; // Display text
410
+ onClick?: () => void; // Click handler
411
+ // Additional props can be passed based on implementation
412
+ }
413
+
414
+ // Example items array
415
+ const items = [
416
+ { text: 'Edit', onClick: handleEdit },
417
+ { text: 'Delete', onClick: handleDelete },
418
+ ];
419
+ ```
420
+
421
+ ### Variant and Color Options
422
+
423
+ ```tsx
424
+ // Primary solid (default)
425
+ <MenuButton buttonText="Actions" variant="solid" color="primary" />
426
+
427
+ // Subtle outlined
428
+ <MenuButton buttonText="Actions" variant="outlined" color="neutral" />
429
+
430
+ // Soft background
431
+ <MenuButton buttonText="Actions" variant="soft" color="primary" />
432
+
433
+ // Plain/text only
434
+ <MenuButton buttonText="Actions" variant="plain" color="neutral" />
435
+
436
+ // Danger color
437
+ <MenuButton buttonText="Delete" variant="soft" color="danger" />
438
+ ```
439
+
440
+ ### Size Options
441
+
442
+ ```tsx
443
+ // Small
444
+ <MenuButton buttonText="Small" size="sm" items={items} />
445
+
446
+ // Medium (default)
447
+ <MenuButton buttonText="Medium" size="md" items={items} />
448
+
449
+ // Large
450
+ <MenuButton buttonText="Large" size="lg" items={items} />
451
+ ```
452
+
453
+ ### Custom Button Component
454
+
455
+ ```tsx
456
+ // Use with custom components like Link
457
+ import { Link } from 'react-router-dom';
458
+
459
+ <MenuButton
460
+ buttonText="Dashboard"
461
+ buttonComponent={Link}
462
+ buttonComponentProps={{ to: '/dashboard' }}
463
+ showIcon={false}
464
+ items={items}
465
+ />
466
+ ```
467
+
468
+ ### Decorators
469
+
470
+ ```tsx
471
+ // Start decorator (icon before text)
472
+ <MenuButton
473
+ buttonText="Export"
474
+ startDecorator={<DownloadIcon />}
475
+ items={exportItems}
476
+ />
477
+
478
+ // End decorator (badge after text)
479
+ <MenuButton
480
+ buttonText="Inbox"
481
+ endDecorator={<Chip size="sm">5</Chip>}
482
+ items={inboxItems}
483
+ />
484
+
485
+ // Both decorators
486
+ <MenuButton
487
+ buttonText="User"
488
+ startDecorator={<Avatar size="sm" />}
489
+ endDecorator={<ChevronDownIcon />}
490
+ showIcon={false}
491
+ items={userItems}
492
+ />
493
+ ```
494
+
495
+ ## Accessibility
496
+
497
+ MenuButton includes built-in accessibility features:
498
+
499
+ ### ARIA Attributes
500
+
501
+ - Button has `aria-haspopup="true"` indicating it opens a menu
502
+ - Button has `aria-expanded` reflecting menu open state
503
+ - Menu has proper `role="menu"` and `role="menuitem"` structure
504
+ - Focus is managed between button and menu items
505
+
506
+ ### Keyboard Navigation
507
+
508
+ - **Enter/Space**: Open menu when button is focused
509
+ - **Arrow Down**: Move to next menu item
510
+ - **Arrow Up**: Move to previous menu item
511
+ - **Escape**: Close menu and return focus to button
512
+ - **Tab**: Close menu and move to next focusable element
513
+ - **Enter**: Select focused menu item
514
+
515
+ ### Screen Reader Support
516
+
517
+ ```tsx
518
+ // Button announces: "Account, menu button, expanded/collapsed"
519
+ <MenuButton
520
+ buttonText="Account"
521
+ items={[...]}
522
+ />
523
+
524
+ // Menu items are announced as: "Profile, menu item, 1 of 3"
525
+ ```
526
+
527
+ ### Focus Management
528
+
529
+ - Focus moves to first menu item when menu opens
530
+ - Focus returns to button when menu closes
531
+ - Menu items are focusable with arrow keys
532
+
533
+ ## Best Practices
534
+
535
+ ### ✅ Do
536
+
537
+ 1. **Use clear, action-oriented labels**: Button text should indicate what the menu contains
538
+
539
+ ```tsx
540
+ // ✅ Good: Clear menu purpose
541
+ <MenuButton buttonText="Export Options" items={exportItems} />
542
+ <MenuButton buttonText="User Actions" items={userItems} />
543
+ ```
544
+
545
+ 2. **Group related actions**: Keep menu items logically related
546
+
547
+ ```tsx
548
+ // ✅ Good: Related file actions
549
+ <MenuButton
550
+ buttonText="File"
551
+ items={[
552
+ { text: 'New', onClick: handleNew },
553
+ { text: 'Open', onClick: handleOpen },
554
+ { text: 'Save', onClick: handleSave },
555
+ { text: 'Save As...', onClick: handleSaveAs },
556
+ ]}
557
+ />
558
+ ```
559
+
560
+ 3. **Use appropriate variants**: Match the button style to its context
561
+
562
+ ```tsx
563
+ // ✅ Good: Primary for main actions, plain for navigation
564
+ <MenuButton buttonText="Create" variant="solid" color="primary" />
565
+ <MenuButton buttonText="Settings" variant="plain" color="neutral" />
566
+ ```
567
+
568
+ 4. **Limit menu items**: Keep menus scannable (5-7 items max)
569
+
570
+ ### ❌ Don't
571
+
572
+ 1. **Don't use for form inputs**: Use Select for value selection
573
+
574
+ ```tsx
575
+ // ❌ Bad: Using MenuButton for form selection
576
+ <MenuButton buttonText={selectedOption} items={options} />
577
+
578
+ // ✅ Good: Use Select for forms
579
+ <Select value={selectedOption} onChange={handleChange}>
580
+ {options.map((opt) => <Option key={opt.value} value={opt.value}>{opt.label}</Option>)}
581
+ </Select>
582
+ ```
583
+
584
+ 2. **Don't use vague labels**: Be specific about menu contents
585
+
586
+ ```tsx
587
+ // ❌ Bad: Unclear what "More" contains
588
+ <MenuButton buttonText="More" items={items} />
589
+
590
+ // ✅ Good: Clear menu purpose
591
+ <MenuButton buttonText="More Actions" items={actionItems} />
592
+ ```
593
+
594
+ 3. **Don't overload menus**: Split large menus into categories
595
+
596
+ ```tsx
597
+ // ❌ Bad: Too many unrelated items
598
+ <MenuButton
599
+ buttonText="Options"
600
+ items={[
601
+ { text: 'Edit' },
602
+ { text: 'Delete' },
603
+ { text: 'Share' },
604
+ { text: 'Settings' },
605
+ { text: 'Help' },
606
+ { text: 'About' },
607
+ // ... many more items
608
+ ]}
609
+ />
610
+ ```
611
+
612
+ 4. **Don't hide primary actions**: Important actions should be visible
613
+
614
+ ## Performance Considerations
615
+
616
+ ### Memoize Items Array
617
+
618
+ Prevent unnecessary re-renders by memoizing the items array:
619
+
620
+ ```tsx
621
+ const menuItems = useMemo(
622
+ () => [
623
+ { text: 'Edit', onClick: handleEdit },
624
+ { text: 'Delete', onClick: handleDelete },
625
+ ],
626
+ [handleEdit, handleDelete]
627
+ );
628
+
629
+ <MenuButton buttonText="Actions" items={menuItems} />
630
+ ```
631
+
632
+ ### Memoize Click Handlers
633
+
634
+ ```tsx
635
+ const handleExport = useCallback(
636
+ (format: string) => {
637
+ exportData(data, format);
638
+ },
639
+ [data]
640
+ );
641
+
642
+ const exportItems = useMemo(
643
+ () => [
644
+ { text: 'CSV', onClick: () => handleExport('csv') },
645
+ { text: 'Excel', onClick: () => handleExport('xlsx') },
646
+ ],
647
+ [handleExport]
648
+ );
649
+ ```
650
+
651
+ ### Lazy Load Heavy Actions
652
+
653
+ For actions that trigger heavy operations:
654
+
655
+ ```tsx
656
+ const items = useMemo(
657
+ () => [
658
+ {
659
+ text: 'Generate Report',
660
+ onClick: async () => {
661
+ setLoading(true);
662
+ await generateReport();
663
+ setLoading(false);
664
+ },
665
+ },
666
+ ],
667
+ []
668
+ );
669
+ ```
670
+
671
+ MenuButton provides a convenient way to group related actions behind a single button trigger. Use it for account menus, action groups, and navigation shortcuts while keeping the menu contents focused and scannable. For icon-only triggers in space-constrained areas, consider IconMenuButton instead.