@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.
- package/README.md +31 -0
- package/dist/globals.css +1 -1
- package/dist/index.d.ts +94 -33
- package/dist/index.js +292 -42
- package/package.json +16 -3
- 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,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`
|