@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,322 @@
1
+ ---
2
+ name: aircall-ds/migrate-tractor/toast
3
+ description: >
4
+ Migrate Tractor useToast, toasts (pubsub), and ToastManager to the @aircall/ds
5
+ toast imperative API and Toaster component. Load when a file imports useToast,
6
+ toasts, ToastManager, or Toast 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:packages/ds/src/components/sonner.tsx"
15
+ ---
16
+
17
+ This skill builds on aircall-ds/migrate-tractor.
18
+
19
+ ## 1. Component / API mapping
20
+
21
+ Tractor had a context-based system (`ToastManager` provider + `useToast` hook or `toasts` pubsub object). DS replaces it with [sonner](https://sonner.emilkowal.ski/), exposed as an imperative `toast` function and a `<Toaster>` mount point.
22
+
23
+ | Tractor part | DS replacement | Notes |
24
+ | --- | --- | --- |
25
+ | `<ToastManager>` in app root | `<Toaster>` in app root | Place once near the root; no children needed |
26
+ | `useToast()` → `toast.showToast({ … })` | `toast(message, options?)` | Direct import — no hook, no context |
27
+ | `toasts.showToast({ … })` (pubsub) | `toast(message, options?)` | Same replacement; the pubsub layer is gone |
28
+ | `toast.showToast({ variant: 'success' })` | `toast.success(message)` | Convenience method per variant |
29
+ | `toast.showToast({ variant: 'info' })` | `toast.info(message)` | |
30
+ | `toast.showToast({ variant: 'critical' })` | `toast.error(message)` | Tractor `critical` → DS `error` |
31
+ | `toast.showToast({ variant: 'warning' })` | `toast.warning(message)` | |
32
+ | `toast.showToast({ dismissIn: N })` | `toast(msg, { duration: N })` | Same unit (ms) |
33
+ | `toast.showToast({ message: <ReactNode> })` | `toast(message)` | Pass string or JSX directly |
34
+ | cleanup fn returned by `showToast` | `toast.dismiss(id)` | Call with the id returned by `toast()` |
35
+
36
+ ## 2. Verified DS exports (`packages/ds/src/index.ts`)
37
+
38
+ ```
39
+ Toaster, toast
40
+ ```
41
+
42
+ ## 3. Imports
43
+
44
+ ```tsx
45
+ import { Toaster, toast } from '@aircall/ds';
46
+ ```
47
+
48
+ `toast` is an imperative function (not a React hook) — it can be called from anywhere: event handlers, async callbacks, utility functions, without `useToast` or a context consumer.
49
+
50
+ ## 4. Mount the Toaster once
51
+
52
+ Place `<Toaster>` once in the component tree, typically at app root. It renders the toast portal and applies DS theming (rounded corners, semantic colour tokens, design-system icons).
53
+
54
+ ```tsx
55
+ import { Toaster } from '@aircall/ds';
56
+
57
+ function App() {
58
+ return (
59
+ <>
60
+ <Toaster />
61
+ {/* rest of the app */}
62
+ </>
63
+ );
64
+ }
65
+ ```
66
+
67
+ `Toaster` accepts all [sonner `ToasterProps`](https://sonner.emilkowal.ski/getting-started) — e.g. `position`, `richColors`, `closeButton`, `duration`.
68
+
69
+ ## 5. Trigger toasts imperatively
70
+
71
+ ```tsx
72
+ import { toast } from '@aircall/ds';
73
+
74
+ // Generic (default styling)
75
+ toast('Settings saved');
76
+
77
+ // Variant convenience methods
78
+ toast.success('Call recorded successfully');
79
+ toast.info('Sync in progress');
80
+ toast.error('Could not reach the server'); // Tractor: variant="critical"
81
+ toast.warning('Your plan is almost at capacity');
82
+
83
+ // With duration (ms)
84
+ toast.success('Done', { duration: 3000 });
85
+
86
+ // With a description
87
+ toast.success('Recording saved', {
88
+ description: 'The file has been uploaded to your workspace.'
89
+ });
90
+
91
+ // Capture id to dismiss later
92
+ const id = toast.loading('Uploading…');
93
+ // … async work …
94
+ toast.dismiss(id);
95
+ toast.success('Upload complete');
96
+ ```
97
+
98
+ ## 6. Before / After examples
99
+
100
+ ### 6a. Provider setup
101
+
102
+ **Before (Tractor):**
103
+ ```tsx
104
+ import { ToastManager } from '@aircall/tractor';
105
+
106
+ function App({ children }: { children: React.ReactNode }) {
107
+ return (
108
+ <ToastManager placement="bottom">
109
+ {children}
110
+ </ToastManager>
111
+ );
112
+ }
113
+ ```
114
+
115
+ **After (DS):**
116
+ ```tsx
117
+ import { Toaster } from '@aircall/ds';
118
+
119
+ function App({ children }: { children: React.ReactNode }) {
120
+ return (
121
+ <>
122
+ <Toaster position="bottom-center" />
123
+ {children}
124
+ </>
125
+ );
126
+ }
127
+ ```
128
+
129
+ ### 6b. Hook-based trigger → imperative call
130
+
131
+ **Before (Tractor):**
132
+ ```tsx
133
+ import { useToast } from '@aircall/tractor';
134
+
135
+ function SaveButton() {
136
+ const toast = useToast();
137
+
138
+ const handleSave = async () => {
139
+ await save();
140
+ toast.showToast({
141
+ variant: 'success',
142
+ icon: true,
143
+ dismissIn: 3000,
144
+ message: 'Settings saved'
145
+ });
146
+ };
147
+
148
+ return <button onClick={handleSave}>Save</button>;
149
+ }
150
+ ```
151
+
152
+ **After (DS):**
153
+ ```tsx
154
+ import { toast } from '@aircall/ds';
155
+
156
+ function SaveButton() {
157
+ const handleSave = async () => {
158
+ await save();
159
+ toast.success('Settings saved', { duration: 3000 });
160
+ };
161
+
162
+ return <button onClick={handleSave}>Save</button>;
163
+ }
164
+ ```
165
+
166
+ ### 6c. Pubsub trigger → imperative call
167
+
168
+ **Before (Tractor):**
169
+ ```tsx
170
+ import { toasts } from '@aircall/tractor';
171
+
172
+ async function handleUpload(file: File) {
173
+ try {
174
+ await upload(file);
175
+ toasts.showToast({ variant: 'success', message: 'File uploaded', dismissIn: 4000 });
176
+ } catch {
177
+ toasts.showToast({ variant: 'critical', message: 'Upload failed' });
178
+ }
179
+ }
180
+ ```
181
+
182
+ **After (DS):**
183
+ ```tsx
184
+ import { toast } from '@aircall/ds';
185
+
186
+ async function handleUpload(file: File) {
187
+ try {
188
+ await upload(file);
189
+ toast.success('File uploaded', { duration: 4000 });
190
+ } catch {
191
+ toast.error('Upload failed');
192
+ }
193
+ }
194
+ ```
195
+
196
+ ### 6d. Loading state with manual dismiss
197
+
198
+ **Before (Tractor):**
199
+ ```tsx
200
+ import { useToast } from '@aircall/tractor';
201
+
202
+ function SyncButton() {
203
+ const { showToast } = useToast();
204
+
205
+ const handleSync = async () => {
206
+ const dismiss = showToast({ variant: 'info', message: 'Syncing…', dismissIn: 0 });
207
+ await syncData();
208
+ dismiss();
209
+ showToast({ variant: 'success', message: 'Sync complete', dismissIn: 3000 });
210
+ };
211
+
212
+ return <button onClick={handleSync}>Sync</button>;
213
+ }
214
+ ```
215
+
216
+ **After (DS):**
217
+ ```tsx
218
+ import { toast } from '@aircall/ds';
219
+
220
+ function SyncButton() {
221
+ const handleSync = async () => {
222
+ const id = toast.loading('Syncing…');
223
+ await syncData();
224
+ toast.dismiss(id);
225
+ toast.success('Sync complete', { duration: 3000 });
226
+ };
227
+
228
+ return <button onClick={handleSync}>Sync</button>;
229
+ }
230
+ ```
231
+
232
+ ## 7. Common Mistakes
233
+
234
+ ### Mistake 1 — Keeping `<ToastManager>` with children wrapping the app
235
+
236
+ **Wrong:**
237
+ ```tsx
238
+ import { ToastManager } from '@aircall/tractor';
239
+
240
+ <ToastManager placement="bottom">
241
+ <App />
242
+ </ToastManager>
243
+ ```
244
+
245
+ **Correct:**
246
+ ```tsx
247
+ import { Toaster } from '@aircall/ds';
248
+
249
+ <>
250
+ <Toaster position="bottom-center" />
251
+ <App />
252
+ </>
253
+ ```
254
+
255
+ `<Toaster>` is a portal mount point, not a layout wrapper — it renders the toast container into a portal and takes no children. Wrapping the app in it gains nothing; sonner manages the DOM insertion independently.
256
+
257
+ Source: `packages/ds/src/components/sonner.tsx`
258
+
259
+ ---
260
+
261
+ ### Mistake 2 — Mapping Tractor `critical` variant to `toast.success` or plain `toast`
262
+
263
+ **Wrong:**
264
+ ```tsx
265
+ // Tractor variant="critical" silently maps to wrong DS call
266
+ toast('Payment method declined');
267
+ toast.success('Payment method declined');
268
+ ```
269
+
270
+ **Correct:**
271
+ ```tsx
272
+ toast.error('Payment method declined');
273
+ ```
274
+
275
+ Tractor named its destructive variant `"critical"`. DS (via sonner) uses `error`. The `toast()` default call produces a plain (default-styled) toast with no semantic colour — it does not inherit critical/error styling. Only `toast.error()` applies the destructive background and icon token.
276
+
277
+ Source: `packages/ds/src/components/sonner.tsx` — `classNames.error = '!bg-destructive-background !border-destructive/35 …'`.
278
+
279
+ ---
280
+
281
+ ### Mistake 3 — Calling `useToast()` expecting a hook
282
+
283
+ **Wrong:**
284
+ ```tsx
285
+ import { useToast } from '@aircall/ds'; // does not exist
286
+
287
+ function MyComponent() {
288
+ const toast = useToast(); // build error
289
+ toast.showToast({ variant: 'info', message: 'Hello' });
290
+ }
291
+ ```
292
+
293
+ **Correct:**
294
+ ```tsx
295
+ import { toast } from '@aircall/ds';
296
+
297
+ function MyComponent() {
298
+ toast.info('Hello');
299
+ }
300
+ ```
301
+
302
+ `@aircall/ds` exports no `useToast` hook — `toast` is a plain function, not a hook. Importing `useToast` from `@aircall/ds` causes a build-time error. The `toast` function is call-anywhere: event handlers, async functions, plain modules outside React.
303
+
304
+ Source: `packages/ds/src/components/sonner.tsx` — `export { Toaster, toast }`, re-exported from `sonner`.
305
+
306
+ ---
307
+
308
+ ### Mistake 4 — Using `dismissIn` option key instead of `duration`
309
+
310
+ **Wrong:**
311
+ ```tsx
312
+ toast.success('Done', { dismissIn: 3000 });
313
+ ```
314
+
315
+ **Correct:**
316
+ ```tsx
317
+ toast.success('Done', { duration: 3000 });
318
+ ```
319
+
320
+ Tractor's `showToast` accepted a `dismissIn` property (milliseconds). Sonner's option key is `duration`. Passing `dismissIn` is silently ignored — the toast will use the default duration (~4 s) regardless of the value passed.
321
+
322
+ Source: `packages/ds/src/components/sonner.tsx` — wraps sonner `Toaster`; see [sonner API](https://sonner.emilkowal.ski/toast) for `duration`.
@@ -0,0 +1,204 @@
1
+ ---
2
+ name: aircall-ds/migrate-tractor/tooltip
3
+ description: >
4
+ Migrate Tractor Tooltip to the @aircall/ds Tooltip compound (Tooltip,
5
+ TooltipTrigger, TooltipContent, TooltipProvider). Load when a file imports
6
+ Tooltip 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/tooltip.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 tooltip-specific steps below.
18
+
19
+ ## 1. Component mapping
20
+
21
+ | Tractor | @aircall/ds |
22
+ | --- | --- |
23
+ | `Tooltip` (self-contained, `content` prop) | `Tooltip` (Root state owner) |
24
+ | — | `TooltipTrigger` (the element that activates the tooltip) |
25
+ | `content` prop (tooltip text) | `TooltipContent` (children, compound part) |
26
+ | — | `TooltipProvider` (once at app root; sets delay for all tooltips) |
27
+ | `placement` prop | `side` + `align` props on `TooltipContent` |
28
+
29
+ ## 2. Imports
30
+
31
+ ```tsx
32
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, Button } from '@aircall/ds';
33
+ ```
34
+
35
+ For icon-only triggers, import icons from `@aircall/react-icons`:
36
+
37
+ ```tsx
38
+ import { Info } from '@aircall/react-icons';
39
+ ```
40
+
41
+ ## 3. Provider (once, at the app root)
42
+
43
+ Wrap the app with `TooltipProvider` once. All `Tooltip` instances inside share the delay.
44
+
45
+ ```tsx
46
+ // Before — no equivalent; Tractor's Tooltip managed its own timing internally
47
+
48
+ // After — add once near the root (e.g. in App.tsx or a layout component)
49
+ import { TooltipProvider } from '@aircall/ds';
50
+
51
+ <TooltipProvider delay={0}>
52
+ <App />
53
+ </TooltipProvider>
54
+ ```
55
+
56
+ The prop is `delay` (Base UI), not `delayDuration` (Radix). Default is `0`.
57
+
58
+ ## 4. Before / After
59
+
60
+ ### Basic text trigger
61
+
62
+ ```tsx
63
+ // Before
64
+ import { Tooltip, Button } from '@aircall/tractor';
65
+
66
+ <Tooltip content="Helpful hint">
67
+ <Button variant="ghost">Help</Button>
68
+ </Tooltip>
69
+
70
+ // After
71
+ import { Tooltip, TooltipTrigger, TooltipContent, Button } from '@aircall/ds';
72
+
73
+ <Tooltip>
74
+ <TooltipTrigger render={<Button variant="ghost" />}>Help</TooltipTrigger>
75
+ <TooltipContent>Helpful hint</TooltipContent>
76
+ </Tooltip>
77
+ ```
78
+
79
+ ### Icon-only trigger (most common pattern)
80
+
81
+ ```tsx
82
+ // Before
83
+ import { Tooltip, IconButton } from '@aircall/tractor';
84
+ import { InfoIcon } from '@aircall/react-icons';
85
+
86
+ <Tooltip content="More information" placement="bottom">
87
+ <IconButton label="More information" icon={<InfoIcon />} />
88
+ </Tooltip>
89
+
90
+ // After
91
+ import { Tooltip, TooltipTrigger, TooltipContent, Button } from '@aircall/ds';
92
+ import { Info } from '@aircall/react-icons';
93
+
94
+ <Tooltip>
95
+ <TooltipTrigger render={<Button variant="ghost" size="icon" aria-label="More information" />}>
96
+ <Info className="size-4" />
97
+ </TooltipTrigger>
98
+ <TooltipContent side="bottom">More information</TooltipContent>
99
+ </Tooltip>
100
+ ```
101
+
102
+ Key changes:
103
+ - `content` prop is gone — tooltip text moves to children of `TooltipContent`
104
+ - The trigger element is wrapped in `TooltipTrigger` with the DS component passed via `render`
105
+ - `placement` → `side` on `TooltipContent` (default `side="top"`, `sideOffset={4}`)
106
+ - Icon-only triggers must have an explicit `aria-label` — the tooltip text is not exposed as the control's accessible name
107
+ - The arrow and `accent-foreground` styling are built in — do not re-skin them
108
+
109
+ ### Controlled placement
110
+
111
+ ```tsx
112
+ // Before
113
+ import { Tooltip } from '@aircall/tractor';
114
+
115
+ <Tooltip content="Save changes" placement="right">
116
+ <button>Save</button>
117
+ </Tooltip>
118
+
119
+ // After
120
+ import { Tooltip, TooltipTrigger, TooltipContent } from '@aircall/ds';
121
+
122
+ <Tooltip>
123
+ <TooltipTrigger>Save</TooltipTrigger>
124
+ <TooltipContent side="right">Save changes</TooltipContent>
125
+ </Tooltip>
126
+ ```
127
+
128
+ `align` defaults to `"center"`. Use `align="start"` or `align="end"` to fine-tune alignment along the cross axis.
129
+
130
+ ---
131
+
132
+ ## 5. Common mistakes
133
+
134
+ ### Mistake 1 — Passing tooltip text via a `content` prop instead of `TooltipContent` children
135
+
136
+ ```tsx
137
+ // ❌ Wrong — content prop is a Tractor API; DS ignores it silently
138
+ <Tooltip content="Helpful hint">
139
+ <TooltipTrigger>Help</TooltipTrigger>
140
+ </Tooltip>
141
+
142
+ // ✅ Correct — tooltip text is children of TooltipContent
143
+ <Tooltip>
144
+ <TooltipTrigger>Help</TooltipTrigger>
145
+ <TooltipContent>Helpful hint</TooltipContent>
146
+ </Tooltip>
147
+ ```
148
+
149
+ DS `Tooltip` is a Base UI compound — the tooltip label lives in `TooltipContent`, not on the root. The `content` prop is silently discarded and no tooltip is shown.
150
+
151
+ Source: `packages/ds/src/components/tooltip.tsx`
152
+
153
+ ### Mistake 2 — Using `placement` instead of `side` on `TooltipContent`
154
+
155
+ ```tsx
156
+ // ❌ Wrong — placement is the Tractor/Radix prop name; DS ignores it
157
+ <TooltipContent placement="bottom">Helpful hint</TooltipContent>
158
+
159
+ // ✅ Correct — use side (and optionally align) on TooltipContent
160
+ <TooltipContent side="bottom" align="start">Helpful hint</TooltipContent>
161
+ ```
162
+
163
+ `TooltipContent` exposes `side`, `sideOffset`, `align`, and `alignOffset` (picked from `TooltipPrimitive.Positioner.Props`). `placement` is not forwarded and the tooltip will render at the default `side="top"` position.
164
+
165
+ Source: `packages/ds/src/components/tooltip.tsx`
166
+
167
+ ### Mistake 3 — Omitting `TooltipProvider` or using `delayDuration` instead of `delay`
168
+
169
+ ```tsx
170
+ // ❌ Wrong — no provider mounted; all tooltips will fail to open
171
+ <App />
172
+
173
+ // ❌ Wrong — delayDuration is the Radix prop; Base UI ignores it
174
+ <TooltipProvider delayDuration={300}>
175
+ <App />
176
+ </TooltipProvider>
177
+
178
+ // ✅ Correct — mount once with the Base UI prop name
179
+ <TooltipProvider delay={0}>
180
+ <App />
181
+ </TooltipProvider>
182
+ ```
183
+
184
+ `TooltipProvider` is required for all DS tooltips (Base UI mandates a Provider context). The prop is `delay` (milliseconds), not `delayDuration`. Omitting the provider causes tooltips to never open; using `delayDuration` silently falls back to Base UI's internal default.
185
+
186
+ Source: `packages/ds/src/components/tooltip.tsx`
187
+
188
+ ### Mistake 4 — Missing `aria-label` on an icon-only trigger
189
+
190
+ ```tsx
191
+ // ❌ Wrong — no accessible name; tooltip text is not the control's name
192
+ <TooltipTrigger render={<Button variant="ghost" size="icon" />}>
193
+ <Info className="size-4" />
194
+ </TooltipTrigger>
195
+
196
+ // ✅ Correct — aria-label names the control independently of the tooltip
197
+ <TooltipTrigger render={<Button variant="ghost" size="icon" aria-label="More information" />}>
198
+ <Info className="size-4" />
199
+ </TooltipTrigger>
200
+ ```
201
+
202
+ The tooltip text shown in `TooltipContent` is a supplemental hint, not the accessible name of the trigger element. Screen readers announce the control by its label, not by the tooltip. Icon-only buttons must carry an explicit `aria-label`.
203
+
204
+ Source: `packages/ds/src/components/tooltip.tsx`