@aircall/ds 0.14.0 → 0.15.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 +31 -0
- package/dist/globals.css +1 -1
- package/dist/index.d.ts +28 -28
- package/dist/index.js +1 -1
- package/package.json +12 -2
- package/skills/aircall-ds/migrate-icons/SKILL.md +346 -0
- package/skills/aircall-ds/migrate-tractor/SKILL.md +314 -0
- package/skills/aircall-ds/migrate-tractor/accordion/SKILL.md +276 -0
- package/skills/aircall-ds/migrate-tractor/alert/SKILL.md +225 -0
- package/skills/aircall-ds/migrate-tractor/avatar/SKILL.md +272 -0
- package/skills/aircall-ds/migrate-tractor/badge/SKILL.md +274 -0
- package/skills/aircall-ds/migrate-tractor/button/SKILL.md +277 -0
- package/skills/aircall-ds/migrate-tractor/card/SKILL.md +278 -0
- package/skills/aircall-ds/migrate-tractor/combobox/SKILL.md +346 -0
- package/skills/aircall-ds/migrate-tractor/data-table/SKILL.md +333 -0
- package/skills/aircall-ds/migrate-tractor/dialog/SKILL.md +206 -0
- package/skills/aircall-ds/migrate-tractor/divider/SKILL.md +226 -0
- package/skills/aircall-ds/migrate-tractor/dropdown-menu/SKILL.md +266 -0
- package/skills/aircall-ds/migrate-tractor/dropzone/SKILL.md +338 -0
- package/skills/aircall-ds/migrate-tractor/form-and-field/SKILL.md +325 -0
- package/skills/aircall-ds/migrate-tractor/gauge/SKILL.md +248 -0
- package/skills/aircall-ds/migrate-tractor/input/SKILL.md +261 -0
- package/skills/aircall-ds/migrate-tractor/item/SKILL.md +298 -0
- package/skills/aircall-ds/migrate-tractor/link/SKILL.md +263 -0
- package/skills/aircall-ds/migrate-tractor/popover/SKILL.md +214 -0
- package/skills/aircall-ds/migrate-tractor/select/SKILL.md +245 -0
- package/skills/aircall-ds/migrate-tractor/sheet-vs-drawer/SKILL.md +272 -0
- package/skills/aircall-ds/migrate-tractor/skeleton/SKILL.md +190 -0
- package/skills/aircall-ds/migrate-tractor/styling/SKILL.md +421 -0
- package/skills/aircall-ds/migrate-tractor/tabs/SKILL.md +250 -0
- package/skills/aircall-ds/migrate-tractor/toast/SKILL.md +322 -0
- package/skills/aircall-ds/migrate-tractor/tooltip/SKILL.md +204 -0
- package/skills/aircall-ds/migrate-tractor/tree/SKILL.md +346 -0
- package/skills/aircall-ds/setup/SKILL.md +347 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: aircall-ds/migrate-tractor/divider
|
|
3
|
+
description: >
|
|
4
|
+
Migrate Tractor Divider to the @aircall/ds Separator. Load when a file imports
|
|
5
|
+
Divider from @aircall/tractor. Maps orientation (vertical|horizontal), drops the
|
|
6
|
+
size and bg props, and replaces Box-level color overrides with Tailwind className.
|
|
7
|
+
type: sub-skill
|
|
8
|
+
library: aircall-ds
|
|
9
|
+
library_version: "0.13.0"
|
|
10
|
+
requires:
|
|
11
|
+
- aircall-ds/setup
|
|
12
|
+
- aircall-ds/migrate-tractor
|
|
13
|
+
sources:
|
|
14
|
+
- "aircall/hydra:packages/ds/src/components/separator.tsx"
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
This skill builds on aircall-ds/migrate-tractor.
|
|
18
|
+
|
|
19
|
+
## 1. Component mapping
|
|
20
|
+
|
|
21
|
+
| Tractor part | @aircall/ds part | Notes |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| `<Divider>` | `<Separator>` | Name change; orientation prop is preserved |
|
|
24
|
+
| `size` prop | _(removed)_ | DS Separator is always 1 px; no thickness control |
|
|
25
|
+
| `bg` prop | `className` | Pass a Tailwind background utility to override the border color |
|
|
26
|
+
| Extends `BoxProps` | Extends `SeparatorPrimitive.Props` | DS root is a Base UI element, not a styled Box |
|
|
27
|
+
|
|
28
|
+
## 2. Verified DS export (`packages/ds/src/index.ts`)
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
Separator
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 3. Imports
|
|
35
|
+
|
|
36
|
+
**Before (Tractor):**
|
|
37
|
+
```tsx
|
|
38
|
+
import { Divider } from '@aircall/tractor';
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**After (DS):**
|
|
42
|
+
```tsx
|
|
43
|
+
import { Separator } from '@aircall/ds';
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 4. Prop changes
|
|
47
|
+
|
|
48
|
+
| Tractor prop | DS prop | Action |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| `orientation?: 'vertical' \| 'horizontal'` | `orientation?: 'vertical' \| 'horizontal'` | Same values — pass through as-is |
|
|
51
|
+
| `size?: 'xSmall' \| 'small'` | _(removed)_ | Delete the prop; DS Separator is always 1 px thick |
|
|
52
|
+
| `bg?: string` | `className` | Replace with a Tailwind `bg-*` utility on `className` |
|
|
53
|
+
|
|
54
|
+
The DS `Separator` accepts all standard HTML attributes (`id`, `aria-*`, `data-*`, `style`, `className`) plus `orientation`. It does not accept `children`.
|
|
55
|
+
|
|
56
|
+
### Default orientation difference
|
|
57
|
+
|
|
58
|
+
Tractor's `Divider` defaulted to `orientation="vertical"`. The DS `Separator` defaults to `orientation="horizontal"`. Always pass `orientation` explicitly on both sides of the migration to avoid a silent layout change.
|
|
59
|
+
|
|
60
|
+
## 5. Sizing behavior
|
|
61
|
+
|
|
62
|
+
Tractor's `Divider` used a `size` prop to control thickness (`xSmall` = 1 px, `small` = 2 px). The DS `Separator` is always 1 px thick via Tailwind classes applied internally (`data-[orientation=horizontal]:h-px` / `data-[orientation=vertical]:w-px`). There is no thickness prop — if you need a 2 px separator, add `className="data-[orientation=horizontal]:h-0.5 data-[orientation=vertical]:w-0.5"`.
|
|
63
|
+
|
|
64
|
+
## 6. Color behavior
|
|
65
|
+
|
|
66
|
+
Tractor's `Divider` accepted a Tractor design-token string via `bg` (e.g. `"graphic-default"`, `"neutral-700"`). The DS `Separator` uses `bg-border` by default (mapped to the DS border token). To override, pass a Tailwind utility class via `className`:
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
// Tractor custom color
|
|
70
|
+
<Divider orientation="vertical" bg="neutral-700" />
|
|
71
|
+
|
|
72
|
+
// DS equivalent
|
|
73
|
+
<Separator orientation="vertical" className="bg-neutral-700" />
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 7. Before / After examples
|
|
77
|
+
|
|
78
|
+
### 7a. Horizontal separator between sections (default use case)
|
|
79
|
+
|
|
80
|
+
**Before (Tractor):**
|
|
81
|
+
```tsx
|
|
82
|
+
import { Divider } from '@aircall/tractor';
|
|
83
|
+
|
|
84
|
+
export function SectionBreak() {
|
|
85
|
+
return <Divider orientation="horizontal" />;
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**After (DS):**
|
|
90
|
+
```tsx
|
|
91
|
+
import { Separator } from '@aircall/ds';
|
|
92
|
+
|
|
93
|
+
export function SectionBreak() {
|
|
94
|
+
return <Separator orientation="horizontal" />;
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 7b. Vertical separator between inline items
|
|
99
|
+
|
|
100
|
+
**Before (Tractor):**
|
|
101
|
+
```tsx
|
|
102
|
+
import { Divider } from '@aircall/tractor';
|
|
103
|
+
|
|
104
|
+
export function InlineActions() {
|
|
105
|
+
return (
|
|
106
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
107
|
+
<span>Calls</span>
|
|
108
|
+
<Divider orientation="vertical" />
|
|
109
|
+
<span>Contacts</span>
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**After (DS):**
|
|
116
|
+
```tsx
|
|
117
|
+
import { Separator } from '@aircall/ds';
|
|
118
|
+
|
|
119
|
+
export function InlineActions() {
|
|
120
|
+
return (
|
|
121
|
+
<div className="flex items-center gap-2">
|
|
122
|
+
<span>Calls</span>
|
|
123
|
+
<Separator orientation="vertical" />
|
|
124
|
+
<span>Contacts</span>
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 7c. Thin divider with `size="xSmall"` dropped
|
|
131
|
+
|
|
132
|
+
**Before (Tractor):**
|
|
133
|
+
```tsx
|
|
134
|
+
import { Divider } from '@aircall/tractor';
|
|
135
|
+
|
|
136
|
+
export function MenuDivider() {
|
|
137
|
+
return <Divider orientation="horizontal" size="xSmall" />;
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**After (DS):**
|
|
142
|
+
```tsx
|
|
143
|
+
import { Separator } from '@aircall/ds';
|
|
144
|
+
|
|
145
|
+
export function MenuDivider() {
|
|
146
|
+
return <Separator orientation="horizontal" />;
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
> Both `xSmall` (1 px) and DS default (1 px) are identical. Drop `size` without any replacement.
|
|
151
|
+
|
|
152
|
+
### 7d. Custom color via `bg` → `className`
|
|
153
|
+
|
|
154
|
+
**Before (Tractor):**
|
|
155
|
+
```tsx
|
|
156
|
+
import { Divider } from '@aircall/tractor';
|
|
157
|
+
|
|
158
|
+
export function BoldDivider() {
|
|
159
|
+
return <Divider orientation="horizontal" size="small" bg="neutral-700" />;
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**After (DS):**
|
|
164
|
+
```tsx
|
|
165
|
+
import { Separator } from '@aircall/ds';
|
|
166
|
+
|
|
167
|
+
export function BoldDivider() {
|
|
168
|
+
return (
|
|
169
|
+
<Separator
|
|
170
|
+
orientation="horizontal"
|
|
171
|
+
className="bg-neutral-700 data-[orientation=horizontal]:h-0.5"
|
|
172
|
+
/>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
> `size="small"` (2 px) has no direct DS equivalent — use `data-[orientation=horizontal]:h-0.5` to restore the 2 px thickness.
|
|
178
|
+
|
|
179
|
+
## 8. Common mistakes
|
|
180
|
+
|
|
181
|
+
### Mistake 1: Relying on the default orientation matching Tractor's default
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
// WRONG — Tractor default is "vertical"; DS default is "horizontal"; omitting orientation
|
|
185
|
+
// silently changes the rendered layout
|
|
186
|
+
<Separator />
|
|
187
|
+
|
|
188
|
+
// CORRECT — always pass orientation explicitly
|
|
189
|
+
<Separator orientation="vertical" />
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Tractor's `Divider` defaulted to `orientation="vertical"`. The DS `Separator` defaults to `orientation="horizontal"` (matching the Base UI primitive default). Omitting the prop after renaming the component causes a horizontal line to appear where a vertical one was expected — no warning, no error.
|
|
193
|
+
|
|
194
|
+
Source: `packages/ds/src/components/separator.tsx` — `orientation = 'horizontal'` is the destructured default.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
### Mistake 2: Passing the `size` prop expecting thickness control
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
// WRONG — DS Separator has no `size` prop; it spreads onto the DOM element as a string attribute
|
|
202
|
+
<Separator orientation="horizontal" size="small" />
|
|
203
|
+
|
|
204
|
+
// CORRECT — drop `size`; for 2 px thickness add a Tailwind class
|
|
205
|
+
<Separator orientation="horizontal" className="data-[orientation=horizontal]:h-0.5" />
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
`SeparatorProps` extends `SeparatorPrimitive.Props`, which has no `size` field. Because `Separator` spreads unknown props onto the Base UI primitive, `size="small"` becomes a non-standard DOM attribute, triggering a React warning and failing strict DOM validation.
|
|
209
|
+
|
|
210
|
+
Source: `packages/ds/src/components/separator.tsx` — `SeparatorProps` extends `SeparatorPrimitive.Props` with no additional fields.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
### Mistake 3: Passing a Tractor design-token string to `bg` or `color`
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
// WRONG — DS Separator has no `bg` prop; passing it spreads a raw string onto the DOM
|
|
218
|
+
<Separator orientation="vertical" bg="graphic-default" />
|
|
219
|
+
|
|
220
|
+
// CORRECT — use a Tailwind bg-* utility via className
|
|
221
|
+
<Separator orientation="vertical" className="bg-border" />
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Tractor's `Divider` was built on `Box`, which accepted Tractor design-token strings for `bg`. `Separator` is a Base UI element with no styled-system layer — there is no `bg` prop. Passing it results in `bg="graphic-default"` becoming an invalid HTML attribute on the rendered DOM element.
|
|
225
|
+
|
|
226
|
+
Source: `packages/ds/src/components/separator.tsx` — root renders `<SeparatorPrimitive>` directly; `bg` is not in `SeparatorPrimitive.Props`.
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: aircall-ds/migrate-tractor/dropdown-menu
|
|
3
|
+
description: >
|
|
4
|
+
Migrate Tractor Dropdown and ActionMenu to the @aircall/ds DropdownMenu compound.
|
|
5
|
+
Load when a file imports Dropdown, ActionMenu, or ActionMenuItem from @aircall/tractor.
|
|
6
|
+
Covers trigger render prop, destructive item variant, icon placement, and the Group-wraps-Label rule.
|
|
7
|
+
type: sub-skill
|
|
8
|
+
library: aircall-ds
|
|
9
|
+
library_version: "0.13.0"
|
|
10
|
+
requires:
|
|
11
|
+
- aircall-ds/setup
|
|
12
|
+
- aircall-ds/migrate-tractor
|
|
13
|
+
sources:
|
|
14
|
+
- "aircall/hydra:docs/migration-guides/tractor-to-ds/recipes/dropdown-menu.md"
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
This skill builds on aircall-ds/migrate-tractor. Apply all cross-cutting rules from that skill (prop renames, `render` prop, data attributes) before the dropdown-menu-specific steps below.
|
|
18
|
+
|
|
19
|
+
## 1. Component mapping
|
|
20
|
+
|
|
21
|
+
Tractor `Dropdown` (labelled button trigger) and `ActionMenu` (icon-only "•••" trigger) both map to the same DS `DropdownMenu` compound. The visual difference is achieved through the `Button` variant/size passed to `DropdownMenuTrigger`'s `render` prop.
|
|
22
|
+
|
|
23
|
+
| Tractor part | DS compound part | Role |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| `<Dropdown>` / `<ActionMenu>` | `<DropdownMenu>` | State owner (open, modal) |
|
|
26
|
+
| _(trigger button)_ | `<DropdownMenuTrigger>` | Clickable trigger — accepts any button via `render` |
|
|
27
|
+
| `<DropdownItem>` / `<ActionMenuItem>` | `<DropdownMenuItem>` | Individual action item |
|
|
28
|
+
| _(no equivalent)_ | `<DropdownMenuContent>` | Floating panel container |
|
|
29
|
+
| _(no equivalent)_ | `<DropdownMenuGroup>` | Required wrapper around a labelled section |
|
|
30
|
+
| _(no equivalent)_ | `<DropdownMenuLabel>` | Section heading — must be inside `DropdownMenuGroup` |
|
|
31
|
+
| _(no equivalent)_ | `<DropdownMenuSeparator>` | Horizontal rule between sections |
|
|
32
|
+
| _(no equivalent)_ | `<DropdownMenuAddon>` | Trailing right-aligned slot inside an item |
|
|
33
|
+
|
|
34
|
+
## 2. Verified DS exports (`packages/ds/src/index.ts`)
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
DropdownMenu, DropdownMenuContent, DropdownMenuGroup,
|
|
38
|
+
DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator,
|
|
39
|
+
DropdownMenuTrigger, DropdownMenuAddon,
|
|
40
|
+
DropdownMenuSub, DropdownMenuSubTrigger, DropdownMenuSubContent,
|
|
41
|
+
DropdownMenuCheckboxItem, DropdownMenuRadioGroup, DropdownMenuRadioItem,
|
|
42
|
+
DropdownMenuPortal, DropdownMenuShortcut,
|
|
43
|
+
Button
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 3. Imports
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import {
|
|
50
|
+
DropdownMenu,
|
|
51
|
+
DropdownMenuContent,
|
|
52
|
+
DropdownMenuGroup,
|
|
53
|
+
DropdownMenuItem,
|
|
54
|
+
DropdownMenuLabel,
|
|
55
|
+
DropdownMenuSeparator,
|
|
56
|
+
DropdownMenuTrigger,
|
|
57
|
+
Button,
|
|
58
|
+
} from '@aircall/ds';
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
For ActionMenu (icon trigger + icon items), also import from `@aircall/react-icons`:
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
import { MoreVerticalFill, EditPencilFill, DeleteTrashFill } from '@aircall/react-icons';
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 4. Prop and pattern changes
|
|
68
|
+
|
|
69
|
+
| Tractor | DS | Notes |
|
|
70
|
+
| --- | --- | --- |
|
|
71
|
+
| `<Dropdown label="Options">` | `<DropdownMenuTrigger render={<Button variant="outline" size="lg" />}>Options</DropdownMenuTrigger>` | Trigger button is composed via `render` prop |
|
|
72
|
+
| `<ActionMenu>` | `<DropdownMenuTrigger render={<Button variant="ghost" size="icon" />}><MoreVerticalFill /></DropdownMenuTrigger>` | Icon-only trigger uses `size="icon"` |
|
|
73
|
+
| `<ActionMenuItem variant="critical">` | `<DropdownMenuItem variant="destructive">` | Use `variant` prop, not `className` |
|
|
74
|
+
| `icon={EditOutlined}` on `ActionMenuItem` | Icon as first child of `DropdownMenuItem` | Render icon directly inside the item |
|
|
75
|
+
| `<DropdownSection label="X">` | `<DropdownMenuGroup><DropdownMenuLabel>X</DropdownMenuLabel>…</DropdownMenuGroup>` | Label must be inside a `DropdownMenuGroup` |
|
|
76
|
+
|
|
77
|
+
## 5. Before / After — plain Dropdown
|
|
78
|
+
|
|
79
|
+
### Before (Tractor)
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import { Dropdown, DropdownItem } from '@aircall/tractor';
|
|
83
|
+
|
|
84
|
+
<Dropdown label="Options">
|
|
85
|
+
<DropdownItem onClick={handleEdit}>Edit</DropdownItem>
|
|
86
|
+
<DropdownItem onClick={handleDelete}>Delete</DropdownItem>
|
|
87
|
+
</Dropdown>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### After (DS)
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
import {
|
|
94
|
+
DropdownMenu,
|
|
95
|
+
DropdownMenuContent,
|
|
96
|
+
DropdownMenuItem,
|
|
97
|
+
DropdownMenuTrigger,
|
|
98
|
+
Button,
|
|
99
|
+
} from '@aircall/ds';
|
|
100
|
+
|
|
101
|
+
<DropdownMenu>
|
|
102
|
+
<DropdownMenuTrigger render={<Button variant="outline" size="lg" />}>
|
|
103
|
+
Options
|
|
104
|
+
</DropdownMenuTrigger>
|
|
105
|
+
<DropdownMenuContent>
|
|
106
|
+
<DropdownMenuItem onClick={handleEdit}>Edit</DropdownMenuItem>
|
|
107
|
+
<DropdownMenuItem onClick={handleDelete}>Delete</DropdownMenuItem>
|
|
108
|
+
</DropdownMenuContent>
|
|
109
|
+
</DropdownMenu>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## 6. Before / After — ActionMenu (icon trigger + icon items)
|
|
113
|
+
|
|
114
|
+
### Before (Tractor)
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
import { ActionMenu, ActionMenuItem } from '@aircall/tractor';
|
|
118
|
+
|
|
119
|
+
<ActionMenu>
|
|
120
|
+
<ActionMenuItem icon={EditOutlined} onClick={handleEdit}>Edit</ActionMenuItem>
|
|
121
|
+
<ActionMenuItem icon={DeleteOutlined} variant="critical" onClick={handleDelete}>Delete</ActionMenuItem>
|
|
122
|
+
</ActionMenu>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### After (DS)
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
import {
|
|
129
|
+
DropdownMenu,
|
|
130
|
+
DropdownMenuContent,
|
|
131
|
+
DropdownMenuItem,
|
|
132
|
+
DropdownMenuTrigger,
|
|
133
|
+
Button,
|
|
134
|
+
} from '@aircall/ds';
|
|
135
|
+
import { MoreVerticalFill, EditPencilFill, DeleteTrashFill } from '@aircall/react-icons';
|
|
136
|
+
|
|
137
|
+
<DropdownMenu>
|
|
138
|
+
<DropdownMenuTrigger render={<Button variant="ghost" size="icon" />}>
|
|
139
|
+
<MoreVerticalFill className="size-4" />
|
|
140
|
+
</DropdownMenuTrigger>
|
|
141
|
+
<DropdownMenuContent>
|
|
142
|
+
<DropdownMenuItem onClick={handleEdit}>
|
|
143
|
+
<EditPencilFill className="mr-2 size-4" />
|
|
144
|
+
Edit
|
|
145
|
+
</DropdownMenuItem>
|
|
146
|
+
<DropdownMenuItem variant="destructive" onClick={handleDelete}>
|
|
147
|
+
<DeleteTrashFill className="mr-2 size-4" />
|
|
148
|
+
Delete
|
|
149
|
+
</DropdownMenuItem>
|
|
150
|
+
</DropdownMenuContent>
|
|
151
|
+
</DropdownMenu>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## 7. Sections with labels
|
|
155
|
+
|
|
156
|
+
`DropdownMenuLabel` must be placed inside a `DropdownMenuGroup` alongside its items. A label as a direct child of `DropdownMenuContent` is not accessibility-correct in Base UI.
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
import {
|
|
160
|
+
DropdownMenu,
|
|
161
|
+
DropdownMenuContent,
|
|
162
|
+
DropdownMenuGroup,
|
|
163
|
+
DropdownMenuItem,
|
|
164
|
+
DropdownMenuLabel,
|
|
165
|
+
DropdownMenuSeparator,
|
|
166
|
+
DropdownMenuTrigger,
|
|
167
|
+
Button,
|
|
168
|
+
} from '@aircall/ds';
|
|
169
|
+
|
|
170
|
+
<DropdownMenu>
|
|
171
|
+
<DropdownMenuTrigger render={<Button variant="outline" size="lg" />}>
|
|
172
|
+
Account
|
|
173
|
+
</DropdownMenuTrigger>
|
|
174
|
+
<DropdownMenuContent>
|
|
175
|
+
<DropdownMenuGroup>
|
|
176
|
+
<DropdownMenuLabel>Account</DropdownMenuLabel>
|
|
177
|
+
<DropdownMenuItem>Profile</DropdownMenuItem>
|
|
178
|
+
<DropdownMenuItem>Settings</DropdownMenuItem>
|
|
179
|
+
</DropdownMenuGroup>
|
|
180
|
+
<DropdownMenuSeparator />
|
|
181
|
+
<DropdownMenuItem>Logout</DropdownMenuItem>
|
|
182
|
+
</DropdownMenuContent>
|
|
183
|
+
</DropdownMenu>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## 8. Common Mistakes
|
|
187
|
+
|
|
188
|
+
### Mistake 1 — Using `className="text-destructive"` instead of `variant="destructive"`
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
// Wrong
|
|
192
|
+
<DropdownMenuItem className="text-destructive" onClick={handleDelete}>
|
|
193
|
+
Delete
|
|
194
|
+
</DropdownMenuItem>
|
|
195
|
+
|
|
196
|
+
// Correct
|
|
197
|
+
<DropdownMenuItem variant="destructive" onClick={handleDelete}>
|
|
198
|
+
Delete
|
|
199
|
+
</DropdownMenuItem>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`DropdownMenuItem` has a first-class `variant` prop (`'default' | 'destructive'`). Using `variant="destructive"` applies the correct destructive text colour, hover background, and dark-mode overrides via `data-[variant=destructive]` selectors. A bare `className="text-destructive"` only sets the text colour and loses the focus-state and dark-mode styling baked into the variant.
|
|
203
|
+
Source: `packages/ds/src/components/dropdown-menu.tsx`
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
### Mistake 2 — Placing `DropdownMenuLabel` as a direct child of `DropdownMenuContent`
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
// Wrong
|
|
211
|
+
<DropdownMenuContent>
|
|
212
|
+
<DropdownMenuLabel>Account</DropdownMenuLabel>
|
|
213
|
+
<DropdownMenuItem>Profile</DropdownMenuItem>
|
|
214
|
+
</DropdownMenuContent>
|
|
215
|
+
|
|
216
|
+
// Correct
|
|
217
|
+
<DropdownMenuContent>
|
|
218
|
+
<DropdownMenuGroup>
|
|
219
|
+
<DropdownMenuLabel>Account</DropdownMenuLabel>
|
|
220
|
+
<DropdownMenuItem>Profile</DropdownMenuItem>
|
|
221
|
+
</DropdownMenuGroup>
|
|
222
|
+
</DropdownMenuContent>
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`DropdownMenuLabel` renders a Base UI `MenuPrimitive.GroupLabel`, which must be a sibling of its items inside a `MenuPrimitive.Group`. Placing it directly in `DropdownMenuContent` breaks the ARIA group association that screen readers depend on.
|
|
226
|
+
Source: `packages/ds/src/components/dropdown-menu.tsx`
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
### Mistake 3 — Omitting the `render` prop on `DropdownMenuTrigger`
|
|
231
|
+
|
|
232
|
+
```tsx
|
|
233
|
+
// Wrong
|
|
234
|
+
<DropdownMenuTrigger>
|
|
235
|
+
Options
|
|
236
|
+
</DropdownMenuTrigger>
|
|
237
|
+
|
|
238
|
+
// Correct
|
|
239
|
+
<DropdownMenuTrigger render={<Button variant="outline" size="lg" />}>
|
|
240
|
+
Options
|
|
241
|
+
</DropdownMenuTrigger>
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Without a `render` prop, `DropdownMenuTrigger` renders a plain unstyled `<button>`. In the DS, trigger appearance is composed by passing a `Button` via the Base UI `render` prop — the button's variant and size control the visual style. This follows the DS-wide `render` prop pattern that replaces the old `asChild` prop.
|
|
245
|
+
Source: `packages/ds/src/components/dropdown-menu.tsx`
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
### Mistake 4 — Passing icon as a prop instead of rendering it as a child
|
|
250
|
+
|
|
251
|
+
```tsx
|
|
252
|
+
// Wrong
|
|
253
|
+
import { ActionMenu, ActionMenuItem } from '@aircall/tractor';
|
|
254
|
+
<ActionMenuItem icon={EditOutlined}>Edit</ActionMenuItem>
|
|
255
|
+
|
|
256
|
+
// Correct
|
|
257
|
+
import { DropdownMenuItem } from '@aircall/ds';
|
|
258
|
+
import { EditPencilFill } from '@aircall/react-icons';
|
|
259
|
+
<DropdownMenuItem>
|
|
260
|
+
<EditPencilFill className="mr-2 size-4" />
|
|
261
|
+
Edit
|
|
262
|
+
</DropdownMenuItem>
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Tractor `ActionMenuItem` accepted an `icon` prop. DS `DropdownMenuItem` has no `icon` prop — the icon is placed as the first child. The `[&_svg:not([class*='size-'])]:size-4` selector on the item auto-sizes icon children that lack an explicit size class.
|
|
266
|
+
Source: `packages/ds/src/components/dropdown-menu.tsx`
|