@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,421 @@
1
+ ---
2
+ name: aircall-ds/migrate-tractor/styling
3
+ description: >
4
+ Convert Tractor layout and style primitives to @aircall/ds Tailwind classes and
5
+ tokens. Load when a file uses Typography, Flex, Box, Grid, Spacer, or the getColor
6
+ / getSpace / getRadii / defaultTheme helpers from @aircall/tractor. Covers the
7
+ highest-volume Tractor usages (these are not components with recipes — they map to
8
+ utility classes).
9
+ type: sub-skill
10
+ library: aircall-ds
11
+ library_version: "0.13.0"
12
+ requires:
13
+ - aircall-ds/setup
14
+ - aircall-ds/migrate-tractor
15
+ sources:
16
+ - "aircall/hydra:docs/migration-guides/tractor-to-ds/02-design-tokens.md"
17
+ - "aircall/hydra:docs/migration-guides/tractor-to-ds/03-spacing.md"
18
+ - "aircall/hydra:docs/migration-guides/tractor-to-ds/07-typography.md"
19
+ ---
20
+
21
+ This skill builds on aircall-ds/migrate-tractor.
22
+
23
+ ## 1. Typography
24
+
25
+ Tractor's `Typography` component is replaced by native HTML elements with Tailwind classes. No import needed.
26
+
27
+ ### Variant → Tailwind mapping
28
+
29
+ #### Legacy variants
30
+
31
+ | Tractor `variant=` | Tailwind classes |
32
+ | --- | --- |
33
+ | `displayXL` | `text-5xl font-bold` |
34
+ | `displayXL2` | `text-5xl font-light` |
35
+ | `displayL` | `text-4xl font-bold` |
36
+ | `displayL2` | `text-4xl font-light` |
37
+ | `displayM` | `text-3xl font-bold` |
38
+ | `displayM2` | `text-3xl font-light` |
39
+ | `displayS` | `text-xl font-bold` |
40
+ | `displayS2` | `text-xl font-light` |
41
+ | `heading` | `text-lg font-bold` |
42
+ | `heading2` | `text-lg font-light` |
43
+ | `subheading` | `text-base font-semibold` |
44
+ | `subheading2` | `text-base font-normal` |
45
+ | `body` | `text-sm font-medium` |
46
+ | `body2` | `text-sm font-normal` |
47
+ | `caption` | `text-xs font-medium` |
48
+ | `caption2` | `text-xs font-normal` |
49
+ | `overline` | `text-tiny font-medium` |
50
+ | `overline2` | `text-tiny font-normal` |
51
+
52
+ #### New variants
53
+
54
+ | Tractor `variant=` | Tailwind classes |
55
+ | --- | --- |
56
+ | `supportingRegularXS` | `text-tiny font-normal` |
57
+ | `supportingMediumXS` | `text-tiny font-medium` |
58
+ | `supportingSemiboldXS` | `text-tiny font-semibold` |
59
+ | `supportingRegularS` | `text-xs font-normal` |
60
+ | `supportingMediumS` | `text-xs font-medium` |
61
+ | `supportingSemiboldS` | `text-xs font-semibold` |
62
+ | `bodyRegularS` | `text-sm font-normal` |
63
+ | `bodyMediumS` | `text-sm font-medium` |
64
+ | `bodySemiboldS` | `text-sm font-semibold` |
65
+ | `bodyRegularM` | `text-base font-normal` |
66
+ | `bodyMediumM` | `text-base font-medium` |
67
+ | `bodySemiboldM` | `text-base font-semibold` |
68
+ | `headingLightXS` | `text-lg font-light` |
69
+ | `headingMediumXS` | `text-lg font-medium` |
70
+ | `headingBoldXS` | `text-lg font-bold` |
71
+ | `headingLightS` | `text-xl font-light` |
72
+ | `headingMediumS` | `text-xl font-medium` |
73
+ | `headingBoldS` | `text-xl font-bold` |
74
+ | `headingLightM` | `text-3xl font-light` |
75
+ | `headingMediumM` | `text-3xl font-medium` |
76
+ | `headingBoldM` | `text-3xl font-bold` |
77
+ | `headingLightL` | `text-[2.625rem] font-light` |
78
+ | `headingMediumL` | `text-[2.625rem] font-medium` |
79
+ | `headingBoldL` | `text-[2.625rem] font-bold` |
80
+ | `headingLightXL` | `text-5xl font-light` |
81
+ | `headingMediumXL` | `text-5xl font-medium` |
82
+ | `headingBoldXL` | `text-5xl font-bold` |
83
+
84
+ ### Typography color prop mapping
85
+
86
+ | Tractor `color=` | Tailwind class |
87
+ | --- | --- |
88
+ | `text.base` | `text-foreground` |
89
+ | `text.secondary` | `text-muted-foreground` |
90
+ | `text.disabled` | `text-muted-foreground/50` |
91
+ | `text.inverted` | `text-primary-foreground` |
92
+ | `primary.500` | `text-primary` |
93
+ | `critical.500` / `critical.800` | `text-destructive` |
94
+ | `informative.500` | `text-info` |
95
+ | `success.500` / `success.800` | `text-success` |
96
+ | `warning.500` / `warning.800` | `text-warning` |
97
+ | `secondary.light` | `text-muted-foreground` |
98
+
99
+ ### Typography examples
100
+
101
+ ```tsx
102
+ // Before
103
+ import { Typography } from '@aircall/tractor';
104
+ <Typography variant="heading">Page Title</Typography>
105
+ <Typography variant="body" mt={2} mb={4} color="text.secondary">Text</Typography>
106
+ <Typography ellipsis>Long text</Typography>
107
+ <Typography ellipsis={2}>Multi-line clamp</Typography>
108
+ <Typography wordBreak="break-word">Text</Typography>
109
+
110
+ // After — no import needed
111
+ <h1 className="text-lg font-bold">Page Title</h1>
112
+ <p className="text-sm font-medium mt-2 mb-4 text-muted-foreground">Text</p>
113
+ <p className="truncate">Long text</p>
114
+ <p className="line-clamp-2">Multi-line clamp</p>
115
+ <p className="break-words">Text</p>
116
+ ```
117
+
118
+ **Do NOT add `leading-*` classes.** Tailwind's `text-*` size classes already include appropriate line-heights. Adding `leading-6` alongside `text-base` is redundant and overrides the built-in value.
119
+
120
+ ### Semantic HTML elements
121
+
122
+ | Use case | Element |
123
+ | --- | --- |
124
+ | Page title | `<h1>` |
125
+ | Section title | `<h2>` |
126
+ | Subsection | `<h3>`, `<h4>` |
127
+ | Paragraph | `<p>` |
128
+ | Inline text | `<span>` |
129
+ | Link | `<a className="text-sm font-medium text-primary hover:underline">` |
130
+
131
+ ---
132
+
133
+ ## 2. Layout — Flex, Box, Grid, Spacer
134
+
135
+ Tractor's `Flex`, `Box`, `Grid`, and `Spacer` become plain `<div>` elements (or semantic HTML) with Tailwind classes. No import needed.
136
+
137
+ ### Flex
138
+
139
+ ```tsx
140
+ // Before
141
+ <Flex gap={2} alignItems="center" justifyContent="space-between" p={3}>
142
+ {children}
143
+ </Flex>
144
+
145
+ // After
146
+ <div className="flex gap-2 items-center justify-between p-3">
147
+ {children}
148
+ </div>
149
+ ```
150
+
151
+ | Tractor prop | Tailwind class |
152
+ | --- | --- |
153
+ | `flexDirection="column"` | `flex-col` |
154
+ | `alignItems="center"` | `items-center` |
155
+ | `justifyContent="space-between"` | `justify-between` |
156
+ | `flexWrap="wrap"` | `flex-wrap` |
157
+ | `flex={1}` | `flex-1` |
158
+ | `gap={N}` | `gap-{tw}` (see spacing table) |
159
+
160
+ ### Box
161
+
162
+ `Box` is a generic layout primitive — replace with `<div>` and move all system props to Tailwind classes.
163
+
164
+ ```tsx
165
+ // Before
166
+ <Box p={4} bg="primary.500" borderRadius="sm" />
167
+
168
+ // After
169
+ <div className="p-4 bg-primary rounded-md" />
170
+ ```
171
+
172
+ ### Grid
173
+
174
+ ```tsx
175
+ // Before
176
+ <Grid gridTemplateColumns="1fr 1fr" gap={3} />
177
+
178
+ // After
179
+ <div className="grid grid-cols-2 gap-3" />
180
+ ```
181
+
182
+ ### Spacer
183
+
184
+ `Spacer` adds whitespace between siblings. Replace with `gap-*` on the parent flex/grid container, or `space-x-*` / `space-y-*` as a fallback.
185
+
186
+ ```tsx
187
+ // Before
188
+ <Flex>
189
+ <A />
190
+ <Spacer space="s" /> {/* 16px */}
191
+ <B />
192
+ </Flex>
193
+
194
+ // After — prefer gap on the parent
195
+ <div className="flex gap-4">
196
+ <A />
197
+ <B />
198
+ </div>
199
+ ```
200
+
201
+ ---
202
+
203
+ ## 3. Spacing scale
204
+
205
+ **Convert by pixel equivalence** — Tractor and Tailwind scales differ. Never assume the key number maps to the same Tailwind key.
206
+
207
+ | Tractor key | px | Tailwind |
208
+ | --- | --- | --- |
209
+ | `0` | 0 | `0` |
210
+ | `0.5` | 2px | `0.5` |
211
+ | `1` | 4px | `1` |
212
+ | `2` | 8px | `2` |
213
+ | `3` | 12px | `3` |
214
+ | `4` | 16px | `4` |
215
+ | `5` | 24px | `6` |
216
+ | `6` | 32px | `8` |
217
+ | `7` | 40px | `10` |
218
+ | `8` | 64px | `16` |
219
+ | `9` | 80px | `20` |
220
+
221
+ ### Named spacing
222
+
223
+ | Tractor name | px | Tailwind |
224
+ | --- | --- | --- |
225
+ | `xxxs` | 4px | `1` |
226
+ | `xxs` | 8px | `2` |
227
+ | `xs` | 12px | `3` |
228
+ | `s` | 16px | `4` |
229
+ | `m` | 24px | `6` |
230
+ | `l` | 32px | `8` |
231
+ | `xl` | 40px | `10` |
232
+ | `xxl` | 64px | `16` |
233
+ | `xxxl` | 80px | `20` |
234
+
235
+ ### Spacing prop mapping
236
+
237
+ | Tractor prop | Tailwind prefix |
238
+ | --- | --- |
239
+ | `m` | `m-` |
240
+ | `mx` | `mx-` |
241
+ | `my` | `my-` |
242
+ | `mt` | `mt-` |
243
+ | `mr` | `mr-` |
244
+ | `mb` | `mb-` |
245
+ | `ml` | `ml-` |
246
+ | `p` | `p-` |
247
+ | `px` | `px-` |
248
+ | `py` | `py-` |
249
+ | `pt` | `pt-` |
250
+ | `pr` | `pr-` |
251
+ | `pb` | `pb-` |
252
+ | `pl` | `pl-` |
253
+
254
+ Pixel literal values become Tailwind arbitrary values: `mt="16px"` → `mt-[16px]`.
255
+
256
+ Responsive objects become breakpoint prefixes: `m={{ _: 2, md: 4 }}` → `m-2 md:m-4`.
257
+
258
+ ---
259
+
260
+ ## 4. Design tokens — colors, radius, helpers
261
+
262
+ ### Replacing `getColor` / `getSpace` / `getRadii` / `defaultTheme.*`
263
+
264
+ These runtime helpers do not exist in DS. They lived inside `styled()` blocks, which are removed entirely. Map each consumed value to its Tailwind equivalent:
265
+
266
+ ```tsx
267
+ // Before
268
+ const Styled = styled.div`
269
+ color: ${getColor('primary.500')};
270
+ padding: ${getSpace('m')};
271
+ border-radius: ${getRadii('sm')};
272
+ `;
273
+
274
+ // After
275
+ <div className="text-primary p-6 rounded-md" />
276
+ ```
277
+
278
+ For values that genuinely must be dynamic (computed at runtime), use the CSS variable directly:
279
+
280
+ ```tsx
281
+ style={{ color: 'var(--primary)' }}
282
+ ```
283
+
284
+ ### Semantic color tokens (text)
285
+
286
+ | Tractor token | Tailwind class |
287
+ | --- | --- |
288
+ | `text-base` | `text-foreground` |
289
+ | `text-secondary` | `text-muted-foreground` |
290
+ | `text-placeholder` | `text-muted-foreground` |
291
+ | `text-disabled` | `text-muted-foreground` |
292
+ | `text-highlight` | `text-accent-foreground` |
293
+ | `text-critical` | `text-destructive` |
294
+ | `text-success` | `text-success` |
295
+ | `text-warning` | `text-warning` |
296
+ | `text-info` | `text-info` |
297
+
298
+ ### Semantic color tokens (surfaces)
299
+
300
+ | Tractor token | Tailwind class |
301
+ | --- | --- |
302
+ | `surface-background` | `bg-page-background` |
303
+ | `surface-default` | `bg-background` |
304
+ | `surface-overlay` | `bg-overlay` |
305
+ | `surface-info` | `bg-info-background` |
306
+ | `surface-success` | `bg-success-background` |
307
+ | `surface-warning` | `bg-warning-background` |
308
+ | `surface-critical` | `bg-destructive-background` |
309
+ | `surface-disabled` | `bg-muted` |
310
+
311
+ ### Icon color tokens
312
+
313
+ Icon color tokens share the text semantic layer — apply `text-*` to the icon's container or rely on `currentColor` fill.
314
+
315
+ | Tractor token | Tailwind class |
316
+ | --- | --- |
317
+ | `icon-base` | `text-foreground` |
318
+ | `icon-default` | `text-muted-foreground` |
319
+ | `icon-highlight` | `text-accent-foreground` |
320
+ | `icon-primary` | `text-primary` |
321
+ | `icon-disabled` | `text-muted-foreground` |
322
+ | `icon-critical` | `text-destructive` |
323
+ | `icon-success` | `text-success` |
324
+ | `icon-warning` | `text-warning` |
325
+ | `icon-info` | `text-info` |
326
+
327
+ ### Border radius
328
+
329
+ | Tractor token | Tailwind class | Notes |
330
+ | --- | --- | --- |
331
+ | `none` | `rounded-none` | Exact |
332
+ | `xs` | `rounded-sm` | Closest (2px off) |
333
+ | `sm` | `rounded-md` | Exact |
334
+ | `md` | `rounded-xl` | Closest (2px off) |
335
+ | `default` | `rounded-2xl` | Exact |
336
+ | `lg` | `rounded-3xl` | Exact |
337
+ | `full` | `rounded-full` | Exact |
338
+
339
+ `xs` and `md` have no exact DS equivalent — flag for visual QA.
340
+
341
+ ### Palette tokens and raw colors — designer sign-off required
342
+
343
+ If you encounter a **palette token** (e.g. `primary.500`, `critical.700`, `neutral-200`) or a **raw hex/rgb color** in the codebase, **do not map it yourself**. Flag it for designer review. The tables above cover only Tractor _semantic_ tokens (no trailing number), which can be migrated directly.
344
+
345
+ ---
346
+
347
+ ## 5. Dark mode
348
+
349
+ DS supports `light`, `dark`, and `system` themes via `ThemeProvider`. Always use **semantic tokens** so colors swap automatically:
350
+
351
+ ```tsx
352
+ // ✅ Adapts automatically
353
+ <div className="bg-card text-card-foreground border border-border" />
354
+
355
+ // ❌ Locked to light mode
356
+ <div className="bg-white text-black border border-gray-200" />
357
+ ```
358
+
359
+ For dark-mode-only overrides use the `dark:` variant: `dark:border-input`.
360
+
361
+ ---
362
+
363
+ ## 6. Common mistakes
364
+
365
+ ### Mistake 1 — Copying the Tractor spacing key as the Tailwind key
366
+
367
+ ```tsx
368
+ // ❌ Wrong — Tractor 5 = 24px, but Tailwind 5 = 20px
369
+ <Box gap={5} /> → <div className="gap-5" />
370
+
371
+ // ✅ Correct — convert by px: 24px = Tailwind 6
372
+ <div className="gap-6" />
373
+ ```
374
+
375
+ Tractor's scale diverges from Tailwind's at key `5` (24px vs 20px). Always resolve the pixel value first, then find the Tailwind equivalent.
376
+
377
+ **Source:** `packages/ds/src/styles/globals.css`
378
+
379
+ ### Mistake 2 — Translating token names directly to matching class names
380
+
381
+ ```tsx
382
+ // ❌ Wrong — Tractor `getRadii('sm')` = 8px, Tailwind `rounded-sm` = 6px
383
+ getRadii('sm') → rounded-sm
384
+
385
+ // ✅ Correct — resolve to px first: 8px = rounded-md
386
+ getRadii('sm') → rounded-md
387
+ ```
388
+
389
+ Token names that look identical (`sm`, `xs`, `lg`) resolve to different pixel values in Tractor vs DS. Read the resolved pixel value, then pick the Tailwind class that matches.
390
+
391
+ **Source:** `packages/ds/src/styles/globals.css`
392
+
393
+ ### Mistake 3 — Adding `leading-*` when migrating Typography
394
+
395
+ ```tsx
396
+ // ❌ Wrong — Tailwind text-base already includes the correct line-height
397
+ <h2 className="text-base leading-6 font-semibold">Title</h2>
398
+
399
+ // ✅ Correct — text size class sets line-height; font weight only
400
+ <h2 className="text-base font-semibold">Title</h2>
401
+ ```
402
+
403
+ Tailwind's `text-*` utility classes bundle a default `line-height`. Adding `leading-*` overrides the built-in value and is almost always wrong during migration.
404
+
405
+ **Source:** `packages/ds/src/styles/globals.css`
406
+
407
+ ### Mistake 4 — Mapping palette tokens without designer sign-off
408
+
409
+ ```tsx
410
+ // ❌ Wrong — palette tokens cannot be mechanically mapped
411
+ getColor('primary.500') → bg-primary // may be wrong design intent
412
+
413
+ // ✅ Correct for semantic tokens only
414
+ getColor('text-base') → text-foreground
415
+
416
+ // For palette tokens (primary.500, critical.700, neutral-200), flag for designer review.
417
+ ```
418
+
419
+ Palette tokens (those with a trailing number) carry design intent that cannot be determined mechanically. Only semantic tokens (no trailing number) have a direct mapping.
420
+
421
+ **Source:** `packages/ds/src/styles/globals.css`
@@ -0,0 +1,250 @@
1
+ ---
2
+ name: aircall-ds/migrate-tractor/tabs
3
+ description: >
4
+ Migrate Tractor Tab, TabList, Tab.Item, TabPanel, and TabToggle (segmented
5
+ control) to the @aircall/ds Tabs compound (Tabs, TabsList, TabsTrigger,
6
+ TabsContent). Load when a file imports Tab, TabList, TabPanel, or TabToggle
7
+ from @aircall/tractor.
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/tabs.md"
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 tabs-specific steps below.
19
+
20
+ ## 1. Component mapping
21
+
22
+ | Tractor | @aircall/ds |
23
+ | --- | --- |
24
+ | `TabList` | `TabsList` |
25
+ | `Tab.Item` (individual tab trigger) | `TabsTrigger` |
26
+ | `TabPanel` | `TabsContent` |
27
+ | `Tab` (state owner) | `Tabs` |
28
+ | `TabToggle` (segmented control) | `ToggleGroup` — **not** Tabs (separate migration) |
29
+
30
+ Active state in Tractor was tracked by a numeric index. DS tracks it with a string `value` that links each `TabsTrigger` to its matching `TabsContent`.
31
+
32
+ ## 2. Verified DS exports (`packages/ds/src/index.ts`)
33
+
34
+ ```
35
+ Tabs, TabsList, TabsTrigger, TabsContent
36
+ ToggleGroup, ToggleGroupItem
37
+ ```
38
+
39
+ ## 3. Imports
40
+
41
+ ```tsx
42
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from '@aircall/ds';
43
+ ```
44
+
45
+ For the segmented-control pattern (`TabToggle`):
46
+
47
+ ```tsx
48
+ import { ToggleGroup, ToggleGroupItem } from '@aircall/ds';
49
+ ```
50
+
51
+ ## 4. Before / After
52
+
53
+ ### 4a. Uncontrolled tabs (default open)
54
+
55
+ **Before (Tractor):**
56
+ ```tsx
57
+ import { Tab, TabList, TabPanel } from '@aircall/tractor';
58
+
59
+ <Tab defaultIndex={0}>
60
+ <TabList>
61
+ <Tab.Item>Overview</Tab.Item>
62
+ <Tab.Item>Activity</Tab.Item>
63
+ </TabList>
64
+ <TabPanel>Overview panel.</TabPanel>
65
+ <TabPanel>Activity panel.</TabPanel>
66
+ </Tab>
67
+ ```
68
+
69
+ **After (DS):**
70
+ ```tsx
71
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from '@aircall/ds';
72
+
73
+ <Tabs defaultValue="overview">
74
+ <TabsList>
75
+ <TabsTrigger value="overview">Overview</TabsTrigger>
76
+ <TabsTrigger value="activity">Activity</TabsTrigger>
77
+ </TabsList>
78
+ <TabsContent value="overview">Overview panel.</TabsContent>
79
+ <TabsContent value="activity">Activity panel.</TabsContent>
80
+ </Tabs>
81
+ ```
82
+
83
+ Key changes:
84
+ - `defaultIndex={0}` (numeric) → `defaultValue="overview"` (string)
85
+ - `Tab.Item` → `TabsTrigger` with a `value` prop
86
+ - `TabPanel` → `TabsContent` with a matching `value` prop
87
+ - There is no `size` prop and no `TabsList` `variant` — drop any Tractor equivalents
88
+
89
+ ### 4b. Controlled tabs
90
+
91
+ **Before (Tractor):**
92
+ ```tsx
93
+ import { Tab, TabList, TabPanel } from '@aircall/tractor';
94
+
95
+ <Tab selectedIndex={activeTab} onChange={setActiveTab}>
96
+ <TabList>
97
+ <Tab.Item>Overview</Tab.Item>
98
+ <Tab.Item>Activity</Tab.Item>
99
+ </TabList>
100
+ <TabPanel>Overview panel.</TabPanel>
101
+ <TabPanel>Activity panel.</TabPanel>
102
+ </Tab>
103
+ ```
104
+
105
+ **After (DS):**
106
+ ```tsx
107
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from '@aircall/ds';
108
+
109
+ <Tabs value={activeTab} onValueChange={setActiveTab}>
110
+ <TabsList>
111
+ <TabsTrigger value="overview">Overview</TabsTrigger>
112
+ <TabsTrigger value="activity">Activity</TabsTrigger>
113
+ </TabsList>
114
+ <TabsContent value="overview">Overview panel.</TabsContent>
115
+ <TabsContent value="activity">Activity panel.</TabsContent>
116
+ </Tabs>
117
+ ```
118
+
119
+ Key changes:
120
+ - `selectedIndex` (number) → `value` (string)
121
+ - `onChange` → `onValueChange`
122
+ - State must be converted from a numeric index to a string key
123
+
124
+ ### 4c. Vertical orientation
125
+
126
+ **Before (Tractor):**
127
+ ```tsx
128
+ import { Tab, TabList, TabPanel } from '@aircall/tractor';
129
+
130
+ <Tab orientation="vertical" defaultIndex={0}>
131
+ <TabList>
132
+ <Tab.Item>Settings</Tab.Item>
133
+ <Tab.Item>Billing</Tab.Item>
134
+ </TabList>
135
+ <TabPanel>Settings panel.</TabPanel>
136
+ <TabPanel>Billing panel.</TabPanel>
137
+ </Tab>
138
+ ```
139
+
140
+ **After (DS):**
141
+ ```tsx
142
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from '@aircall/ds';
143
+
144
+ <Tabs orientation="vertical" defaultValue="settings">
145
+ <TabsList>
146
+ <TabsTrigger value="settings">Settings</TabsTrigger>
147
+ <TabsTrigger value="billing">Billing</TabsTrigger>
148
+ </TabsList>
149
+ <TabsContent value="settings">Settings panel.</TabsContent>
150
+ <TabsContent value="billing">Billing panel.</TabsContent>
151
+ </Tabs>
152
+ ```
153
+
154
+ When conditionally styling by orientation in `className`, use `data-[orientation=horizontal]:` / `data-[orientation=vertical]:` — DS emits a `data-orientation` attribute, so a bare `data-horizontal:` selector won't match.
155
+
156
+ ---
157
+
158
+ ## 5. Common mistakes
159
+
160
+ ### Mistake 1 — Using numeric index state instead of string value
161
+
162
+ ```tsx
163
+ // ❌ Wrong — value must be a string; passing a number silently prevents matching
164
+ const [tab, setTab] = useState(0);
165
+ <Tabs value={tab} onValueChange={setTab}>
166
+ <TabsTrigger value={0}>Overview</TabsTrigger>
167
+ <TabsContent value={0}>Overview panel.</TabsContent>
168
+ </Tabs>
169
+
170
+ // ✅ Correct — derive a string key; value must match between TabsTrigger and TabsContent
171
+ const [tab, setTab] = useState('overview');
172
+ <Tabs value={tab} onValueChange={setTab}>
173
+ <TabsTrigger value="overview">Overview</TabsTrigger>
174
+ <TabsContent value="overview">Overview panel.</TabsContent>
175
+ </Tabs>
176
+ ```
177
+
178
+ DS matches the active tab by strict string equality between `Tabs value`/`defaultValue`, `TabsTrigger value`, and `TabsContent value`. A numeric `0` never equals the string `"0"` — the active panel will not render.
179
+
180
+ Source: `packages/ds/src/components/tabs.tsx`
181
+
182
+ ### Mistake 2 — Matching tabs by position instead of value
183
+
184
+ ```tsx
185
+ // ❌ Wrong — panel order is irrelevant without value matching
186
+ <Tabs defaultValue="overview">
187
+ <TabsList>
188
+ <TabsTrigger value="overview">Overview</TabsTrigger>
189
+ <TabsTrigger value="activity">Activity</TabsTrigger>
190
+ </TabsList>
191
+ <TabsContent value="activity">Overview panel.</TabsContent>
192
+ <TabsContent value="overview">Activity panel.</TabsContent>
193
+ </Tabs>
194
+
195
+ // ✅ Correct — value on TabsTrigger must match value on TabsContent
196
+ <Tabs defaultValue="overview">
197
+ <TabsList>
198
+ <TabsTrigger value="overview">Overview</TabsTrigger>
199
+ <TabsTrigger value="activity">Activity</TabsTrigger>
200
+ </TabsList>
201
+ <TabsContent value="overview">Overview panel.</TabsContent>
202
+ <TabsContent value="activity">Activity panel.</TabsContent>
203
+ </Tabs>
204
+ ```
205
+
206
+ Tractor matched panels by their position index, so DOM order determined which panel showed. DS matches by string value — the order of `TabsContent` elements in the DOM is irrelevant; mismatched `value` strings silently show the wrong panel.
207
+
208
+ Source: `packages/ds/src/components/tabs.tsx`
209
+
210
+ ### Mistake 3 — Applying a `size` or `variant` prop
211
+
212
+ ```tsx
213
+ // ❌ Wrong — DS Tabs has no size or variant prop; they are silently ignored
214
+ <Tabs size="large" defaultValue="overview">…</Tabs>
215
+ <TabsList variant="pills">…</TabsList>
216
+
217
+ // ✅ Correct — DS renders exactly one style; drop size and variant entirely
218
+ <Tabs defaultValue="overview">…</Tabs>
219
+ <TabsList>…</TabsList>
220
+ ```
221
+
222
+ DS Tabs has exactly one visual style. There is no `size` prop on `Tabs` and no `variant` prop on `TabsList`. Tractor's `size`/`variant` props do not map to anything — remove them without replacement.
223
+
224
+ Source: `packages/ds/src/components/tabs.tsx`
225
+
226
+ ### Mistake 4 — Migrating `TabToggle` to `Tabs` instead of `ToggleGroup`
227
+
228
+ ```tsx
229
+ // ❌ Wrong — TabToggle is a segmented control, not a tab panel pattern
230
+ import { Tabs, TabsList, TabsTrigger } from '@aircall/ds';
231
+
232
+ <Tabs defaultValue="month">
233
+ <TabsList>
234
+ <TabsTrigger value="week">Week</TabsTrigger>
235
+ <TabsTrigger value="month">Month</TabsTrigger>
236
+ </TabsList>
237
+ </Tabs>
238
+
239
+ // ✅ Correct — use ToggleGroup for segmented controls with no content panels
240
+ import { ToggleGroup, ToggleGroupItem } from '@aircall/ds';
241
+
242
+ <ToggleGroup type="single" defaultValue="month">
243
+ <ToggleGroupItem value="week">Week</ToggleGroupItem>
244
+ <ToggleGroupItem value="month">Month</ToggleGroupItem>
245
+ </ToggleGroup>
246
+ ```
247
+
248
+ `TabToggle` in Tractor is a segmented button control — it switches a value with no associated content panels. `Tabs` always expects paired `TabsContent` panels; using it for a segmented control couples content rendering to the selector unnecessarily. `ToggleGroup` is the direct DS equivalent.
249
+
250
+ Source: `packages/ds/src/components/tabs.tsx`