UNPKG

@aircall/blocks

Version:

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

394 lines (323 loc) 14.6 kB
--- name: aircall-blocks/migrate-dashboard/page-header description: > Migrate @dashboard/library PageHeader, PageHeaderLink, and GaramondTitleTypography to @aircall/blocks DashboardPageHeader and its sub-components (DashboardPageHeaderTitle, DashboardPageHeaderActions, DashboardPageHeaderAction, DashboardPageHeaderNav, DashboardPageHeaderNavBack, DashboardPageHeaderPrefix, DashboardPageHeaderTitleGroup, DashboardPageHeaderSubtitle, DashboardPageHeaderDescription). Load when a file imports PageHeader or PageHeaderLink from @dashboard/library. type: sub-skill library: aircall-blocks requires: - aircall-blocks/setup - aircall-blocks/migrate-dashboard sources: - "aircall/hydra:packages/blocks/src/index.ts" --- This skill builds on aircall-blocks/migrate-dashboard. ## 1. Component mapping | @dashboard/library | @aircall/blocks | | --- | --- | | `PageHeader` (root container) | `DashboardPageHeader` | | `PageHeader` `title` prop (small heading) | `DashboardPageHeaderTitle size="sm"` | | `PageHeader` `largeTitle` prop (large heading) | `DashboardPageHeaderTitle size="lg"` | | `PageHeader` `subtitle` prop | `DashboardPageHeaderDescription` inside `DashboardPageHeaderSubtitle` inside `DashboardPageHeaderTitleGroup` | | `PageHeader` `renderTitleRight` render prop | `DashboardPageHeaderActions` + `DashboardPageHeaderAction` | | `PageHeader` `renderExtra` render prop | Extra children appended directly inside `DashboardPageHeader` | | `PageHeader` `renderSubLeft` / `renderSubRight` render props | Children inside `DashboardPageHeaderSubtitle` | | `PageHeader` `gobackLinkRoute` + `gobackLinkText` props | `DashboardPageHeaderNav` + `DashboardPageHeaderNavBack` | | `PageHeaderLink` (standalone back-link component) | `DashboardPageHeaderNavBack` inside `DashboardPageHeaderNav` | | `PageHeader` `icon` prop (tractor `Icon` component) | `DashboardPageHeaderPrefix` (put icon/flag/avatar inside) | | `PageHeader` `avatarSrc` / `avatarInitials` / `renderAvatar` props | `DashboardPageHeaderPrefix` (put `Avatar` or equivalent inside) | | `PageHeader` `activeTabId` / `onTabChange` / `Tab.*` children | No direct equivalent — implement tabs separately below the header | | `GaramondTitleTypography` / `useGaramondFont` prop | Not reproduced — use `DashboardPageHeaderTitle size="lg"` (system font) | | `PageHeader` `titleProps` (BoxProps spread) | Remove — `DashboardPageHeaderTitleGroup` handles layout | `DashboardPageHeader` is a flat-children composition: sub-components self-place via CSS grid areas — declaration order determines render order within each area. You do **not** pass data via props on the root; every piece of content is a named sub-component child. ## 2. Imports ```tsx // All DashboardPageHeader parts from @aircall/blocks import { DashboardPageHeader, DashboardPageHeaderTitle, DashboardPageHeaderTitleGroup, DashboardPageHeaderSubtitle, DashboardPageHeaderDescription, DashboardPageHeaderActions, DashboardPageHeaderAction, DashboardPageHeaderNav, DashboardPageHeaderNavBack, DashboardPageHeaderPrefix, } from '@aircall/blocks'; // DS primitives used alongside (Tooltip, DropdownMenu, etc.) import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@aircall/ds'; ``` ## 3. Before / After ### 3a. Simple title-only header **Before (`@dashboard/library`):** ```tsx import { PageHeader } from '@dashboard/library'; function AnalyticsHeader() { return <PageHeader largeTitle="Analytics" />; } ``` **After (`@aircall/blocks`):** ```tsx import { DashboardPageHeader, DashboardPageHeaderTitle } from '@aircall/blocks'; function AnalyticsHeader() { return ( <DashboardPageHeader> <DashboardPageHeaderTitle size="lg">Analytics</DashboardPageHeaderTitle> </DashboardPageHeader> ); } ``` Key changes: - `largeTitle` string prop `DashboardPageHeaderTitle size="lg"` child with the string as children. - `title` string prop `DashboardPageHeaderTitle size="sm"` (level-2 pages that have a nav row). - No `data-test` prop — set `data-test` directly on `DashboardPageHeader` if needed. --- ### 3b. Title with actions (renderTitleRight) **Before (`@dashboard/library`):** ```tsx import { PageHeader } from '@dashboard/library'; import { Button } from '@aircall/tractor'; function CampaignsHeader() { return ( <PageHeader largeTitle="Campaigns" renderTitleRight={() => ( <Button variant="primary" size="regular">Create campaign</Button> )} /> ); } ``` **After (`@aircall/blocks`):** ```tsx import { DashboardPageHeader, DashboardPageHeaderTitle, DashboardPageHeaderActions, DashboardPageHeaderAction, } from '@aircall/blocks'; function CampaignsHeader() { return ( <DashboardPageHeader> <DashboardPageHeaderTitle size="lg">Campaigns</DashboardPageHeaderTitle> <DashboardPageHeaderActions> <DashboardPageHeaderAction variant="default">Create campaign</DashboardPageHeaderAction> </DashboardPageHeaderActions> </DashboardPageHeader> ); } ``` Key changes: - `renderTitleRight` render prop `DashboardPageHeaderActions` child containing `DashboardPageHeaderAction` elements. - Tractor `Button variant="primary"` `DashboardPageHeaderAction variant="default"` (size is fixed by the component). - Icon-only action buttons: `DashboardPageHeaderAction variant="outline" size="icon-lg"`. --- ### 3c. Back navigation (gobackLinkRoute / gobackLinkText) **Before (`@dashboard/library`):** ```tsx import { PageHeader } from '@dashboard/library'; function NumberDetailHeader() { return ( <PageHeader title="Number settings" gobackLinkRoute="/numbers" gobackLinkText="Back to numbers" renderTitleRight={() => <button>Save</button>} /> ); } ``` **After (`@aircall/blocks`):** ```tsx import { DashboardPageHeader, DashboardPageHeaderNav, DashboardPageHeaderNavBack, DashboardPageHeaderTitle, DashboardPageHeaderActions, DashboardPageHeaderAction, } from '@aircall/blocks'; import { Link } from 'react-router-dom'; function NumberDetailHeader() { return ( <DashboardPageHeader> <DashboardPageHeaderNav> <DashboardPageHeaderNavBack render={<Link to="/numbers" />}> Back to numbers </DashboardPageHeaderNavBack> </DashboardPageHeaderNav> <DashboardPageHeaderTitle size="sm">Number settings</DashboardPageHeaderTitle> <DashboardPageHeaderActions> <DashboardPageHeaderAction variant="default">Save</DashboardPageHeaderAction> </DashboardPageHeaderActions> </DashboardPageHeader> ); } ``` Key changes: - `gobackLinkRoute` + `gobackLinkText` props `DashboardPageHeaderNav` > `DashboardPageHeaderNavBack` with `render={<Link to="…" />}` (Base UI render prop — avoids a nested `<a><button>` violation). - When nav is present, use `size="sm"` on `DashboardPageHeaderTitle` (level-2 pattern). - The `PageHeaderLink` standalone export is replaced the same way. --- ### 3d. Header with prefix (icon / avatar) and subtitle **Before (`@dashboard/library`):** ```tsx import { PageHeader } from '@dashboard/library'; import { PhoneOutlined } from '@aircall/icons'; function NumberHeader({ number, description }) { return ( <PageHeader largeTitle={number.name} subtitle={description} icon={PhoneOutlined} renderSubRight={() => <span>{number.countryCode}</span>} /> ); } ``` **After (`@aircall/blocks`):** ```tsx import { DashboardPageHeader, DashboardPageHeaderPrefix, DashboardPageHeaderTitleGroup, DashboardPageHeaderTitle, DashboardPageHeaderSubtitle, DashboardPageHeaderDescription, } from '@aircall/blocks'; import { Phone } from '@aircall/react-icons'; function NumberHeader({ number, description }) { return ( <DashboardPageHeader> <DashboardPageHeaderPrefix> <Phone className="size-5 text-muted-foreground" /> </DashboardPageHeaderPrefix> <DashboardPageHeaderTitleGroup> <DashboardPageHeaderTitle size="lg">{number.name}</DashboardPageHeaderTitle> <DashboardPageHeaderSubtitle> <DashboardPageHeaderDescription>{description}</DashboardPageHeaderDescription> <span className="text-sm text-muted-foreground">{number.countryCode}</span> </DashboardPageHeaderSubtitle> </DashboardPageHeaderTitleGroup> </DashboardPageHeader> ); } ``` Key changes: - `icon` prop `DashboardPageHeaderPrefix` containing the icon element; icons come from `@aircall/react-icons`, not `@aircall/icons`. - `subtitle` prop `DashboardPageHeaderDescription` inside `DashboardPageHeaderSubtitle` inside `DashboardPageHeaderTitleGroup`. - `renderSubLeft` / `renderSubRight` siblings of `DashboardPageHeaderDescription` inside `DashboardPageHeaderSubtitle`. - When `DashboardPageHeaderPrefix` is present, `DashboardPageHeaderTitleGroup` resets its left padding automatically (CSS group selector). - `avatarSrc` / `avatarInitials` / `renderAvatar` render your avatar element inside `DashboardPageHeaderPrefix`. --- ### 3e. Tabs (activeTabId / onTabChange / Tab.Menu children) `PageHeader` accepted `Tab.Menu` / `Tab.Content` children and wired them into a `Tab.Container`. `DashboardPageHeader` has no built-in tab support. Pattern: render a DS `Tabs` compound below `DashboardPageHeader`, outside of it. ```tsx // Before import { PageHeader, Tab } from '@dashboard/library'; <PageHeader largeTitle="Reports" activeTabId="overview" onTabChange={setTab}> <Tab.Menu tabId="overview">Overview</Tab.Menu> <Tab.Menu tabId="calls">Calls</Tab.Menu> <Tab.Content tabId="overview"><Overview /></Tab.Content> <Tab.Content tabId="calls"><Calls /></Tab.Content> </PageHeader> // After — use DS Tabs below the header import { DashboardPageHeader, DashboardPageHeaderTitle } from '@aircall/blocks'; import { Tabs, TabsList, TabsTrigger, TabsContent } from '@aircall/ds'; <DashboardPageHeader> <DashboardPageHeaderTitle size="lg">Reports</DashboardPageHeaderTitle> </DashboardPageHeader> <Tabs value={tab} onValueChange={setTab}> <TabsList> <TabsTrigger value="overview">Overview</TabsTrigger> <TabsTrigger value="calls">Calls</TabsTrigger> </TabsList> <TabsContent value="overview"><Overview /></TabsContent> <TabsContent value="calls"><Calls /></TabsContent> </Tabs> ``` --- ## 4. Common mistakes ### Mistake 1Passing title/largeTitle as props instead of children ```tsx // ❌ Wrong — PageHeader prop API; DashboardPageHeader ignores unknown props <DashboardPageHeader largeTitle="Analytics" title="Overview" /> // ✅ Correct — title is a child component <DashboardPageHeader> <DashboardPageHeaderTitle size="lg">Analytics</DashboardPageHeaderTitle> </DashboardPageHeader> ``` `DashboardPageHeader` extends `React.ComponentProps<'div'>` — it has no `title`, `largeTitle`, or `subtitle` props. Passing them results in unknown HTML attributes forwarded to the DOM and no visible heading rendered. Source: `packages/blocks/src/components/dashboard-page-header.tsx` ### Mistake 2Using a Router Link directly instead of the `render` prop on DashboardPageHeaderNavBack ```tsx // ❌ Wrong — nests <a> inside <button>, invalid HTML <DashboardPageHeaderNavBack> <Link to="/numbers">Back to numbers</Link> </DashboardPageHeaderNavBack> // ✅ Correct — render prop replaces the <button> element with the Link <DashboardPageHeaderNavBack render={<Link to="/numbers" />}> Back to numbers </DashboardPageHeaderNavBack> ``` `DashboardPageHeaderNavBack` renders a `Button` (which renders a `<button>`). Nesting a `<Link>` (`<a>`) inside a `<button>` is invalid HTML and breaks keyboard navigation. The `render` prop (Base UI pattern) polymorphically replaces the root element while keeping all button behavior. Source: `packages/blocks/src/components/dashboard-page-header.tsx` ### Mistake 3Placing DashboardPageHeaderActions outside DashboardPageHeader ```tsx // ❌ Wrong — outside the @container context; grid-area:actions has no effect <DashboardPageHeader> <DashboardPageHeaderTitle size="lg">Analytics</DashboardPageHeaderTitle> </DashboardPageHeader> <DashboardPageHeaderActions> <DashboardPageHeaderAction variant="default">Export</DashboardPageHeaderAction> </DashboardPageHeaderActions> // ✅ Correct — all sub-components are children of DashboardPageHeader <DashboardPageHeader> <DashboardPageHeaderTitle size="lg">Analytics</DashboardPageHeaderTitle> <DashboardPageHeaderActions> <DashboardPageHeaderAction variant="default">Export</DashboardPageHeaderAction> </DashboardPageHeaderActions> </DashboardPageHeader> ``` Sub-components use `[grid-area:actions]` Tailwind classes that only resolve inside the CSS grid established by `DashboardPageHeader`'s inner grid `div`. Rendered outside, they appear in normal document flow and lose their placement entirely. Source: `packages/blocks/src/components/dashboard-page-header.tsx` ### Mistake 4Importing icons from @aircall/icons instead of @aircall/react-icons ```tsx // ❌ Wrong — @aircall/icons is the old tractor-era icon package import { PhoneOutlined } from '@aircall/icons'; // ✅ Correct — @aircall/react-icons is the current icon package import { Phone } from '@aircall/react-icons'; ``` `DashboardPageHeader` and the DS components it composes (`Button`, etc.) are built for `@aircall/react-icons` (lucide-based). The `@aircall/icons` package ships SVG components with a different size/color API (`size` / `color` props vs `className`). Source: `packages/blocks/src/components/dashboard-page-header.tsx` ### Mistake 5Using GaramondTitleTypography / useGaramondFont in the new stack ```tsx // ❌ Wrong — GaramondTitleTypography and useGaramondFont are @dashboard/library internals import { GaramondTitleTypography } from '@dashboard/library'; <GaramondTitleTypography>{title}</GaramondTitleTypography> // ❌ Wrong — no equivalent prop on DashboardPageHeaderTitle <DashboardPageHeaderTitle size="lg" useGaramondFont>Analytics</DashboardPageHeaderTitle> // ✅ Correct — use DashboardPageHeaderTitle with size="lg" (system font, trimmed baseline) <DashboardPageHeaderTitle size="lg">Analytics</DashboardPageHeaderTitle> ``` `GaramondTitleTypography` and `useGaramondFont` were `@dashboard/library`-only overrides for the ITC Garamond Narrow font. `DashboardPageHeaderTitle size="lg"` renders `text-3xl font-bold` in the system font stack with `leading-10`, matching the design spec without a custom typeface dependency. Source: `packages/blocks/src/components/dashboard-page-header.tsx`