@aircall/ds 0.13.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 +94 -33
  4. package/dist/index.js +292 -42
  5. package/package.json +16 -3
  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,346 @@
1
+ ---
2
+ name: aircall-ds/migrate-icons
3
+ description: >
4
+ Migrate icons from @aircall/icons to @aircall/react-icons — the old→new name map,
5
+ color/size → Tailwind, custom & brand icons. Works standalone (an icons-only swap)
6
+ or inside a Tractor migration (also drops Tractor's <Icon> wrapper). Load when a file
7
+ imports any named icon from @aircall/icons, or Icon from @aircall/tractor.
8
+ type: sub-skill
9
+ library: aircall-ds
10
+ library_version: "0.13.0"
11
+ requires:
12
+ - aircall-ds/setup
13
+ sources:
14
+ - "aircall/hydra:docs/migration-guides/tractor-to-ds/06-react-icons.md"
15
+ ---
16
+
17
+ Migrate icons from `@aircall/icons` to `@aircall/react-icons`. This is **independent of
18
+ the Tractor migration** — run it on its own for an icons-only swap, or as part of
19
+ `aircall-ds/migrate-tractor`. The name / color / size mappings below apply whether an
20
+ icon is rendered directly (`<SomeIcon />`) or through Tractor's `<Icon component={…} />`
21
+ wrapper; when it was wrapped in Tractor's `<Icon>`, also remove that wrapper (see §1 and §7).
22
+
23
+ > **Upgrade `@aircall/react-icons` to `>= 0.4.0` as part of this migration.** `@aircall/ds`
24
+ > itself imports flags (`CountryFlag` → `FlagUs`, …) from it; an older version (≤ 0.3.0)
25
+ > breaks the ds bundle at build time, not just your own icon imports. `pnpm add @aircall/react-icons@latest`.
26
+
27
+ ## 1. What changes
28
+
29
+ | Tractor part | @aircall/react-icons part |
30
+ | --- | --- |
31
+ | `import { Icon } from '@aircall/tractor'` | _(removed — no wrapper needed)_ |
32
+ | `import { SomeIcon } from '@aircall/icons'` | `import { NewName } from '@aircall/react-icons'` |
33
+ | `<Icon component={SomeIcon} size={N} color="primary.500" />` | `<NewName className="size-N text-primary" />` |
34
+ | `color="…"` (Tractor token) | `text-*` Tailwind class (see color table) |
35
+ | `size={N}` (number) | `size-N` Tailwind class (`size-4`=16px, `size-5`=20px, `size-6`=24px) |
36
+
37
+ `@aircall/react-icons` re-exports all lucide icons plus a set of Aircall custom icons. Never import from `lucide-react` directly.
38
+
39
+ ## 2. Color mapping
40
+
41
+ | Tractor `color` value | Tailwind class |
42
+ | --- | --- |
43
+ | `primary.500` | `text-primary` |
44
+ | `critical.500` | `text-destructive` |
45
+ | `success.500` | `text-success` |
46
+ | `warning.500` | `text-warning` |
47
+ | `informative.500` | `text-info` |
48
+ | `text.base` | `text-foreground` |
49
+ | `text.secondary` | `text-muted-foreground` |
50
+
51
+ ## 3. Size mapping
52
+
53
+ | Tractor `size` | Tailwind class | px |
54
+ | --- | --- | --- |
55
+ | `16` | `size-4` | 16px |
56
+ | `20` | `size-5` | 20px |
57
+ | `24` | `size-6` | 24px |
58
+
59
+ ## 4. Icon name mapping (selected)
60
+
61
+ This is a representative sample. For the full table consult the icon name tables in the migration guide.
62
+
63
+ ### Phone & Calls
64
+
65
+ | Old (`@aircall/icons`) | New (`@aircall/react-icons`) | Source |
66
+ | --- | --- | --- |
67
+ | `CallFilled`, `CallOutlined`, `CallCircleFilled` | `Phone` | lucide |
68
+ | `HangUpFilled` | `PhoneDecline` | custom |
69
+ | `HangUpOutlined` | `PhoneOff` | lucide |
70
+ | `MissedInboundFilled` | `PhoneMissed` | lucide |
71
+ | `TransferFilled`, `TransferredOutlined` | `Forward` | lucide |
72
+ | `VoicemailOutlined` | `Voicemail` | lucide |
73
+ | `VoicemailDrop` | `VoicemailDrop` | custom |
74
+ | `RingingFilled`, `CircledPhoneCallingFilled` | `PhoneCall` | lucide |
75
+ | `KeypadFilled`, `KeypadOutlined` | `Keypad` | custom |
76
+ | `ConferenceFilled`, `ConferenceOutlined` | `Conference` | custom |
77
+
78
+ ### Actions
79
+
80
+ | Old (`@aircall/icons`) | New (`@aircall/react-icons`) | Source |
81
+ | --- | --- | --- |
82
+ | `CloseOutlined`, `CloseCircleFilled` | `X` | lucide |
83
+ | `CheckOutlined`, `TickOutlined` | `Check` | lucide |
84
+ | `SearchOutlined` | `Search` | lucide |
85
+ | `EditFilled`, `EditOutlined` | `Pencil` | lucide |
86
+ | `TrashFilled`, `TrashOutlined` | `Trash2` | lucide |
87
+ | `CopyFilled`, `CopyOutlined` | `Copy` | lucide |
88
+ | `FilterOutlined`, `FilterFilled` | `ListFilter` | lucide |
89
+ | `AddCircleFilled`, `PlusCircleFilled` | `Plus` | lucide |
90
+
91
+ ### Arrows & Navigation
92
+
93
+ | Old (`@aircall/icons`) | New (`@aircall/react-icons`) | Source |
94
+ | --- | --- | --- |
95
+ | `ChevronDownOutlined`, `ArrowDownFilled` | `ChevronDown` | lucide |
96
+ | `ChevronLeftOutlined` | `ChevronLeft` | lucide |
97
+ | `ChevronRightOutlined`, `ArrowRightFilled` | `ChevronRight` | lucide |
98
+ | `ChevronUpOutlined`, `ArrowUpFilled` | `ChevronUp` | lucide |
99
+ | `SendFilled`, `SendOutlined` | `Send` | lucide |
100
+ | `RefreshOutlined` | `RefreshCw` | lucide |
101
+
102
+ ### Audio & Video
103
+
104
+ | Old (`@aircall/icons`) | New (`@aircall/react-icons`) | Source |
105
+ | --- | --- | --- |
106
+ | `PlayFilled`, `PlayOutlined` | `Play` | lucide |
107
+ | `PauseFilled`, `PauseOutlined`, `HoldFilled` | `Pause` | lucide |
108
+ | `MicOnFilled`, `MicOnOutlined` | `Mic` | lucide |
109
+ | `MicOffFilled`, `MicOffOutlined` | `MicOff` | lucide |
110
+ | `SoundHighFilled`, `SoundHighOutlined` | `Volume2` | lucide |
111
+ | `HeadsetFilled`, `HeadsetOutlined` | `Headset` | lucide |
112
+ | `ReplayFilled`, `ReplayOutlined` | `Replay` | custom |
113
+
114
+ ### Users & Contacts
115
+
116
+ | Old (`@aircall/icons`) | New (`@aircall/react-icons`) | Source |
117
+ | --- | --- | --- |
118
+ | `UserFilled`, `UserOutlined` | `UserRound` | lucide |
119
+ | `PeopleFilled`, `PeopleOutlined` | `UsersRound` | lucide |
120
+ | `ContactsFilled`, `ContactsOutlined` | `ContactRound` | lucide |
121
+ | `AssignedFilled`, `AssignedOutlined` | `Assigned` | custom |
122
+ | `CompanyFilled`, `CompanyOutlined` | `Building` | lucide |
123
+
124
+ ### Feedback & Status
125
+
126
+ | Old (`@aircall/icons`) | New (`@aircall/react-icons`) | Source |
127
+ | --- | --- | --- |
128
+ | `InformationFilled`, `InformationOutlined` | `Info` | lucide |
129
+ | `WarningFilled`, `WarningOutlined` | `AlertTriangle` | lucide |
130
+ | `NotificationOnFilled`, `NotificationOnOutlined` | `Bell` | lucide |
131
+ | `StarOutlined` | `Star` | lucide |
132
+ | `StarFilled` | `StarFilled` | custom |
133
+
134
+ ### Messages & SMS
135
+
136
+ | Old (`@aircall/icons`) | New (`@aircall/react-icons`) | Source |
137
+ | --- | --- | --- |
138
+ | `MessageFilled`, `SmsInboundFilled` | `MessageSquare` | lucide |
139
+ | `EmailFilled`, `MailOutlined` | `Mail` | lucide |
140
+ | `InboxFilled`, `InboxOutlined` | `Inbox` | lucide |
141
+ | `ChatFilled`, `ChatOutlined` | `MessagesSquare` | lucide |
142
+
143
+ ### System & Settings
144
+
145
+ | Old (`@aircall/icons`) | New (`@aircall/react-icons`) | Source |
146
+ | --- | --- | --- |
147
+ | `SettingsFilled`, `SettingsOutlined` | `Settings` | lucide |
148
+ | `MenuVerticalFilled`, `MenuVerticalOutlined` | `MoreVertical` | lucide |
149
+ | `MenuHorizontalFilled`, `MenuHorizontalOutlined` | `MoreHorizontal` | lucide |
150
+ | `SpinnerOutlined` | `Loader2` | lucide |
151
+
152
+ ### Analytics & AI
153
+
154
+ | Old (`@aircall/icons`) | New (`@aircall/react-icons`) | Source |
155
+ | --- | --- | --- |
156
+ | `SparklesFilled`, `LivePromptFilled`, `MagicFilled` | `AiAssist` | custom |
157
+ | `AIAgent`, `VirtualAgentFilled` | `AiAgents` | custom |
158
+ | `MonitoringFilled`, `AnalyticsFilled` | `BarChartBig` | lucide |
159
+ | `ActivityFilled`, `BoltFilled` | `Zap` | lucide |
160
+
161
+ ## 5. Icons with no equivalent
162
+
163
+ A small number of old icons have no direct mapping. Handle them as noted:
164
+
165
+ | Old icon | Action |
166
+ | --- | --- |
167
+ | `PlayPauseFilled`, `PlayPauseOutlined` | Use `Play` or `Pause` contextually — no combined equivalent |
168
+ | `Speed100XFilled`, `Speed125XFilled`, `Speed150XFilled`, `Speed200XFilled` | Render as text or a custom badge — no icon equivalent |
169
+ | `AIMessageFilled` | No equivalent — treat as custom SVG until a replacement ships |
170
+ | `OnboardingFilled` | Use `Flag` or `Rocket` as a placeholder; confirm with design |
171
+
172
+ ## 6. Brand / platform icons (not in @aircall/react-icons)
173
+
174
+ Keep these as separate inline SVGs or local wrappers — they are not in `@aircall/react-icons`:
175
+
176
+ - **Aircall brand**: `AircallIcon`, `AircallLogo`, `AircallLogoFull`, logo-mark variants
177
+ - **OS / platform**: `AppleFilled`, `AndroidFilled`, `WindowsFilled`, `MacFilled`
178
+ - **Browsers**: `ChromeFilled`, `EdgeFilled`, `FirefoxFilled`
179
+ - **Messaging**: `WhatsappFilled`, `WhatsappOutlined` — use the existing `WhatsAppIcon.tsx` in `packages/aw-ui`
180
+ - **Text badges**: `BetaOutlined` — keep as a custom SVG or badge component
181
+
182
+ ## 7. Before / After examples
183
+
184
+ ### 7a. Basic icon with color and size
185
+
186
+ **Before (Tractor + @aircall/icons):**
187
+ ```tsx
188
+ import { Icon } from '@aircall/tractor';
189
+ import { CallFilled } from '@aircall/icons';
190
+
191
+ <Icon component={CallFilled} size={24} color="primary.500" />
192
+ ```
193
+
194
+ **After (@aircall/react-icons + Tailwind):**
195
+ ```tsx
196
+ import { Phone } from '@aircall/react-icons';
197
+
198
+ <Phone className="size-6 text-primary" />
199
+ ```
200
+
201
+ ### 7b. Icon inside a button (critical color)
202
+
203
+ **Before (Tractor + @aircall/icons):**
204
+ ```tsx
205
+ import { Icon } from '@aircall/tractor';
206
+ import { TrashFilled } from '@aircall/icons';
207
+ import { Button } from '@aircall/tractor';
208
+
209
+ <Button variant="danger" size="small">
210
+ <Icon component={TrashFilled} size={16} color="critical.500" />
211
+ Delete
212
+ </Button>
213
+ ```
214
+
215
+ **After (@aircall/ds + @aircall/react-icons):**
216
+ ```tsx
217
+ import { Trash2 } from '@aircall/react-icons';
218
+ import { Button } from '@aircall/ds';
219
+
220
+ <Button variant="destructive" size="lg">
221
+ <Trash2 />
222
+ Delete
223
+ </Button>
224
+ ```
225
+
226
+ > When an icon sits inside a DS `Button`, omit the `size-*` class — the Button's `[&_svg]:size-4` rule sizes it automatically.
227
+
228
+ ### 7c. Icon with muted color (secondary text)
229
+
230
+ **Before (Tractor + @aircall/icons):**
231
+ ```tsx
232
+ import { Icon } from '@aircall/tractor';
233
+ import { ClockFilled } from '@aircall/icons';
234
+
235
+ <Icon component={ClockFilled} size={20} color="text.secondary" />
236
+ ```
237
+
238
+ **After (@aircall/react-icons + Tailwind):**
239
+ ```tsx
240
+ import { Clock } from '@aircall/react-icons';
241
+
242
+ <Clock className="size-5 text-muted-foreground" />
243
+ ```
244
+
245
+ ### 7d. Custom Aircall icon
246
+
247
+ **Before (Tractor + @aircall/icons):**
248
+ ```tsx
249
+ import { Icon } from '@aircall/tractor';
250
+ import { VoicemailDrop } from '@aircall/icons';
251
+
252
+ <Icon component={VoicemailDrop} size={24} />
253
+ ```
254
+
255
+ **After (@aircall/react-icons):**
256
+ ```tsx
257
+ import { VoicemailDrop } from '@aircall/react-icons';
258
+
259
+ <VoicemailDrop className="size-6" />
260
+ ```
261
+
262
+ > Aircall custom icons are also re-exported from `@aircall/react-icons`. The import source changes but the component name stays the same for custom icons that survived into the new package.
263
+
264
+ ## 8. Common mistakes
265
+
266
+ ### Mistake 1: Keeping the Tractor `<Icon>` wrapper
267
+
268
+ ```tsx
269
+ // WRONG — @aircall/ds has no Icon component; wrapper is unused
270
+ import { Icon } from '@aircall/tractor';
271
+ import { Phone } from '@aircall/react-icons';
272
+
273
+ <Icon component={Phone} size={24} color="primary.500" />
274
+
275
+ // CORRECT — use the icon component directly with Tailwind classes
276
+ import { Phone } from '@aircall/react-icons';
277
+
278
+ <Phone className="size-6 text-primary" />
279
+ ```
280
+
281
+ **Mechanism:** `@aircall/react-icons` exports standard SVG React components. The Tractor `Icon` wrapper was a thin adapter that forwarded `size` and `color` as `width`/`height`/`fill` attributes. Without it, those props are simply ignored — or worse, they pass unknown attributes to the SVG element.
282
+
283
+ Source: `@aircall/react-icons` re-exports lucide + Aircall icons as plain SVG components with no wrapper.
284
+
285
+ ---
286
+
287
+ ### Mistake 2: Using the old `@aircall/icons` name unchanged
288
+
289
+ ```tsx
290
+ // WRONG — old name does not exist in @aircall/react-icons; TS compile error
291
+ import { CallFilled } from '@aircall/react-icons';
292
+
293
+ // CORRECT — lucide consolidates filled/outlined variants; use the new name
294
+ import { Phone } from '@aircall/react-icons';
295
+ ```
296
+
297
+ **Mechanism:** `@aircall/react-icons` intentionally consolidates `*Filled`/`*Outlined` variants into a single icon name (e.g. `CallFilled` + `CallOutlined` → `Phone`). The visual distinction (filled vs stroke) is gone by design — lucide icons are all stroke-outlined. Importing the old name compiles to `undefined` and renders nothing.
298
+
299
+ Source: `@aircall/react-icons` re-exports lucide + Aircall icons; custom icons live in `packages/react-icons/src/generated/custom`.
300
+
301
+ ---
302
+
303
+ ### Mistake 3: Using a `color` or `size` number prop instead of Tailwind classes
304
+
305
+ ```tsx
306
+ // WRONG — color and size number are Tractor Icon props, not valid SVG props
307
+ import { Mic } from '@aircall/react-icons';
308
+
309
+ <Mic size={24} color="primary.500" />
310
+
311
+ // CORRECT — use Tailwind utility classes
312
+ import { Mic } from '@aircall/react-icons';
313
+
314
+ <Mic className="size-6 text-primary" />
315
+ ```
316
+
317
+ **Mechanism:** lucide-react icons accept a `size` prop and a `color` prop via the lucide component API — but `@aircall/react-icons` wraps them in a way that Tailwind classes are the intended styling mechanism. Using Tractor color tokens like `"primary.500"` as a CSS value produces an invalid color string that browsers ignore silently, and the icon renders in the inherited color instead of the intended one.
318
+
319
+ Source: `@aircall/react-icons` re-exports lucide + Aircall icons; Tailwind design tokens (`text-primary`, `text-destructive`, etc.) are the authoritative color system in `@aircall/ds`.
320
+
321
+ ---
322
+
323
+ ### Mistake 4: Importing icons directly from `lucide-react`
324
+
325
+ ```tsx
326
+ // WRONG — bypasses the Aircall icon layer; icon swaps or overrides won't apply
327
+ import { Phone } from 'lucide-react';
328
+
329
+ // CORRECT — always route through @aircall/react-icons
330
+ import { Phone } from '@aircall/react-icons';
331
+ ```
332
+
333
+ **Mechanism:** `@aircall/react-icons` is the single source of truth for all icons — it re-exports lucide icons and adds Aircall custom icons. Importing from `lucide-react` directly means any future override, version pin, or custom replacement in `@aircall/react-icons` won't be picked up. It also breaks tree-shaking assumptions and makes it harder to audit icon usage across the codebase.
334
+
335
+ Source: `@aircall/react-icons` re-exports lucide + Aircall icons as the canonical icon surface for Aircall apps.
336
+
337
+ ## 9. Migration checklist
338
+
339
+ - [ ] Swap `@aircall/icons` → `@aircall/react-icons` in `package.json`
340
+ - [ ] Remove `import { Icon } from '@aircall/tractor'` — no wrapper needed
341
+ - [ ] Rename icons per the tables above (many `*Filled`/`*Outlined` pairs merge into one name)
342
+ - [ ] Replace `color="…"` with `className="text-*"` using the color table
343
+ - [ ] Replace `size={N}` with `className="size-N"` (or omit when inside a DS `Button`)
344
+ - [ ] Run `pnpm tsc --noEmit` — missing exports surface as TS compile errors
345
+ - [ ] Keep brand/platform icons (Aircall logo, OS, browser, WhatsApp) as separate assets
346
+ - [ ] For icons marked "validate usage", confirm the new shape is visually correct in context
@@ -0,0 +1,314 @@
1
+ ---
2
+ name: aircall-ds/migrate-tractor
3
+ description: >
4
+ Migrate a file from @aircall/tractor to @aircall/ds. Load FIRST when converting
5
+ Tractor components (Modal, Banner, Select, Button, Typography, Flex, Tooltip, Tag,
6
+ Dropdown, Form, …) to @aircall/ds. Carries the cross-cutting rules (import form,
7
+ prop renames, the render prop, data-attribute shape) and a lookup table mapping
8
+ each Tractor component to its @aircall/ds target and the recipe skill to load next.
9
+ type: core
10
+ library: aircall-ds
11
+ library_version: "0.13.0"
12
+ requires:
13
+ - aircall-ds/setup
14
+ sources:
15
+ - "aircall/hydra:docs/migration-guides/tractor-to-ds/01-global-rules.md"
16
+ - "aircall/hydra:docs/migration-guides/tractor-to-ds/04-lookup.md"
17
+ ---
18
+
19
+ # Migrating from @aircall/tractor to @aircall/ds
20
+
21
+ Load this skill first for any Tractor → DS migration. It gives the cross-cutting
22
+ rules that apply to every component and a lookup table to find the DS replacement.
23
+ Then load the component-specific recipe skill from the `recipe to load` column.
24
+
25
+ ## How to run this migration (end-to-end)
26
+
27
+ This skill and its recipes cover *converting code*. The full migration is a repeatable
28
+ loop — do it incrementally, one file/screen at a time, shipping each green:
29
+
30
+ 0. **Set up once** — load `@aircall/ds#aircall-ds/setup` and do the wiring before any
31
+ conversion: install `@aircall/ds` + `@aircall/react-icons` (>= 0.4.0), import the
32
+ precompiled `globals.css`, mount the root providers (incl. `DsI18nProvider` under
33
+ react-i18next + `NotificationQueueProvider`), keep `TractorProvider` mounted for
34
+ cohabitation, and add the jsdom test shims (selector guard for DS popups +
35
+ Switch-via-hidden-checkbox). DS and Tractor run side by side until the last Tractor
36
+ import is gone. (This skill `requires` setup so it loads automatically — but do the
37
+ install/provider/jest wiring first.)
38
+ 1. **Inventory** — list the file's `@aircall/tractor` and `@aircall/icons` imports. Each
39
+ maps to a row in the lookup table below, or to `@aircall/ds#aircall-ds/migrate-icons`
40
+ for icons.
41
+ 2. **Migrate** — apply the cross-cutting rules (§1–§3), then load the per-component recipe
42
+ from the `recipe to load` column for each component; use `migrate-icons` for icons.
43
+ 3. **Verify green** — `tsc --noEmit`, the test suite (DS popups/Switch need the setup jsdom
44
+ shims), and biome/lint. Re-screenshot if the screen changed visually.
45
+ 4. **Repeat** per file/screen until no `@aircall/tractor` / `@aircall/icons` imports remain,
46
+ then drop them from `package.json`.
47
+
48
+ ## 1. Imports
49
+
50
+ Always import from the top-level package only:
51
+
52
+ ```tsx
53
+ import { Button, Dialog, Input } from '@aircall/ds';
54
+ ```
55
+
56
+ Subpath imports (`@aircall/ds/components/button`) work only inside the hydra monorepo
57
+ and break in external apps. Use the root form everywhere.
58
+
59
+ ## 2. Drop styled-components
60
+
61
+ Remove `styled()` wrappers, `fromTheme`, `getColor`/`getSpace`/`getRadii`/`getShadow`,
62
+ and `useTheme()`. Replace them with Tailwind classes on the DS component's `className`
63
+ or a plain `<div>`:
64
+
65
+ ```tsx
66
+ // Before
67
+ const Styled = styled(Button)`font-size: ${fromTheme('typography.variants.body.fontSize')};`;
68
+ <Box mx={2} my={4} bg="primary.500" />
69
+
70
+ // After
71
+ <Button className="text-sm" />
72
+ <div className="mx-2 my-4 bg-primary" />
73
+ ```
74
+
75
+ The migration is progressive — Tractor and DS can coexist. Remove `TractorProvider`
76
+ only when the last `@aircall/tractor` import is gone.
77
+
78
+ ## 3. Standard prop renames
79
+
80
+ These apply to every component that had them in Tractor:
81
+
82
+ | Tractor | DS |
83
+ | --- | --- |
84
+ | `isOpen` | `open` |
85
+ | `onClose` | `onOpenChange` (receives `boolean`) |
86
+ | `onChange` (value-based: Select, Tabs, RadioGroup, Slider, ToggleGroup) | `onValueChange` |
87
+ | `onChange` (boolean: Checkbox, Switch) | `onCheckedChange` |
88
+ | `validationStatus="error"` | `aria-invalid={true}` |
89
+ | `variant="critical"` | `variant="destructive"` (Button) |
90
+ | `as="…"` | `render={<Element />}` |
91
+
92
+ > `onValueChange` for Select receives `(value: string | null, event)` — `null` when
93
+ > cleared. Widen your handler to accept `null`.
94
+
95
+ ## 4. The `render` prop (replaces `asChild`)
96
+
97
+ When a Trigger or Close must render as a custom element, pass it to `render`:
98
+
99
+ ```tsx
100
+ <DialogTrigger render={<Button variant="outline" />}>Open</DialogTrigger>
101
+ ```
102
+
103
+ Affected: Trigger/Close on Dialog, Sheet, Popover, Tooltip, DropdownMenu, Collapsible,
104
+ Drawer, plus `Button`, `Badge`, `Item`, `PaginationLink`, and Sidebar subcomponents.
105
+
106
+ Drawer is Base UI (via Coss UI) — same `render` rule applies. Its body wrapper is
107
+ `DrawerPopup` (not `DrawerContent`), and Tractor's `direction` becomes `position`.
108
+
109
+ ## 5. Labels live inside Groups
110
+
111
+ For `DropdownMenu` and `Select`, `Label` must be a child of a `*Group`:
112
+
113
+ ```tsx
114
+ <DropdownMenuContent>
115
+ <DropdownMenuGroup>
116
+ <DropdownMenuLabel>Account</DropdownMenuLabel>
117
+ <DropdownMenuItem>Profile</DropdownMenuItem>
118
+ </DropdownMenuGroup>
119
+ </DropdownMenuContent>
120
+ ```
121
+
122
+ ## 6. Data attributes in Tailwind classes
123
+
124
+ DS uses single-key data attributes for state (not Radix `[state=…]` form):
125
+
126
+ | Use | Not |
127
+ | --- | --- |
128
+ | `data-open:animate-in` | `data-[state=open]:animate-in` |
129
+ | `data-checked:bg-primary` | `data-[state=checked]:bg-primary` |
130
+ | `data-disabled:opacity-50` | `data-[disabled]:opacity-50` |
131
+
132
+ **Orientation is the exception** — it's a value attribute, not a flag. Style with
133
+ `data-[orientation=horizontal]:` / `data-[orientation=vertical]:` (Tabs, Slider,
134
+ Separator).
135
+
136
+ **Collapsible asymmetry:** Root uses `data-open` / `data-closed`; Trigger uses
137
+ `data-panel-open` (no closed counterpart).
138
+
139
+ ## 7. Size baselines
140
+
141
+ > These are recommended starting points. Verify with design — pixel sizes have shifted
142
+ > between Tractor and DS, so a verbatim same-size match isn't always possible.
143
+
144
+ **Button** (DS heights: `sm`=24px, `default`=32px, `lg`=40px):
145
+
146
+ | Tractor | DS | Diff |
147
+ | --- | --- | --- |
148
+ | `xSmall` (28px) | `size="default"` (32px) | +4px |
149
+ | `small` (40px) | `size="lg"` (40px) | exact |
150
+ | `regular` (48px, default) | `size="lg"` (40px) | -8px |
151
+ | `large` (56px) | `className="h-14"` | no built-in size |
152
+
153
+ **Icon Button** (DS sizes: `icon-sm`=24px, `icon`=32px, `icon-lg`=40px):
154
+
155
+ | Tractor IconButton | DS |
156
+ | --- | --- |
157
+ | Default (24px) | `size="icon"` |
158
+ | Smaller than default | `size="icon-sm"` |
159
+ | Larger than default | `size="icon-lg"` |
160
+
161
+ **Select Trigger** has two sizes: `default` (40px) and `sm` (32px). Use `default`
162
+ for any Tractor `regular` / `small`.
163
+
164
+ **Avatar** (DS sizes: `xs`=20px, `sm`=24px, `default`=32px, `lg`=40px, `xl`=48px):
165
+
166
+ | Tractor | DS |
167
+ | --- | --- |
168
+ | `small` (24px) | `sm` |
169
+ | `regular` (32px) | `default` |
170
+ | `large` (48px) | `xl` |
171
+ | `xLarge` (64px) | `xl` (-16px, no 64px size) |
172
+
173
+ **Toggle / ToggleGroupItem**: same heights as Button (`sm`/`default`/`lg`).
174
+
175
+ **No `size` prop:** Input, Textarea, Switch, Checkbox, RadioGroup, Slider. Drop the
176
+ Tractor `size` prop entirely.
177
+
178
+ ## 8. Variant naming
179
+
180
+ Tractor used `variant` + `mode`. DS uses a single `variant`.
181
+
182
+ | Tractor | DS |
183
+ | --- | --- |
184
+ | `critical` | `destructive` (Button) or `error` (Alert) |
185
+ | `informative` | `info` (Alert) |
186
+ | `primary` + `mode="fill"` | `default` (Button) |
187
+ | `primary` + `mode="outline"` | `outline` (Button) |
188
+ | `primary` + `mode="ghost"` | `ghost` (Button) |
189
+ | `primary` + `mode="link"` | `link` (Button) |
190
+
191
+ Each DS component has its own variant set — check the lookup table below.
192
+
193
+ ## 9. Icons
194
+
195
+ `@aircall/icons` → `@aircall/react-icons`. The `<Icon component={X} />` wrapper is
196
+ gone; import the icon directly:
197
+
198
+ ```tsx
199
+ // Before
200
+ import { AddOutlined, Icon } from '@aircall/tractor';
201
+ <Icon component={AddOutlined} mr={2} />
202
+
203
+ // After
204
+ import { AddCircleFill } from '@aircall/react-icons';
205
+ <AddCircleFill className="mr-2 size-4" />
206
+ ```
207
+
208
+ Some names differ significantly (e.g. `InfoCircleFilled` → `InformationCircleFill`,
209
+ `PlayFilled` → `ControlPlayFill`).
210
+
211
+ ## 10. Forms & validation
212
+
213
+ A Tractor `Form`/`FormItem` that collects and submits data migrates to **`@aircall/blocks` `useForm` + the `Form*Field` wrappers** — do NOT keep field values in React `useState`, and do NOT hand-wire the ds `Field`/`Input` primitives. The form owns state, validation, dirty/`canSubmit`/`isSubmitting`, and errors (which drive `SubmitButton`/`CardSaveBar`). Use the bare ds `Field` primitives only for non-form display. See `@aircall/ds#aircall-ds/migrate-tractor/form-and-field` and `@aircall/blocks#aircall-blocks/migrate-dashboard/form-wizard`.
214
+
215
+ Field-level validation state is `aria-invalid` — DS components style themselves from it:
216
+
217
+ ```tsx
218
+ <Input aria-invalid={hasError} />
219
+ ```
220
+
221
+ ## 11. Cleanup items
222
+
223
+ - **Base font size**: Tractor used 14px. DS uses 16px. Remove any `font-size: 14px`
224
+ on `html` or `body` — DS's `globals.css` already sets the correct base.
225
+ - **SVG workarounds**: Remove any `svg { display: block; line-height: 0; }` global CSS.
226
+
227
+ ---
228
+
229
+ ## Component lookup table
230
+
231
+ Grep your code for `@aircall/tractor` imports. For each component, find the row below.
232
+ Then load the listed recipe skill (if `available`) for the detailed swap.
233
+
234
+ | Tractor component | @aircall/ds target | recipe to load | status |
235
+ | --- | --- | --- | --- |
236
+ | `Accordion` (+ `AccordionSection`) | `Accordion` + `AccordionItem` + `AccordionTrigger` + `AccordionContent` | `@aircall/ds#aircall-ds/migrate-tractor/accordion` | available |
237
+ | `ActionMenu` | `DropdownMenu` (compound) | `@aircall/ds#aircall-ds/migrate-tractor/dropdown-menu` | available |
238
+ | `AudioPlayer` | — (no DS equivalent yet) | — | not-yet |
239
+ | `Avatar` (+ `QuickAvatar`) | `Avatar` + `AvatarImage` + `AvatarFallback` | `@aircall/ds#aircall-ds/migrate-tractor/avatar` | available |
240
+ | `Badge` | `AvatarBadge` (status dot on avatar); `Badge` (label/tag style) | `@aircall/ds#aircall-ds/migrate-tractor/badge` | available |
241
+ | `Banner` (+ `BannerHeading`/`BannerIcon`/`BannerSuffix`) | `Alert` (rounded card) / `Banner` (inline full-width) | `@aircall/ds#aircall-ds/migrate-tractor/alert` | available |
242
+ | `BannerButton` | `Button` inside `BannerAction` | `@aircall/ds#aircall-ds/migrate-tractor/alert` | available |
243
+ | `Box` | native `<div>` + Tailwind classes | `@aircall/ds#aircall-ds/migrate-tractor/styling` | available |
244
+ | `Button` | `Button` | `@aircall/ds#aircall-ds/migrate-tractor/button` | available |
245
+ | `Checkbox` | `Checkbox` + `<Label>` | — | not-yet |
246
+ | `ComboBox` | `Combobox` (compound) | `@aircall/ds#aircall-ds/migrate-tractor/combobox` | available |
247
+ | `CounterBadge` | `CounterBadge` | `@aircall/ds#aircall-ds/migrate-tractor/badge` | available |
248
+ | `DatePicker` | `Calendar` + your own `Popover` trigger | — | not-yet |
249
+ | `Divider` | `Separator` | `@aircall/ds#aircall-ds/migrate-tractor/divider` | available |
250
+ | `Drawer` | `Drawer` (compound, `DrawerPopup` body) | `@aircall/ds#aircall-ds/migrate-tractor/sheet-vs-drawer` | available |
251
+ | `Dropdown` | `DropdownMenu` (compound) | `@aircall/ds#aircall-ds/migrate-tractor/dropdown-menu` | available |
252
+ | `Flex` | native `<div className="flex …">` + Tailwind | `@aircall/ds#aircall-ds/migrate-tractor/styling` | available |
253
+ | `FlagIcon` | `CountryFlag` (prop: `countryIsoCode`) | — | not-yet |
254
+ | `Form` | native `<form>` + `Field` per row | `@aircall/ds#aircall-ds/migrate-tractor/form-and-field` | available |
255
+ | `FormItem` | `Field` + `FieldLabel` + `FieldDescription` + `FieldError` | `@aircall/ds#aircall-ds/migrate-tractor/form-and-field` | available |
256
+ | `Gauge` | `Gauge` (8-segment audio meter) | `@aircall/ds#aircall-ds/migrate-tractor/gauge` | available |
257
+ | `Grid` | native `<div className="grid …">` + Tailwind | `@aircall/ds#aircall-ds/migrate-tractor/styling` | available |
258
+ | `Icon` | direct icon import from `@aircall/react-icons` | `@aircall/ds#aircall-ds/migrate-icons` | available |
259
+ | `IconButton` | `Button` with `size` `icon` / `icon-sm` / `icon-lg` | `@aircall/ds#aircall-ds/migrate-tractor/button` | available |
260
+ | `Link` | `Link` | `@aircall/ds#aircall-ds/migrate-tractor/link` | available |
261
+ | `List` (+ `ListItem`) | `ItemGroup` + `Item` + `ItemMedia`/`ItemContent`/`ItemActions` | `@aircall/ds#aircall-ds/migrate-tractor/item` | available |
262
+ | `Menu` (+ `MenuItem`) | standalone list → `ItemGroup` + `Item`; **inside a `Dropdown`/`ActionMenu`** → `DropdownMenuContent` + `DropdownMenuItem` | `@aircall/ds#aircall-ds/migrate-tractor/item` (standalone) or `@aircall/ds#aircall-ds/migrate-tractor/dropdown-menu` (in a Dropdown) | available |
263
+ | `Modal` | `Dialog` (compound) | `@aircall/ds#aircall-ds/migrate-tractor/dialog` | available |
264
+ | `PasswordInput` | `InputGroup` recipe | `@aircall/ds#aircall-ds/migrate-tractor/input` | available |
265
+ | `Popover` | `Popover` + `PopoverTrigger` + `PopoverContent` | `@aircall/ds#aircall-ds/migrate-tractor/popover` | available |
266
+ | `Progress` | `Progress` (compound: `ProgressTrack` + `ProgressIndicator`) | — | not-yet |
267
+ | `QuickAvatar` | `Avatar` (same as Avatar row) | `@aircall/ds#aircall-ds/migrate-tractor/avatar` | available |
268
+ | `Radio` + `RadioGroup` | `RadioGroup` + `RadioGroupItem` | — | not-yet |
269
+ | `SegmentedControl` | `Tabs` (with panel) or `ToggleGroup` (visual only) | `@aircall/ds#aircall-ds/migrate-tractor/tabs` | available |
270
+ | `Select` + `SelectOption` | `Select` (compound: `SelectTrigger` + `SelectValue` + `SelectContent` + `SelectGroup` + `SelectItem`) | `@aircall/ds#aircall-ds/migrate-tractor/select` | available |
271
+ | `SidenavDropdown` | `Sidebar` + `Collapsible` (compose) | — | not-yet |
272
+ | `SidenavItem` | `Sidebar*` family | — | not-yet |
273
+ | `Skeleton` | `Skeleton` — size via Tailwind (`className="h-4 w-32"`) | `@aircall/ds#aircall-ds/migrate-tractor/skeleton` | available |
274
+ | `Slider` | `Slider` (`onValueChange`, value is `number[]`, no `size`) | — | not-yet |
275
+ | `Spacer` | `<div className="flex flex-col gap-*">` or `FieldGroup` | `@aircall/ds#aircall-ds/migrate-tractor/styling` | available |
276
+ | `SpinnerOutlined` | `Spinner` (sizes: `sm`/`default`/`lg`/`xl`, always animated) | — | not-yet |
277
+ | `Tab` (+ `Tab.Item`/`TabList`/`TabPanel`) | `Tabs` + `TabsTrigger` + `TabsList` + `TabsContent` | `@aircall/ds#aircall-ds/migrate-tractor/tabs` | available |
278
+ | `Table` (+ `ActionBar`) | `DataTable` (data-driven) or `Table` primitive (static) | `@aircall/ds#aircall-ds/migrate-tractor/data-table` | available |
279
+ | `Tag` | `Badge` (`color` + `tone` props; `legacyColor` for custom hex) | `@aircall/ds#aircall-ds/migrate-tractor/badge` | available |
280
+ | `Textarea` | `Textarea` (no `size`, use `aria-invalid`) | — | not-yet |
281
+ | `TextFieldInput` | `Input` (no `sizing`, 40px fixed, use `aria-invalid`) | `@aircall/ds#aircall-ds/migrate-tractor/input` | available |
282
+ | `Toggle` | `Switch` (`onCheckedChange`, no `size`) | — | not-yet |
283
+ | `ToggleGroup` / `TabToggle` | `ToggleGroup` (`multiple` boolean; value always `string[]`) | — | not-yet |
284
+ | `Tooltip` | `Tooltip` (compound) + `TooltipProvider` at app root | `@aircall/ds#aircall-ds/migrate-tractor/tooltip` | available |
285
+ | `Tree` | `DataTree` (data-driven) or `Tree` primitive | `@aircall/ds#aircall-ds/migrate-tractor/tree` | available |
286
+ | `TreeSelect` | no turn-key equivalent — `DataTree` inside a `Popover` | `@aircall/ds#aircall-ds/migrate-tractor/tree` | available |
287
+ | `Typography` | native HTML + Tailwind text classes | `@aircall/ds#aircall-ds/migrate-tractor/styling` | available |
288
+ | `useToast` | `toast` from `sonner` + `<Toaster />` at app root | `@aircall/ds#aircall-ds/migrate-tractor/toast` | available |
289
+ | `WhatsAppTemplatePreview` | — (no DS equivalent) | — | not-yet |
290
+
291
+ ---
292
+
293
+ ## Facts easy to get wrong
294
+
295
+ These APIs do not exist in DS. Do not propose them.
296
+
297
+ - `<Input sizing="lg" />` — Input has **no** `sizing`/`size` prop. It is 40px, period.
298
+ - `<Textarea size="…" />` — no size prop.
299
+ - `<Switch size="…" />` — no size prop.
300
+ - `<Checkbox size="…" />` — no size prop.
301
+ - `<RadioGroup size="…" />` — no size prop.
302
+ - `<Slider size="…" />` — no size prop.
303
+ - `<Tabs size="…" />` / `<TabsList variant="…" />` — no such props.
304
+ - `<Badge variant="destructive" | "success" | "warning" | "info" />` — Badge has only
305
+ `default`, `secondary`, `outline`. For semantic colors use `Alert`, `CounterBadge`,
306
+ or `className` with a semantic token.
307
+ - `<Alert variant="destructive" />` — Alert's error variant is `error`, not
308
+ `destructive`. Full set: `default` / `info` / `success` / `warning` / `error`.
309
+ - `<CounterBadge count={…} max={…} />` — no `count`/`max` props. Pass the already-
310
+ capped value as children. Variants: `default` / `secondary` / `ghost`.
311
+ - `<TooltipProvider delayDuration={…} />` — Base UI prop is `delay`, default `0`.
312
+ `delayDuration` is silently ignored.
313
+ - Subpath imports like `@aircall/ds/components/<name>` — never propose. Always use
314
+ `import { X } from '@aircall/ds'`.