@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.
Files changed (34) hide show
  1. package/README.md +31 -0
  2. package/dist/globals.css +1 -1
  3. package/dist/index.d.ts +28 -28
  4. package/dist/index.js +1 -1
  5. package/package.json +12 -2
  6. package/skills/aircall-ds/migrate-icons/SKILL.md +346 -0
  7. package/skills/aircall-ds/migrate-tractor/SKILL.md +314 -0
  8. package/skills/aircall-ds/migrate-tractor/accordion/SKILL.md +276 -0
  9. package/skills/aircall-ds/migrate-tractor/alert/SKILL.md +225 -0
  10. package/skills/aircall-ds/migrate-tractor/avatar/SKILL.md +272 -0
  11. package/skills/aircall-ds/migrate-tractor/badge/SKILL.md +274 -0
  12. package/skills/aircall-ds/migrate-tractor/button/SKILL.md +277 -0
  13. package/skills/aircall-ds/migrate-tractor/card/SKILL.md +278 -0
  14. package/skills/aircall-ds/migrate-tractor/combobox/SKILL.md +346 -0
  15. package/skills/aircall-ds/migrate-tractor/data-table/SKILL.md +333 -0
  16. package/skills/aircall-ds/migrate-tractor/dialog/SKILL.md +206 -0
  17. package/skills/aircall-ds/migrate-tractor/divider/SKILL.md +226 -0
  18. package/skills/aircall-ds/migrate-tractor/dropdown-menu/SKILL.md +266 -0
  19. package/skills/aircall-ds/migrate-tractor/dropzone/SKILL.md +338 -0
  20. package/skills/aircall-ds/migrate-tractor/form-and-field/SKILL.md +325 -0
  21. package/skills/aircall-ds/migrate-tractor/gauge/SKILL.md +248 -0
  22. package/skills/aircall-ds/migrate-tractor/input/SKILL.md +261 -0
  23. package/skills/aircall-ds/migrate-tractor/item/SKILL.md +298 -0
  24. package/skills/aircall-ds/migrate-tractor/link/SKILL.md +263 -0
  25. package/skills/aircall-ds/migrate-tractor/popover/SKILL.md +214 -0
  26. package/skills/aircall-ds/migrate-tractor/select/SKILL.md +245 -0
  27. package/skills/aircall-ds/migrate-tractor/sheet-vs-drawer/SKILL.md +272 -0
  28. package/skills/aircall-ds/migrate-tractor/skeleton/SKILL.md +190 -0
  29. package/skills/aircall-ds/migrate-tractor/styling/SKILL.md +421 -0
  30. package/skills/aircall-ds/migrate-tractor/tabs/SKILL.md +250 -0
  31. package/skills/aircall-ds/migrate-tractor/toast/SKILL.md +322 -0
  32. package/skills/aircall-ds/migrate-tractor/tooltip/SKILL.md +204 -0
  33. package/skills/aircall-ds/migrate-tractor/tree/SKILL.md +346 -0
  34. 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`.