@aircall/blocks
Version:
Aircall Blocks — higher-level UI compositions built on @aircall/ds
147 lines (123 loc) • 13.8 kB
Markdown
---
name: aircall-blocks/migrate-dashboard
description: >
Migrate a file from /library to @aircall/blocks / /ds. Load FIRST
to get the full component-level routing table before picking a recipe. Identifies what
has a blocks/ds equivalent, what is out of scope, and which sub-skill to load next.
type: core
library: aircall-blocks
requires:
- aircall-blocks/setup
sources:
- "aircall/hydra:docs/migration-guides/dashboard-lib-to-blocks/AGENTS.md"
---
# Migrate /library → @aircall/blocks / /ds
This is the overview router for the `/library` migration. Load it first to
identify the right sub-skill for each component you are migrating, and to determine what
falls outside the scope of blocks and ds entirely.
> **Already migrated?** To *verify* existing `/blocks` / `/ds` code is on the
> latest shared components and props (rather than convert `/library` imports), load
> `/blocks#aircall-blocks/migrate-dashboard/catch-up-checklist`.
## How to run this migration (end-to-end)
Migrate incrementally — one screen/file at a time, shipping each green:
0. **Set up once** — load `/blocks#aircall-blocks/setup` (it builds on
`/ds#aircall-ds/setup`): install `/blocks` + `@aircall/ds`, import both
`globals.css` bundles, and mount the DS root providers — including `DsI18nProvider`
(under your react-i18next provider, fed the user's language; importing `/blocks`
registers a `blocks` i18n namespace, so it needs DS's i18n engine) and
`NotificationQueueProvider` if you use banners. Add the jsdom test shims from setup.
(Loaded automatically via `requires`, but do the wiring first.)
1. **Inventory** — list the file's `/library` imports. The routing table below
says which have a blocks/ds equivalent and which are out of scope (hooks/utils/constants
don't migrate — leave them on `/library`).
2. **Migrate** — load the per-area recipe from the routing table for each UI component. Any
Tractor primitives in the same file migrate via `/ds#aircall-ds/migrate-tractor`;
icons via `/ds#aircall-ds/migrate-icons`.
3. **Verify green** — `tsc --noEmit`, tests (DS popups/Switch need the setup jsdom shims),
biome/lint.
4. **Repeat** until no in-scope `/library` UI imports remain.
## Cross-cutting note
`/library` mixes UI components with non-UI utilities. Only the UI components
migrate. When writing replacement code:
- Import block-level compositions (page layout, empty states, form layer) from
`'/blocks'`.
- Import DS primitives (Card, Spinner, DataTable, Combobox, ItemGroup, etc.) from
`'/ds'`.
- Never mix the two import paths for the same logical component — pick the package that
owns it per the table below.
## Forms — never local state
Any form that collects and submits data migrates to **`/blocks` `useForm` + the
`Form*Field` wrappers** (the Storybook-proven `CommonForm` pattern) — NOT React
`useState` per field, and NOT bare ds `Field`/`Input` primitives wired by hand. The form
owns field state, validation, dirty/`canSubmit`/`isSubmitting`, and error display; those
are what drive `SubmitButton`/`CardSaveBar`. Hand-rolled `useState` bypasses all of it and
must be rewritten, not preserved, during migration. (`useState` for non-field UI — open,
active tab — is fine.) Converting a large legacy `useState` form is a deliberate refactor,
not a 1:1 swap. See `…/migrate-dashboard/form-wizard` and `@aircall/ds#aircall-ds/migrate-tractor/form-and-field`.
## Out of scope
The following `/library` exports do NOT have an equivalent in `@aircall/blocks`
or `/ds`. Do not try to map them to a DS component. They need a shared
utils/data home or must stay in the consuming app.
**Hooks**: `useGraphQuery`, `useGraphMutation`, `useToast`, `useToggle`,
`useBroadcastChannel`
**Helpers / utils**: `generateRandomUUID`, `isTruthy`, `toFixedNumber`,
`capitalizeFirstLetter`, `getInitials`
**Constants, types, and contexts**: `ROLE_NAME`, `RESOURCE`,
`NavigationBlockerProvider`, `ClientError`
**UI with no blocks/ds equivalent yet**: some `@dashboard/library` UI components have
no target in `/blocks` or `@aircall/ds` yet (e.g. `PieChart` and other charts,
`TileLegend`). If a UI component is not in the routing table below and
not listed above, leave it imported from `/library` for now — do not force a
migration or invent a target.
## Not yet migratable (tracked elsewhere)
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).
## Component routing table
| /library | Target (pkg) | Recipe to load | Status |
|---|---|---|---|
| `MessageScreen` family + `UnknownError` | `ComingSoonEmptyState`, `RestrictedAccessEmptyState`, `NotFoundEmptyState`, `NoDataEmptyState`, `NoSupportEmptyState`, `UnknownErrorEmptyState` and the `EmptyState*` parts (/blocks) | load @aircall/blocks#aircall-blocks/migrate-dashboard/empty-states | available |
| `PageHeader` | `DashboardPageHeader` (+ `DashboardPageHeaderTitle` / `DashboardPageHeaderActions` / `DashboardPageHeaderAction` / `DashboardPageHeaderNav` / `DashboardPageHeaderDescription`) (/blocks) | load @aircall/blocks#aircall-blocks/migrate-dashboard/page-header | available |
| Page / screen shell — incl. full-page flows (Campaign Creation, Add Contacts) | `DashboardPage` (standard: sidebar + header + tabs + content) / `DashboardStandalonePage` (full-page, no sidebar) (/blocks) | load @aircall/blocks#aircall-blocks/migrate-dashboard/dashboard-page | available |
| `Paper` / `PaperForm` (settings-page surface) | `SettingCard` (+ `SettingCardHeader` / `SettingCardTitle` / `SettingCardDescription` / `SettingCardAction` / `SettingCardContent`), plus `useForm` + `CardSaveBar` for the save bar (/blocks). Keep the `errorBoundary` (`ScopedErrorBoundary`) **outside** the card. | load @aircall/blocks#aircall-blocks/migrate-dashboard/setting-card | available |
| `Paper` (generic non-settings surface — tile/container, no title/toggle semantics) | `Card` (+ parts) (/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/card | available |
| `LoadMoreTable` | `DataTable` (/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/data-table | available |
| `MultiSearchSelect` + `MultiInlineSearchSelect` + `MultiSelectOption` | `Combobox` with `multiple` (multi-select) (/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/combobox | available |
| `SingleSearchSelect` / `SearchSelect` / single-value `MultiSelect` | `Combobox` WITHOUT `multiple` (single-select) (/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/combobox | available |
| `FormWizard` + `useFormWizard` + `FormField` | `useForm`, `CommonForm`, `FormInputField`/`FormSelectField`/`FormComboboxField`/etc., `CardSaveBar`/`SaveBar` (/blocks) | load @aircall/blocks#aircall-blocks/migrate-dashboard/form-wizard | available |
| `Loading` | `Spinner` (and `Skeleton` for content placeholders) (/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/loading | available |
| `List` + `ListItem` | `ItemGroup`, `Item`, `ItemMedia`, `ItemContent`, `ItemActions` (/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/list | available |
| `Tile` + `TileHeader` + `TileValue` | `KpiCard` + `KpiValue` + `KpiDescription` (+ `KpiValueSkeleton` / `KpiDescriptionSkeleton`) with DS `CardHeader` / `CardTitle` / `CardContent` / `CardAction` (/blocks + @aircall/ds) | load /blocks#aircall-blocks/migrate-dashboard/tile | available |
| `GridLayout` + `GridItem` + `Gap` | native `<div>` + Tailwind grid/flex/gap utilities (NO component import needed — these are layout primitives) (/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/layout | available |
| `AudioPlayer` | `Audio`, `AudioPlayerRow`, `AudioPlayerButton`, `AudioPlayerBar`, `AudioPlayerTime`, `AudioPlayerSpeed`, `AudioPlayerSkip`, `AudioPlayerManager`, `useAudioPlayer` (/ds) | load @aircall/ds#aircall-ds/migrate-audio-player | available |
| `InfoPopup` + `InfoPopupTrigger` + `InfoPopupContent` | `HoverCard`, `HoverCardTrigger`, `HoverCardContent` (/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/info-popup | available |
| `CopyToClipboardButton` + `CopyToClipboardText` | `CopyButton`, `CopyButtonIcon`, `CopyButtonLabel` (/blocks) | load @aircall/blocks#aircall-blocks/migrate-dashboard/copy-button | available |
| `ToggleRow` | `Field` (orientation="horizontal") + `Switch` + `FieldLabel` + optional `FieldDescription` (/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/toggle-row | available |
| `AddButton` + `SaveButton` + `LoadingButton` | `Button` (/ds) — `AddButton` → `Button` with leading icon; `LoadingButton` loading state → `Button disabled` + `Spinner`; `SaveButton` saved state → `Button variant="outline"` + check icon | load @aircall/ds#aircall-ds/migrate-tractor/button | available |
| `ConditionalTooltip` | Conditional JSX: wrap children in `<Tooltip>` / `<TooltipTrigger>` / `<TooltipContent>` when condition is true, render children bare when false — no wrapper component needed (/ds) | inline — no sub-skill | available |
| `RadioBoxGroup` + `RadioBox` | `RadioGroup` + `RadioGroupItem` wrapped in `Field` + `FieldLabel` + `FieldContent` (/ds) | inline — see @aircall/ds#aircall-ds/migrate-tractor/form-and-field for Field patterns | available |
| `TagBeautified` | `Badge` with `legacyColor` prop for stored hex colors; `Badge` with `color` + `tone` props for new tags (/ds) | inline — `<Badge legacyColor="#0662B5">Sales</Badge>` | available |
| `RolesTags` | `RoleBadge` with `role` prop (`"owner"` \| `"admin"` \| `"supervisor"` \| `"agent"`) (/blocks) | inline — `<RoleBadge role="admin" />` | available |
| `Count` | `CounterBadge` — pass the capped value as children: `<CounterBadge>{n > 99 ? '99+' : n}</CounterBadge>` (/ds) | inline — no size or max props | available |
| `Avatar` | `Avatar`, `AvatarImage`, `AvatarFallback` compound (/ds) | load @aircall/ds#aircall-ds/migrate-tractor/avatar | available |
| `ProgressBar` + `OptimisticProgressBar` | `Progress` with `value` prop (0–100) (/ds) | inline — `<Progress value={60} />` | available |
| `ShadowScrollContainer` | `ScrollArea` with `scrollFade` prop — use a fixed `h-[…]` not `max-h-[…]` on the root (/ds) | inline — `<ScrollArea scrollFade className="h-[400px] rounded-md border">` | available |
| `AccordionSection` | `Accordion`, `AccordionItem`, `AccordionTrigger`, `AccordionContent` (/ds) | load @aircall/ds#aircall-ds/migrate-tractor/accordion | available |
| `ConfirmationModal` | `AlertDialog` and its parts (/ds) | load @aircall/ds#aircall-ds/migrate-tractor/dialog | available |
| `Tab` | `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent` (/ds) | load @aircall/ds#aircall-ds/migrate-tractor/tabs | available |
| `Skeleton` + `SkeletonText` | `Skeleton` (/ds) | load @aircall/ds#aircall-ds/migrate-tractor/skeleton | available |
| `Editable` | `InlineEditInput` / `InlineEditTextarea` / `InlineEditSelect` / `InlineEditMultiselect` (+ sub-parts) (/ds) | inline — click-to-edit; single-value via `InlineEditInput`/`InlineEditTextarea`/`InlineEditSelect`, multi via `InlineEditMultiselect`. Data-driven accessor API like `DataCombobox` (`items` + `getItemValue`/`getItemLabel`) | available |
| `Dropzone` | `Dropzone` (/ds); render the dropped files with `Attachment` (@aircall/ds) | load /ds#aircall-ds/migrate-tractor/dropzone — display files via @aircall/ds#aircall-ds/adopt-attachment | available |
| `EmojiPicker` | `EmojiPicker` (/ds) | inline — no sub-skill | available |
| `DateSelect` | `Input` + `Popover` + `Calendar` (/ds) | inline — a `Calendar` in a `Popover` behind an `Input` trigger | available |
| `TagStack` | `BadgeGroup` (/ds) | inline — `<BadgeGroup>` wrapping `Badge` children | available |
| `TagHighlightTextarea` | `RichTextarea` with `#tag` colored pills (/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/rich-textarea | available |
| `AvailabilitySelect` / `Select` | `Select` (/ds) | load @aircall/ds#aircall-ds/migrate-tractor/select | available |
| `Chevron` | `Icon` (/react-icons) | inline — import the specific directional icon (`ChevronDown`, `ChevronRight`, …); no `<Chevron>` wrapper | available |
| `InlineForm` / `InlineFormWithSingleSearchSelect` | `useForm` + DS primitives (+ `Combobox` for the search-select variant) (/blocks) | load @aircall/blocks#aircall-blocks/migrate-dashboard/form-wizard | available |
| `TimeSlotsForm` | `useForm` + 2 blocks (/blocks) | load @aircall/blocks#aircall-blocks/migrate-dashboard/form-wizard — too specific to extract; compose at app level | available |
## How to use this router
1. Identify the `/library` export(s) in the file you are migrating.
2. Check the **Out of scope** section first — if the export is listed there, skip it.
3. For each UI component, find the matching row in the table above and load the
indicated recipe sub-skill.
4. Each sub-skill is self-contained: it carries the full prop mapping, common mistakes,
and import paths for its component family.