UNPKG

@aircall/blocks

Version:

Aircall Blocks — higher-level UI compositions built on @aircall/ds

147 lines (123 loc) 13.8 kB
--- name: aircall-blocks/migrate-dashboard description: > Migrate a file from @dashboard/library to @aircall/blocks / @aircall/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 @dashboard/library → @aircall/blocks / @aircall/ds This is the overview router for the `@dashboard/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 `@aircall/blocks` / `@aircall/ds` code is on the > latest shared components and props (rather than convert `@dashboard/library` imports), load > `@aircall/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 `@aircall/blocks#aircall-blocks/setup` (it builds on `@aircall/ds#aircall-ds/setup`): install `@aircall/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 `@aircall/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 `@dashboard/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 `@dashboard/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 `@aircall/ds#aircall-ds/migrate-tractor`; icons via `@aircall/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 `@dashboard/library` UI imports remain. ## Cross-cutting note `@dashboard/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 `'@aircall/blocks'`. - Import DS primitives (Card, Spinner, DataTable, Combobox, ItemGroup, etc.) from `'@aircall/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 **`@aircall/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 UIopen, 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 `@dashboard/library` exports do NOT have an equivalent in `@aircall/blocks` or `@aircall/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 `@aircall/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 `@dashboard/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 | @dashboard/library | Target (pkg) | Recipe to load | Status | |---|---|---|---| | `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 | | `PageHeader` | `DashboardPageHeader` (+ `DashboardPageHeaderTitle` / `DashboardPageHeaderActions` / `DashboardPageHeaderAction` / `DashboardPageHeaderNav` / `DashboardPageHeaderDescription`) (@aircall/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) (@aircall/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 (@aircall/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) (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/card | available | | `LoadMoreTable` | `DataTable` (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/data-table | available | | `MultiSearchSelect` + `MultiInlineSearchSelect` + `MultiSelectOption` | `Combobox` with `multiple` (multi-select) (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/combobox | available | | `SingleSearchSelect` / `SearchSelect` / single-value `MultiSelect` | `Combobox` WITHOUT `multiple` (single-select) (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/combobox | available | | `FormWizard` + `useFormWizard` + `FormField` | `useForm`, `CommonForm`, `FormInputField`/`FormSelectField`/`FormComboboxField`/etc., `CardSaveBar`/`SaveBar` (@aircall/blocks) | load @aircall/blocks#aircall-blocks/migrate-dashboard/form-wizard | available | | `Loading` | `Spinner` (and `Skeleton` for content placeholders) (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/loading | available | | `List` + `ListItem` | `ItemGroup`, `Item`, `ItemMedia`, `ItemContent`, `ItemActions` (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/list | available | | `Tile` + `TileHeader` + `TileValue` | `KpiCard` + `KpiValue` + `KpiDescription` (+ `KpiValueSkeleton` / `KpiDescriptionSkeleton`) with DS `CardHeader` / `CardTitle` / `CardContent` / `CardAction` (@aircall/blocks + @aircall/ds) | load @aircall/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) (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/layout | available | | `AudioPlayer` | `Audio`, `AudioPlayerRow`, `AudioPlayerButton`, `AudioPlayerBar`, `AudioPlayerTime`, `AudioPlayerSpeed`, `AudioPlayerSkip`, `AudioPlayerManager`, `useAudioPlayer` (@aircall/ds) | load @aircall/ds#aircall-ds/migrate-audio-player | available | | `InfoPopup` + `InfoPopupTrigger` + `InfoPopupContent` | `HoverCard`, `HoverCardTrigger`, `HoverCardContent` (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/info-popup | available | | `CopyToClipboardButton` + `CopyToClipboardText` | `CopyButton`, `CopyButtonIcon`, `CopyButtonLabel` (@aircall/blocks) | load @aircall/blocks#aircall-blocks/migrate-dashboard/copy-button | available | | `ToggleRow` | `Field` (orientation="horizontal") + `Switch` + `FieldLabel` + optional `FieldDescription` (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/toggle-row | available | | `AddButton` + `SaveButton` + `LoadingButton` | `Button` (@aircall/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 (@aircall/ds) | inline — no sub-skill | available | | `RadioBoxGroup` + `RadioBox` | `RadioGroup` + `RadioGroupItem` wrapped in `Field` + `FieldLabel` + `FieldContent` (@aircall/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 (@aircall/ds) | inline — `<Badge legacyColor="#0662B5">Sales</Badge>` | available | | `RolesTags` | `RoleBadge` with `role` prop (`"owner"` \| `"admin"` \| `"supervisor"` \| `"agent"`) (@aircall/blocks) | inline — `<RoleBadge role="admin" />` | available | | `Count` | `CounterBadge` — pass the capped value as children: `<CounterBadge>{n > 99 ? '99+' : n}</CounterBadge>` (@aircall/ds) | inline — no size or max props | available | | `Avatar` | `Avatar`, `AvatarImage`, `AvatarFallback` compound (@aircall/ds) | load @aircall/ds#aircall-ds/migrate-tractor/avatar | available | | `ProgressBar` + `OptimisticProgressBar` | `Progress` with `value` prop (0100) (@aircall/ds) | inline — `<Progress value={60} />` | available | | `ShadowScrollContainer` | `ScrollArea` with `scrollFade` prop — use a fixed `h-[]` not `max-h-[]` on the root (@aircall/ds) | inline — `<ScrollArea scrollFade className="h-[400px] rounded-md border">` | available | | `AccordionSection` | `Accordion`, `AccordionItem`, `AccordionTrigger`, `AccordionContent` (@aircall/ds) | load @aircall/ds#aircall-ds/migrate-tractor/accordion | available | | `ConfirmationModal` | `AlertDialog` and its parts (@aircall/ds) | load @aircall/ds#aircall-ds/migrate-tractor/dialog | available | | `Tab` | `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent` (@aircall/ds) | load @aircall/ds#aircall-ds/migrate-tractor/tabs | available | | `Skeleton` + `SkeletonText` | `Skeleton` (@aircall/ds) | load @aircall/ds#aircall-ds/migrate-tractor/skeleton | available | | `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 | | `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 | | `EmojiPicker` | `EmojiPicker` (@aircall/ds) | inline — no sub-skill | available | | `DateSelect` | `Input` + `Popover` + `Calendar` (@aircall/ds) | inline — a `Calendar` in a `Popover` behind an `Input` trigger | available | | `TagStack` | `BadgeGroup` (@aircall/ds) | inline — `<BadgeGroup>` wrapping `Badge` children | available | | `TagHighlightTextarea` | `RichTextarea` with `#tag` colored pills (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/rich-textarea | available | | `AvailabilitySelect` / `Select` | `Select` (@aircall/ds) | load @aircall/ds#aircall-ds/migrate-tractor/select | available | | `Chevron` | `Icon` (@aircall/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) (@aircall/blocks) | load @aircall/blocks#aircall-blocks/migrate-dashboard/form-wizard | available | | `TimeSlotsForm` | `useForm` + 2 blocks (@aircall/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 `@dashboard/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.