@aircall/blocks 0.6.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -0
- package/dist/globals.css +1 -1
- package/dist/index.d.ts +308 -3
- package/dist/index.js +1021 -147
- package/package.json +17 -3
- package/skills/aircall-blocks/migrate-dashboard/SKILL.md +127 -0
- package/skills/aircall-blocks/migrate-dashboard/card/SKILL.md +364 -0
- package/skills/aircall-blocks/migrate-dashboard/combobox/SKILL.md +487 -0
- package/skills/aircall-blocks/migrate-dashboard/copy-button/SKILL.md +138 -0
- package/skills/aircall-blocks/migrate-dashboard/dashboard-page/SKILL.md +198 -0
- package/skills/aircall-blocks/migrate-dashboard/data-table/SKILL.md +395 -0
- package/skills/aircall-blocks/migrate-dashboard/empty-states/SKILL.md +347 -0
- package/skills/aircall-blocks/migrate-dashboard/form-wizard/SKILL.md +271 -0
- package/skills/aircall-blocks/migrate-dashboard/info-popup/SKILL.md +179 -0
- package/skills/aircall-blocks/migrate-dashboard/layout/SKILL.md +328 -0
- package/skills/aircall-blocks/migrate-dashboard/list/SKILL.md +474 -0
- package/skills/aircall-blocks/migrate-dashboard/loading/SKILL.md +260 -0
- package/skills/aircall-blocks/migrate-dashboard/page-header/SKILL.md +394 -0
- package/skills/aircall-blocks/migrate-dashboard/tile/SKILL.md +429 -0
- package/skills/aircall-blocks/migrate-dashboard/toggle-row/SKILL.md +177 -0
- package/skills/aircall-blocks/setup/SKILL.md +127 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: aircall-blocks/migrate-dashboard/info-popup
|
|
3
|
+
description: >
|
|
4
|
+
Migrate @dashboard/library InfoPopup (and the aw-web shared info-popup) to
|
|
5
|
+
@aircall/ds HoverCard. Load when a file imports InfoPopup, InfoPopupTrigger,
|
|
6
|
+
or InfoPopupContent from @dashboard/library or from the local shared info-popup.
|
|
7
|
+
type: sub-skill
|
|
8
|
+
library: aircall-blocks
|
|
9
|
+
library_version: "0.6.0"
|
|
10
|
+
requires:
|
|
11
|
+
- aircall-blocks/setup
|
|
12
|
+
- aircall-blocks/migrate-dashboard
|
|
13
|
+
sources:
|
|
14
|
+
- "aircall/hydra:packages/ds/src/components/hover-card.tsx"
|
|
15
|
+
- "aircall/hydra:packages/ds/src/index.ts"
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
This skill builds on aircall-blocks/migrate-dashboard.
|
|
19
|
+
|
|
20
|
+
## 1. Component mapping
|
|
21
|
+
|
|
22
|
+
| `@dashboard/library` / shared | `@aircall/ds` |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| `InfoPopup` (root) | `HoverCard` |
|
|
25
|
+
| `InfoPopupTrigger` | `HoverCardTrigger` |
|
|
26
|
+
| `InfoPopupContent` | `HoverCardContent` |
|
|
27
|
+
|
|
28
|
+
`HoverCard` is built on Base UI `PreviewCard`. The hover-grace period (cursor can
|
|
29
|
+
move from trigger into content without the card closing) is handled natively —
|
|
30
|
+
no manual timer cancellation needed.
|
|
31
|
+
|
|
32
|
+
## 2. Verified DS exports (`packages/ds/src/index.ts`)
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
HoverCard, HoverCardTrigger, HoverCardContent
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 3. Imports
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@aircall/ds';
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Icons (the trigger is usually an info icon):
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
import { Info } from '@aircall/react-icons';
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 4. Prop mapping
|
|
51
|
+
|
|
52
|
+
### Root (`InfoPopup` → `HoverCard`)
|
|
53
|
+
|
|
54
|
+
| `InfoPopup` prop | `HoverCard` equivalent | Notes |
|
|
55
|
+
| --- | --- | --- |
|
|
56
|
+
| `side` | `<HoverCardContent side>` | Moved from root to content |
|
|
57
|
+
| `align` | `<HoverCardContent align>` | Moved from root to content |
|
|
58
|
+
| `mouseEnterDelay` | `<HoverCardTrigger delay>` | Default changes: 0 ms → 400 ms |
|
|
59
|
+
| `mouseLeaveDelay` | `<HoverCardTrigger closeDelay>` | Default changes: 100 ms → 300 ms |
|
|
60
|
+
| `open` | `<HoverCard open>` | Same |
|
|
61
|
+
| `defaultOpen` | `<HoverCard defaultOpen>` | Same |
|
|
62
|
+
| `onOpenChange` | `<HoverCard onOpenChange>` | Same |
|
|
63
|
+
| `data-test` | standard `data-test` on trigger or content | Pass directly to the element |
|
|
64
|
+
|
|
65
|
+
### Trigger (`InfoPopupTrigger` → `HoverCardTrigger`)
|
|
66
|
+
|
|
67
|
+
`InfoPopupTrigger` rendered children directly. `HoverCardTrigger` renders an `<a>`
|
|
68
|
+
by default — swap it for any element via the `render` prop:
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
// info icon trigger
|
|
72
|
+
<HoverCardTrigger render={<span className="inline-flex cursor-help" />}>
|
|
73
|
+
<Info size={16} />
|
|
74
|
+
</HoverCardTrigger>
|
|
75
|
+
|
|
76
|
+
// button trigger
|
|
77
|
+
<HoverCardTrigger render={<Button variant="ghost" size="sm" />}>
|
|
78
|
+
Contact insights <Info />
|
|
79
|
+
</HoverCardTrigger>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Content (`InfoPopupContent` → `HoverCardContent`)
|
|
83
|
+
|
|
84
|
+
`InfoPopupContent` accepted Tractor `BoxProps` (`maxW`, `p`, `backgroundColor`,
|
|
85
|
+
`borderRadius`, `boxShadow`). `HoverCardContent` is a plain `<div>` — use Tailwind:
|
|
86
|
+
|
|
87
|
+
| Old Tractor prop | Tailwind equivalent |
|
|
88
|
+
| --- | --- |
|
|
89
|
+
| `maxW="280px"` | `className="max-w-[280px]"` |
|
|
90
|
+
| `p="m"` | `className="p-4"` |
|
|
91
|
+
| `backgroundColor="surface-default"` | default `bg-popover` (no change needed) |
|
|
92
|
+
| `boxShadow={1}` | default `shadow-md` (no change needed) |
|
|
93
|
+
| `borderRadius="sm"` | default `rounded-md` (no change needed) |
|
|
94
|
+
|
|
95
|
+
## 5. Before / After examples
|
|
96
|
+
|
|
97
|
+
### 5a. Basic icon trigger
|
|
98
|
+
|
|
99
|
+
**Before (`@dashboard/library` / shared):**
|
|
100
|
+
```tsx
|
|
101
|
+
import { InfoPopup, InfoPopupTrigger, InfoPopupContent } from '@/components/shared/info-popup';
|
|
102
|
+
import { Icon } from '@aircall/tractor';
|
|
103
|
+
import { InformationOutlined } from '@aircall/icons';
|
|
104
|
+
|
|
105
|
+
<InfoPopup side="top" align="center">
|
|
106
|
+
<InfoPopupTrigger>
|
|
107
|
+
<Icon component={InformationOutlined} size={16} />
|
|
108
|
+
</InfoPopupTrigger>
|
|
109
|
+
<InfoPopupContent>
|
|
110
|
+
<p>Contextual help text.</p>
|
|
111
|
+
</InfoPopupContent>
|
|
112
|
+
</InfoPopup>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**After (`@aircall/ds`):**
|
|
116
|
+
```tsx
|
|
117
|
+
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@aircall/ds';
|
|
118
|
+
import { Info } from '@aircall/react-icons';
|
|
119
|
+
|
|
120
|
+
<HoverCard>
|
|
121
|
+
<HoverCardTrigger render={<span className="inline-flex cursor-help" />}>
|
|
122
|
+
<Info size={16} className="text-muted-foreground" />
|
|
123
|
+
</HoverCardTrigger>
|
|
124
|
+
<HoverCardContent side="top" align="center">
|
|
125
|
+
<p>Contextual help text.</p>
|
|
126
|
+
</HoverCardContent>
|
|
127
|
+
</HoverCard>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 5b. With custom open delay (ContactInsightsWrapper pattern)
|
|
131
|
+
|
|
132
|
+
**Before:**
|
|
133
|
+
```tsx
|
|
134
|
+
<InfoPopup side="left" align="start" mouseEnterDelay={400}>
|
|
135
|
+
<InfoPopupTrigger>
|
|
136
|
+
<Flex gap="xxs" cursor="help">
|
|
137
|
+
<Typography variant="supportingSemiboldS">{title}</Typography>
|
|
138
|
+
<Icon component={InformationOutlined} size={16} />
|
|
139
|
+
</Flex>
|
|
140
|
+
</InfoPopupTrigger>
|
|
141
|
+
<InfoPopupContent maxW="280px">
|
|
142
|
+
<Flex flexDirection="column" gap="xs">
|
|
143
|
+
<Typography variant="headingBoldXS">{heading}</Typography>
|
|
144
|
+
<Typography variant="bodyRegularS">{body}</Typography>
|
|
145
|
+
</Flex>
|
|
146
|
+
</InfoPopupContent>
|
|
147
|
+
</InfoPopup>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**After:**
|
|
151
|
+
```tsx
|
|
152
|
+
<HoverCard>
|
|
153
|
+
<HoverCardTrigger
|
|
154
|
+
delay={400}
|
|
155
|
+
closeDelay={100}
|
|
156
|
+
render={<Button variant="ghost" size="sm" />}
|
|
157
|
+
>
|
|
158
|
+
{title}
|
|
159
|
+
<Info />
|
|
160
|
+
</HoverCardTrigger>
|
|
161
|
+
<HoverCardContent side="left" align="start" className="max-w-[280px]">
|
|
162
|
+
<div className="flex flex-col gap-2">
|
|
163
|
+
<p className="text-xs font-bold">{heading}</p>
|
|
164
|
+
<p className="text-xs text-muted-foreground">{body}</p>
|
|
165
|
+
</div>
|
|
166
|
+
</HoverCardContent>
|
|
167
|
+
</HoverCard>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## 6. Key behaviour differences
|
|
171
|
+
|
|
172
|
+
- **Hover grace** — moving the cursor from trigger into content keeps the card open
|
|
173
|
+
natively. `InfoPopup` required manual timer cancellation.
|
|
174
|
+
- **Focus triggers** — `HoverCard` also opens on keyboard focus (Tab); `InfoPopup`
|
|
175
|
+
was hover-only.
|
|
176
|
+
- **No provider needed** — neither `InfoPopup` nor `HoverCard` requires a provider.
|
|
177
|
+
- **Default delays** — `HoverCardTrigger` defaults to `delay=400` / `closeDelay=300`.
|
|
178
|
+
If you need the original `InfoPopup` behaviour (instant open, 100 ms close), pass
|
|
179
|
+
`delay={0} closeDelay={100}` explicitly.
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: aircall-blocks/migrate-dashboard/layout
|
|
3
|
+
description: >
|
|
4
|
+
Migrate @dashboard/library GridLayout, GridItem, and Gap to native <div> elements
|
|
5
|
+
with Tailwind CSS grid, flex, and gap utilities — no component import needed.
|
|
6
|
+
Load when a file imports GridLayout, GridItem, or Gap from @dashboard/library.
|
|
7
|
+
type: sub-skill
|
|
8
|
+
library: aircall-blocks
|
|
9
|
+
library_version: "0.5.1"
|
|
10
|
+
requires:
|
|
11
|
+
- aircall-blocks/setup
|
|
12
|
+
- aircall-blocks/migrate-dashboard
|
|
13
|
+
sources:
|
|
14
|
+
- "aircall/hydra:packages/ds/src/index.ts"
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
This skill builds on aircall-blocks/migrate-dashboard.
|
|
18
|
+
|
|
19
|
+
## 1. Target mapping
|
|
20
|
+
|
|
21
|
+
`GridLayout`, `GridItem`, and `Gap` are layout-only primitives backed by tractor's
|
|
22
|
+
`Grid` and `Flex` (xstyled). Their sole purpose is to apply CSS grid/flex/gap rules.
|
|
23
|
+
The direct replacement is native `<div>` elements with Tailwind utility classes —
|
|
24
|
+
**no component import is needed**. Do not import anything from `@aircall/blocks` or
|
|
25
|
+
`@aircall/ds` for these three components unless your replacement also composes a real
|
|
26
|
+
DS component (e.g. a `Card` inside a grid cell).
|
|
27
|
+
|
|
28
|
+
| `@dashboard/library` | Replacement | Package |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| `GridLayout` | `<div className="grid grid-cols-12 gap-x-4 gap-y-4 md:gap-x-6">` | native HTML |
|
|
31
|
+
| `GridItem` | `<div className="col-span-{xs} sm:col-span-{sm} …">` | native HTML |
|
|
32
|
+
| `Gap` (row, default) | `<div className="flex flex-col gap-4">` | native HTML |
|
|
33
|
+
| `Gap` (row, explicit direction) | `<div className="flex flex-row gap-{n}">` | native HTML |
|
|
34
|
+
| `GapContainer` (internal) | (never used directly — see `Gap`) | — |
|
|
35
|
+
|
|
36
|
+
### Space token → Tailwind gap class
|
|
37
|
+
|
|
38
|
+
`Gap` and `GridLayout` express spacing using tractor `SpaceVariants` (string keys) or
|
|
39
|
+
numeric xstyled scale values. Map them to Tailwind `gap-*` classes:
|
|
40
|
+
|
|
41
|
+
| Tractor token | px | Tailwind class |
|
|
42
|
+
| --- | --- | --- |
|
|
43
|
+
| `0` / `0` | 0 | `gap-0` |
|
|
44
|
+
| `xxxs` / `1` | 4 | `gap-1` |
|
|
45
|
+
| `xxs` / `2` | 8 | `gap-2` |
|
|
46
|
+
| `xs` / `3` | 12 | `gap-3` |
|
|
47
|
+
| `s` / `4` (default) | 16 | `gap-4` |
|
|
48
|
+
| `m` / `5` | 24 | `gap-6` |
|
|
49
|
+
| `l` / `6` | 32 | `gap-8` |
|
|
50
|
+
| `xl` / `7` | 40 | `gap-10` |
|
|
51
|
+
| `xxl` / `8` | 64 | `gap-16` |
|
|
52
|
+
| `xxxl` / `9` | 80 | `gap-20` |
|
|
53
|
+
|
|
54
|
+
For column-only or row-only overrides use `gap-x-*` / `gap-y-*`.
|
|
55
|
+
|
|
56
|
+
### GridLayout defaults
|
|
57
|
+
|
|
58
|
+
`GridLayout` hardcoded `columnGap: { _: 16, lg: 24 }` and `rowGap: "s"` (16 px).
|
|
59
|
+
The canonical Tailwind equivalent is `gap-x-4 lg:gap-x-6 gap-y-4`.
|
|
60
|
+
|
|
61
|
+
### GridItem column-span mapping
|
|
62
|
+
|
|
63
|
+
`GridItem` accepted `xs | sm | md | lg | xl` as a column count (1–12) and mapped each
|
|
64
|
+
to a `span N` value. The Tailwind replacement uses `col-span-{n}` with responsive
|
|
65
|
+
prefixes:
|
|
66
|
+
|
|
67
|
+
| Breakpoint | `@dashboard/library` token | Tailwind prefix |
|
|
68
|
+
| --- | --- | --- |
|
|
69
|
+
| all sizes | `xs` | `col-span-{n}` |
|
|
70
|
+
| ≥ 576 px | `sm` | `sm:col-span-{n}` |
|
|
71
|
+
| ≥ 768 px | `md` | `md:col-span-{n}` |
|
|
72
|
+
| ≥ 992 px | `lg` | `lg:col-span-{n}` |
|
|
73
|
+
| ≥ 1200 px | `xl` | `xl:col-span-{n}` |
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 2. Before / After examples
|
|
78
|
+
|
|
79
|
+
### 2a. GridLayout with GridItem — responsive page grid
|
|
80
|
+
|
|
81
|
+
**Before (`@dashboard/library`):**
|
|
82
|
+
```tsx
|
|
83
|
+
import { GridItem, GridLayout } from '@dashboard/library';
|
|
84
|
+
|
|
85
|
+
function AnalyticsDashboard() {
|
|
86
|
+
return (
|
|
87
|
+
<GridLayout>
|
|
88
|
+
<GridItem xs={12} lg={8}>
|
|
89
|
+
<MainChart />
|
|
90
|
+
</GridItem>
|
|
91
|
+
<GridItem xs={12} lg={4}>
|
|
92
|
+
<SideStats />
|
|
93
|
+
</GridItem>
|
|
94
|
+
<GridItem xs={12} sm={6} lg={4}>
|
|
95
|
+
<MetricCard title="Calls" />
|
|
96
|
+
</GridItem>
|
|
97
|
+
<GridItem xs={12} sm={6} lg={4}>
|
|
98
|
+
<MetricCard title="Missed" />
|
|
99
|
+
</GridItem>
|
|
100
|
+
<GridItem xs={12} sm={6} lg={4}>
|
|
101
|
+
<MetricCard title="Duration" />
|
|
102
|
+
</GridItem>
|
|
103
|
+
</GridLayout>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**After (native `<div>` + Tailwind):**
|
|
109
|
+
```tsx
|
|
110
|
+
function AnalyticsDashboard() {
|
|
111
|
+
return (
|
|
112
|
+
<div className="grid grid-cols-12 gap-x-4 lg:gap-x-6 gap-y-4">
|
|
113
|
+
<div className="col-span-12 lg:col-span-8">
|
|
114
|
+
<MainChart />
|
|
115
|
+
</div>
|
|
116
|
+
<div className="col-span-12 lg:col-span-4">
|
|
117
|
+
<SideStats />
|
|
118
|
+
</div>
|
|
119
|
+
<div className="col-span-12 sm:col-span-6 lg:col-span-4">
|
|
120
|
+
<MetricCard title="Calls" />
|
|
121
|
+
</div>
|
|
122
|
+
<div className="col-span-12 sm:col-span-6 lg:col-span-4">
|
|
123
|
+
<MetricCard title="Missed" />
|
|
124
|
+
</div>
|
|
125
|
+
<div className="col-span-12 sm:col-span-6 lg:col-span-4">
|
|
126
|
+
<MetricCard title="Duration" />
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Key changes:
|
|
134
|
+
- `GridLayout` → `<div className="grid grid-cols-12 gap-x-4 lg:gap-x-6 gap-y-4">`.
|
|
135
|
+
- `GridItem xs={N}` → `col-span-N`; each named breakpoint prop becomes its Tailwind prefix.
|
|
136
|
+
- No import needed — remove the `@dashboard/library` import entirely.
|
|
137
|
+
|
|
138
|
+
### 2b. Gap — vertical stack (default direction)
|
|
139
|
+
|
|
140
|
+
**Before (`@dashboard/library`):**
|
|
141
|
+
```tsx
|
|
142
|
+
import { Gap } from '@dashboard/library';
|
|
143
|
+
|
|
144
|
+
function SectionStack() {
|
|
145
|
+
return (
|
|
146
|
+
<Gap flexDirection="column" gap="m">
|
|
147
|
+
<FilterBar />
|
|
148
|
+
<DataPanel />
|
|
149
|
+
<PaginationRow />
|
|
150
|
+
</Gap>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**After (native `<div>` + Tailwind):**
|
|
156
|
+
```tsx
|
|
157
|
+
function SectionStack() {
|
|
158
|
+
return (
|
|
159
|
+
<div className="flex flex-col gap-6">
|
|
160
|
+
<FilterBar />
|
|
161
|
+
<DataPanel />
|
|
162
|
+
<PaginationRow />
|
|
163
|
+
</div>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Key changes:
|
|
169
|
+
- `Gap flexDirection="column"` → `flex flex-col`.
|
|
170
|
+
- `gap="m"` (24 px) → `gap-6` (see token table above).
|
|
171
|
+
- Drop the import.
|
|
172
|
+
|
|
173
|
+
### 2c. Gap — horizontal row
|
|
174
|
+
|
|
175
|
+
**Before (`@dashboard/library`):**
|
|
176
|
+
```tsx
|
|
177
|
+
import { Gap } from '@dashboard/library';
|
|
178
|
+
|
|
179
|
+
function ActionRow() {
|
|
180
|
+
return (
|
|
181
|
+
<Gap gap="xs" alignItems="center">
|
|
182
|
+
<Badge />
|
|
183
|
+
<Label />
|
|
184
|
+
<Button />
|
|
185
|
+
</Gap>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**After (native `<div>` + Tailwind):**
|
|
191
|
+
```tsx
|
|
192
|
+
function ActionRow() {
|
|
193
|
+
return (
|
|
194
|
+
<div className="flex flex-row items-center gap-3">
|
|
195
|
+
<Badge />
|
|
196
|
+
<Label />
|
|
197
|
+
<Button />
|
|
198
|
+
</div>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Key changes:
|
|
204
|
+
- `Gap` with no explicit `flexDirection` defaulted to `row` (inherits `Flex` default).
|
|
205
|
+
- `alignItems="center"` → `items-center`.
|
|
206
|
+
- `gap="xs"` (12 px) → `gap-3`.
|
|
207
|
+
|
|
208
|
+
### 2d. Gap — independent column-gap / row-gap
|
|
209
|
+
|
|
210
|
+
**Before (`@dashboard/library`):**
|
|
211
|
+
```tsx
|
|
212
|
+
import { Gap } from '@dashboard/library';
|
|
213
|
+
|
|
214
|
+
function TagCloud() {
|
|
215
|
+
return (
|
|
216
|
+
<Gap flexWrap="wrap" columnGap="s" rowGap="xxs">
|
|
217
|
+
{tags.map(tag => <Chip key={tag}>{tag}</Chip>)}
|
|
218
|
+
</Gap>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**After (native `<div>` + Tailwind):**
|
|
224
|
+
```tsx
|
|
225
|
+
function TagCloud() {
|
|
226
|
+
return (
|
|
227
|
+
<div className="flex flex-wrap gap-x-4 gap-y-2">
|
|
228
|
+
{tags.map(tag => <Chip key={tag}>{tag}</Chip>)}
|
|
229
|
+
</div>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Key changes:
|
|
235
|
+
- `columnGap="s"` (16 px) → `gap-x-4`.
|
|
236
|
+
- `rowGap="xxs"` (8 px) → `gap-y-2`.
|
|
237
|
+
- `flexWrap="wrap"` → `flex-wrap`.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## 3. Common mistakes
|
|
242
|
+
|
|
243
|
+
### Mistake 1 — Keeping a component import for layout primitives
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
// ❌ Wrong — GridLayout and Gap are layout-only; importing from blocks/ds adds bundle weight
|
|
247
|
+
import { GridLayout, GridItem } from '@dashboard/library';
|
|
248
|
+
|
|
249
|
+
<GridLayout>
|
|
250
|
+
<GridItem xs={12}>content</GridItem>
|
|
251
|
+
</GridLayout>
|
|
252
|
+
|
|
253
|
+
// ✅ Correct — delete the import; write native HTML with Tailwind classes
|
|
254
|
+
<div className="grid grid-cols-12 gap-x-4 lg:gap-x-6 gap-y-4">
|
|
255
|
+
<div className="col-span-12">content</div>
|
|
256
|
+
</div>
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
`GridLayout` and `Gap` had no semantic meaning — they rendered a plain `<div>` (via
|
|
260
|
+
`Grid` / `Flex` from `@aircall/tractor`). Tailwind utility classes applied directly to
|
|
261
|
+
`<div>` produce identical CSS with zero runtime overhead.
|
|
262
|
+
|
|
263
|
+
Source: `packages/library/src/components/GridLayout/GridLayout.tsx`
|
|
264
|
+
|
|
265
|
+
### Mistake 2 — Omitting `grid-cols-12` on the GridLayout replacement
|
|
266
|
+
|
|
267
|
+
```tsx
|
|
268
|
+
// ❌ Wrong — missing column definition; col-span-* has nothing to span against
|
|
269
|
+
<div className="grid gap-x-4 gap-y-4">
|
|
270
|
+
<div className="col-span-6">left</div>
|
|
271
|
+
<div className="col-span-6">right</div>
|
|
272
|
+
</div>
|
|
273
|
+
|
|
274
|
+
// ✅ Correct — declare the 12-column template that GridLayout hardcoded
|
|
275
|
+
<div className="grid grid-cols-12 gap-x-4 lg:gap-x-6 gap-y-4">
|
|
276
|
+
<div className="col-span-6">left</div>
|
|
277
|
+
<div className="col-span-6">right</div>
|
|
278
|
+
</div>
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
`GridLayout` hardcoded `gridTemplateColumns="repeat(12, 1fr)"`. Without `grid-cols-12`
|
|
282
|
+
the browser defaults to `auto` columns and `col-span-*` values are ignored.
|
|
283
|
+
|
|
284
|
+
Source: `packages/library/src/components/GridLayout/GridLayout.tsx`
|
|
285
|
+
|
|
286
|
+
### Mistake 3 — Forgetting the `lg:gap-x-6` breakpoint variant
|
|
287
|
+
|
|
288
|
+
```tsx
|
|
289
|
+
// ❌ Wrong — loses the responsive gutter that GridLayout applied at lg
|
|
290
|
+
<div className="grid grid-cols-12 gap-x-4 gap-y-4">
|
|
291
|
+
…
|
|
292
|
+
</div>
|
|
293
|
+
|
|
294
|
+
// ✅ Correct — GridLayout used DEFAULT_GUTTER = { _: 16, lg: 24 }; match it
|
|
295
|
+
<div className="grid grid-cols-12 gap-x-4 lg:gap-x-6 gap-y-4">
|
|
296
|
+
…
|
|
297
|
+
</div>
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
`GridLayout` sourced its column gutter from `DEFAULT_GUTTER = { _: 16, lg: 24 }` (the
|
|
301
|
+
xstyled responsive object syntax). At viewports ≥ lg the gutter widens from 16 px
|
|
302
|
+
(`gap-x-4`) to 24 px (`gap-x-6`). Omitting `lg:gap-x-6` silently produces narrower
|
|
303
|
+
gutters on large screens.
|
|
304
|
+
|
|
305
|
+
Source: `packages/library/src/components/GridLayout/GridLayout.tsx`
|
|
306
|
+
|
|
307
|
+
### Mistake 4 — Passing the `space` alias instead of `gap` to Gap
|
|
308
|
+
|
|
309
|
+
```tsx
|
|
310
|
+
// ❌ Wrong — `space` was a Gap-specific alias; Tailwind has no "space" concept here
|
|
311
|
+
import { Gap } from '@dashboard/library';
|
|
312
|
+
|
|
313
|
+
// Old code used `space` prop:
|
|
314
|
+
<Gap space="m">…</Gap>
|
|
315
|
+
|
|
316
|
+
// Migrated incorrectly as if `space` maps directly:
|
|
317
|
+
<div className="space-y-6">…</div> // space-y-* distributes margin, not gap
|
|
318
|
+
|
|
319
|
+
// ✅ Correct — map to gap-* utility which uses CSS gap (same as what Gap rendered)
|
|
320
|
+
<div className="flex flex-col gap-6">…</div>
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
`Gap` treated `space` as an alias for `gap` (both resolved the same tractor spacing
|
|
324
|
+
token into a CSS `gap` value). Tailwind's `space-y-*` / `space-x-*` utilities work via
|
|
325
|
+
child selectors and `margin`, not CSS `gap` — they produce different stacking behavior
|
|
326
|
+
when children have their own margins.
|
|
327
|
+
|
|
328
|
+
Source: `packages/library/src/components/Gap/Gap.tsx`
|