@aircall/blocks
Version:
Aircall Blocks — higher-level UI compositions built on @aircall/ds
328 lines (267 loc) • 9.67 kB
Markdown
---
name: aircall-blocks/migrate-dashboard/layout
description: >
Migrate @dashboard/library GridLayout, GridItem, and Gap to native <div> elements
with Tailwind CSS grid, flex, and gap utilities — no component import needed.
Load when a file imports GridLayout, GridItem, or Gap 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. Target mapping
`GridLayout`, `GridItem`, and `Gap` are layout-only primitives backed by tractor's
`Grid` and `Flex` (xstyled). Their sole purpose is to apply CSS grid/flex/gap rules.
The direct replacement is native `<div>` elements with Tailwind utility classes —
**no component import is needed**. Do not import anything from `@aircall/blocks` or
`@aircall/ds` for these three components unless your replacement also composes a real
DS component (e.g. a `Card` inside a grid cell).
| `@dashboard/library` | Replacement | Package |
| --- | --- | --- |
| `GridLayout` | `<div className="grid grid-cols-12 gap-x-4 gap-y-4 md:gap-x-6">` | native HTML |
| `GridItem` | `<div className="col-span-{xs} sm:col-span-{sm} …">` | native HTML |
| `Gap` (row, default) | `<div className="flex flex-col gap-4">` | native HTML |
| `Gap` (row, explicit direction) | `<div className="flex flex-row gap-{n}">` | native HTML |
| `GapContainer` (internal) | (never used directly — see `Gap`) | — |
### Space token → Tailwind gap class
`Gap` and `GridLayout` express spacing using tractor `SpaceVariants` (string keys) or
numeric xstyled scale values. Map them to Tailwind `gap-*` classes:
| Tractor token | px | Tailwind class |
| --- | --- | --- |
| `0` / `0` | 0 | `gap-0` |
| `xxxs` / `1` | 4 | `gap-1` |
| `xxs` / `2` | 8 | `gap-2` |
| `xs` / `3` | 12 | `gap-3` |
| `s` / `4` (default) | 16 | `gap-4` |
| `m` / `5` | 24 | `gap-6` |
| `l` / `6` | 32 | `gap-8` |
| `xl` / `7` | 40 | `gap-10` |
| `xxl` / `8` | 64 | `gap-16` |
| `xxxl` / `9` | 80 | `gap-20` |
For column-only or row-only overrides use `gap-x-*` / `gap-y-*`.
### GridLayout defaults
`GridLayout` hardcoded `columnGap: { _: 16, lg: 24 }` and `rowGap: "s"` (16 px).
The canonical Tailwind equivalent is `gap-x-4 lg:gap-x-6 gap-y-4`.
### GridItem column-span mapping
`GridItem` accepted `xs | sm | md | lg | xl` as a column count (1–12) and mapped each
to a `span N` value. The Tailwind replacement uses `col-span-{n}` with responsive
prefixes:
| Breakpoint | `@dashboard/library` token | Tailwind prefix |
| --- | --- | --- |
| all sizes | `xs` | `col-span-{n}` |
| ≥ 576 px | `sm` | `sm:col-span-{n}` |
| ≥ 768 px | `md` | `md:col-span-{n}` |
| ≥ 992 px | `lg` | `lg:col-span-{n}` |
| ≥ 1200 px | `xl` | `xl:col-span-{n}` |
---
## 2. Before / After examples
### 2a. GridLayout with GridItem — responsive page grid
**Before (`@dashboard/library`):**
```tsx
import { GridItem, GridLayout } from '@dashboard/library';
function AnalyticsDashboard() {
return (
<GridLayout>
<GridItem xs={12} lg={8}>
<MainChart />
</GridItem>
<GridItem xs={12} lg={4}>
<SideStats />
</GridItem>
<GridItem xs={12} sm={6} lg={4}>
<MetricCard title="Calls" />
</GridItem>
<GridItem xs={12} sm={6} lg={4}>
<MetricCard title="Missed" />
</GridItem>
<GridItem xs={12} sm={6} lg={4}>
<MetricCard title="Duration" />
</GridItem>
</GridLayout>
);
}
```
**After (native `<div>` + Tailwind):**
```tsx
function AnalyticsDashboard() {
return (
<div className="grid grid-cols-12 gap-x-4 lg:gap-x-6 gap-y-4">
<div className="col-span-12 lg:col-span-8">
<MainChart />
</div>
<div className="col-span-12 lg:col-span-4">
<SideStats />
</div>
<div className="col-span-12 sm:col-span-6 lg:col-span-4">
<MetricCard title="Calls" />
</div>
<div className="col-span-12 sm:col-span-6 lg:col-span-4">
<MetricCard title="Missed" />
</div>
<div className="col-span-12 sm:col-span-6 lg:col-span-4">
<MetricCard title="Duration" />
</div>
</div>
);
}
```
Key changes:
- `GridLayout` → `<div className="grid grid-cols-12 gap-x-4 lg:gap-x-6 gap-y-4">`.
- `GridItem xs={N}` → `col-span-N`; each named breakpoint prop becomes its Tailwind prefix.
- No import needed — remove the `@dashboard/library` import entirely.
### 2b. Gap — vertical stack (default direction)
**Before (`@dashboard/library`):**
```tsx
import { Gap } from '@dashboard/library';
function SectionStack() {
return (
<Gap flexDirection="column" gap="m">
<FilterBar />
<DataPanel />
<PaginationRow />
</Gap>
);
}
```
**After (native `<div>` + Tailwind):**
```tsx
function SectionStack() {
return (
<div className="flex flex-col gap-6">
<FilterBar />
<DataPanel />
<PaginationRow />
</div>
);
}
```
Key changes:
- `Gap flexDirection="column"` → `flex flex-col`.
- `gap="m"` (24 px) → `gap-6` (see token table above).
- Drop the import.
### 2c. Gap — horizontal row
**Before (`@dashboard/library`):**
```tsx
import { Gap } from '@dashboard/library';
function ActionRow() {
return (
<Gap gap="xs" alignItems="center">
<Badge />
<Label />
<Button />
</Gap>
);
}
```
**After (native `<div>` + Tailwind):**
```tsx
function ActionRow() {
return (
<div className="flex flex-row items-center gap-3">
<Badge />
<Label />
<Button />
</div>
);
}
```
Key changes:
- `Gap` with no explicit `flexDirection` defaulted to `row` (inherits `Flex` default).
- `alignItems="center"` → `items-center`.
- `gap="xs"` (12 px) → `gap-3`.
### 2d. Gap — independent column-gap / row-gap
**Before (`@dashboard/library`):**
```tsx
import { Gap } from '@dashboard/library';
function TagCloud() {
return (
<Gap flexWrap="wrap" columnGap="s" rowGap="xxs">
{tags.map(tag => <Chip key={tag}>{tag}</Chip>)}
</Gap>
);
}
```
**After (native `<div>` + Tailwind):**
```tsx
function TagCloud() {
return (
<div className="flex flex-wrap gap-x-4 gap-y-2">
{tags.map(tag => <Chip key={tag}>{tag}</Chip>)}
</div>
);
}
```
Key changes:
- `columnGap="s"` (16 px) → `gap-x-4`.
- `rowGap="xxs"` (8 px) → `gap-y-2`.
- `flexWrap="wrap"` → `flex-wrap`.
---
## 3. Common mistakes
### Mistake 1 — Keeping a component import for layout primitives
```tsx
// ❌ Wrong — GridLayout and Gap are layout-only; importing from blocks/ds adds bundle weight
import { GridLayout, GridItem } from '@dashboard/library';
<GridLayout>
<GridItem xs={12}>content</GridItem>
</GridLayout>
// ✅ Correct — delete the import; write native HTML with Tailwind classes
<div className="grid grid-cols-12 gap-x-4 lg:gap-x-6 gap-y-4">
<div className="col-span-12">content</div>
</div>
```
`GridLayout` and `Gap` had no semantic meaning — they rendered a plain `<div>` (via
`Grid` / `Flex` from `@aircall/tractor`). Tailwind utility classes applied directly to
`<div>` produce identical CSS with zero runtime overhead.
Source: `packages/library/src/components/GridLayout/GridLayout.tsx`
### Mistake 2 — Omitting `grid-cols-12` on the GridLayout replacement
```tsx
// ❌ Wrong — missing column definition; col-span-* has nothing to span against
<div className="grid gap-x-4 gap-y-4">
<div className="col-span-6">left</div>
<div className="col-span-6">right</div>
</div>
// ✅ Correct — declare the 12-column template that GridLayout hardcoded
<div className="grid grid-cols-12 gap-x-4 lg:gap-x-6 gap-y-4">
<div className="col-span-6">left</div>
<div className="col-span-6">right</div>
</div>
```
`GridLayout` hardcoded `gridTemplateColumns="repeat(12, 1fr)"`. Without `grid-cols-12`
the browser defaults to `auto` columns and `col-span-*` values are ignored.
Source: `packages/library/src/components/GridLayout/GridLayout.tsx`
### Mistake 3 — Forgetting the `lg:gap-x-6` breakpoint variant
```tsx
// ❌ Wrong — loses the responsive gutter that GridLayout applied at lg
<div className="grid grid-cols-12 gap-x-4 gap-y-4">
…
</div>
// ✅ Correct — GridLayout used DEFAULT_GUTTER = { _: 16, lg: 24 }; match it
<div className="grid grid-cols-12 gap-x-4 lg:gap-x-6 gap-y-4">
…
</div>
```
`GridLayout` sourced its column gutter from `DEFAULT_GUTTER = { _: 16, lg: 24 }` (the
xstyled responsive object syntax). At viewports ≥ lg the gutter widens from 16 px
(`gap-x-4`) to 24 px (`gap-x-6`). Omitting `lg:gap-x-6` silently produces narrower
gutters on large screens.
Source: `packages/library/src/components/GridLayout/GridLayout.tsx`
### Mistake 4 — Passing the `space` alias instead of `gap` to Gap
```tsx
// ❌ Wrong — `space` was a Gap-specific alias; Tailwind has no "space" concept here
import { Gap } from '@dashboard/library';
// Old code used `space` prop:
<Gap space="m">…</Gap>
// Migrated incorrectly as if `space` maps directly:
<div className="space-y-6">…</div> // space-y-* distributes margin, not gap
// ✅ Correct — map to gap-* utility which uses CSS gap (same as what Gap rendered)
<div className="flex flex-col gap-6">…</div>
```
`Gap` treated `space` as an alias for `gap` (both resolved the same tractor spacing
token into a CSS `gap` value). Tailwind's `space-y-*` / `space-x-*` utilities work via
child selectors and `margin`, not CSS `gap` — they produce different stacking behavior
when children have their own margins.
Source: `packages/library/src/components/Gap/Gap.tsx`