@aircall/blocks 0.6.0 → 0.7.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.
@@ -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`