@aircall/blocks 0.6.0 → 0.7.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 +43 -0
- package/dist/globals.css +1 -1
- package/dist/index.d.ts +80 -23
- package/dist/index.js +339 -86
- package/package.json +17 -3
- package/skills/aircall-blocks/migrate-dashboard/SKILL.md +127 -0
- package/skills/aircall-blocks/migrate-dashboard/card/SKILL.md +364 -0
- package/skills/aircall-blocks/migrate-dashboard/combobox/SKILL.md +487 -0
- package/skills/aircall-blocks/migrate-dashboard/copy-button/SKILL.md +138 -0
- package/skills/aircall-blocks/migrate-dashboard/dashboard-page/SKILL.md +198 -0
- package/skills/aircall-blocks/migrate-dashboard/data-table/SKILL.md +395 -0
- package/skills/aircall-blocks/migrate-dashboard/empty-states/SKILL.md +347 -0
- package/skills/aircall-blocks/migrate-dashboard/form-wizard/SKILL.md +271 -0
- package/skills/aircall-blocks/migrate-dashboard/info-popup/SKILL.md +179 -0
- package/skills/aircall-blocks/migrate-dashboard/layout/SKILL.md +328 -0
- package/skills/aircall-blocks/migrate-dashboard/list/SKILL.md +474 -0
- package/skills/aircall-blocks/migrate-dashboard/loading/SKILL.md +260 -0
- package/skills/aircall-blocks/migrate-dashboard/page-header/SKILL.md +394 -0
- package/skills/aircall-blocks/migrate-dashboard/tile/SKILL.md +429 -0
- package/skills/aircall-blocks/migrate-dashboard/toggle-row/SKILL.md +177 -0
- package/skills/aircall-blocks/setup/SKILL.md +127 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: aircall-blocks/migrate-dashboard/card
|
|
3
|
+
description: >
|
|
4
|
+
Migrate @dashboard/library Paper and PaperForm to @aircall/ds Card primitives
|
|
5
|
+
(Card, CardHeader, CardTitle, CardDescription, CardAction, CardContent, CardFooter)
|
|
6
|
+
and @aircall/blocks CardSaveBar for save/discard bars. Load when a file imports
|
|
7
|
+
Paper or PaperForm from @dashboard/library.
|
|
8
|
+
type: sub-skill
|
|
9
|
+
library: aircall-blocks
|
|
10
|
+
library_version: "0.5.1"
|
|
11
|
+
requires:
|
|
12
|
+
- aircall-blocks/setup
|
|
13
|
+
- aircall-blocks/migrate-dashboard
|
|
14
|
+
sources:
|
|
15
|
+
- "aircall/hydra:packages/ds/src/index.ts"
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
This skill builds on aircall-blocks/migrate-dashboard.
|
|
19
|
+
|
|
20
|
+
## 1. Component mapping
|
|
21
|
+
|
|
22
|
+
| @dashboard/library | @aircall/ds / @aircall/blocks |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| `Paper` (root surface) | `Card` (`@aircall/ds`) |
|
|
25
|
+
| `Paper` `title` prop | `CardTitle` inside `CardHeader` (`@aircall/ds`) |
|
|
26
|
+
| `Paper` `subtitle` prop | `CardDescription` inside `CardHeader` (`@aircall/ds`) |
|
|
27
|
+
| `Paper` `titleSide` prop | `CardAction` inside `CardHeader` (`@aircall/ds`) |
|
|
28
|
+
| `Paper` `children` (body) | `CardContent` (`@aircall/ds`) |
|
|
29
|
+
| `Paper` `footer` prop (generic) | `CardFooter` (`@aircall/ds`) |
|
|
30
|
+
| `Paper` `footer` prop (save/discard bar) | `CardSaveBar` (`@aircall/blocks`) |
|
|
31
|
+
| `PaperForm` (form wrapper with save bar) | `Card` + `useForm` + `CardSaveBar` (`@aircall/blocks`) |
|
|
32
|
+
| `Paper` `banner` prop | Inline `Banner` before `CardHeader` inside `Card` (`@aircall/ds`) |
|
|
33
|
+
| `Paper` `disabled` / `disabledText` props | `className="opacity-50 cursor-not-allowed"` on `Card` + custom label |
|
|
34
|
+
| `Paper` `fluid` prop | `className="w-full"` on `CardContent` (default is already full-width) |
|
|
35
|
+
|
|
36
|
+
`PaperForm` is a compound: it wires a `react-final-form` form, renders `Paper`, and
|
|
37
|
+
appends a `SaveBar` footer. The replacement is `Card` with TanStack Form (`useForm` from
|
|
38
|
+
`@aircall/blocks`) and `CardSaveBar` in a `CardFooter`.
|
|
39
|
+
|
|
40
|
+
## 2. Imports
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
// DS primitives — structural card shell
|
|
44
|
+
import {
|
|
45
|
+
Card,
|
|
46
|
+
CardAction,
|
|
47
|
+
CardContent,
|
|
48
|
+
CardDescription,
|
|
49
|
+
CardFooter,
|
|
50
|
+
CardHeader,
|
|
51
|
+
CardTitle,
|
|
52
|
+
Banner,
|
|
53
|
+
BannerTitle,
|
|
54
|
+
BannerDescription
|
|
55
|
+
} from '@aircall/ds';
|
|
56
|
+
|
|
57
|
+
// blocks — TanStack Form + animated save bar
|
|
58
|
+
import { useForm, CardSaveBar } from '@aircall/blocks';
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 3. Before / After
|
|
62
|
+
|
|
63
|
+
### 3a. Basic Paper — read-only surface
|
|
64
|
+
|
|
65
|
+
**Before (`@dashboard/library`):**
|
|
66
|
+
```tsx
|
|
67
|
+
import { Paper } from '@dashboard/library';
|
|
68
|
+
|
|
69
|
+
function SettingsSection() {
|
|
70
|
+
return (
|
|
71
|
+
<Paper
|
|
72
|
+
title="General settings"
|
|
73
|
+
subtitle="Manage your workspace preferences."
|
|
74
|
+
>
|
|
75
|
+
<p>Section content here.</p>
|
|
76
|
+
</Paper>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**After (`@aircall/ds`):**
|
|
82
|
+
```tsx
|
|
83
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@aircall/ds';
|
|
84
|
+
|
|
85
|
+
function SettingsSection() {
|
|
86
|
+
return (
|
|
87
|
+
<Card>
|
|
88
|
+
<CardHeader>
|
|
89
|
+
<CardTitle>General settings</CardTitle>
|
|
90
|
+
<CardDescription>Manage your workspace preferences.</CardDescription>
|
|
91
|
+
</CardHeader>
|
|
92
|
+
<CardContent>
|
|
93
|
+
<p>Section content here.</p>
|
|
94
|
+
</CardContent>
|
|
95
|
+
</Card>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Key changes:
|
|
101
|
+
- `Paper` `title` → `CardTitle` inside `CardHeader`.
|
|
102
|
+
- `Paper` `subtitle` → `CardDescription` inside `CardHeader`.
|
|
103
|
+
- `Paper` `children` → wrapped in `CardContent`.
|
|
104
|
+
- Drop `BoxProps` spread (`maxWidth`, `borderColor`, etc.) — `Card` owns the surface.
|
|
105
|
+
|
|
106
|
+
### 3b. Paper with a header action (titleSide)
|
|
107
|
+
|
|
108
|
+
**Before (`@dashboard/library`):**
|
|
109
|
+
```tsx
|
|
110
|
+
import { Paper } from '@dashboard/library';
|
|
111
|
+
import { Button } from '@aircall/tractor';
|
|
112
|
+
|
|
113
|
+
function IntegrationCard() {
|
|
114
|
+
return (
|
|
115
|
+
<Paper
|
|
116
|
+
title="Integrations"
|
|
117
|
+
subtitle="Connect your tools."
|
|
118
|
+
titleSide={<Button variant="primary" size="small">Add integration</Button>}
|
|
119
|
+
>
|
|
120
|
+
<p>Integration list here.</p>
|
|
121
|
+
</Paper>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**After (`@aircall/ds`):**
|
|
127
|
+
```tsx
|
|
128
|
+
import { Button, Card, CardAction, CardContent, CardDescription, CardHeader, CardTitle } from '@aircall/ds';
|
|
129
|
+
|
|
130
|
+
function IntegrationCard() {
|
|
131
|
+
return (
|
|
132
|
+
<Card>
|
|
133
|
+
<CardHeader>
|
|
134
|
+
<CardTitle>Integrations</CardTitle>
|
|
135
|
+
<CardDescription>Connect your tools.</CardDescription>
|
|
136
|
+
<CardAction>
|
|
137
|
+
<Button variant="default" size="sm">Add integration</Button>
|
|
138
|
+
</CardAction>
|
|
139
|
+
</CardHeader>
|
|
140
|
+
<CardContent>
|
|
141
|
+
<p>Integration list here.</p>
|
|
142
|
+
</CardContent>
|
|
143
|
+
</Card>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`CardAction` must be inside `CardHeader` — the header grid (`grid-cols-[1fr_auto]`) positions it top-right automatically.
|
|
149
|
+
|
|
150
|
+
### 3c. PaperForm — form with save/discard bar
|
|
151
|
+
|
|
152
|
+
**Before (`@dashboard/library`):**
|
|
153
|
+
```tsx
|
|
154
|
+
import { PaperForm } from '@dashboard/library';
|
|
155
|
+
|
|
156
|
+
function ProfileForm() {
|
|
157
|
+
return (
|
|
158
|
+
<PaperForm
|
|
159
|
+
title="Profile"
|
|
160
|
+
subtitle="Update your display name."
|
|
161
|
+
formProps={{
|
|
162
|
+
initialValues: { name: '' },
|
|
163
|
+
onSubmit: async (values) => { /* submit */ }
|
|
164
|
+
}}
|
|
165
|
+
submitButtonText="Save"
|
|
166
|
+
undoButtonText="Discard"
|
|
167
|
+
>
|
|
168
|
+
{({ values }) => (
|
|
169
|
+
<input name="name" defaultValue={values.name} />
|
|
170
|
+
)}
|
|
171
|
+
</PaperForm>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**After (`@aircall/blocks`):**
|
|
177
|
+
```tsx
|
|
178
|
+
import { useForm, CardSaveBar } from '@aircall/blocks';
|
|
179
|
+
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@aircall/ds';
|
|
180
|
+
|
|
181
|
+
const form = useForm({
|
|
182
|
+
defaultValues: { name: '' },
|
|
183
|
+
onSubmit: async ({ value }) => { /* submit */ }
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
function ProfileForm() {
|
|
187
|
+
return (
|
|
188
|
+
<form.AppForm>
|
|
189
|
+
<form onSubmit={(e) => { e.preventDefault(); form.handleSubmit(); }}>
|
|
190
|
+
<Card className="overflow-hidden pb-0">
|
|
191
|
+
<CardHeader>
|
|
192
|
+
<CardTitle>Profile</CardTitle>
|
|
193
|
+
<CardDescription>Update your display name.</CardDescription>
|
|
194
|
+
</CardHeader>
|
|
195
|
+
<CardContent>
|
|
196
|
+
<form.AppField name="name">
|
|
197
|
+
{(field) => (
|
|
198
|
+
<input
|
|
199
|
+
value={field.state.value}
|
|
200
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
201
|
+
/>
|
|
202
|
+
)}
|
|
203
|
+
</form.AppField>
|
|
204
|
+
</CardContent>
|
|
205
|
+
<CardFooter className="p-0">
|
|
206
|
+
<CardSaveBar submitLabel="Save" resetLabel="Discard" />
|
|
207
|
+
</CardFooter>
|
|
208
|
+
</Card>
|
|
209
|
+
</form>
|
|
210
|
+
</form.AppForm>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Key changes:
|
|
216
|
+
- `PaperForm` `formProps` + `react-final-form` render-prop → `useForm` from `@aircall/blocks` (TanStack Form).
|
|
217
|
+
- `PaperForm` `footer` (implicit `SaveBar`) → `CardSaveBar` inside `CardFooter`.
|
|
218
|
+
- Add `className="overflow-hidden pb-0"` to `Card` so the animated save bar's border sits flush at the card edge.
|
|
219
|
+
- Add `className="p-0"` to `CardFooter` to let `CardSaveBar` own its padding.
|
|
220
|
+
- `CardSaveBar` reads `isDirty` / `canSubmit` / `isSubmitting` from form context automatically — no extra props needed.
|
|
221
|
+
|
|
222
|
+
### 3d. Paper with a generic footer
|
|
223
|
+
|
|
224
|
+
**Before (`@dashboard/library`):**
|
|
225
|
+
```tsx
|
|
226
|
+
import { Paper } from '@dashboard/library';
|
|
227
|
+
import { Button } from '@aircall/tractor';
|
|
228
|
+
|
|
229
|
+
function BillingCard() {
|
|
230
|
+
return (
|
|
231
|
+
<Paper
|
|
232
|
+
title="Billing"
|
|
233
|
+
footer={
|
|
234
|
+
<div style={{ padding: 16 }}>
|
|
235
|
+
<Button variant="primary">Upgrade plan</Button>
|
|
236
|
+
</div>
|
|
237
|
+
}
|
|
238
|
+
>
|
|
239
|
+
<p>Current plan: Free</p>
|
|
240
|
+
</Paper>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
**After (`@aircall/ds`):**
|
|
246
|
+
```tsx
|
|
247
|
+
import { Button, Card, CardContent, CardFooter, CardHeader, CardTitle } from '@aircall/ds';
|
|
248
|
+
|
|
249
|
+
function BillingCard() {
|
|
250
|
+
return (
|
|
251
|
+
<Card>
|
|
252
|
+
<CardHeader>
|
|
253
|
+
<CardTitle>Billing</CardTitle>
|
|
254
|
+
</CardHeader>
|
|
255
|
+
<CardContent>
|
|
256
|
+
<p>Current plan: Free</p>
|
|
257
|
+
</CardContent>
|
|
258
|
+
<CardFooter>
|
|
259
|
+
<Button variant="default">Upgrade plan</Button>
|
|
260
|
+
</CardFooter>
|
|
261
|
+
</Card>
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
`CardFooter` applies `border-t bg-muted/50 p-4` by default. For a transparent footer pass `className="bg-transparent border-none"`.
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## 4. Common mistakes
|
|
271
|
+
|
|
272
|
+
### Mistake 1 — Spreading BoxProps onto Card
|
|
273
|
+
|
|
274
|
+
```tsx
|
|
275
|
+
// ❌ Wrong — tractor/dashboard BoxProps (maxWidth, borderRadius, backgroundColor…) on Card
|
|
276
|
+
<Card maxWidth={1372} borderRadius="sm" backgroundColor="white">
|
|
277
|
+
<CardContent>Content</CardContent>
|
|
278
|
+
</Card>
|
|
279
|
+
|
|
280
|
+
// ✅ Correct — Card owns the surface; use className for true one-offs only
|
|
281
|
+
<Card className="max-w-[1372px]">
|
|
282
|
+
<CardContent>Content</CardContent>
|
|
283
|
+
</Card>
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
`Paper` accepted arbitrary `BoxProps` from `@aircall/tractor`. `Card` extends `React.ComponentProps<'div'>` — it accepts `className`, not tractor spacing/color tokens. Spread them and React will warn on unknown HTML attributes.
|
|
287
|
+
|
|
288
|
+
Source: `packages/ds/src/components/card.tsx`
|
|
289
|
+
|
|
290
|
+
### Mistake 2 — Placing CardAction outside CardHeader
|
|
291
|
+
|
|
292
|
+
```tsx
|
|
293
|
+
// ❌ Wrong — renders in normal flow, loses top-right pinning
|
|
294
|
+
<Card>
|
|
295
|
+
<CardHeader>
|
|
296
|
+
<CardTitle>Title</CardTitle>
|
|
297
|
+
</CardHeader>
|
|
298
|
+
<CardAction>
|
|
299
|
+
<Button variant="ghost" size="icon" aria-label="Menu">…</Button>
|
|
300
|
+
</CardAction>
|
|
301
|
+
<CardContent>Body</CardContent>
|
|
302
|
+
</Card>
|
|
303
|
+
|
|
304
|
+
// ✅ Correct — CardAction inside CardHeader uses the header grid
|
|
305
|
+
<Card>
|
|
306
|
+
<CardHeader>
|
|
307
|
+
<CardTitle>Title</CardTitle>
|
|
308
|
+
<CardAction>
|
|
309
|
+
<Button variant="ghost" size="icon" aria-label="Menu">…</Button>
|
|
310
|
+
</CardAction>
|
|
311
|
+
</CardHeader>
|
|
312
|
+
<CardContent>Body</CardContent>
|
|
313
|
+
</Card>
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
`CardHeader` uses `grid auto-rows-min has-data-[slot=card-action]:grid-cols-[1fr_auto]`. `CardAction` carries `data-slot="card-action"` and is placed at `col-start-2 row-span-2`. Outside `CardHeader`, no grid parent exists and the action renders in document flow.
|
|
317
|
+
|
|
318
|
+
Source: `packages/ds/src/components/card.tsx`
|
|
319
|
+
|
|
320
|
+
### Mistake 3 — Omitting overflow-hidden/pb-0 on Card when using CardSaveBar
|
|
321
|
+
|
|
322
|
+
```tsx
|
|
323
|
+
// ❌ Wrong — animated bar peeks out of the card's rounded corners
|
|
324
|
+
<Card>
|
|
325
|
+
<CardContent>…</CardContent>
|
|
326
|
+
<CardFooter className="p-0">
|
|
327
|
+
<CardSaveBar />
|
|
328
|
+
</CardFooter>
|
|
329
|
+
</Card>
|
|
330
|
+
|
|
331
|
+
// ✅ Correct — overflow-hidden clips the bar; pb-0 removes the gap below the footer
|
|
332
|
+
<Card className="overflow-hidden pb-0">
|
|
333
|
+
<CardContent>…</CardContent>
|
|
334
|
+
<CardFooter className="p-0">
|
|
335
|
+
<CardSaveBar />
|
|
336
|
+
</CardFooter>
|
|
337
|
+
</Card>
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
`CardSaveBar` slides in via a `grid-rows-[0fr→1fr]` transition. Without `overflow-hidden` on `Card`, the collapsed bar overflows the card's rounded corners during animation. Without `pb-0` the card's own `py-4` leaves a gap between the footer and the card bottom edge.
|
|
341
|
+
|
|
342
|
+
Source: `packages/blocks/src/components/card-save-bar.tsx`
|
|
343
|
+
|
|
344
|
+
### Mistake 4 — Passing react-final-form formProps to useForm
|
|
345
|
+
|
|
346
|
+
```tsx
|
|
347
|
+
// ❌ Wrong — react-final-form API; useForm from @aircall/blocks is TanStack Form
|
|
348
|
+
import { useForm } from '@aircall/blocks';
|
|
349
|
+
const form = useForm({
|
|
350
|
+
initialValues: { name: '' }, // ← react-final-form key
|
|
351
|
+
onSubmit: (values) => save(values) // ← sync signature; TanStack expects async
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// ✅ Correct — TanStack Form API
|
|
355
|
+
import { useForm } from '@aircall/blocks';
|
|
356
|
+
const form = useForm({
|
|
357
|
+
defaultValues: { name: '' },
|
|
358
|
+
onSubmit: async ({ value }) => { await save(value); }
|
|
359
|
+
});
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
`useForm` from `@aircall/blocks` wraps TanStack Form, not `react-final-form`. The key differences: `defaultValues` (not `initialValues`), and `onSubmit` receives `{ value }` (not the bare values object).
|
|
363
|
+
|
|
364
|
+
Source: `packages/blocks/src/components/card-save-bar.tsx`
|