@g4rcez/components 3.0.0-0 → 3.0.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.
- package/dist/ai/SKILL.md +266 -0
- package/dist/ai/docs/Alert.md +167 -0
- package/dist/ai/docs/AnimatedList.md +205 -0
- package/dist/ai/docs/Autocomplete.md +225 -0
- package/dist/ai/docs/Button.md +182 -0
- package/dist/ai/docs/Calendar.md +219 -0
- package/dist/ai/docs/Card.md +174 -0
- package/dist/ai/docs/Checkbox.md +199 -0
- package/dist/ai/docs/CommandPalette.md +293 -0
- package/dist/ai/docs/DatePicker.md +171 -0
- package/dist/ai/docs/Dropdown.md +223 -0
- package/dist/ai/docs/Empty.md +163 -0
- package/dist/ai/docs/Expand.md +143 -0
- package/dist/ai/docs/FileUpload.md +225 -0
- package/dist/ai/docs/Form.md +107 -0
- package/dist/ai/docs/FormReset.md +117 -0
- package/dist/ai/docs/Heading.md +88 -0
- package/dist/ai/docs/Input.md +237 -0
- package/dist/ai/docs/InputField.md +170 -0
- package/dist/ai/docs/List.md +205 -0
- package/dist/ai/docs/Menu.md +166 -0
- package/dist/ai/docs/Modal.md +280 -0
- package/dist/ai/docs/MultiSelect.md +196 -0
- package/dist/ai/docs/Notifications.md +231 -0
- package/dist/ai/docs/PageCalendar.md +271 -0
- package/dist/ai/docs/Polymorph.md +159 -0
- package/dist/ai/docs/Progress.md +145 -0
- package/dist/ai/docs/Radiobox.md +128 -0
- package/dist/ai/docs/RenderOnView.md +138 -0
- package/dist/ai/docs/Resizable.md +159 -0
- package/dist/ai/docs/Select.md +284 -0
- package/dist/ai/docs/Shortcut.md +105 -0
- package/dist/ai/docs/Skeleton.md +166 -0
- package/dist/ai/docs/Slider.md +144 -0
- package/dist/ai/docs/Slot.md +173 -0
- package/dist/ai/docs/Spinner.md +118 -0
- package/dist/ai/docs/Stats.md +137 -0
- package/dist/ai/docs/Step.md +159 -0
- package/dist/ai/docs/Switch.md +167 -0
- package/dist/ai/docs/Table.md +298 -0
- package/dist/ai/docs/Tabs.md +191 -0
- package/dist/ai/docs/Tag.md +224 -0
- package/dist/ai/docs/TaskList.md +144 -0
- package/dist/ai/docs/Textarea.md +167 -0
- package/dist/ai/docs/Timeline.md +210 -0
- package/dist/ai/docs/Toolbar.md +132 -0
- package/dist/ai/docs/Tooltip.md +231 -0
- package/dist/ai/docs/TransferList.md +142 -0
- package/dist/ai/docs/Typography.md +187 -0
- package/dist/ai/docs/Wizard.md +213 -0
- package/dist/ai/docs/index.md +183 -0
- package/dist/components/core/button.d.ts +2 -8
- package/dist/components/core/button.d.ts.map +1 -1
- package/dist/components/core/polymorph.d.ts.map +1 -1
- package/dist/components/core/slot.d.ts +1 -1
- package/dist/components/core/slot.d.ts.map +1 -1
- package/dist/components/core/tag.d.ts +2 -2
- package/dist/components/core/tag.d.ts.map +1 -1
- package/dist/components/core/typography.d.ts.map +1 -1
- package/dist/components/display/alert.d.ts.map +1 -1
- package/dist/components/display/calendar.d.ts.map +1 -1
- package/dist/components/display/card.d.ts.map +1 -1
- package/dist/components/display/list.d.ts.map +1 -1
- package/dist/components/display/notifications.d.ts +2 -0
- package/dist/components/display/notifications.d.ts.map +1 -1
- package/dist/components/display/progress.d.ts.map +1 -1
- package/dist/components/display/skeleton.d.ts.map +1 -1
- package/dist/components/display/step.d.ts.map +1 -1
- package/dist/components/display/tabs.d.ts.map +1 -1
- package/dist/components/floating/command-palette.d.ts +1 -0
- package/dist/components/floating/command-palette.d.ts.map +1 -1
- package/dist/components/floating/dropdown.d.ts +1 -0
- package/dist/components/floating/dropdown.d.ts.map +1 -1
- package/dist/components/floating/menu.d.ts +2 -2
- package/dist/components/floating/menu.d.ts.map +1 -1
- package/dist/components/floating/modal.d.ts +20 -53
- package/dist/components/floating/modal.d.ts.map +1 -1
- package/dist/components/floating/tooltip.d.ts.map +1 -1
- package/dist/components/floating/wizard.d.ts +1 -1
- package/dist/components/floating/wizard.d.ts.map +1 -1
- package/dist/components/form/autocomplete.d.ts.map +1 -1
- package/dist/components/form/date-picker.d.ts.map +1 -1
- package/dist/components/form/free-text.d.ts.map +1 -1
- package/dist/components/form/input-field.d.ts +3 -2
- package/dist/components/form/input-field.d.ts.map +1 -1
- package/dist/components/form/multi-select.d.ts.map +1 -1
- package/dist/components/form/select.d.ts.map +1 -1
- package/dist/components/form/slider.d.ts.map +1 -1
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/page-calendar/calendar-header.d.ts +16 -0
- package/dist/components/page-calendar/calendar-header.d.ts.map +1 -0
- package/dist/components/page-calendar/day-view.d.ts +12 -0
- package/dist/components/page-calendar/day-view.d.ts.map +1 -0
- package/dist/components/page-calendar/event-pill.d.ts +9 -0
- package/dist/components/page-calendar/event-pill.d.ts.map +1 -0
- package/dist/components/page-calendar/index.d.ts +4 -0
- package/dist/components/page-calendar/index.d.ts.map +1 -0
- package/dist/components/page-calendar/month-view.d.ts +11 -0
- package/dist/components/page-calendar/month-view.d.ts.map +1 -0
- package/dist/components/page-calendar/page-calendar.d.ts +18 -0
- package/dist/components/page-calendar/page-calendar.d.ts.map +1 -0
- package/dist/components/page-calendar/page-calendar.types.d.ts +18 -0
- package/dist/components/page-calendar/page-calendar.types.d.ts.map +1 -0
- package/dist/components/page-calendar/page-calendar.utils.d.ts +18 -0
- package/dist/components/page-calendar/page-calendar.utils.d.ts.map +1 -0
- package/dist/components/page-calendar/week-view.d.ts +11 -0
- package/dist/components/page-calendar/week-view.d.ts.map +1 -0
- package/dist/components/table/index.d.ts.map +1 -1
- package/dist/components/table/inner-table.d.ts.map +1 -1
- package/dist/components/table/metadata.d.ts.map +1 -1
- package/dist/components/table/row.d.ts.map +1 -1
- package/dist/components/table/table-lib.d.ts.map +1 -1
- package/dist/components/table/thead.d.ts.map +1 -1
- package/dist/config/context.d.ts.map +1 -1
- package/dist/config/default-translations.d.ts +21 -4
- package/dist/config/default-translations.d.ts.map +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/hooks/use-components-provider.d.ts.map +1 -1
- package/dist/hooks/use-form.d.ts +11 -11
- package/dist/hooks/use-form.d.ts.map +1 -1
- package/dist/hooks/use-input-id.d.ts.map +1 -1
- package/dist/hooks/use-preferences.d.ts.map +1 -1
- package/dist/hooks/use-previous.d.ts.map +1 -1
- package/dist/hooks/use-reactive.d.ts.map +1 -1
- package/dist/hooks/use-resize-observer.d.ts.map +1 -1
- package/dist/hooks/use-stable-ref.d.ts.map +1 -1
- package/dist/hooks/use-swipe.d.ts.map +1 -1
- package/dist/hooks/use-translations.d.ts +21 -4
- package/dist/hooks/use-translations.d.ts.map +1 -1
- package/dist/index.css +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +13862 -12512
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +24 -17
- package/dist/index.umd.js.map +1 -1
- package/dist/lib/dom.d.ts +1 -0
- package/dist/lib/dom.d.ts.map +1 -1
- package/dist/lib/fns.d.ts.map +1 -1
- package/dist/preset/plugin.tailwind.d.ts +9 -0
- package/dist/preset/plugin.tailwind.d.ts.map +1 -0
- package/dist/preset/plugin.tailwind.js +27 -0
- package/dist/preset/preset.tailwind.d.ts +8 -0
- package/dist/preset/preset.tailwind.d.ts.map +1 -0
- package/dist/preset/preset.tailwind.js +54 -0
- package/dist/preset/src/styles/common.d.ts +2 -14
- package/dist/preset/src/styles/common.d.ts.map +1 -1
- package/dist/preset/src/styles/common.js +1 -0
- package/dist/preset/src/styles/dark.d.ts.map +1 -1
- package/dist/preset/src/styles/dark.js +119 -114
- package/dist/preset/src/styles/light.d.ts.map +1 -1
- package/dist/preset/src/styles/light.js +111 -106
- package/dist/preset/src/styles/theme.types.d.ts +17 -8
- package/dist/preset/src/styles/theme.types.d.ts.map +1 -1
- package/dist/styles/common.d.ts +2 -14
- package/dist/styles/common.d.ts.map +1 -1
- package/dist/styles/dark.d.ts.map +1 -1
- package/dist/styles/light.d.ts.map +1 -1
- package/dist/styles/theme.types.d.ts +17 -8
- package/dist/styles/theme.types.d.ts.map +1 -1
- package/package.json +299 -301
- package/dist/components/core/button.jsx +0 -86
- package/dist/components/core/heading.jsx +0 -4
- package/dist/components/core/polymorph.jsx +0 -5
- package/dist/components/core/render-on-view.jsx +0 -31
- package/dist/components/core/resizable.jsx +0 -51
- package/dist/components/core/slot.jsx +0 -163
- package/dist/components/core/tag.jsx +0 -51
- package/dist/components/core/typography.jsx +0 -26
- package/dist/components/display/alert.jsx +0 -56
- package/dist/components/display/calendar.jsx +0 -301
- package/dist/components/display/card.jsx +0 -43
- package/dist/components/display/empty.jsx +0 -11
- package/dist/components/display/list.jsx +0 -81
- package/dist/components/display/notifications.jsx +0 -98
- package/dist/components/display/progress.jsx +0 -13
- package/dist/components/display/shortcut.jsx +0 -23
- package/dist/components/display/skeleton.jsx +0 -14
- package/dist/components/display/spinner.jsx +0 -7
- package/dist/components/display/stats.jsx +0 -20
- package/dist/components/display/step.jsx +0 -131
- package/dist/components/display/tabs.jsx +0 -100
- package/dist/components/display/timeline.jsx +0 -25
- package/dist/components/floating/command-palette.jsx +0 -172
- package/dist/components/floating/dropdown.jsx +0 -53
- package/dist/components/floating/expand.jsx +0 -44
- package/dist/components/floating/menu.jsx +0 -147
- package/dist/components/floating/modal.jsx +0 -241
- package/dist/components/floating/toolbar.jsx +0 -5
- package/dist/components/floating/tooltip.jsx +0 -64
- package/dist/components/floating/wizard.jsx +0 -164
- package/dist/components/form/autocomplete.jsx +0 -275
- package/dist/components/form/checkbox.jsx +0 -12
- package/dist/components/form/date-picker.jsx +0 -115
- package/dist/components/form/file-upload.jsx +0 -133
- package/dist/components/form/form.jsx +0 -10
- package/dist/components/form/formReset.jsx +0 -17
- package/dist/components/form/free-text.jsx +0 -41
- package/dist/components/form/input-field.jsx +0 -54
- package/dist/components/form/input.jsx +0 -36
- package/dist/components/form/multi-select.jsx +0 -328
- package/dist/components/form/radiobox.jsx +0 -6
- package/dist/components/form/select.jsx +0 -42
- package/dist/components/form/slider.jsx +0 -45
- package/dist/components/form/switch.jsx +0 -46
- package/dist/components/form/task-list.jsx +0 -26
- package/dist/components/form/textarea.jsx +0 -12
- package/dist/components/form/transfer-list.jsx +0 -39
- package/dist/components/index.js +0 -43
- package/dist/components/table/filter.jsx +0 -141
- package/dist/components/table/group.jsx +0 -68
- package/dist/components/table/index.jsx +0 -60
- package/dist/components/table/inner-table.jsx +0 -104
- package/dist/components/table/metadata.jsx +0 -37
- package/dist/components/table/pagination.jsx +0 -73
- package/dist/components/table/row.jsx +0 -58
- package/dist/components/table/sort.jsx +0 -105
- package/dist/components/table/table-lib.js +0 -84
- package/dist/components/table/table.context.jsx +0 -4
- package/dist/components/table/thead.jsx +0 -103
- package/dist/config/context.js +0 -12
- package/dist/config/default-translations.jsx +0 -66
- package/dist/config/default-tweaks.js +0 -4
- package/dist/constants.js +0 -2
- package/dist/hooks/use-click-outside.js +0 -17
- package/dist/hooks/use-color-parser.js +0 -9
- package/dist/hooks/use-components-provider.jsx +0 -16
- package/dist/hooks/use-debounce.js +0 -12
- package/dist/hooks/use-floating-ref.js +0 -6
- package/dist/hooks/use-form.js +0 -549
- package/dist/hooks/use-hover.js +0 -18
- package/dist/hooks/use-input-id.js +0 -5
- package/dist/hooks/use-is-coarse-device.js +0 -12
- package/dist/hooks/use-locale.js +0 -10
- package/dist/hooks/use-media-query.js +0 -25
- package/dist/hooks/use-on-event.js +0 -7
- package/dist/hooks/use-parent.js +0 -21
- package/dist/hooks/use-preferences.js +0 -23
- package/dist/hooks/use-previous.js +0 -8
- package/dist/hooks/use-reactive.js +0 -8
- package/dist/hooks/use-remove-scroll.js +0 -61
- package/dist/hooks/use-resize-observer.js +0 -17
- package/dist/hooks/use-stable-ref.js +0 -8
- package/dist/hooks/use-swipe.js +0 -16
- package/dist/hooks/use-translations.js +0 -9
- package/dist/hooks/use-tweaks.js +0 -9
- package/dist/hooks/use-window-size.js +0 -14
- package/dist/lib/combi-keys.js +0 -60
- package/dist/lib/dict.js +0 -39
- package/dist/lib/dom.js +0 -44
- package/dist/lib/fns.js +0 -46
- package/dist/lib/fzf.js +0 -117
- package/dist/lib/keyboard-area.js +0 -14
- package/dist/preset/tailwindcssv4.d.ts +0 -3
- package/dist/preset/tailwindcssv4.d.ts.map +0 -1
- package/dist/preset/tailwindcssv4.js +0 -75
- package/dist/styles/common.js +0 -28
- package/dist/styles/dark.js +0 -209
- package/dist/styles/design-tokens.js +0 -69
- package/dist/styles/light.js +0 -209
- package/dist/styles/theme.js +0 -4
- package/dist/styles/theme.types.js +0 -1
- package/dist/types.js +0 -1
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Menu
|
|
3
|
+
description: Accessible floating menu system with nested submenus, keyboard navigation, hover support, and typeahead.
|
|
4
|
+
package: "@g4rcez/components"
|
|
5
|
+
export: "{ Menu, MenuItem }"
|
|
6
|
+
import: "import { Menu, MenuItem } from '@g4rcez/components/menu'"
|
|
7
|
+
category: floating
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Menu
|
|
11
|
+
|
|
12
|
+
Accessible floating menu system with nested submenus, keyboard navigation, hover support, and typeahead.
|
|
13
|
+
|
|
14
|
+
## Import
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import { Menu, MenuItem } from "@g4rcez/components/menu";
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Props
|
|
21
|
+
|
|
22
|
+
### Menu
|
|
23
|
+
|
|
24
|
+
| Prop | Type | Default | Description |
|
|
25
|
+
|------|------|---------|-------------|
|
|
26
|
+
| `label` | `string \| React.ReactElement` | — | Trigger content |
|
|
27
|
+
| `title` | `string` | — | Required for accessibility when `label` is an element; also used for typeahead |
|
|
28
|
+
| `hover` | `boolean` | `true` | Open on hover in addition to click |
|
|
29
|
+
| `open` | `boolean` | `false` | Initial open state |
|
|
30
|
+
| `asChild` | `boolean` | `false` | Use the `Slot` pattern — merge props onto the child element instead of wrapping in a `<button>` |
|
|
31
|
+
| `restoreFocus` | `boolean` | `false` | Restore focus to the trigger after the menu closes |
|
|
32
|
+
| `floatingClassName` | `string` | — | Additional CSS classes for the floating list container |
|
|
33
|
+
| `FloatingComponent` | `React.ElementType` | `"div"` | Element type for the floating container |
|
|
34
|
+
|
|
35
|
+
### MenuItem
|
|
36
|
+
|
|
37
|
+
| Prop | Type | Default | Description |
|
|
38
|
+
|------|------|---------|-------------|
|
|
39
|
+
| `title` | `string` | — | Item text; used for typeahead matching and the `title` attribute |
|
|
40
|
+
| `children` | `React.ReactNode` | — | Visual content of the item |
|
|
41
|
+
| `disabled` | `boolean` | `false` | Removes item from keyboard navigation and typeahead |
|
|
42
|
+
| `Right` | `React.FC<LucideProps>` | — | Icon rendered on the right side |
|
|
43
|
+
| `onClick` | `function` | — | Click handler |
|
|
44
|
+
|
|
45
|
+
## Design Tokens
|
|
46
|
+
|
|
47
|
+
Tokens this component reads. Customize by overriding these CSS variables in your theme.
|
|
48
|
+
|
|
49
|
+
| Token | CSS Variable | Purpose |
|
|
50
|
+
|-------|-------------|---------|
|
|
51
|
+
| `bg-floating-background` | `--floating-background` | Menu list surface background |
|
|
52
|
+
| `border-floating-border` | `--floating-border` | Menu list border |
|
|
53
|
+
| `shadow-shadow-floating` | `--shadow-floating` | Menu list drop shadow |
|
|
54
|
+
| `z-tooltip` | `--z-tooltip` | Z-index of the floating list |
|
|
55
|
+
| `bg-primary` | `--primary` | Active/focused item background |
|
|
56
|
+
| `text-primary-foreground` | `--primary-foreground` | Active/focused item text |
|
|
57
|
+
|
|
58
|
+
## Examples
|
|
59
|
+
|
|
60
|
+
### Basic Menu
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { Menu, MenuItem } from "@g4rcez/components/menu";
|
|
64
|
+
|
|
65
|
+
<Menu label="Actions">
|
|
66
|
+
<MenuItem title="Edit">Edit Profile</MenuItem>
|
|
67
|
+
<MenuItem title="Share">Share Profile</MenuItem>
|
|
68
|
+
</Menu>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### With Icons and Shortcuts
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import { EditIcon, TrashIcon } from "lucide-react";
|
|
75
|
+
import { Menu, MenuItem } from "@g4rcez/components/menu";
|
|
76
|
+
|
|
77
|
+
<Menu label="Settings">
|
|
78
|
+
<MenuItem title="Edit" Right={EditIcon}>Edit</MenuItem>
|
|
79
|
+
<MenuItem title="Delete" Right={TrashIcon} className="text-danger">
|
|
80
|
+
Delete
|
|
81
|
+
</MenuItem>
|
|
82
|
+
</Menu>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Nested Submenus
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
import { Menu, MenuItem } from "@g4rcez/components/menu";
|
|
89
|
+
|
|
90
|
+
<Menu label="Actions">
|
|
91
|
+
<MenuItem title="Edit">Edit Profile</MenuItem>
|
|
92
|
+
<MenuItem title="Share">Share Profile</MenuItem>
|
|
93
|
+
<Menu label="More Options" title="More Options">
|
|
94
|
+
<MenuItem title="Archive">Archive Account</MenuItem>
|
|
95
|
+
<MenuItem title="Delete" className="text-danger">
|
|
96
|
+
Delete Account
|
|
97
|
+
</MenuItem>
|
|
98
|
+
</Menu>
|
|
99
|
+
</Menu>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Using asChild for Custom Triggers
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
import { Menu, MenuItem } from "@g4rcez/components/menu";
|
|
106
|
+
import { Button } from "@g4rcez/components/button";
|
|
107
|
+
|
|
108
|
+
<Menu
|
|
109
|
+
label={<Button theme="primary">Main Action</Button>}
|
|
110
|
+
asChild
|
|
111
|
+
title="Main Action"
|
|
112
|
+
>
|
|
113
|
+
<MenuItem title="Save">Save Version</MenuItem>
|
|
114
|
+
<MenuItem title="Publish">Publish Now</MenuItem>
|
|
115
|
+
</Menu>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Disabled Items
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
import { Menu, MenuItem } from "@g4rcez/components/menu";
|
|
122
|
+
|
|
123
|
+
<Menu label="Options">
|
|
124
|
+
<MenuItem title="Export">Export Data</MenuItem>
|
|
125
|
+
<MenuItem title="Import" disabled>Import (unavailable)</MenuItem>
|
|
126
|
+
<MenuItem title="Delete" className="text-danger">Delete</MenuItem>
|
|
127
|
+
</Menu>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Do
|
|
131
|
+
|
|
132
|
+
- Provide a `title` string whenever `label` is a React element — it enables typeahead and improves accessibility.
|
|
133
|
+
- Use nested `Menu` components (not just `MenuItem`) for logical groupings of secondary actions.
|
|
134
|
+
- Use `disabled` for actions temporarily unavailable, so users know the option exists.
|
|
135
|
+
- Use design-token classes (`text-danger`, `text-foreground`) for item text colors.
|
|
136
|
+
|
|
137
|
+
## Don't
|
|
138
|
+
|
|
139
|
+
- Don't make menus deeper than 2–3 levels — deeply nested submenus are hard to navigate.
|
|
140
|
+
- Don't use raw Tailwind color classes (`text-red-600`, `hover:bg-gray-100`) on items — use design-token classes.
|
|
141
|
+
- Don't use arbitrary Tailwind values (`bg-[#abc]`) — override CSS variables in your `@theme` block.
|
|
142
|
+
- Don't use `Menu` for primary navigation that should be crawlable — use standard `<a>` links instead.
|
|
143
|
+
|
|
144
|
+
## Accessibility
|
|
145
|
+
|
|
146
|
+
- Full keyboard navigation: arrow keys navigate items, `Enter`/`Space` select, `Escape` closes.
|
|
147
|
+
- Typeahead: typing the first characters of an item's `title` focuses it instantly (resets after inactivity).
|
|
148
|
+
- Correct ARIA roles: `menu` on the container, `menuitem` on each item, `aria-expanded` on nested triggers.
|
|
149
|
+
- `data-active` marks the currently focused item for CSS styling.
|
|
150
|
+
- `FloatingFocusManager` handles focus trapping and restoration.
|
|
151
|
+
|
|
152
|
+
## Data Attributes
|
|
153
|
+
|
|
154
|
+
| Attribute | Applied to | Description |
|
|
155
|
+
|-----------|-----------|-------------|
|
|
156
|
+
| `data-open` | Menu trigger, MenuItem | Present when the menu/item is open |
|
|
157
|
+
| `data-nested` | Nested menu trigger | Present on triggers that open a submenu |
|
|
158
|
+
| `data-focus-inside` | Menu trigger | Present when focus is inside an open submenu |
|
|
159
|
+
| `data-active` | MenuItem | Present on the currently focused item |
|
|
160
|
+
|
|
161
|
+
## Notes
|
|
162
|
+
|
|
163
|
+
- The root `Menu` wraps itself in a `FloatingTree`; nested `Menu` components detect this via `useFloatingParentNodeId` and behave as submenus.
|
|
164
|
+
- Hover delay is controlled by the `FLOATING_DELAY` constant. The safe-polygon handler keeps the menu open while the pointer moves toward the submenu.
|
|
165
|
+
- When `hover` is `false`, the menu opens only on click.
|
|
166
|
+
- The floating container default `max-h-80` prevents the list from growing taller than the viewport.
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Modal
|
|
3
|
+
description: Animated modal with dialog, drawer, and sheet variants; includes drag-to-resize and a programmatic confirm utility.
|
|
4
|
+
package: "@g4rcez/components"
|
|
5
|
+
export: "{ Modal, ModalConfirmProvider, useConfirm }"
|
|
6
|
+
import: "import { Modal } from '@g4rcez/components/modal'"
|
|
7
|
+
category: floating
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Modal
|
|
11
|
+
|
|
12
|
+
Animated modal with dialog, drawer, and sheet variants; includes drag-to-resize and a programmatic confirm utility.
|
|
13
|
+
|
|
14
|
+
## Import
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import { Modal, ModalConfirmProvider, useConfirm } from "@g4rcez/components/modal";
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Props
|
|
21
|
+
|
|
22
|
+
| Prop | Type | Default | Description |
|
|
23
|
+
|------|------|---------|-------------|
|
|
24
|
+
| `open` | `boolean` | — | Controls modal visibility |
|
|
25
|
+
| `onChange` | `(nextState: boolean) => void` | — | Callback when modal state changes |
|
|
26
|
+
| `title` | `React.ReactNode` | — | Modal title; creates a `<h2>` header |
|
|
27
|
+
| `ariaTitle` | `string` | — | ARIA label used when no visible `title` is provided |
|
|
28
|
+
| `footer` | `React.ReactNode` | — | Footer content |
|
|
29
|
+
| `type` | `"dialog" \| "drawer" \| "sheet"` | `"dialog"` | Modal display variant |
|
|
30
|
+
| `position` | `"left" \| "right"` | `"right"` | Drawer slide-in side (drawer type only) |
|
|
31
|
+
| `animated` | `boolean` | `true` | Enable enter/exit animations |
|
|
32
|
+
| `closable` | `boolean` | `true` | Show the close button |
|
|
33
|
+
| `resizer` | `boolean` | `true` | Show the drag-to-resize handle (drawer and sheet) |
|
|
34
|
+
| `forceType` | `boolean` | `false` | Disable responsive behavior — keep `type` on all screen sizes |
|
|
35
|
+
| `overlayClickClose` | `boolean` | `false` | Close when clicking the backdrop |
|
|
36
|
+
| `trigger` | `React.ReactNode \| React.FC` | — | Element that toggles the modal when clicked |
|
|
37
|
+
| `asChild` | `boolean` | `false` | Merge trigger props onto the child element via `Slot` |
|
|
38
|
+
| `className` | `string` | — | Additional classes for the modal surface |
|
|
39
|
+
| `bodyClassName` | `string` | — | Additional classes for the scrollable body |
|
|
40
|
+
| `overlayClassName` | `string` | — | Additional classes for the backdrop overlay |
|
|
41
|
+
| `layoutId` | `string` | — | Framer Motion layout ID for shared-element transitions |
|
|
42
|
+
| `role` | `"dialog"` | `"dialog"` | ARIA role |
|
|
43
|
+
| `interactions` | `ElementProps[]` | `[]` | Extra Floating UI interaction hooks |
|
|
44
|
+
|
|
45
|
+
## Modal Types
|
|
46
|
+
|
|
47
|
+
| Type | Behavior | Best for |
|
|
48
|
+
|------|----------|---------|
|
|
49
|
+
| `dialog` | Centered overlay with backdrop | Confirmations, forms |
|
|
50
|
+
| `drawer` | Slides in from left or right; full height | Navigation, detail panels |
|
|
51
|
+
| `sheet` | Slides up from bottom; full width | Mobile action sheets |
|
|
52
|
+
|
|
53
|
+
On mobile (`< 64 rem`) drawers automatically become sheets unless `forceType` is set.
|
|
54
|
+
|
|
55
|
+
## Design Tokens
|
|
56
|
+
|
|
57
|
+
Tokens this component reads. Customize by overriding these CSS variables in your theme.
|
|
58
|
+
|
|
59
|
+
| Token | CSS Variable | Purpose |
|
|
60
|
+
|-------|-------------|---------|
|
|
61
|
+
| `bg-floating-background` | `--floating-background` | Modal surface background |
|
|
62
|
+
| `border-floating-border` | `--floating-border` | Modal border, header/footer dividers, resizer |
|
|
63
|
+
| `bg-floating-overlay` | `--floating-overlay` | Backdrop color (with `/70` opacity) |
|
|
64
|
+
| `z-overlay` | `--z-overlay` | Z-index of the backdrop |
|
|
65
|
+
| `z-floating` | `--z-floating` | Z-index of the modal surface and close button |
|
|
66
|
+
| `w-dialog` | `--dialog` | Default max-width for dialog type (`max-w-dialog`) |
|
|
67
|
+
| `text-foreground` | `--foreground` | Body text color |
|
|
68
|
+
| `text-danger` | `--danger` | Close button hover color |
|
|
69
|
+
|
|
70
|
+
## Examples
|
|
71
|
+
|
|
72
|
+
### Basic Dialog
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
import { useState } from "react";
|
|
76
|
+
import { Modal } from "@g4rcez/components/modal";
|
|
77
|
+
import { Button } from "@g4rcez/components/button";
|
|
78
|
+
|
|
79
|
+
function BasicDialog() {
|
|
80
|
+
const [open, setOpen] = useState(false);
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<>
|
|
84
|
+
<Button onClick={() => setOpen(true)}>Open Dialog</Button>
|
|
85
|
+
|
|
86
|
+
<Modal open={open} onChange={setOpen} title="Confirm Action">
|
|
87
|
+
<p className="text-foreground">Are you sure you want to proceed?</p>
|
|
88
|
+
<div className="flex gap-2 mt-4">
|
|
89
|
+
<Button theme="ghost-muted" onClick={() => setOpen(false)}>
|
|
90
|
+
Cancel
|
|
91
|
+
</Button>
|
|
92
|
+
<Button theme="danger" onClick={() => setOpen(false)}>
|
|
93
|
+
Confirm
|
|
94
|
+
</Button>
|
|
95
|
+
</div>
|
|
96
|
+
</Modal>
|
|
97
|
+
</>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Modal with Footer
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
<Modal
|
|
106
|
+
open={open}
|
|
107
|
+
onChange={setOpen}
|
|
108
|
+
title="User Profile"
|
|
109
|
+
footer={
|
|
110
|
+
<div className="flex justify-end gap-2">
|
|
111
|
+
<Button theme="ghost-muted" onClick={() => setOpen(false)}>
|
|
112
|
+
Cancel
|
|
113
|
+
</Button>
|
|
114
|
+
<Button theme="primary">Save Changes</Button>
|
|
115
|
+
</div>
|
|
116
|
+
}
|
|
117
|
+
>
|
|
118
|
+
<form className="space-y-4">
|
|
119
|
+
<div>
|
|
120
|
+
<label className="text-sm font-medium text-foreground">Name</label>
|
|
121
|
+
<input type="text" className="w-full p-2 rounded-button border border-border bg-background text-foreground" />
|
|
122
|
+
</div>
|
|
123
|
+
</form>
|
|
124
|
+
</Modal>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Drawer
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
<Modal
|
|
131
|
+
open={open}
|
|
132
|
+
onChange={setOpen}
|
|
133
|
+
type="drawer"
|
|
134
|
+
position="right"
|
|
135
|
+
title="Navigation"
|
|
136
|
+
>
|
|
137
|
+
<nav className="space-y-2">
|
|
138
|
+
<a href="/dashboard" className="block rounded-button p-2 text-foreground hover:bg-muted">
|
|
139
|
+
Dashboard
|
|
140
|
+
</a>
|
|
141
|
+
<a href="/profile" className="block rounded-button p-2 text-foreground hover:bg-muted">
|
|
142
|
+
Profile
|
|
143
|
+
</a>
|
|
144
|
+
<a href="/settings" className="block rounded-button p-2 text-foreground hover:bg-muted">
|
|
145
|
+
Settings
|
|
146
|
+
</a>
|
|
147
|
+
</nav>
|
|
148
|
+
</Modal>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Sheet
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
<Modal open={open} onChange={setOpen} type="sheet" title="Quick Actions">
|
|
155
|
+
<div className="grid grid-cols-2 gap-4">
|
|
156
|
+
<Button theme="ghost-neutral" className="flex-col h-24">
|
|
157
|
+
Analytics
|
|
158
|
+
</Button>
|
|
159
|
+
<Button theme="ghost-neutral" className="flex-col h-24">
|
|
160
|
+
Revenue
|
|
161
|
+
</Button>
|
|
162
|
+
</div>
|
|
163
|
+
</Modal>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Modal with Built-in Trigger
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
<Modal
|
|
170
|
+
open={open}
|
|
171
|
+
onChange={setOpen}
|
|
172
|
+
title="Settings"
|
|
173
|
+
trigger={<Button theme="primary">Open Settings</Button>}
|
|
174
|
+
>
|
|
175
|
+
<p className="text-foreground">Settings content here.</p>
|
|
176
|
+
</Modal>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Non-closable Processing Modal
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
<Modal open={open} onChange={setOpen} title="Processing..." closable={false}>
|
|
183
|
+
<div className="text-center text-foreground">
|
|
184
|
+
<div className="animate-spin w-8 h-8 border-4 border-primary border-t-transparent rounded-full mx-auto mb-4" />
|
|
185
|
+
<p>Please wait while we process your request.</p>
|
|
186
|
+
</div>
|
|
187
|
+
</Modal>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Programmatic Confirm Dialog
|
|
191
|
+
|
|
192
|
+
Wrap your app with `ModalConfirmProvider` once:
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
import { ModalConfirmProvider } from "@g4rcez/components/modal";
|
|
196
|
+
|
|
197
|
+
export default function App({ children }) {
|
|
198
|
+
return <ModalConfirmProvider>{children}</ModalConfirmProvider>;
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Then call `Modal.confirm` anywhere:
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
import { Modal } from "@g4rcez/components/modal";
|
|
206
|
+
|
|
207
|
+
async function handleDelete() {
|
|
208
|
+
const confirmed = await Modal.confirm({
|
|
209
|
+
title: "Delete item",
|
|
210
|
+
description: "This action cannot be undone.",
|
|
211
|
+
confirm: { text: "Delete", theme: "danger" },
|
|
212
|
+
cancel: { text: "Cancel" },
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
if (confirmed) {
|
|
216
|
+
// proceed with deletion
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Or use the `useConfirm` hook inside a `ModalConfirmProvider` subtree:
|
|
222
|
+
|
|
223
|
+
```tsx
|
|
224
|
+
import { useConfirm } from "@g4rcez/components/modal";
|
|
225
|
+
|
|
226
|
+
function DeleteButton() {
|
|
227
|
+
const confirm = useConfirm();
|
|
228
|
+
|
|
229
|
+
const handleClick = async () => {
|
|
230
|
+
const ok = await confirm({
|
|
231
|
+
title: "Delete item",
|
|
232
|
+
description: "Are you sure?",
|
|
233
|
+
});
|
|
234
|
+
if (ok) {
|
|
235
|
+
// delete
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
return <Button theme="danger" onClick={handleClick}>Delete</Button>;
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Do
|
|
244
|
+
|
|
245
|
+
- Choose the correct `type` (`dialog`, `drawer`, `sheet`) for the content and context.
|
|
246
|
+
- Use `<Modal type="dialog" />` as the default — only switch to `drawer` or `sheet` when layout demands it.
|
|
247
|
+
- Always provide either `title` or `ariaTitle` so screen readers can announce the modal.
|
|
248
|
+
- Use `footer` for action buttons to keep them separated from body content.
|
|
249
|
+
|
|
250
|
+
## Don't
|
|
251
|
+
|
|
252
|
+
- Don't pass custom `z-*`, `rounded-*`, or `p-*` overrides directly — use `type` to control layout variants.
|
|
253
|
+
- Don't use raw Tailwind color classes (`bg-blue-500`, `text-white`, `bg-gray-100`) — use design-token classes.
|
|
254
|
+
- Don't use arbitrary Tailwind values (`bg-[#abc]`, `z-[9999]`) — override CSS variables in your `@theme` block.
|
|
255
|
+
- Don't use `color-mix()` in `className` or `style` props.
|
|
256
|
+
- Don't nest modals inside each other — it creates broken focus and z-index behavior.
|
|
257
|
+
- Don't hide a modal's only close affordance (`closable={false}`) without providing another way to close.
|
|
258
|
+
|
|
259
|
+
## Accessibility
|
|
260
|
+
|
|
261
|
+
- Focus is automatically trapped inside the modal via `FloatingFocusManager` with `guards` and `modal`.
|
|
262
|
+
- `Escape` closes the modal (via `useDismiss` with `escapeKey: true`).
|
|
263
|
+
- The modal surface receives `aria-labelledby` (when `title` is set) or `aria-label` (when `ariaTitle` is set).
|
|
264
|
+
- `aria-modal="true"` is applied to the floating surface.
|
|
265
|
+
- Scroll is locked on the body while the modal is open.
|
|
266
|
+
|
|
267
|
+
## Data Attributes
|
|
268
|
+
|
|
269
|
+
| Attribute | Applied to | Description |
|
|
270
|
+
|-----------|-----------|-------------|
|
|
271
|
+
| `data-component="modal"` | Modal surface `<div>` | Identifies the modal root |
|
|
272
|
+
| `data-component="modal-body"` | Scrollable body `<section>` | Identifies the content area |
|
|
273
|
+
|
|
274
|
+
## Notes
|
|
275
|
+
|
|
276
|
+
- Drawers auto-switch to sheets on viewports narrower than `64rem` unless `forceType={true}`.
|
|
277
|
+
- `resizer` adds a draggable handle: horizontal for drawers, vertical for sheets. Dragging a sheet past 60 % of screen height closes it.
|
|
278
|
+
- `layoutId` enables Framer Motion shared-element transitions between a trigger and the modal surface.
|
|
279
|
+
- `ModalConfirmProvider` sets a module-level `confirmGlobal` function so `Modal.confirm` works outside React trees (e.g., in event handlers).
|
|
280
|
+
- The confirm dialog uses `max-w-dialog` (`w-dialog` token, default `20rem`) and cannot be closed by clicking the overlay.
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: MultiSelect
|
|
3
|
+
description: Dropdown that lets users select multiple options with fuzzy search and tag display.
|
|
4
|
+
package: "@g4rcez/components"
|
|
5
|
+
export: "{ MultiSelect }"
|
|
6
|
+
import: "import { MultiSelect } from '@g4rcez/components'"
|
|
7
|
+
category: form
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# MultiSelect
|
|
11
|
+
|
|
12
|
+
Dropdown that lets users select multiple options with fuzzy search and tag display.
|
|
13
|
+
|
|
14
|
+
## Import
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import { MultiSelect } from "@g4rcez/components";
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Props
|
|
21
|
+
|
|
22
|
+
The `MultiSelect` component inherits all props from `InputField`, plus:
|
|
23
|
+
|
|
24
|
+
| Prop | Type | Default | Description |
|
|
25
|
+
|------|------|---------|-------------|
|
|
26
|
+
| `options` | `MultiSelectItemProps[]` | — | Array of `{ value, label, Render? }` option objects. |
|
|
27
|
+
| `value` | `string[]` | — | Controlled selected values. |
|
|
28
|
+
| `defaultValue` | `string[]` | `[]` | Initial selected values for uncontrolled usage. |
|
|
29
|
+
| `onChangeOptions` | `(options: string[]) => void` | — | Called when the selection changes. |
|
|
30
|
+
| `dynamicOption` | `boolean` | `false` | Allows users to select their search query as a new option. |
|
|
31
|
+
| `emptyMessage` | `Label` | — | Message shown when no options match the search. |
|
|
32
|
+
| `selectedLabel` | `string` | — | Text shown in the overflow counter (e.g., "selected"). |
|
|
33
|
+
|
|
34
|
+
### MultiSelectItemProps
|
|
35
|
+
|
|
36
|
+
Extends `OptionProps` with an optional custom renderer:
|
|
37
|
+
|
|
38
|
+
| Field | Type | Description |
|
|
39
|
+
|-------|------|-------------|
|
|
40
|
+
| `value` | `string` | Option value. |
|
|
41
|
+
| `label` | `string` | Option display text. |
|
|
42
|
+
| `Render` | `React.FC<OptionProps>` | Custom render component for the dropdown row. |
|
|
43
|
+
| `hidden` | `boolean` | Hides the option from the list. |
|
|
44
|
+
|
|
45
|
+
## Design Tokens
|
|
46
|
+
|
|
47
|
+
Tokens this component reads. Customize by overriding these CSS variables in your theme.
|
|
48
|
+
|
|
49
|
+
| Token | CSS Variable | Purpose |
|
|
50
|
+
|-------|-------------|---------|
|
|
51
|
+
| `placeholder-input-mask` | `--input-mask` | Placeholder text color |
|
|
52
|
+
| `placeholder-input-mask-error` | `--input-mask-error` | Placeholder color in error state |
|
|
53
|
+
| `border-input-border` | `--input-border` | Search input bottom border in dropdown |
|
|
54
|
+
| `bg-floating-background` | `--floating-background` | Dropdown panel background |
|
|
55
|
+
| `border-floating-border` | `--floating-border` | Dropdown panel border |
|
|
56
|
+
| `bg-floating-hover` | `--floating-hover` | Option row hover/active background |
|
|
57
|
+
| `text-foreground` | `--foreground` | Option text color |
|
|
58
|
+
| `text-input-placeholder` | `--input-placeholder` | Placeholder li color |
|
|
59
|
+
| `text-disabled` | `--disabled` | Empty-state text color |
|
|
60
|
+
| `focus:ring-primary` | `--primary` | Keyboard focus ring |
|
|
61
|
+
| `h-input-height` | `--input-height` | Trigger element height (2.5 rem) |
|
|
62
|
+
| `px-input-x` | `--input-x` | Horizontal padding |
|
|
63
|
+
| `py-input-y` | `--input-y` | Vertical padding |
|
|
64
|
+
|
|
65
|
+
## Examples
|
|
66
|
+
|
|
67
|
+
### Basic usage
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
import { MultiSelect } from "@g4rcez/components";
|
|
71
|
+
|
|
72
|
+
const options = [
|
|
73
|
+
{ value: "react", label: "React" },
|
|
74
|
+
{ value: "vue", label: "Vue" },
|
|
75
|
+
{ value: "angular", label: "Angular" },
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
export default function FrameworkPicker() {
|
|
79
|
+
return (
|
|
80
|
+
<MultiSelect
|
|
81
|
+
title="Frameworks"
|
|
82
|
+
options={options}
|
|
83
|
+
onChangeOptions={(values) => console.log(values)}
|
|
84
|
+
/>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Controlled selection
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
import { useState } from "react";
|
|
93
|
+
import { MultiSelect } from "@g4rcez/components";
|
|
94
|
+
|
|
95
|
+
export default function ControlledMultiSelect() {
|
|
96
|
+
const [selected, setSelected] = useState<string[]>(["react"]);
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<MultiSelect
|
|
100
|
+
title="Frameworks"
|
|
101
|
+
options={[
|
|
102
|
+
{ value: "react", label: "React" },
|
|
103
|
+
{ value: "vue", label: "Vue" },
|
|
104
|
+
{ value: "angular", label: "Angular" },
|
|
105
|
+
]}
|
|
106
|
+
value={selected}
|
|
107
|
+
onChangeOptions={setSelected}
|
|
108
|
+
/>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Custom option rendering
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
import { ShieldIcon } from "lucide-react";
|
|
117
|
+
import { MultiSelect } from "@g4rcez/components";
|
|
118
|
+
|
|
119
|
+
const roleOptions = roles.map((r) => ({
|
|
120
|
+
value: r.id,
|
|
121
|
+
label: r.name,
|
|
122
|
+
Render: () => (
|
|
123
|
+
<span className="flex items-center gap-base">
|
|
124
|
+
<ShieldIcon size={14} className="text-primary" />
|
|
125
|
+
{r.name}
|
|
126
|
+
</span>
|
|
127
|
+
),
|
|
128
|
+
}));
|
|
129
|
+
|
|
130
|
+
export default function RolePicker() {
|
|
131
|
+
return (
|
|
132
|
+
<MultiSelect
|
|
133
|
+
title="Roles"
|
|
134
|
+
options={roleOptions}
|
|
135
|
+
onChangeOptions={(ids) => console.log(ids)}
|
|
136
|
+
/>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Dynamic option creation
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
import { MultiSelect } from "@g4rcez/components";
|
|
145
|
+
|
|
146
|
+
export default function TagInput() {
|
|
147
|
+
return (
|
|
148
|
+
<MultiSelect
|
|
149
|
+
title="Tags"
|
|
150
|
+
dynamicOption
|
|
151
|
+
options={existingTags}
|
|
152
|
+
onChangeOptions={(tags) => console.log(tags)}
|
|
153
|
+
/>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Do
|
|
159
|
+
|
|
160
|
+
- Provide a descriptive `title` to label the trigger.
|
|
161
|
+
- Use `dynamicOption` when users may need to add items not in the predefined list.
|
|
162
|
+
- Use design-token classes for any wrapper elements (`bg-background`, `border-border`, `text-foreground`).
|
|
163
|
+
- Use `theme="<value>"` on nested elements — never hardcode colors in `className`.
|
|
164
|
+
|
|
165
|
+
## Don't
|
|
166
|
+
|
|
167
|
+
- Don't use `MultiSelect` when only a single selection is needed — use `Select` or `Autocomplete` instead.
|
|
168
|
+
- Don't pass raw Tailwind color classes (`bg-blue-500`, `text-white`) — use theme props or design tokens instead.
|
|
169
|
+
- Don't use arbitrary Tailwind values (`bg-[#abc]`, `bg-[--my-var]`) — override CSS variables in your `@theme` block instead.
|
|
170
|
+
- Don't use `color-mix()` in className or style props for theming.
|
|
171
|
+
|
|
172
|
+
## Accessibility
|
|
173
|
+
|
|
174
|
+
- The trigger uses `aria-autocomplete="list"` and `role="button"`.
|
|
175
|
+
- The dropdown list uses `role="listbox"` with individual items carrying `role="option"`, `aria-selected`, `aria-checked`, and `aria-current`.
|
|
176
|
+
- `FloatingFocusManager` traps focus inside the open dropdown.
|
|
177
|
+
- Arrow keys navigate the virtual list; Escape closes it; Enter selects the focused item.
|
|
178
|
+
|
|
179
|
+
## Data Attributes
|
|
180
|
+
|
|
181
|
+
| Attribute | Element | Value | Description |
|
|
182
|
+
|-----------|---------|-------|-------------|
|
|
183
|
+
| `data-component` | trigger `ul` | `"autocomplete"` | Identifies the component type. |
|
|
184
|
+
| `data-shadow` | trigger `ul` | `"true"` | Marks the visual shadow trigger. |
|
|
185
|
+
| `data-value` | trigger `ul` | JSON string | Currently selected values as a JSON array. |
|
|
186
|
+
| `data-floating` | dropdown root | `"true"` | Marks the floating panel. |
|
|
187
|
+
| `data-dynamic` | option button | `"true"` | Marks options injected via `dynamicOption`. |
|
|
188
|
+
| `data-error` | trigger `ul` | boolean string | Reflects the error state. |
|
|
189
|
+
|
|
190
|
+
## Notes
|
|
191
|
+
|
|
192
|
+
- Uses `react-virtuoso` for virtualized rendering — large option lists perform well.
|
|
193
|
+
- The overflow control collapses selected tags into a counter badge when they exceed the trigger width.
|
|
194
|
+
- The hidden `<input type="hidden">` stores the comma-separated selected values for native form submission.
|
|
195
|
+
- Floating position is computed with `@floating-ui/react` and updates automatically while open (`autoUpdate`).
|
|
196
|
+
- The dropdown search uses `fzf` (fuzzy matching) across both `value` and `label` fields.
|