@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,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`
|