@aircall/blocks
Version:
Aircall Blocks — higher-level UI compositions built on @aircall/ds
179 lines (147 loc) • 5.72 kB
Markdown
---
name: aircall-blocks/migrate-dashboard/info-popup
description: >
Migrate @dashboard/library InfoPopup (and the aw-web shared info-popup) to
@aircall/ds HoverCard. Load when a file imports InfoPopup, InfoPopupTrigger,
or InfoPopupContent from @dashboard/library or from the local shared info-popup.
type: sub-skill
library: aircall-blocks
requires:
- aircall-blocks/setup
- aircall-blocks/migrate-dashboard
sources:
- "aircall/hydra:packages/ds/src/components/hover-card.tsx"
- "aircall/hydra:packages/ds/src/index.ts"
---
This skill builds on aircall-blocks/migrate-dashboard.
## 1. Component mapping
| `@dashboard/library` / shared | `@aircall/ds` |
| --- | --- |
| `InfoPopup` (root) | `HoverCard` |
| `InfoPopupTrigger` | `HoverCardTrigger` |
| `InfoPopupContent` | `HoverCardContent` |
`HoverCard` is built on Base UI `PreviewCard`. The hover-grace period (cursor can
move from trigger into content without the card closing) is handled natively —
no manual timer cancellation needed.
## 2. Verified DS exports (`packages/ds/src/index.ts`)
```
HoverCard, HoverCardTrigger, HoverCardContent
```
## 3. Imports
```tsx
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@aircall/ds';
```
Icons (the trigger is usually an info icon):
```tsx
import { Info } from '@aircall/react-icons';
```
## 4. Prop mapping
### Root (`InfoPopup` → `HoverCard`)
| `InfoPopup` prop | `HoverCard` equivalent | Notes |
| --- | --- | --- |
| `side` | `<HoverCardContent side>` | Moved from root to content |
| `align` | `<HoverCardContent align>` | Moved from root to content |
| `mouseEnterDelay` | `<HoverCardTrigger delay>` | Default changes: 0 ms → 400 ms |
| `mouseLeaveDelay` | `<HoverCardTrigger closeDelay>` | Default changes: 100 ms → 300 ms |
| `open` | `<HoverCard open>` | Same |
| `defaultOpen` | `<HoverCard defaultOpen>` | Same |
| `onOpenChange` | `<HoverCard onOpenChange>` | Same |
| `data-test` | standard `data-test` on trigger or content | Pass directly to the element |
### Trigger (`InfoPopupTrigger` → `HoverCardTrigger`)
`InfoPopupTrigger` rendered children directly. `HoverCardTrigger` renders an `<a>`
by default — swap it for any element via the `render` prop:
```tsx
// info icon trigger
<HoverCardTrigger render={<span className="inline-flex cursor-help" />}>
<Info size={16} />
</HoverCardTrigger>
// button trigger
<HoverCardTrigger render={<Button variant="ghost" size="sm" />}>
Contact insights <Info />
</HoverCardTrigger>
```
### Content (`InfoPopupContent` → `HoverCardContent`)
`InfoPopupContent` accepted Tractor `BoxProps` (`maxW`, `p`, `backgroundColor`,
`borderRadius`, `boxShadow`). `HoverCardContent` is a plain `<div>` — use Tailwind:
| Old Tractor prop | Tailwind equivalent |
| --- | --- |
| `maxW="280px"` | `className="max-w-[280px]"` |
| `p="m"` | `className="p-4"` |
| `backgroundColor="surface-default"` | default `bg-popover` (no change needed) |
| `boxShadow={1}` | default `shadow-md` (no change needed) |
| `borderRadius="sm"` | default `rounded-md` (no change needed) |
## 5. Before / After examples
### 5a. Basic icon trigger
**Before (`@dashboard/library` / shared):**
```tsx
import { InfoPopup, InfoPopupTrigger, InfoPopupContent } from '@/components/shared/info-popup';
import { Icon } from '@aircall/tractor';
import { InformationOutlined } from '@aircall/icons';
<InfoPopup side="top" align="center">
<InfoPopupTrigger>
<Icon component={InformationOutlined} size={16} />
</InfoPopupTrigger>
<InfoPopupContent>
<p>Contextual help text.</p>
</InfoPopupContent>
</InfoPopup>
```
**After (`@aircall/ds`):**
```tsx
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@aircall/ds';
import { Info } from '@aircall/react-icons';
<HoverCard>
<HoverCardTrigger render={<span className="inline-flex cursor-help" />}>
<Info size={16} className="text-muted-foreground" />
</HoverCardTrigger>
<HoverCardContent side="top" align="center">
<p>Contextual help text.</p>
</HoverCardContent>
</HoverCard>
```
### 5b. With custom open delay (ContactInsightsWrapper pattern)
**Before:**
```tsx
<InfoPopup side="left" align="start" mouseEnterDelay={400}>
<InfoPopupTrigger>
<Flex gap="xxs" cursor="help">
<Typography variant="supportingSemiboldS">{title}</Typography>
<Icon component={InformationOutlined} size={16} />
</Flex>
</InfoPopupTrigger>
<InfoPopupContent maxW="280px">
<Flex flexDirection="column" gap="xs">
<Typography variant="headingBoldXS">{heading}</Typography>
<Typography variant="bodyRegularS">{body}</Typography>
</Flex>
</InfoPopupContent>
</InfoPopup>
```
**After:**
```tsx
<HoverCard>
<HoverCardTrigger
delay={400}
closeDelay={100}
render={<Button variant="ghost" size="sm" />}
>
{title}
<Info />
</HoverCardTrigger>
<HoverCardContent side="left" align="start" className="max-w-[280px]">
<div className="flex flex-col gap-2">
<p className="text-xs font-bold">{heading}</p>
<p className="text-xs text-muted-foreground">{body}</p>
</div>
</HoverCardContent>
</HoverCard>
```
## 6. Key behaviour differences
- **Hover grace** — moving the cursor from trigger into content keeps the card open
natively. `InfoPopup` required manual timer cancellation.
- **Focus triggers** — `HoverCard` also opens on keyboard focus (Tab); `InfoPopup`
was hover-only.
- **No provider needed** — neither `InfoPopup` nor `HoverCard` requires a provider.
- **Default delays** — `HoverCardTrigger` defaults to `delay=400` / `closeDelay=300`.
If you need the original `InfoPopup` behaviour (instant open, 100 ms close), pass
`delay={0} closeDelay={100}` explicitly.