@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,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'`.
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: aircall-ds/migrate-tractor/accordion
|
|
3
|
+
description: >
|
|
4
|
+
Migrate Tractor Accordion and AccordionSection to the @aircall/ds Accordion
|
|
5
|
+
compound (AccordionItem, AccordionTrigger, AccordionContent). Load when a file
|
|
6
|
+
imports Accordion or AccordionSection from @aircall/tractor.
|
|
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/accordion.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 accordion-specific steps below.
|
|
18
|
+
|
|
19
|
+
## 1. Component mapping
|
|
20
|
+
|
|
21
|
+
Tractor `Accordion` accepted `AccordionSection` children that bundled header, chevron, and body in a single component. DS splits each section into three named parts:
|
|
22
|
+
|
|
23
|
+
| Tractor | @aircall/ds |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| `Accordion` | `Accordion` (state owner) |
|
|
26
|
+
| `AccordionSection` | `AccordionItem` + `AccordionTrigger` + `AccordionContent` |
|
|
27
|
+
| _(built into AccordionSection)_ | `AccordionTrigger` (header + built-in chevron) |
|
|
28
|
+
| _(built into AccordionSection)_ | `AccordionContent` (body panel) |
|
|
29
|
+
|
|
30
|
+
## 2. Verified DS exports (`packages/ds/src/index.ts`)
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
Accordion, AccordionItem, AccordionTrigger, AccordionContent
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
All names used in the Before/After section below exist in the published public API.
|
|
37
|
+
|
|
38
|
+
## 3. Imports
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import {
|
|
42
|
+
Accordion,
|
|
43
|
+
AccordionItem,
|
|
44
|
+
AccordionTrigger,
|
|
45
|
+
AccordionContent,
|
|
46
|
+
} from '@aircall/ds';
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## 4. Prop changes
|
|
50
|
+
|
|
51
|
+
| Tractor | DS | Notes |
|
|
52
|
+
| --- | --- | --- |
|
|
53
|
+
| _(implicit — one open at a time)_ | default (no extra prop needed) | Single-open is the DS default |
|
|
54
|
+
| _(allowMultiple or equivalent)_ | `multiple` on `<Accordion>` | Boolean — allows multiple items open simultaneously |
|
|
55
|
+
| _(section key)_ | `value` on `<AccordionItem>` | Required; `number` or `string`; index works for static lists |
|
|
56
|
+
| _(section disabled)_ | `disabled` on `<AccordionItem>` | Disables the trigger and keeps the panel closed |
|
|
57
|
+
| `defaultExpanded` / open sections | `defaultValue` on `<Accordion>` | Always an array, even in single-open mode: `defaultValue={[0]}` |
|
|
58
|
+
| controlled open | `value` + `onValueChange` on `<Accordion>` | `value` is an array; `onValueChange` receives `(number \| string)[]` |
|
|
59
|
+
|
|
60
|
+
## 5. Before / After
|
|
61
|
+
|
|
62
|
+
### Static list (most common)
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
// Before
|
|
66
|
+
import { Accordion, AccordionSection } from '@aircall/tractor';
|
|
67
|
+
|
|
68
|
+
<Accordion>
|
|
69
|
+
<AccordionSection title="General">
|
|
70
|
+
<p>General settings content.</p>
|
|
71
|
+
</AccordionSection>
|
|
72
|
+
<AccordionSection title="Billing">
|
|
73
|
+
<p>Billing details content.</p>
|
|
74
|
+
</AccordionSection>
|
|
75
|
+
</Accordion>
|
|
76
|
+
|
|
77
|
+
// After
|
|
78
|
+
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@aircall/ds';
|
|
79
|
+
|
|
80
|
+
<Accordion defaultValue={[0]}>
|
|
81
|
+
<AccordionItem value={0}>
|
|
82
|
+
<AccordionTrigger>General</AccordionTrigger>
|
|
83
|
+
<AccordionContent>
|
|
84
|
+
<p>General settings content.</p>
|
|
85
|
+
</AccordionContent>
|
|
86
|
+
</AccordionItem>
|
|
87
|
+
<AccordionItem value={1}>
|
|
88
|
+
<AccordionTrigger>Billing</AccordionTrigger>
|
|
89
|
+
<AccordionContent>
|
|
90
|
+
<p>Billing details content.</p>
|
|
91
|
+
</AccordionContent>
|
|
92
|
+
</AccordionItem>
|
|
93
|
+
</Accordion>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Key changes:
|
|
97
|
+
- Each `AccordionSection` becomes three components: `AccordionItem` (identity + state), `AccordionTrigger` (header label), `AccordionContent` (body)
|
|
98
|
+
- `title` prop on `AccordionSection` becomes the children of `AccordionTrigger`
|
|
99
|
+
- Every `AccordionItem` requires a `value` prop — the index is fine for static lists
|
|
100
|
+
- `defaultValue` is always an array on `Accordion`
|
|
101
|
+
|
|
102
|
+
### Dynamic list (map over data)
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
// Before
|
|
106
|
+
import { Accordion, AccordionSection } from '@aircall/tractor';
|
|
107
|
+
|
|
108
|
+
<Accordion>
|
|
109
|
+
{sections.map(section => (
|
|
110
|
+
<AccordionSection key={section.id} title={section.title}>
|
|
111
|
+
{section.body}
|
|
112
|
+
</AccordionSection>
|
|
113
|
+
))}
|
|
114
|
+
</Accordion>
|
|
115
|
+
|
|
116
|
+
// After
|
|
117
|
+
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@aircall/ds';
|
|
118
|
+
|
|
119
|
+
<Accordion defaultValue={[0]}>
|
|
120
|
+
{sections.map((section, index) => (
|
|
121
|
+
<AccordionItem key={section.id} value={index}>
|
|
122
|
+
<AccordionTrigger>{section.title}</AccordionTrigger>
|
|
123
|
+
<AccordionContent>{section.body}</AccordionContent>
|
|
124
|
+
</AccordionItem>
|
|
125
|
+
))}
|
|
126
|
+
</Accordion>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Multiple sections open simultaneously
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
// After — allow more than one panel open at a time
|
|
133
|
+
<Accordion multiple defaultValue={[0, 1]}>
|
|
134
|
+
<AccordionItem value={0}>
|
|
135
|
+
<AccordionTrigger>General</AccordionTrigger>
|
|
136
|
+
<AccordionContent>General settings content.</AccordionContent>
|
|
137
|
+
</AccordionItem>
|
|
138
|
+
<AccordionItem value={1}>
|
|
139
|
+
<AccordionTrigger>Billing</AccordionTrigger>
|
|
140
|
+
<AccordionContent>Billing details content.</AccordionContent>
|
|
141
|
+
</AccordionItem>
|
|
142
|
+
</Accordion>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Controlled
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
// After — controlled open state
|
|
149
|
+
const [openSections, setOpenSections] = React.useState<number[]>([0]);
|
|
150
|
+
|
|
151
|
+
<Accordion value={openSections} onValueChange={setOpenSections}>
|
|
152
|
+
<AccordionItem value={0}>
|
|
153
|
+
<AccordionTrigger>General</AccordionTrigger>
|
|
154
|
+
<AccordionContent>General settings content.</AccordionContent>
|
|
155
|
+
</AccordionItem>
|
|
156
|
+
<AccordionItem value={1}>
|
|
157
|
+
<AccordionTrigger>Billing</AccordionTrigger>
|
|
158
|
+
<AccordionContent>Billing details content.</AccordionContent>
|
|
159
|
+
</AccordionItem>
|
|
160
|
+
</Accordion>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Disabled section
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
// After — disable one item
|
|
167
|
+
<Accordion defaultValue={[0]}>
|
|
168
|
+
<AccordionItem value={0}>
|
|
169
|
+
<AccordionTrigger>General</AccordionTrigger>
|
|
170
|
+
<AccordionContent>General settings content.</AccordionContent>
|
|
171
|
+
</AccordionItem>
|
|
172
|
+
<AccordionItem value={1} disabled>
|
|
173
|
+
<AccordionTrigger>Billing</AccordionTrigger>
|
|
174
|
+
<AccordionContent>Billing details content.</AccordionContent>
|
|
175
|
+
</AccordionItem>
|
|
176
|
+
</Accordion>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Width / spacing
|
|
180
|
+
|
|
181
|
+
`Accordion` has no layout props. Apply width via `className`:
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
<Accordion defaultValue={[0]} className="w-[450px]">
|
|
185
|
+
...
|
|
186
|
+
</Accordion>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## 6. Common Mistakes
|
|
192
|
+
|
|
193
|
+
### Mistake 1 — Adding a custom chevron inside `AccordionTrigger`
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
// Wrong — AccordionTrigger renders its own chevron; adding another doubles it
|
|
197
|
+
<AccordionTrigger>
|
|
198
|
+
General
|
|
199
|
+
<ChevronDownIcon />
|
|
200
|
+
</AccordionTrigger>
|
|
201
|
+
|
|
202
|
+
// Correct — pass only the header label as children
|
|
203
|
+
<AccordionTrigger>General</AccordionTrigger>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
`AccordionTrigger` internally renders a `ChevronDownIcon` / `ChevronUpIcon` pair that swaps on open/close state. Adding your own icon duplicates it visually. Pass only the label text (or non-icon content) as children.
|
|
207
|
+
|
|
208
|
+
Source: `packages/ds/src/components/accordion.tsx`
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
### Mistake 2 — Passing `defaultValue` / `value` as a scalar instead of an array
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
// Wrong — scalar causes a TypeScript error and the panel does not open
|
|
216
|
+
<Accordion defaultValue={0}>
|
|
217
|
+
<AccordionItem value={0}>
|
|
218
|
+
<AccordionTrigger>General</AccordionTrigger>
|
|
219
|
+
<AccordionContent>General settings content.</AccordionContent>
|
|
220
|
+
</AccordionItem>
|
|
221
|
+
</Accordion>
|
|
222
|
+
|
|
223
|
+
// Correct — always wrap in an array, even for a single default item
|
|
224
|
+
<Accordion defaultValue={[0]}>
|
|
225
|
+
<AccordionItem value={0}>
|
|
226
|
+
<AccordionTrigger>General</AccordionTrigger>
|
|
227
|
+
<AccordionContent>General settings content.</AccordionContent>
|
|
228
|
+
</AccordionItem>
|
|
229
|
+
</Accordion>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Both `defaultValue` and `value` on `Accordion` are typed as `(number | string)[]`. Passing a bare scalar is a type error and the Base UI primitive will not open the panel.
|
|
233
|
+
|
|
234
|
+
Source: `packages/ds/src/components/accordion.tsx`
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
### Mistake 3 — Omitting the `value` prop on `AccordionItem`
|
|
239
|
+
|
|
240
|
+
```tsx
|
|
241
|
+
// Wrong — AccordionItem without value cannot be identified for open/close state
|
|
242
|
+
<AccordionItem>
|
|
243
|
+
<AccordionTrigger>General</AccordionTrigger>
|
|
244
|
+
<AccordionContent>General settings content.</AccordionContent>
|
|
245
|
+
</AccordionItem>
|
|
246
|
+
|
|
247
|
+
// Correct — every AccordionItem needs a unique value
|
|
248
|
+
<AccordionItem value={0}>
|
|
249
|
+
<AccordionTrigger>General</AccordionTrigger>
|
|
250
|
+
<AccordionContent>General settings content.</AccordionContent>
|
|
251
|
+
</AccordionItem>
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
`value` is required by the Base UI Accordion primitive to track which items are open. Without it the item cannot be matched against `defaultValue` / `value` on the root `Accordion`, so the panel stays permanently closed and toggling has no effect.
|
|
255
|
+
|
|
256
|
+
Source: `packages/ds/src/components/accordion.tsx`
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
### Mistake 4 — Using `type="multiple"` instead of the `multiple` boolean
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
// Wrong — type prop does not exist on DS Accordion; silently ignored
|
|
264
|
+
<Accordion type="multiple" defaultValue={[0, 1]}>
|
|
265
|
+
...
|
|
266
|
+
</Accordion>
|
|
267
|
+
|
|
268
|
+
// Correct — DS uses a boolean `multiple` prop (same convention as ToggleGroup)
|
|
269
|
+
<Accordion multiple defaultValue={[0, 1]}>
|
|
270
|
+
...
|
|
271
|
+
</Accordion>
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Tractor (and some other libraries) use `type="single" | "multiple"` to control this behavior. DS uses the simpler boolean `multiple` prop. Passing `type="multiple"` is silently ignored — the accordion reverts to single-open mode with no TypeScript error.
|
|
275
|
+
|
|
276
|
+
Source: `packages/ds/src/components/accordion.tsx`
|