@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,274 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: aircall-ds/migrate-tractor/badge
|
|
3
|
+
description: >
|
|
4
|
+
Migrate Tractor Tag and CounterBadge to @aircall/ds Badge, CounterBadge, BadgeGroup,
|
|
5
|
+
and BadgeGroupCount. Load when a file imports Tag or CounterBadge from @aircall/tractor.
|
|
6
|
+
Maps Tractor variant (grey/primary/blue/red/yellow/green/purple/pink/dark* prefixes)
|
|
7
|
+
to the DS color + tone two-axis model, and Tractor status Badge (dot) to AvatarBadge.
|
|
8
|
+
type: sub-skill
|
|
9
|
+
library: aircall-ds
|
|
10
|
+
library_version: "0.13.0"
|
|
11
|
+
requires:
|
|
12
|
+
- aircall-ds/setup
|
|
13
|
+
- aircall-ds/migrate-tractor
|
|
14
|
+
sources:
|
|
15
|
+
- "aircall/hydra:docs/migration-guides/tractor-to-ds/recipes/badge.md"
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
This skill builds on aircall-ds/migrate-tractor.
|
|
19
|
+
|
|
20
|
+
## 1. Component mapping
|
|
21
|
+
|
|
22
|
+
Tractor's badge surface had three distinct concepts that map to different DS components:
|
|
23
|
+
|
|
24
|
+
| Tractor component | DS replacement | Notes |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| `<Tag>` | `<Badge>` | Color model changes from single `variant` to `color` + `tone` |
|
|
27
|
+
| `<CounterBadge>` | `<CounterBadge>` | No `max` prop — cap the value yourself before passing as children |
|
|
28
|
+
| `<Badge>` (status dot) | `<AvatarBadge>` | Tractor's dot-indicator badge is unrelated to DS `Badge`; see the avatar skill |
|
|
29
|
+
| _(grouping)_ | `<BadgeGroup>` + `<BadgeGroupCount>` | Wraps multiple badges; `BadgeGroupCount` renders the overflow "+N" chip |
|
|
30
|
+
|
|
31
|
+
## 2. Verified DS exports (`packages/ds/src/index.ts`)
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
Badge, BadgeGroup, BadgeGroupCount
|
|
35
|
+
CounterBadge, counterBadgeVariants
|
|
36
|
+
AvatarBadge
|
|
37
|
+
Chip, ChipRemove
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 3. Imports
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { Badge, BadgeGroup, BadgeGroupCount, CounterBadge } from '@aircall/ds';
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Icons inside badges import from `@aircall/react-icons` — never from `lucide-react` directly.
|
|
47
|
+
|
|
48
|
+
## 4. `Tag` variant → `Badge` color + tone
|
|
49
|
+
|
|
50
|
+
DS Badge uses two orthogonal props instead of Tractor's single `variant`:
|
|
51
|
+
|
|
52
|
+
- `color`: `charcoal` (default) · `red` · `green` · `blue` · `purple` · `yellow` · `pink`
|
|
53
|
+
- `tone`: `dark` · `medium-dark` · `medium-light` · `light` (default)
|
|
54
|
+
|
|
55
|
+
| Tractor `variant` | DS `color` | DS `tone` |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| `grey` / `white` | `charcoal` | `light` |
|
|
58
|
+
| `secondary` | `charcoal` | `medium-light` |
|
|
59
|
+
| `lightSecondary` | `charcoal` | `light` |
|
|
60
|
+
| `darkSecondary` | `charcoal` | `medium-dark` |
|
|
61
|
+
| `primary` | `green` | `medium-light` |
|
|
62
|
+
| `lightPrimary` | `green` | `light` |
|
|
63
|
+
| `darkPrimary` | `green` | `dark` |
|
|
64
|
+
| `blue` | `blue` | `medium-light` |
|
|
65
|
+
| `darkBlue` | `blue` | `dark` |
|
|
66
|
+
| `red` | `red` | `medium-light` |
|
|
67
|
+
| `darkRed` | `red` | `dark` |
|
|
68
|
+
| `yellow` | `yellow` | `medium-light` |
|
|
69
|
+
| `darkYellow` | `yellow` | `dark` |
|
|
70
|
+
| `green` | `green` | `medium-light` |
|
|
71
|
+
| `darkGreen` | `green` | `dark` |
|
|
72
|
+
| `purple` | `purple` | `medium-light` |
|
|
73
|
+
| `darkPurple` | `purple` | `dark` |
|
|
74
|
+
| `pink` | `pink` | `medium-light` |
|
|
75
|
+
|
|
76
|
+
> The base color variant maps to `medium-light`; `dark*` variants map to `dark` or `medium-dark`. `light*` variants map to `light`. Pick the tone that best matches the surface.
|
|
77
|
+
|
|
78
|
+
### Hard-coded hex colors (`bg` prop)
|
|
79
|
+
|
|
80
|
+
When Tractor used a hard-coded hex (e.g. `bg="#0761B5"`), pass it via `legacyColor` — DS resolves it to the nearest `color` + `tone` at render time from an internal map. Unknown hexes fall back to `charcoal` / `dark`.
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
<Badge legacyColor="#0761B5">Sales</Badge>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## 5. Size mapping
|
|
87
|
+
|
|
88
|
+
| Tractor `size` | DS `size` |
|
|
89
|
+
| --- | --- |
|
|
90
|
+
| `xSmall` | `default` (20px) |
|
|
91
|
+
| `small` | `default` (20px) |
|
|
92
|
+
| `regular` (default) | `default` (20px) |
|
|
93
|
+
| `large` | `lg` (24px) |
|
|
94
|
+
|
|
95
|
+
DS has no `mode` prop (`outline` / `fill`). Use a lighter `tone` (e.g. `light`) to approximate the old outline look.
|
|
96
|
+
|
|
97
|
+
## 6. CounterBadge
|
|
98
|
+
|
|
99
|
+
DS `CounterBadge` has no `max` prop. Cap the displayed value yourself before passing as children:
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
<CounterBadge>{count > 99 ? '99+' : count}</CounterBadge>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Available `variant` values: `default` (destructive background) · `secondary` · `ghost`.
|
|
106
|
+
|
|
107
|
+
## 7. Grouping badges
|
|
108
|
+
|
|
109
|
+
Use `BadgeGroup` to wrap a set of badges with consistent spacing, and `BadgeGroupCount` for the trailing overflow chip:
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
<BadgeGroup>
|
|
113
|
+
<Badge color="blue" tone="medium-light">Support</Badge>
|
|
114
|
+
<Badge color="green" tone="medium-light">Active</Badge>
|
|
115
|
+
<BadgeGroupCount>{overflow}</BadgeGroupCount>
|
|
116
|
+
</BadgeGroup>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## 8. Before / After examples
|
|
120
|
+
|
|
121
|
+
### 8a. Simple Tag → Badge (variant rename)
|
|
122
|
+
|
|
123
|
+
**Before (Tractor):**
|
|
124
|
+
```tsx
|
|
125
|
+
import { Tag } from '@aircall/tractor';
|
|
126
|
+
|
|
127
|
+
<Tag variant="primary">Active</Tag>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**After (DS):**
|
|
131
|
+
```tsx
|
|
132
|
+
import { Badge } from '@aircall/ds';
|
|
133
|
+
|
|
134
|
+
<Badge color="green" tone="medium-light">Active</Badge>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
> Tractor `primary` = green brand color → DS `color="green"` `tone="medium-light"`.
|
|
138
|
+
|
|
139
|
+
### 8b. Dark variant
|
|
140
|
+
|
|
141
|
+
**Before (Tractor):**
|
|
142
|
+
```tsx
|
|
143
|
+
import { Tag } from '@aircall/tractor';
|
|
144
|
+
|
|
145
|
+
<Tag variant="darkBlue">Escalated</Tag>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**After (DS):**
|
|
149
|
+
```tsx
|
|
150
|
+
import { Badge } from '@aircall/ds';
|
|
151
|
+
|
|
152
|
+
<Badge color="blue" tone="dark">Escalated</Badge>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### 8c. Hard-coded hex color
|
|
156
|
+
|
|
157
|
+
**Before (Tractor):**
|
|
158
|
+
```tsx
|
|
159
|
+
import { Tag } from '@aircall/tractor';
|
|
160
|
+
|
|
161
|
+
<Tag bg="#0761B5">Sales</Tag>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**After (DS):**
|
|
165
|
+
```tsx
|
|
166
|
+
import { Badge } from '@aircall/ds';
|
|
167
|
+
|
|
168
|
+
<Badge legacyColor="#0761B5">Sales</Badge>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### 8d. CounterBadge with max cap
|
|
172
|
+
|
|
173
|
+
**Before (Tractor):**
|
|
174
|
+
```tsx
|
|
175
|
+
import { CounterBadge } from '@aircall/tractor';
|
|
176
|
+
|
|
177
|
+
<CounterBadge count={count} max={99} />
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**After (DS):**
|
|
181
|
+
```tsx
|
|
182
|
+
import { CounterBadge } from '@aircall/ds';
|
|
183
|
+
|
|
184
|
+
<CounterBadge>{count > 99 ? '99+' : count}</CounterBadge>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### 8e. Icon inside a Badge
|
|
188
|
+
|
|
189
|
+
**Before (Tractor):**
|
|
190
|
+
```tsx
|
|
191
|
+
import { Tag } from '@aircall/tractor';
|
|
192
|
+
import { CheckCircle } from '@aircall/react-icons';
|
|
193
|
+
|
|
194
|
+
<Tag variant="green"><CheckCircle size={12} /> Verified</Tag>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**After (DS):**
|
|
198
|
+
```tsx
|
|
199
|
+
import { Badge } from '@aircall/ds';
|
|
200
|
+
import { CheckCircle } from '@aircall/react-icons';
|
|
201
|
+
|
|
202
|
+
<Badge color="green" tone="medium-light"><CheckCircle /> Verified</Badge>
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
> DS auto-sizes SVG children via `[&>svg]:size-3!` (default) or `[&>svg]:size-4!` (lg). Do not pass a `size` prop to the icon.
|
|
206
|
+
|
|
207
|
+
## 9. Common mistakes
|
|
208
|
+
|
|
209
|
+
### Mistake 1: Using `variant` instead of `color` + `tone`
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
// Wrong — DS Badge has no `variant` prop; it is silently ignored
|
|
213
|
+
<Badge variant="primary">Active</Badge>
|
|
214
|
+
|
|
215
|
+
// Correct — map to the two-axis color + tone model
|
|
216
|
+
<Badge color="green" tone="medium-light">Active</Badge>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Mechanism:** DS Badge replaced Tractor's single `variant` string with two orthogonal props `color` and `tone`. Passing `variant` goes to the underlying `span` as an unknown HTML attribute and has no visual effect — the badge renders with the default charcoal/light appearance instead of the intended color.
|
|
220
|
+
|
|
221
|
+
Source: `packages/ds/src/components/badge.tsx` — `badgeVariants` has `color` and `tone` keys, no `variant` key.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
### Mistake 2: Mapping Tractor status `Badge` (dot) to DS `Badge`
|
|
226
|
+
|
|
227
|
+
```tsx
|
|
228
|
+
// Wrong — DS Badge is a label chip, not a status dot
|
|
229
|
+
<Badge status="online" />
|
|
230
|
+
|
|
231
|
+
// Correct — Tractor's status dot maps to AvatarBadge
|
|
232
|
+
import { Avatar, AvatarBadge, AvatarImage } from '@aircall/ds';
|
|
233
|
+
|
|
234
|
+
<Avatar>
|
|
235
|
+
<AvatarImage src={src} alt={name} />
|
|
236
|
+
<AvatarBadge />
|
|
237
|
+
</Avatar>
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Mechanism:** Tractor had two components both named `Badge`: a label chip (`Tag`) and a small status dot (`Badge`). DS names them differently — `Badge` is the label chip and `AvatarBadge` is the status indicator that composes inside `Avatar`. Migrating the dot to DS `Badge` renders a text chip with no content instead of a dot.
|
|
241
|
+
|
|
242
|
+
Source: `packages/ds/src/components/badge.tsx` — `Badge` is a label chip; `packages/ds/src/components/avatar.tsx` exports `AvatarBadge` for the status dot.
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
### Mistake 3: Relying on DS `CounterBadge` to cap overflow automatically
|
|
247
|
+
|
|
248
|
+
```tsx
|
|
249
|
+
// Wrong — DS CounterBadge has no `max` prop; it renders the raw number
|
|
250
|
+
<CounterBadge max={99} count={count} />
|
|
251
|
+
|
|
252
|
+
// Correct — cap the value yourself before passing as children
|
|
253
|
+
<CounterBadge>{count > 99 ? '99+' : count}</CounterBadge>
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
**Mechanism:** Tractor's `CounterBadge` accepted a `max` prop and formatted "99+" internally. DS `CounterBadge` is a presentational span that renders whatever children it receives — the cap logic is the caller's responsibility. Passing `max` and `count` as props results in an empty badge with two unknown HTML attributes instead of the formatted string.
|
|
257
|
+
|
|
258
|
+
Source: `packages/ds/src/components/counter-badge.tsx` — `CounterBadge.Props` extends `useRender.ComponentProps<'span'>` with only a `variant` prop; no `max` or `count` keys.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
### Mistake 4: Using `outline` / `fill` mode to style a badge
|
|
263
|
+
|
|
264
|
+
```tsx
|
|
265
|
+
// Wrong — DS Badge has no `mode` prop
|
|
266
|
+
<Badge color="blue" mode="outline">Beta</Badge>
|
|
267
|
+
|
|
268
|
+
// Correct — reach for a lighter tone to approximate the outline look
|
|
269
|
+
<Badge color="blue" tone="light">Beta</Badge>
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
**Mechanism:** Tractor's `Tag` supported a `mode` prop (`outline` / `fill`) that toggled between a bordered and a filled appearance. DS Badge has no `mode` prop — the visual weight is controlled entirely by `tone`. A lighter tone (`light` or `medium-light`) produces a softer background that approximates the old outline look.
|
|
273
|
+
|
|
274
|
+
Source: `packages/ds/src/components/badge.tsx` — `badgeVariants` has `size`, `color`, and `tone` variants; no `mode` variant.
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: aircall-ds/migrate-tractor/button
|
|
3
|
+
description: >
|
|
4
|
+
Migrate Tractor Button and IconButton to the @aircall/ds Button. Load when a file
|
|
5
|
+
imports Button or IconButton from @aircall/tractor. Maps Tractor variant/mode/size
|
|
6
|
+
to the ds Button variant/size (default|outline|secondary|ghost|destructive|link;
|
|
7
|
+
default|sm|lg|icon|icon-sm|icon-lg) and the block prop.
|
|
8
|
+
type: sub-skill
|
|
9
|
+
library: aircall-ds
|
|
10
|
+
library_version: "0.13.0"
|
|
11
|
+
requires:
|
|
12
|
+
- aircall-ds/setup
|
|
13
|
+
- aircall-ds/migrate-tractor
|
|
14
|
+
sources:
|
|
15
|
+
- "aircall/hydra:packages/ds/src/components/button.tsx"
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
This skill builds on aircall-ds/migrate-tractor. Apply all cross-cutting rules from that skill (prop renames, `render` prop, data attributes) before the button-specific steps below.
|
|
19
|
+
|
|
20
|
+
## 1. Component mapping
|
|
21
|
+
|
|
22
|
+
Tractor had two separate components: `Button` (text + optional icon) and `IconButton` (icon-only, square). DS unifies both into a single `Button` component — the `size` prop controls the icon-only square shape.
|
|
23
|
+
|
|
24
|
+
| Tractor component | DS replacement | Notes |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| `<Button>` | `<Button>` | Drop-in with prop renames (see tables below) |
|
|
27
|
+
| `<IconButton component={X}>` | `<Button size="icon"><X /></Button>` | Icon becomes a child; `component` prop is removed |
|
|
28
|
+
|
|
29
|
+
## 2. Verified DS export (`packages/ds/src/index.ts`)
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
Button, buttonVariants
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 3. Imports
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
import { Button } from '@aircall/ds';
|
|
39
|
+
import { SomeIcon } from '@aircall/react-icons';
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Never import icons from `lucide-react` directly — always use `@aircall/react-icons`.
|
|
43
|
+
|
|
44
|
+
## 4. Variant mapping (Tractor → DS)
|
|
45
|
+
|
|
46
|
+
Tractor had two orthogonal props — `variant` (color role) and `mode` (fill style). DS collapses both into a single `variant` prop. Use the combined (variant × mode) pair to pick the DS variant.
|
|
47
|
+
|
|
48
|
+
| Tractor `variant` | Tractor `mode` | DS `variant` |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| `primary` | _(default / filled)_ | `default` |
|
|
51
|
+
| `primary` | `outline` | `outline` |
|
|
52
|
+
| `secondary` | _(default / filled)_ | `secondary` |
|
|
53
|
+
| `secondary` | `outline` | `outline` |
|
|
54
|
+
| `secondary` | `ghost` | `ghost` |
|
|
55
|
+
| `danger` | _(default / filled)_ | `destructive` |
|
|
56
|
+
| `danger` | `ghost` | `ghost` + `className="text-destructive"` |
|
|
57
|
+
| _(no equivalent)_ | _(no equivalent)_ | `link` |
|
|
58
|
+
|
|
59
|
+
> When both `variant` and `mode` appear on the same Tractor button, derive the DS variant from the **combination**, not from either prop alone. Dropping `mode` without updating `variant` is the most common migration error.
|
|
60
|
+
|
|
61
|
+
> `danger` + `ghost` has no single DS variant: DS `destructive` is filled-only. Map to `ghost` and add `className="text-destructive"` to keep the destructive color — plain `ghost` silently loses it.
|
|
62
|
+
|
|
63
|
+
## 5. Size mapping (Tractor → DS)
|
|
64
|
+
|
|
65
|
+
| Tractor `size` | DS `size` | Note |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| `xSmall` (28px) | `size="default"` (32px) | +4px |
|
|
68
|
+
| `small` (40px) | `size="lg"` (40px) | exact |
|
|
69
|
+
| `regular` (48px, default) | `size="lg"` (40px) | −8px |
|
|
70
|
+
| `large` (56px) | `className="h-14"` | no built-in size |
|
|
71
|
+
| _(icon-only button)_ | `icon` | size-8 |
|
|
72
|
+
| _(icon-only, small)_ | `icon-sm` | size-6 |
|
|
73
|
+
| _(icon-only, large)_ | `icon-lg` | size-10 |
|
|
74
|
+
|
|
75
|
+
## 6. Other prop changes
|
|
76
|
+
|
|
77
|
+
| Tractor prop | DS equivalent | Notes |
|
|
78
|
+
| --- | --- | --- |
|
|
79
|
+
| `block` | `block` | Identical — `true` makes the button full-width |
|
|
80
|
+
| `component={X}` (IconButton) | _(removed)_ | Icon becomes a JSX child instead |
|
|
81
|
+
| `isLoading` | _(removed)_ | Render your own spinner as a child |
|
|
82
|
+
| `leftIcon` / `rightIcon` | _(removed)_ | Pass the icon as a child; position is inferred from DOM order |
|
|
83
|
+
| `as` | `render` | Base UI render-prop pattern (see cross-cutting skill) |
|
|
84
|
+
|
|
85
|
+
## 7. Before / After examples
|
|
86
|
+
|
|
87
|
+
### 7a. Standard button (variant + mode → DS variant)
|
|
88
|
+
|
|
89
|
+
**Before (Tractor):**
|
|
90
|
+
```tsx
|
|
91
|
+
import { Button } from '@aircall/tractor';
|
|
92
|
+
|
|
93
|
+
<Button variant="secondary" mode="outline" size="small">
|
|
94
|
+
Cancel
|
|
95
|
+
</Button>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**After (DS):**
|
|
99
|
+
```tsx
|
|
100
|
+
import { Button } from '@aircall/ds';
|
|
101
|
+
|
|
102
|
+
<Button variant="outline" size="lg">
|
|
103
|
+
Cancel
|
|
104
|
+
</Button>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
> Tractor `small` = 40px → DS `size="lg"` = 40px (exact match). Do not use `size="sm"` (32px).
|
|
108
|
+
|
|
109
|
+
### 7b. Primary button with icon child
|
|
110
|
+
|
|
111
|
+
**Before (Tractor):**
|
|
112
|
+
```tsx
|
|
113
|
+
import { Play } from '@aircall/react-icons';
|
|
114
|
+
import { Button } from '@aircall/tractor';
|
|
115
|
+
|
|
116
|
+
<Button variant="primary" size="small" onClick={onLaunch}>
|
|
117
|
+
<Icon component={Play} size={16} />
|
|
118
|
+
Launch
|
|
119
|
+
</Button>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**After (DS):**
|
|
123
|
+
```tsx
|
|
124
|
+
import { Play } from '@aircall/react-icons';
|
|
125
|
+
import { Button } from '@aircall/ds';
|
|
126
|
+
|
|
127
|
+
<Button variant="default" size="lg" onClick={onLaunch}>
|
|
128
|
+
<Play />
|
|
129
|
+
Launch
|
|
130
|
+
</Button>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
> Tractor `small` = 40px → DS `size="lg"` = 40px (exact match).
|
|
134
|
+
|
|
135
|
+
> DS sizes SVGs automatically via `[&_svg:not([class*='size-'])]:size-4`. Do not pass a `size` prop to the icon.
|
|
136
|
+
|
|
137
|
+
### 7c. IconButton → icon-only Button
|
|
138
|
+
|
|
139
|
+
**Before (Tractor):**
|
|
140
|
+
```tsx
|
|
141
|
+
import { MoreVertical } from '@aircall/react-icons';
|
|
142
|
+
import { IconButton } from '@aircall/tractor';
|
|
143
|
+
|
|
144
|
+
<IconButton
|
|
145
|
+
component={MoreVertical}
|
|
146
|
+
aria-label="More actions"
|
|
147
|
+
disabled={isLoading}
|
|
148
|
+
/>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**After (DS):**
|
|
152
|
+
```tsx
|
|
153
|
+
import { MoreVertical } from '@aircall/react-icons';
|
|
154
|
+
import { Button } from '@aircall/ds';
|
|
155
|
+
|
|
156
|
+
<Button size="icon" aria-label="More actions" disabled={isLoading}>
|
|
157
|
+
<MoreVertical />
|
|
158
|
+
</Button>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### 7d. Ghost icon-only button (mode + variant combination)
|
|
162
|
+
|
|
163
|
+
**Before (Tractor):**
|
|
164
|
+
```tsx
|
|
165
|
+
import { Pause } from '@aircall/react-icons';
|
|
166
|
+
import { Button } from '@aircall/tractor';
|
|
167
|
+
|
|
168
|
+
<Button mode="ghost" variant="secondary" size="xSmall" aria-label="Pause">
|
|
169
|
+
<Pause />
|
|
170
|
+
</Button>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**After (DS):**
|
|
174
|
+
```tsx
|
|
175
|
+
import { Pause } from '@aircall/react-icons';
|
|
176
|
+
import { Button } from '@aircall/ds';
|
|
177
|
+
|
|
178
|
+
<Button variant="ghost" size="icon-sm" aria-label="Pause">
|
|
179
|
+
<Pause />
|
|
180
|
+
</Button>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### 7e. Link-style button using `render` prop
|
|
184
|
+
|
|
185
|
+
**Before (Tractor):**
|
|
186
|
+
```tsx
|
|
187
|
+
import { Button } from '@aircall/tractor';
|
|
188
|
+
import { Link } from 'react-router-dom';
|
|
189
|
+
|
|
190
|
+
<Button as={Link} to="/settings" variant="secondary" mode="ghost">
|
|
191
|
+
Settings
|
|
192
|
+
</Button>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**After (DS):**
|
|
196
|
+
```tsx
|
|
197
|
+
import { Button } from '@aircall/ds';
|
|
198
|
+
import { Link } from 'react-router-dom';
|
|
199
|
+
|
|
200
|
+
<Button variant="ghost" render={<Link to="/settings" />}>
|
|
201
|
+
Settings
|
|
202
|
+
</Button>
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## 8. Common mistakes
|
|
206
|
+
|
|
207
|
+
### Mistake 1: Keeping `mode` as a separate prop
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
// WRONG — DS Button has no `mode` prop; it is silently ignored
|
|
211
|
+
<Button variant="secondary" mode="outline">Cancel</Button>
|
|
212
|
+
|
|
213
|
+
// CORRECT — fold mode into variant
|
|
214
|
+
<Button variant="outline">Cancel</Button>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**Mechanism:** DS collapses Tractor's orthogonal (variant × mode) space into a single `variant`. Passing `mode` passes an unknown prop that does nothing — the button renders with the `secondary` fill instead of the outline style.
|
|
218
|
+
|
|
219
|
+
`Source: packages/ds/src/components/button.tsx` — `buttonVariants` has no `mode` key.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
### Mistake 2: Icon-only button without an `icon*` size
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
// WRONG — size="default" gives h-8 px-2 padding; the button won't be square
|
|
227
|
+
<Button aria-label="More actions">
|
|
228
|
+
<MoreVertical />
|
|
229
|
+
</Button>
|
|
230
|
+
|
|
231
|
+
// CORRECT — icon-only buttons must use size="icon", "icon-sm", or "icon-lg"
|
|
232
|
+
<Button size="icon" aria-label="More actions">
|
|
233
|
+
<MoreVertical />
|
|
234
|
+
</Button>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Mechanism:** The `icon` sizes set `size-8` (width AND height), producing a square. Non-icon sizes add horizontal padding and a fixed height only — the button will be wider than it is tall, misaligning the icon visually.
|
|
238
|
+
|
|
239
|
+
`Source: packages/ds/src/components/button.tsx` — `size.icon = 'size-8'`, `size.default = 'h-8 gap-1.5 px-2 …'`.
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
### Mistake 3: Using `as` instead of `render` for polymorphic buttons
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
// WRONG — DS Button is Base UI, not Radix; it has no `as` prop
|
|
247
|
+
<Button as={Link} to="/settings" variant="ghost">Settings</Button>
|
|
248
|
+
|
|
249
|
+
// CORRECT — use the Base UI `render` prop
|
|
250
|
+
<Button render={<Link to="/settings" />} variant="ghost">Settings</Button>
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**Mechanism:** Base UI primitives replaced Radix UI in `@aircall/ds`. The `asChild`/`as` pattern from Radix is gone; Base UI uses a `render` prop that accepts a JSX element. Additional props (like `to`) go on the element passed to `render`, not on `Button` itself.
|
|
254
|
+
|
|
255
|
+
`Source: packages/ds/src/components/button.tsx` — `Button` extends `ButtonPrimitive.Props` from `@base-ui/react/button`, which exposes `render`.
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
### Mistake 4: Passing a `size` number to the icon child
|
|
260
|
+
|
|
261
|
+
```tsx
|
|
262
|
+
// WRONG — explicit size prop overrides DS auto-sizing and breaks icon-lg layout
|
|
263
|
+
<Button size="lg">
|
|
264
|
+
<Play size={16} />
|
|
265
|
+
Launch
|
|
266
|
+
</Button>
|
|
267
|
+
|
|
268
|
+
// CORRECT — let DS size the icon automatically
|
|
269
|
+
<Button size="lg">
|
|
270
|
+
<Play />
|
|
271
|
+
Launch
|
|
272
|
+
</Button>
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**Mechanism:** DS applies `[&_svg:not([class*='size-'])]:size-4` (or `size-6` for `lg`/`icon-lg`) to auto-size SVGs. A `size` attribute on the icon sets an inline `width`/`height` that wins over Tailwind utility classes, locking the icon to the wrong size.
|
|
276
|
+
|
|
277
|
+
`Source: packages/ds/src/components/button.tsx` — base class includes `[&_svg:not([class*='size-'])]:size-4`.
|