UNPKG

@aircall/blocks

Version:

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

260 lines (186 loc) 7.87 kB
--- name: aircall-blocks/migrate-dashboard/loading description: > Migrate @dashboard/library Loading, Loader, and Spinner to @aircall/ds Spinner (animated spinner) and Skeleton (content placeholder). Load when a file imports Loading, Loader, or Spinner from @dashboard/library. type: sub-skill library: aircall-blocks requires: - aircall-blocks/setup - aircall-blocks/migrate-dashboard sources: - "aircall/hydra:packages/ds/src/index.ts" --- This skill builds on aircall-blocks/migrate-dashboard. ## 1. Component mapping | `@dashboard/library` | `@aircall/ds` | Notes | | --- | --- | --- | | `Loading` | `Spinner` | Full-area centered spinner — replace the flex wrapper with a parent `div` using Tailwind layout classes | | `Loader` | `Spinner` | Inline spinning icon — direct 1-to-1 replacement | | `Spinner` | `Spinner` | Already a spinner — direct 1-to-1 replacement | **Content placeholders:** when `Loading` was used while async content loads (e.g. inside a card or list panel), prefer `Skeleton` over `Spinner` — it better communicates that content is incoming rather than a blocking operation. ## 2. Verified DS exports (`packages/ds/src/index.ts`) ``` Spinner, spinnerVariants Skeleton ``` ## 3. Prop mapping ### `Loading` `Spinner` | `@dashboard/library` prop | DS equivalent | Action | | --- | --- | --- | | `size` (number, e.g. `40`) | `size` (`"sm"` \| `"default"` \| `"lg"` \| `"xl"`) | Map to the nearest token (see table below) | | `color` (tractor color token, e.g. `"primary-500"`) | `className` | Use a Tailwind text-color class | | `data-test` | `data-test` | Pass through as-is | | Flex layout (`FlexProps`) | Tailwind parent | Wrap `Spinner` in a container `<div className="flex h-full w-full items-center justify-center">` | **Size mapping:** | Old `size` (px) | New `size` token | | --- | --- | | 12 | `"sm"` (12 px) | | 1316 | `"default"` (16 px) | | 1720 | `"lg"` (20 px) | | 21 | `"xl"` (24 px) | When `size` was not set, `Loading` defaulted to `40` and `Spinner` defaulted to `30` — both map to `"xl"`. ### `Loader` `Spinner` | `@dashboard/library` prop | DS equivalent | Action | | --- | --- | --- | | `size` (number) | `size` (token string) | Map using the table above | | `color` | `className` | Use a Tailwind text-color class | | `data-test` | `data-test` | Pass through as-is | ### `Skeleton` `Skeleton` has no direct `@dashboard/library` predecessor — it is introduced as a replacement for `Loading` when used during content fetch. It accepts a plain `<div>` `className` for sizing and shape. ## 4. Before / After examples ### 4a. `Loading` (full-area centered spinner) **Before (`@dashboard/library`):** ```tsx import { Loading } from '@dashboard/library'; <Loading data-test="reports-loading" size={40} color="primary-500" /> ``` **After (`@aircall/ds`):** ```tsx import { Spinner } from '@aircall/ds'; <div data-test="reports-loading" className="flex h-full w-full items-center justify-center" > <Spinner size="xl" className="text-primary" /> </div> ``` Key changes: - The flex-center wrapper that `Loading` rendered internally must now be an explicit parent `<div>`. - Numeric `size` becomes a size token string. - Tractor color token (`"primary-500"`) becomes a Tailwind text-color class (`"text-primary"`). ### 4b. `Loader` (inline spinner icon) **Before (`@dashboard/library`):** ```tsx import { Loader } from '@dashboard/library'; <Loader size={20} color="neutral-700" /> ``` **After (`@aircall/ds`):** ```tsx import { Spinner } from '@aircall/ds'; <Spinner size="lg" className="text-neutral-700" /> ``` `Loader` was already a bare spinning icon with no layout wrapper — `Spinner` is the direct replacement. ### 4c. `Spinner` (bare spinning icon) **Before (`@dashboard/library`):** ```tsx import { Spinner } from '@dashboard/library'; <Spinner size={30} /> ``` **After (`@aircall/ds`):** ```tsx import { Spinner } from '@aircall/ds'; <Spinner size="xl" /> ``` The API is nearly identical; only the `size` type changes from a numeric pixel value to a string token. ### 4d. `Loading` as a content placeholder (prefer `Skeleton`) **Before (`@dashboard/library`):** ```tsx import { Loading } from '@dashboard/library'; function StatsCard({ isLoading, value }) { return ( <div className="p-4"> {isLoading ? <Loading size={24} /> : <span>{value}</span>} </div> ); } ``` **After (`@aircall/ds`):** ```tsx import { Skeleton } from '@aircall/ds'; function StatsCard({ isLoading, value }) { return ( <div className="p-4"> {isLoading ? <Skeleton className="h-6 w-24" /> : <span>{value}</span>} </div> ); } ``` Use `Skeleton` when the spinner was filling the space of specific content that will appear once data loads. `Skeleton` communicates the shape of the incoming content, which reduces layout shift and improves perceived performance compared to a generic spinner. ## 5. Common Mistakes ### Mistake 1Keeping the `Loading` flex wrapper implicit ```tsx // WRONG — Spinner has no built-in flex-center layout; it renders as an inline SVG import { Spinner } from '@aircall/ds'; <Spinner size="xl" className="h-full w-full" /> // CORRECT — wrap in an explicit flex container to center in the available area import { Spinner } from '@aircall/ds'; <div className="flex h-full w-full items-center justify-center"> <Spinner size="xl" /> </div> ``` `Loading` rendered its own `<Flex alignItems="center" justifyContent="center">` internally. DS `Spinner` is a bare SVG icon; the centering layout must be provided by the caller. Source: `packages/ds/src/components/spinner.tsx` --- ### Mistake 2Passing a numeric pixel value to `size` ```tsx // WRONG — size accepts only the four named tokens; a number is a type error import { Spinner } from '@aircall/ds'; <Spinner size={30} /> // CORRECT — use the nearest token string import { Spinner } from '@aircall/ds'; <Spinner size="xl" /> ``` `Spinner.Props` extends `VariantProps<typeof spinnerVariants>` where `size` is `"sm" | "default" | "lg" | "xl" | null | undefined`. Passing a number does not satisfy the type and will error at compile time. Source: `packages/ds/src/components/spinner.tsx` --- ### Mistake 3Passing a Tractor color token to `className` ```tsx // WRONG — Tractor color tokens are not valid Tailwind classes and produce no styling import { Spinner } from '@aircall/ds'; <Spinner className="primary-500" /> // CORRECT — use a Tailwind text-color utility class import { Spinner } from '@aircall/ds'; <Spinner className="text-primary" /> ``` `Spinner` renders a `Loader2Icon` SVG that inherits its color from the CSS `currentColor` (the `text-current` Tailwind class is baked in via `spinnerVariants`). To tint it, apply a `text-*` Tailwind class — a bare Tractor token string like `"primary-500"` is not a recognized utility and has no effect. Source: `packages/ds/src/components/spinner.tsx` --- ### Mistake 4Using `Spinner` for content-area placeholders instead of `Skeleton` ```tsx // WRONG — a spinner implies an active blocking operation, not an inline content gap import { Spinner } from '@aircall/ds'; function UserName({ isLoading, name }) { return isLoading ? <Spinner size="sm" /> : <span>{name}</span>; } // CORRECT — use Skeleton to represent the shape of the incoming content import { Skeleton } from '@aircall/ds'; function UserName({ isLoading, name }) { return isLoading ? <Skeleton className="h-4 w-32" /> : <span>{name}</span>; } ``` `Skeleton` (an animated pulse `<div>`) is semantically and visually correct for inline content placeholders because it mirrors the dimensions of the element it replaces. `Spinner` is intended for full-area or button loading states where the action source is explicit. Source: `packages/ds/src/components/skeleton.tsx`