@aircall/blocks 0.17.0 → 0.18.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/dist/globals.css +1 -1
- package/dist/index.d.ts +630 -17
- package/dist/index.js +668 -80
- package/package.json +2 -2
- package/skills/aircall-blocks/migrate-dashboard/SKILL.md +19 -2
- package/skills/aircall-blocks/migrate-dashboard/card/SKILL.md +43 -71
- package/skills/aircall-blocks/migrate-dashboard/catch-up-checklist/SKILL.md +109 -0
- package/skills/aircall-blocks/migrate-dashboard/combobox/SKILL.md +0 -1
- package/skills/aircall-blocks/migrate-dashboard/copy-button/SKILL.md +0 -1
- package/skills/aircall-blocks/migrate-dashboard/dashboard-page/SKILL.md +39 -1
- package/skills/aircall-blocks/migrate-dashboard/data-table/SKILL.md +10 -1
- package/skills/aircall-blocks/migrate-dashboard/empty-states/SKILL.md +0 -1
- package/skills/aircall-blocks/migrate-dashboard/form-wizard/SKILL.md +40 -1
- package/skills/aircall-blocks/migrate-dashboard/info-popup/SKILL.md +0 -1
- package/skills/aircall-blocks/migrate-dashboard/layout/SKILL.md +0 -1
- package/skills/aircall-blocks/migrate-dashboard/list/SKILL.md +0 -1
- package/skills/aircall-blocks/migrate-dashboard/loading/SKILL.md +0 -1
- package/skills/aircall-blocks/migrate-dashboard/page-header/SKILL.md +0 -1
- package/skills/aircall-blocks/migrate-dashboard/setting-card/SKILL.md +258 -0
- package/skills/aircall-blocks/migrate-dashboard/tile/SKILL.md +0 -1
- package/skills/aircall-blocks/migrate-dashboard/toggle-row/SKILL.md +0 -1
- package/skills/aircall-blocks/setup/SKILL.md +22 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aircall/blocks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"sideEffects": [
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"vite": "7.3.1",
|
|
83
83
|
"vitest": "4.0.17",
|
|
84
84
|
"zod": "4.4.3",
|
|
85
|
-
"@aircall/ds": "0.
|
|
85
|
+
"@aircall/ds": "0.28.0",
|
|
86
86
|
"@aircall/hooks": "0.6.0"
|
|
87
87
|
},
|
|
88
88
|
"keywords": [
|
|
@@ -6,7 +6,6 @@ description: >
|
|
|
6
6
|
has a blocks/ds equivalent, what is out of scope, and which sub-skill to load next.
|
|
7
7
|
type: core
|
|
8
8
|
library: aircall-blocks
|
|
9
|
-
library_version: "0.5.1"
|
|
10
9
|
requires:
|
|
11
10
|
- aircall-blocks/setup
|
|
12
11
|
sources:
|
|
@@ -19,6 +18,10 @@ This is the overview router for the `@dashboard/library` migration. Load it firs
|
|
|
19
18
|
identify the right sub-skill for each component you are migrating, and to determine what
|
|
20
19
|
falls outside the scope of blocks and ds entirely.
|
|
21
20
|
|
|
21
|
+
> **Already migrated?** To *verify* existing `@aircall/blocks` / `@aircall/ds` code is on the
|
|
22
|
+
> latest shared components and props (rather than convert `@dashboard/library` imports), load
|
|
23
|
+
> `@aircall/blocks#aircall-blocks/migrate-dashboard/catch-up-checklist`.
|
|
24
|
+
|
|
22
25
|
## How to run this migration (end-to-end)
|
|
23
26
|
|
|
24
27
|
Migrate incrementally — one screen/file at a time, shipping each green:
|
|
@@ -84,6 +87,10 @@ no target in `@aircall/blocks` or `@aircall/ds` yet (e.g. `PieChart` and other c
|
|
|
84
87
|
not listed above, leave it imported from `@dashboard/library` for now — do not force a
|
|
85
88
|
migration or invent a target.
|
|
86
89
|
|
|
90
|
+
## Not yet migratable (tracked elsewhere)
|
|
91
|
+
|
|
92
|
+
Components whose migration decision is still **TBD**, **TODO**, or **Ready for dev** are not built yet — do not migrate them or invent a target. They are tracked in `dashboard-modules/COMPONENT-MIGRATION-DECISIONS.md`. Known example: `DaysPicker` (→ a future `@aircall/blocks` `DaysPicker` block, not yet built).
|
|
93
|
+
|
|
87
94
|
## Component routing table
|
|
88
95
|
|
|
89
96
|
| @dashboard/library | Target (pkg) | Recipe to load | Status |
|
|
@@ -91,7 +98,8 @@ migration or invent a target.
|
|
|
91
98
|
| `MessageScreen` family + `UnknownError` | `ComingSoonEmptyState`, `RestrictedAccessEmptyState`, `NotFoundEmptyState`, `NoDataEmptyState`, `NoSupportEmptyState`, `UnknownErrorEmptyState` and the `EmptyState*` parts (@aircall/blocks) | load @aircall/blocks#aircall-blocks/migrate-dashboard/empty-states | available |
|
|
92
99
|
| `PageHeader` | `DashboardPageHeader` (+ `DashboardPageHeaderTitle` / `DashboardPageHeaderActions` / `DashboardPageHeaderAction` / `DashboardPageHeaderNav` / `DashboardPageHeaderDescription`) (@aircall/blocks) | load @aircall/blocks#aircall-blocks/migrate-dashboard/page-header | available |
|
|
93
100
|
| Page / screen shell — incl. full-page flows (Campaign Creation, Add Contacts) | `DashboardPage` (standard: sidebar + header + tabs + content) / `DashboardStandalonePage` (full-page, no sidebar) (@aircall/blocks) | load @aircall/blocks#aircall-blocks/migrate-dashboard/dashboard-page | available |
|
|
94
|
-
| `Paper` | `
|
|
101
|
+
| `Paper` / `PaperForm` (settings-page surface) | `SettingCard` (+ `SettingCardHeader` / `SettingCardTitle` / `SettingCardDescription` / `SettingCardAction` / `SettingCardContent`), plus `useForm` + `CardSaveBar` for the save bar (@aircall/blocks). Keep the `errorBoundary` (`ScopedErrorBoundary`) **outside** the card. | load @aircall/blocks#aircall-blocks/migrate-dashboard/setting-card | available |
|
|
102
|
+
| `Paper` (generic non-settings surface — tile/container, no title/toggle semantics) | `Card` (+ parts) (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/card | available |
|
|
95
103
|
| `LoadMoreTable` | `DataTable` (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/data-table | available |
|
|
96
104
|
| `MultiSearchSelect` + `MultiInlineSearchSelect` + `MultiSelectOption` | `Combobox` with `multiple` (multi-select) (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/combobox | available |
|
|
97
105
|
| `SingleSearchSelect` / `SearchSelect` / single-value `MultiSelect` | `Combobox` WITHOUT `multiple` (single-select) (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/combobox | available |
|
|
@@ -117,6 +125,15 @@ migration or invent a target.
|
|
|
117
125
|
| `ConfirmationModal` | `AlertDialog` and its parts (@aircall/ds) | load @aircall/ds#aircall-ds/migrate-tractor/dialog | available |
|
|
118
126
|
| `Tab` | `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent` (@aircall/ds) | load @aircall/ds#aircall-ds/migrate-tractor/tabs | available |
|
|
119
127
|
| `Skeleton` + `SkeletonText` | `Skeleton` (@aircall/ds) | load @aircall/ds#aircall-ds/migrate-tractor/skeleton | available |
|
|
128
|
+
| `Editable` | `InlineEditInput` / `InlineEditTextarea` / `InlineEditSelect` / `InlineEditMultiselect` (+ sub-parts) (@aircall/ds) | inline — click-to-edit; single-value via `InlineEditInput`/`InlineEditTextarea`/`InlineEditSelect`, multi via `InlineEditMultiselect`. Data-driven accessor API like `DataCombobox` (`items` + `getItemValue`/`getItemLabel`) | available |
|
|
129
|
+
| `Dropzone` | `Dropzone` (@aircall/ds); render the dropped files with `Attachment` (@aircall/ds) | load @aircall/ds#aircall-ds/migrate-tractor/dropzone — display files via @aircall/ds#aircall-ds/adopt-attachment | available |
|
|
130
|
+
| `EmojiPicker` | `EmojiPicker` (@aircall/ds) | inline — no sub-skill | available |
|
|
131
|
+
| `DateSelect` | `Input` + `Popover` + `Calendar` (@aircall/ds) | inline — a `Calendar` in a `Popover` behind an `Input` trigger | available |
|
|
132
|
+
| `TagStack` | `BadgeGroup` (@aircall/ds) | inline — `<BadgeGroup>` wrapping `Badge` children | available |
|
|
133
|
+
| `AvailabilitySelect` / `Select` | `Select` (@aircall/ds) | load @aircall/ds#aircall-ds/migrate-tractor/select | available |
|
|
134
|
+
| `Chevron` | `Icon` (@aircall/react-icons) | inline — import the specific directional icon (`ChevronDown`, `ChevronRight`, …); no `<Chevron>` wrapper | available |
|
|
135
|
+
| `InlineForm` / `InlineFormWithSingleSearchSelect` | `useForm` + DS primitives (+ `Combobox` for the search-select variant) (@aircall/blocks) | load @aircall/blocks#aircall-blocks/migrate-dashboard/form-wizard | available |
|
|
136
|
+
| `TimeSlotsForm` | `useForm` + 2 blocks (@aircall/blocks) | load @aircall/blocks#aircall-blocks/migrate-dashboard/form-wizard — too specific to extract; compose at app level | available |
|
|
120
137
|
|
|
121
138
|
## How to use this router
|
|
122
139
|
|
|
@@ -7,7 +7,6 @@ description: >
|
|
|
7
7
|
Paper or PaperForm from @dashboard/library.
|
|
8
8
|
type: sub-skill
|
|
9
9
|
library: aircall-blocks
|
|
10
|
-
library_version: "0.5.1"
|
|
11
10
|
requires:
|
|
12
11
|
- aircall-blocks/setup
|
|
13
12
|
- aircall-blocks/migrate-dashboard
|
|
@@ -17,6 +16,12 @@ sources:
|
|
|
17
16
|
|
|
18
17
|
This skill builds on aircall-blocks/migrate-dashboard.
|
|
19
18
|
|
|
19
|
+
> **Migrating a settings-page `Paper` / `PaperForm`?** Prefer `SettingCard` — load
|
|
20
|
+
> `@aircall/blocks#aircall-blocks/migrate-dashboard/setting-card`. It is purpose-built for
|
|
21
|
+
> the title / description / toggle / save-bar layout and gets the level backgrounds and
|
|
22
|
+
> level-aware title for free. Use the raw `Card` primitives below only for **non-settings**
|
|
23
|
+
> card surfaces (tiles, generic containers with no title/toggle semantics).
|
|
24
|
+
|
|
20
25
|
## 1. Component mapping
|
|
21
26
|
|
|
22
27
|
| @dashboard/library | @aircall/ds / @aircall/blocks |
|
|
@@ -35,7 +40,7 @@ This skill builds on aircall-blocks/migrate-dashboard.
|
|
|
35
40
|
|
|
36
41
|
`PaperForm` is a compound: it wires a `react-final-form` form, renders `Paper`, and
|
|
37
42
|
appends a `SaveBar` footer. The replacement is `Card` with TanStack Form (`useForm` from
|
|
38
|
-
`@aircall/blocks`) and `CardSaveBar`
|
|
43
|
+
`@aircall/blocks`) and `CardSaveBar` as a direct child of `Card` (it is the footer itself).
|
|
39
44
|
|
|
40
45
|
## 2. Imports
|
|
41
46
|
|
|
@@ -176,7 +181,7 @@ function ProfileForm() {
|
|
|
176
181
|
**After (`@aircall/blocks`):**
|
|
177
182
|
```tsx
|
|
178
183
|
import { useForm, CardSaveBar } from '@aircall/blocks';
|
|
179
|
-
import { Card, CardContent, CardDescription,
|
|
184
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@aircall/ds';
|
|
180
185
|
|
|
181
186
|
const form = useForm({
|
|
182
187
|
defaultValues: { name: '' },
|
|
@@ -187,7 +192,7 @@ function ProfileForm() {
|
|
|
187
192
|
return (
|
|
188
193
|
<form.AppForm>
|
|
189
194
|
<form onSubmit={(e) => { e.preventDefault(); form.handleSubmit(); }}>
|
|
190
|
-
<Card
|
|
195
|
+
<Card>
|
|
191
196
|
<CardHeader>
|
|
192
197
|
<CardTitle>Profile</CardTitle>
|
|
193
198
|
<CardDescription>Update your display name.</CardDescription>
|
|
@@ -202,9 +207,7 @@ function ProfileForm() {
|
|
|
202
207
|
)}
|
|
203
208
|
</form.AppField>
|
|
204
209
|
</CardContent>
|
|
205
|
-
<
|
|
206
|
-
<CardSaveBar submitLabel="Save" resetLabel="Discard" />
|
|
207
|
-
</CardFooter>
|
|
210
|
+
<CardSaveBar submitLabel="Save" resetLabel="Discard" />
|
|
208
211
|
</Card>
|
|
209
212
|
</form>
|
|
210
213
|
</form.AppForm>
|
|
@@ -214,11 +217,33 @@ function ProfileForm() {
|
|
|
214
217
|
|
|
215
218
|
Key changes:
|
|
216
219
|
- `PaperForm` `formProps` + `react-final-form` render-prop → `useForm` from `@aircall/blocks` (TanStack Form).
|
|
217
|
-
- `PaperForm` `footer` (implicit `SaveBar`) → `CardSaveBar`
|
|
218
|
-
-
|
|
219
|
-
- Add `className="p-0"` to `CardFooter` to let `CardSaveBar` own its padding.
|
|
220
|
+
- `PaperForm` `footer` (implicit `SaveBar`) → `CardSaveBar` as a **direct child of `Card`** (do NOT wrap it in a `CardFooter` — it already carries `data-slot="card-footer"` itself).
|
|
221
|
+
- **No `className` needed on `Card`**: it already has `overflow-hidden` built in, and `CardSaveBar`'s `data-slot="card-footer"` makes the card auto-collapse its bottom padding (`has-data-[slot=card-footer]:pb-0`) so the bar sits flush.
|
|
220
222
|
- `CardSaveBar` reads `isDirty` / `canSubmit` / `isSubmitting` from form context automatically — no extra props needed.
|
|
221
223
|
|
|
224
|
+
**Submit error banner (`getErrorMessage` / `submitError`).** `PaperForm` surfaced a submit failure via `getErrorMessage` + `submitError`. There is no built-in equivalent — catch and store it in `onSubmit`, then render an inline `Alert` above `CardContent`:
|
|
225
|
+
|
|
226
|
+
```tsx
|
|
227
|
+
const [submitError, setSubmitError] = useState<string | null>(null);
|
|
228
|
+
|
|
229
|
+
const form = useForm({
|
|
230
|
+
defaultValues: { name: '' },
|
|
231
|
+
onSubmit: async ({ value }) => {
|
|
232
|
+
try {
|
|
233
|
+
await save(value);
|
|
234
|
+
setSubmitError(null);
|
|
235
|
+
} catch (e) {
|
|
236
|
+
setSubmitError(getErrorMessage(e));
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// In JSX, above CardContent:
|
|
242
|
+
{submitError && <Alert variant="error">{submitError}</Alert>}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
> DS `Alert`'s error variant is `error` (not Tractor's `critical`/`destructive`). Full set: `default` / `info` / `success` / `warning` / `error`.
|
|
246
|
+
|
|
222
247
|
### 3d. Paper with a generic footer
|
|
223
248
|
|
|
224
249
|
**Before (`@dashboard/library`):**
|
|
@@ -287,78 +312,25 @@ function BillingCard() {
|
|
|
287
312
|
|
|
288
313
|
Source: `packages/ds/src/components/card.tsx`
|
|
289
314
|
|
|
290
|
-
### Mistake 2 —
|
|
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
|
|
315
|
+
### Mistake 2 — Wrapping CardSaveBar in a CardFooter (or adding overflow-hidden/pb-0)
|
|
321
316
|
|
|
322
317
|
```tsx
|
|
323
|
-
// ❌ Wrong —
|
|
324
|
-
|
|
318
|
+
// ❌ Wrong — CardSaveBar already IS the footer; a CardFooter around it doubles
|
|
319
|
+
// the top border and padding, and the className hints are redundant
|
|
320
|
+
<Card className="overflow-hidden pb-0">
|
|
325
321
|
<CardContent>…</CardContent>
|
|
326
322
|
<CardFooter className="p-0">
|
|
327
323
|
<CardSaveBar />
|
|
328
324
|
</CardFooter>
|
|
329
325
|
</Card>
|
|
330
326
|
|
|
331
|
-
// ✅ Correct —
|
|
332
|
-
<Card
|
|
327
|
+
// ✅ Correct — drop CardSaveBar in as a direct child; no wrapper, no className
|
|
328
|
+
<Card>
|
|
333
329
|
<CardContent>…</CardContent>
|
|
334
|
-
<
|
|
335
|
-
<CardSaveBar />
|
|
336
|
-
</CardFooter>
|
|
330
|
+
<CardSaveBar />
|
|
337
331
|
</Card>
|
|
338
332
|
```
|
|
339
333
|
|
|
340
|
-
`CardSaveBar`
|
|
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).
|
|
334
|
+
`CardSaveBar` renders its own `border-t` + padding and carries `data-slot="card-footer"`, so it already behaves as the footer. `Card` has `overflow-hidden` built in (clips the slide-in), and the `data-slot="card-footer"` triggers `has-data-[slot=card-footer]:pb-0`, so the bar sits flush with no manual `overflow-hidden`, `pb-0`, or `CardFooter` wrapper.
|
|
363
335
|
|
|
364
336
|
Source: `packages/blocks/src/components/card-save-bar.tsx`
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: aircall-blocks/migrate-dashboard/catch-up-checklist
|
|
3
|
+
description: >
|
|
4
|
+
Verify that already-migrated dashboard / extension code uses the CURRENT shared
|
|
5
|
+
@aircall/ds + @aircall/blocks components and props — not an earlier or interim
|
|
6
|
+
pattern. Load when someone wants to double-check a codebase that is already off
|
|
7
|
+
@dashboard/library against the latest components (e.g. after a DS/blocks release),
|
|
8
|
+
or to confirm shared-component adoption. This is a VERIFICATION pass, NOT a
|
|
9
|
+
migration — to convert @dashboard/library imports, use aircall-blocks/migrate-dashboard.
|
|
10
|
+
type: sub-skill
|
|
11
|
+
library: aircall-blocks
|
|
12
|
+
requires:
|
|
13
|
+
- aircall-blocks/migrate-dashboard
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Catch-up checklist — are you using the shared component, the latest way?
|
|
17
|
+
|
|
18
|
+
This is **not** a migration skill. It is for code that is **already** on `@aircall/ds` /
|
|
19
|
+
`@aircall/blocks` (the extension especially). Newer releases keep adding components and
|
|
20
|
+
props that supersede patterns written earlier — this checklist confirms each area uses the
|
|
21
|
+
current shared component the intended way, and points you to the skill that does the swap.
|
|
22
|
+
|
|
23
|
+
## How to run
|
|
24
|
+
|
|
25
|
+
For each row:
|
|
26
|
+
|
|
27
|
+
1. **`grep`** the hint to find candidates in the code.
|
|
28
|
+
2. **Check** whether the current pattern is missing or on an older shape (the "Red flag" column).
|
|
29
|
+
3. **Apply** the shared component/prop; open the linked skill for the detailed swap.
|
|
30
|
+
4. **Verify green** — `tsc --noEmit`, tests, lint.
|
|
31
|
+
|
|
32
|
+
Skip a row cleanly if the area isn't present in the codebase — this is a spot-check list, not a required sweep.
|
|
33
|
+
|
|
34
|
+
## Keeping this list evolutive (maintenance rules)
|
|
35
|
+
|
|
36
|
+
- **Add a row** whenever a release supersedes an existing pattern — a new component *or* a new prop/capability on one you already use.
|
|
37
|
+
- **`Added`** is write-once: the month a row was introduced. Never bump it — it lets someone who ran the checklist last quarter jump to what's new. (This is not a version stamp on the skill; rows are keyed by pattern, not version.)
|
|
38
|
+
- **Retire a row** once its old pattern is effectively gone from consumers — prune, don't accumulate.
|
|
39
|
+
- Keep every row **one line**. Deep detail lives in the linked skill, not here. When a group gets long, split it.
|
|
40
|
+
|
|
41
|
+
## Layout & navigation
|
|
42
|
+
|
|
43
|
+
| Use (current) | Red flag to look for | Detail / skill | Added |
|
|
44
|
+
| --- | --- | --- | --- |
|
|
45
|
+
| `DashboardPageHeader` | a hand-rolled header `<div>` with title + actions, or the legacy `PageHeader` from `@dashboard/library` | `@aircall/blocks#aircall-blocks/migrate-dashboard/page-header` | 2026-07 |
|
|
46
|
+
| `StandalonePage` | a full-screen page not wrapped in it | `@aircall/blocks#aircall-blocks/migrate-dashboard/dashboard-page` | 2026-07 |
|
|
47
|
+
| `DashboardPageContent` at extension page level | an extension page that skips it, re-renders its own outer `DashboardPage`/sidebar shell, or still passes `className="h-full"` (now baked into the component) | in an extension the `DashboardPage` shell is provided by the Sandbox and (in integrate mode) by dashboard-v4, so each page returns just `<DashboardPageContent data-test="…">` + `DashboardPageHeader` + `DashboardPageMain`; `@aircall/blocks#aircall-blocks/migrate-dashboard/dashboard-page` | 2026-07 |
|
|
48
|
+
| `Tabs` (`@aircall/ds`) | `@aircall/tractor` Tab imports or a custom tab bar (low priority — mostly done) | `@aircall/ds#aircall-ds/migrate-tractor/tabs` | 2026-07 |
|
|
49
|
+
|
|
50
|
+
## Data display
|
|
51
|
+
|
|
52
|
+
| Use (current) | Red flag to look for | Detail / skill | Added |
|
|
53
|
+
| --- | --- | --- | --- |
|
|
54
|
+
| `Card` (`@aircall/ds`) | a custom card `<div>` with border/shadow/padding on grid pages | `@aircall/blocks#aircall-blocks/migrate-dashboard/card` | 2026-07 |
|
|
55
|
+
| `SettingCard` (`@aircall/blocks`) | `Paper` / `PaperForm` from `@dashboard/library`, or a settings section already migrated to raw `Card` with a hand-rolled title/description/toggle or a hand-placed save bar | use `SettingCard` (+ parts): auto level backgrounds + level-aware title, nesting auto-detected, `form.CardSaveBar` as a direct child; `@aircall/blocks#aircall-blocks/migrate-dashboard/setting-card` | 2026-07 |
|
|
56
|
+
| `DataTable` infinite scroll | a "Load more" button, or `DataTable` without `virtualizeRows`+`fillHeight` on a paginated table | pass `virtualizeRows fillHeight` + `onFetchMore`; `@aircall/blocks#aircall-blocks/migrate-dashboard/data-table` | 2026-07 |
|
|
57
|
+
| `DataTable` selection → `ActionBar` | old `enableRowSelection`/`rowSelection`, or a custom bulk-action bar | use `selectionMode`/`selectedKeys` (drives `ActionBar`); `@aircall/blocks#aircall-blocks/migrate-dashboard/data-table` | 2026-07 |
|
|
58
|
+
| `HoverCard` | a custom info-on-hover popup (old `InfoPopup`) | `@aircall/blocks#aircall-blocks/migrate-dashboard/info-popup` | 2026-07 |
|
|
59
|
+
| Re-evaluate `List`/`ListItem` | an `ItemGroup`/`Item` list that is really editable data or tabular data | **check with a designer**: could be `InlineEditMultiselect` or `Table`/`DataTable` instead — `@aircall/blocks#aircall-blocks/migrate-dashboard/data-table` | 2026-07 |
|
|
60
|
+
|
|
61
|
+
## Forms
|
|
62
|
+
|
|
63
|
+
| Use (current) | Red flag to look for | Detail / skill | Added |
|
|
64
|
+
| --- | --- | --- | --- |
|
|
65
|
+
| `Form*Field` family | `useState`-per-field, or bare ds `Field`/`Input` wired by hand | input/select/combobox/multi-combobox/textarea/switch/radio/slider/toggle-group/OTP/numeric; `@aircall/blocks#aircall-blocks/migrate-dashboard/form-wizard` | 2026-07 |
|
|
66
|
+
| `FormArrayField` | manual add/remove-row state for dynamic lists | `@aircall/blocks#aircall-blocks/migrate-dashboard/form-wizard` | 2026-07 |
|
|
67
|
+
| `SubmitButton` | manual `disabled`/`loading` wiring on a submit button | reads `canSubmit`/`isSubmitting` automatically; `@aircall/blocks#aircall-blocks/migrate-dashboard/form-wizard` | 2026-07 |
|
|
68
|
+
| Field label/description affordances | labels without the newer affordances | `orientation`/`controlPosition` inline layouts, `FieldLabelInfo` (ⓘ), `FieldLabelAside`, contextual vs instructional descriptions; `@aircall/ds#aircall-ds/migrate-tractor/form-and-field` | 2026-07 |
|
|
69
|
+
| `DataCombobox` | a hand-rolled async/paginated combobox, or a custom table filter | `@aircall/ds#aircall-ds/migrate-tractor/combobox` (§5c) | 2026-07 |
|
|
70
|
+
|
|
71
|
+
## Inputs
|
|
72
|
+
|
|
73
|
+
| Use (current) | Red flag to look for | Detail / skill | Added |
|
|
74
|
+
| --- | --- | --- | --- |
|
|
75
|
+
| `NumericInput` | `<Input type="number">`, a custom stepper, or the legacy `NumberInput` from `@dashboard/library`, for numeric fields | prefix/suffix + up/down controls; via `FormNumericField` in forms | 2026-07 |
|
|
76
|
+
|
|
77
|
+
## File upload
|
|
78
|
+
|
|
79
|
+
| Use (current) | Red flag to look for | Detail / skill | Added |
|
|
80
|
+
| --- | --- | --- | --- |
|
|
81
|
+
| `Dropzone` | `react-dropzone` / a custom drag-and-drop wrapper/ Dropzone from dashboard/library | `@aircall/ds#aircall-ds/migrate-tractor/dropzone` | 2026-07 |
|
|
82
|
+
| `Attachment` | a custom file-row/list after uploading a file via dropzone | `AttachmentGroup` + `Attachment`; `@aircall/ds#aircall-ds/adopt-attachment` | 2026-07 |
|
|
83
|
+
|
|
84
|
+
## Links & navigation
|
|
85
|
+
|
|
86
|
+
| Use (current) | Red flag to look for | Detail / skill | Added |
|
|
87
|
+
| --- | --- | --- | --- |
|
|
88
|
+
| `Anchor` or `Button variant="ghost"` | `Button variant="link"` | `Anchor` if it navigates to a page; `Button variant="ghost"` (or another variant) if it triggers an action — `@aircall/ds#aircall-ds/migrate-tractor/link` | 2026-07 |
|
|
89
|
+
|
|
90
|
+
## Audio player
|
|
91
|
+
|
|
92
|
+
| Use (current) | Red flag to look for | Detail / skill | Added |
|
|
93
|
+
| --- | --- | --- | --- |
|
|
94
|
+
| Audio primitives (`@aircall/ds`) | an old Tractor or custom audio player | refer to `@aircall/ds#aircall-ds/migrate-audio-player` and pick the pattern that best fits the current implementation; a skip button is available, and multiple players wrap in `AudioPlayerManager` | 2026-07 |
|
|
95
|
+
|
|
96
|
+
## Editable
|
|
97
|
+
|
|
98
|
+
| Use (current) | Red flag to look for | Detail / skill | Added |
|
|
99
|
+
| --- | --- | --- | --- |
|
|
100
|
+
| `InlineEdit` family | `EditableInput` / `EditableTextarea` / `EditableSelect` from `@dashboard/library`, or a custom click-to-edit control | `InlineEditInput`/`InlineEditTextarea`/`InlineEditSelect`, multi via `InlineEditMultiselect` (data-driven accessor API like `DataCombobox`) | 2026-07 |
|
|
101
|
+
|
|
102
|
+
## Business tags
|
|
103
|
+
|
|
104
|
+
| Use (current) | Red flag to look for | Detail / skill | Added |
|
|
105
|
+
| --- | --- | --- | --- |
|
|
106
|
+
| `SentimentBadge` | a custom mood/sentiment pill | `@aircall/blocks` leaf component | 2026-07 |
|
|
107
|
+
| `ScoreBadge` | a custom score/percentage pill | `@aircall/blocks` leaf component | 2026-07 |
|
|
108
|
+
| `RoleBadge` | a custom role tag | `<RoleBadge role="admin" />` (`@aircall/blocks`) | 2026-07 |
|
|
109
|
+
| `Badge` / `BadgeGroup` | custom tag chips or a stacked tag row | `Badge` with `legacyColor` (stored hex) or `color`+`tone`; `BadgeGroup` for a stack — `@aircall/ds#aircall-ds/migrate-tractor/badge` | 2026-07 |
|
|
@@ -7,7 +7,6 @@ description: >
|
|
|
7
7
|
outer layout, a full-page wizard/creation flow, or a page header+content scaffold.
|
|
8
8
|
type: sub-skill
|
|
9
9
|
library: aircall-blocks
|
|
10
|
-
library_version: "0.5.1"
|
|
11
10
|
requires:
|
|
12
11
|
- aircall-blocks/setup
|
|
13
12
|
- aircall-blocks/migrate-dashboard
|
|
@@ -137,6 +136,45 @@ import {
|
|
|
137
136
|
|
|
138
137
|
`DashboardPageBanner` is available for a page-level banner. The page header itself migrates per `@aircall/blocks#aircall-blocks/migrate-dashboard/page-header`.
|
|
139
138
|
|
|
139
|
+
### Extension pages — start at `DashboardPageContent`, not `DashboardPage`
|
|
140
|
+
|
|
141
|
+
The block above is the **full** shell, which is what the host renders. Inside an
|
|
142
|
+
**extension** you do **not** render `DashboardPage` / `DashboardSidebarProvider` /
|
|
143
|
+
`DashboardSidebar` yourself — that outer shell is provided by the Sandbox during local
|
|
144
|
+
dev (`@dashboard/extension-script`) and by dashboard-v4 in integrate mode. Each extension
|
|
145
|
+
page starts at `DashboardPageContent` and returns just the content card: header +
|
|
146
|
+
`DashboardPageMain`.
|
|
147
|
+
|
|
148
|
+
Do **not** pass `className="h-full"` — the height is now baked into `DashboardPageContent`
|
|
149
|
+
at the component level. Just pass your `data-test`.
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
// conversation-center-ext/src/pages/contact_insights/ContactInsights.page.tsx
|
|
153
|
+
import {
|
|
154
|
+
DashboardPageContent,
|
|
155
|
+
DashboardPageHeader,
|
|
156
|
+
DashboardPageHeaderTitle,
|
|
157
|
+
DashboardPageMain
|
|
158
|
+
} from '@aircall/blocks';
|
|
159
|
+
|
|
160
|
+
export function ContactInsightsPage() {
|
|
161
|
+
const { t } = useTranslation();
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<DashboardPageContent data-test="contact-insights-page">
|
|
165
|
+
<DashboardPageHeader>
|
|
166
|
+
<DashboardPageHeaderTitle size="lg">
|
|
167
|
+
{t('ai_assist.live_assist.pre_call_insights.title')}
|
|
168
|
+
</DashboardPageHeaderTitle>
|
|
169
|
+
</DashboardPageHeader>
|
|
170
|
+
<DashboardPageMain className="flex flex-col gap-4 overflow-scroll">
|
|
171
|
+
<ContactInsights hasTitle={false} />
|
|
172
|
+
</DashboardPageMain>
|
|
173
|
+
</DashboardPageContent>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
140
178
|
### Page-level notices (beta/feature/trial banners) → `NotificationQueue`
|
|
141
179
|
|
|
142
180
|
For app/page-level notices (e.g. a "beta feature" banner), prefer the ds **`NotificationQueue`** over an inline `Banner` — it stacks, dedupes, and orders by priority, and renders wherever you place a `NotificationSlot` (e.g. at the very top of the page, above the header). Mechanism (from `packages/ds/src/components/notification-queue.tsx`):
|
|
@@ -6,7 +6,6 @@ description: >
|
|
|
6
6
|
Load when a file imports LoadMoreTable or LoadMoreStrategy from @dashboard/library.
|
|
7
7
|
type: sub-skill
|
|
8
8
|
library: aircall-blocks
|
|
9
|
-
library_version: "0.5.1"
|
|
10
9
|
requires:
|
|
11
10
|
- aircall-blocks/setup
|
|
12
11
|
- aircall-blocks/migrate-dashboard
|
|
@@ -166,6 +165,16 @@ Key changes:
|
|
|
166
165
|
- `h="420px"` (Tractor Flex prop) → wrap in a flex container with a bounded height + pass `fillHeight`
|
|
167
166
|
- `noDataMessage` string → `emptyState` ReactNode
|
|
168
167
|
|
|
168
|
+
> **Row virtualization (large accumulated lists).** For an infinite-scroll table that
|
|
169
|
+
> piles up many pages, also pass `virtualizeRows` so only visible rows are in the DOM
|
|
170
|
+
> (requires `fillHeight`). **Tradeoff:** `virtualizeRows` implies `fixedLayout`
|
|
171
|
+
> (`table-layout: fixed`) — every column then takes its `size`, and columns with no
|
|
172
|
+
> `size` fall back to TanStack's **150px** default. A table that relied on content-based
|
|
173
|
+
> auto widths (e.g. a narrow icon column) will visibly snap to 150px columns. Add an
|
|
174
|
+
> explicit `size` to each `ColumnDef` to keep the intended widths, and check them in the
|
|
175
|
+
> browser (`tsc`/tests won't catch a width regression). See
|
|
176
|
+
> `@aircall/ds#aircall-ds/migrate-tractor/data-table` §8 for details.
|
|
177
|
+
|
|
169
178
|
### 4b. Button strategy
|
|
170
179
|
|
|
171
180
|
`LoadMoreStrategy.Button` has no direct prop equivalent in `DataTable`. Render the
|
|
@@ -7,7 +7,6 @@ description: >
|
|
|
7
7
|
FormWizard, useFormWizard, or FormField from @dashboard/library.
|
|
8
8
|
type: sub-skill
|
|
9
9
|
library: aircall-blocks
|
|
10
|
-
library_version: "0.5.1"
|
|
11
10
|
requires:
|
|
12
11
|
- aircall-blocks/setup
|
|
13
12
|
- aircall-blocks/migrate-dashboard
|
|
@@ -247,6 +246,46 @@ spread the control bundle onto the matching DS primitive.
|
|
|
247
246
|
| combobox (single) | `FormComboboxField` | switch | `FormSwitchField` |
|
|
248
247
|
| combobox (multi) | `FormMultiComboboxField` | number | `FormNumericField` |
|
|
249
248
|
| textarea | `FormTextareaField` | slider | `FormSliderField` |
|
|
249
|
+
| toggle group | `FormToggleGroupField` | | |
|
|
250
|
+
|
|
251
|
+
### Building a wrapper for an unlisted primitive (`FormFieldBase`)
|
|
252
|
+
|
|
253
|
+
For a DS primitive without a shipped `Form*Field`, build one on `FormFieldBase` — it owns the field binding + the shared `Field`/`Label`/`Description`/`Error` shell; you supply a `buildControl` that maps the typed field to the control bundle. This is the shape every shipped wrapper follows (here re-creating `FormSwitchField`, which already ships — copy the pattern for a genuinely new primitive):
|
|
254
|
+
|
|
255
|
+
```tsx
|
|
256
|
+
import { FormFieldBase, type BoundForm, type FormFieldValidators, type TypedField } from '@aircall/blocks';
|
|
257
|
+
import { Switch } from '@aircall/ds';
|
|
258
|
+
import type { DeepKeysOfType } from '@tanstack/react-form';
|
|
259
|
+
|
|
260
|
+
type SwitchControl = {
|
|
261
|
+
switchProps: { checked: boolean; onCheckedChange: (checked: boolean) => void; onBlur: () => void };
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
function switchControl(field: TypedField<boolean>): SwitchControl {
|
|
265
|
+
return {
|
|
266
|
+
switchProps: {
|
|
267
|
+
checked: field.state.value,
|
|
268
|
+
onCheckedChange: checked => field.handleChange(checked),
|
|
269
|
+
onBlur: field.handleBlur,
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function FormSwitchField<
|
|
275
|
+
TForm extends BoundForm,
|
|
276
|
+
TName extends DeepKeysOfType<TForm['state']['values'], boolean> // only boolean fields
|
|
277
|
+
>(props: {
|
|
278
|
+
form: TForm;
|
|
279
|
+
name: TName;
|
|
280
|
+
label: string;
|
|
281
|
+
validators?: FormFieldValidators<TForm['state']['values'], TName>;
|
|
282
|
+
children: (field: TypedField<boolean>, control: SwitchControl) => React.ReactNode;
|
|
283
|
+
}) {
|
|
284
|
+
return <FormFieldBase<boolean, SwitchControl> {...props} buildControl={switchControl} />;
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
`DeepKeysOfType<…, boolean>` restricts `name` to fields whose value type matches the control (here `boolean`), so the form stays type-safe.
|
|
250
289
|
|
|
251
290
|
## Multi-step gotchas
|
|
252
291
|
|