@aircall/blocks 0.17.0 → 0.18.1
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/dist/globals.css +1 -1
- package/dist/index.d.ts +862 -18
- package/dist/index.js +1044 -82
- package/package.json +2 -2
- package/skills/aircall-blocks/migrate-dashboard/SKILL.md +20 -3
- package/skills/aircall-blocks/migrate-dashboard/card/SKILL.md +43 -71
- package/skills/aircall-blocks/migrate-dashboard/catch-up-checklist/SKILL.md +109 -0
- package/skills/aircall-blocks/migrate-dashboard/combobox/SKILL.md +0 -1
- package/skills/aircall-blocks/migrate-dashboard/copy-button/SKILL.md +0 -1
- package/skills/aircall-blocks/migrate-dashboard/dashboard-page/SKILL.md +39 -1
- package/skills/aircall-blocks/migrate-dashboard/data-table/SKILL.md +10 -1
- package/skills/aircall-blocks/migrate-dashboard/empty-states/SKILL.md +0 -1
- package/skills/aircall-blocks/migrate-dashboard/form-wizard/SKILL.md +40 -1
- package/skills/aircall-blocks/migrate-dashboard/info-popup/SKILL.md +0 -1
- package/skills/aircall-blocks/migrate-dashboard/layout/SKILL.md +0 -1
- package/skills/aircall-blocks/migrate-dashboard/list/SKILL.md +0 -1
- package/skills/aircall-blocks/migrate-dashboard/loading/SKILL.md +0 -1
- package/skills/aircall-blocks/migrate-dashboard/page-header/SKILL.md +0 -1
- package/skills/aircall-blocks/migrate-dashboard/setting-card/SKILL.md +258 -0
- package/skills/aircall-blocks/migrate-dashboard/tile/SKILL.md +211 -161
- package/skills/aircall-blocks/migrate-dashboard/toggle-row/SKILL.md +0 -1
- package/skills/aircall-blocks/setup/SKILL.md +22 -1
|
@@ -2,51 +2,69 @@
|
|
|
2
2
|
name: aircall-blocks/migrate-dashboard/tile
|
|
3
3
|
description: >
|
|
4
4
|
Migrate @dashboard/library Tile, TileHeader, TileValue (+ TileBody, TileActions,
|
|
5
|
-
TileExtra, TileNumber, TileDuration, TileText) to @aircall/
|
|
6
|
-
(
|
|
7
|
-
|
|
5
|
+
TileExtra, TileNumber, TileDuration, TileText) to @aircall/blocks KpiCard
|
|
6
|
+
compositions (KpiCard, KpiValue, KpiDescription, KpiValueSkeleton,
|
|
7
|
+
KpiDescriptionSkeleton) plus DS CardHeader / CardTitle / CardContent / CardAction.
|
|
8
|
+
Load when a file imports Tile, TileHeader, or TileValue from @dashboard/library.
|
|
8
9
|
type: sub-skill
|
|
9
10
|
library: aircall-blocks
|
|
10
|
-
library_version: "0.5.1"
|
|
11
11
|
requires:
|
|
12
12
|
- aircall-blocks/setup
|
|
13
13
|
- aircall-blocks/migrate-dashboard
|
|
14
14
|
sources:
|
|
15
|
-
- "aircall/hydra:packages/
|
|
15
|
+
- "aircall/hydra:packages/blocks/src/components/kpi.tsx"
|
|
16
|
+
- "aircall/hydra:packages/blocks/src/helpers/format-kpi.ts"
|
|
17
|
+
- "aircall/hydra:packages/ds/src/components/card.tsx"
|
|
16
18
|
---
|
|
17
19
|
|
|
18
20
|
This skill builds on aircall-blocks/migrate-dashboard.
|
|
19
21
|
|
|
20
22
|
## 1. Component mapping
|
|
21
23
|
|
|
22
|
-
| @dashboard/library |
|
|
24
|
+
| @dashboard/library | Target |
|
|
23
25
|
| --- | --- |
|
|
24
|
-
| `Tile` (root container) | `Card` |
|
|
25
|
-
| `
|
|
26
|
-
| `TileHeader` `
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
26
|
+
| `Tile` (root container) | `KpiCard` (`@aircall/blocks`) — DS `Card` wrapper, `size="sm"` by default |
|
|
27
|
+
| `Tile` `disabled` prop | `KpiCard disabled` — applies `opacity-50` + `cursor-not-allowed` and cascades to nested `KpiValue`s |
|
|
28
|
+
| `TileHeader` `label` prop | DS `CardTitle` inside DS `CardHeader` |
|
|
29
|
+
| `TileHeader` `tooltipTexts` prop | inline DS `Tooltip` / `TooltipTrigger` / `TooltipContent` next to `CardTitle` |
|
|
30
|
+
| `TileBody` (metric row wrapper) | DS `CardContent` |
|
|
31
|
+
| `TileActions` (top-right action overlay) | DS `CardAction` inside `CardHeader` |
|
|
32
|
+
| `TileValue` (number / duration / text / percent) | `KpiValue` (`@aircall/blocks`) — use `kind`, `durationStyle`, `unit`, `onClick`, `active` |
|
|
33
|
+
| `TileValue loading` | `KpiValueSkeleton` (and `KpiDescriptionSkeleton` when a comparison line loads) |
|
|
34
|
+
| `TileExtra` `secondaryText` prop | `KpiDescription` children |
|
|
35
|
+
| `TileExtra` `trend` prop | `KpiDescription` `trend` + `tone` (`up` / `down` / `neutral` × `positive` / `negative` / `neutral`) |
|
|
36
|
+
| `TileProvider` (internal context) | removed — `KpiCard` provides disabled context |
|
|
37
|
+
|
|
38
|
+
`TILE_VALUE` enum and `TileValueSizes` type are internal to `@dashboard/library`; delete them from the import when migrating. Map former types via `KpiValue` `kind`:
|
|
39
|
+
|
|
40
|
+
| Former `TILE_VALUE` / type | `KpiValue` props |
|
|
41
|
+
| --- | --- |
|
|
42
|
+
| number | `kind="number"` (default for numeric `value`) |
|
|
43
|
+
| percent | `kind="percent"` |
|
|
44
|
+
| duration (long / verbose) | `kind="duration"` (`durationStyle="long"` default) |
|
|
45
|
+
| duration clock HH:MM | `kind="duration" durationStyle="clock" durationScale="hours"` |
|
|
46
|
+
| duration clock MM:SS | `kind="duration" durationStyle="clock" durationScale="minutes"` |
|
|
47
|
+
| opaque string | string `value` (or `kind="text"`) |
|
|
38
48
|
|
|
39
49
|
## 2. Imports
|
|
40
50
|
|
|
41
51
|
```tsx
|
|
42
|
-
//
|
|
52
|
+
// Blocks — KPI composition
|
|
43
53
|
import {
|
|
44
|
-
|
|
54
|
+
KpiCard,
|
|
55
|
+
KpiValue,
|
|
56
|
+
KpiDescription,
|
|
57
|
+
KpiValueSkeleton,
|
|
58
|
+
KpiDescriptionSkeleton
|
|
59
|
+
} from '@aircall/blocks';
|
|
60
|
+
|
|
61
|
+
// DS — card slots + optional tooltip / actions
|
|
62
|
+
import {
|
|
63
|
+
Button,
|
|
45
64
|
CardAction,
|
|
46
65
|
CardContent,
|
|
47
66
|
CardHeader,
|
|
48
67
|
CardTitle,
|
|
49
|
-
Skeleton,
|
|
50
68
|
Tooltip,
|
|
51
69
|
TooltipContent,
|
|
52
70
|
TooltipProvider,
|
|
@@ -54,9 +72,6 @@ import {
|
|
|
54
72
|
} from '@aircall/ds';
|
|
55
73
|
```
|
|
56
74
|
|
|
57
|
-
No `@aircall/blocks` import is required for the tile container migration. Blocks are only
|
|
58
|
-
needed if the tile sits inside a larger page layout from that package.
|
|
59
|
-
|
|
60
75
|
## 3. Before / After
|
|
61
76
|
|
|
62
77
|
### 3a. Basic tile — number value
|
|
@@ -77,29 +92,30 @@ function AnsweredTile() {
|
|
|
77
92
|
}
|
|
78
93
|
```
|
|
79
94
|
|
|
80
|
-
**After (`@aircall/ds`):**
|
|
95
|
+
**After (`@aircall/blocks` + `@aircall/ds`):**
|
|
81
96
|
```tsx
|
|
82
|
-
import {
|
|
97
|
+
import { CardContent, CardHeader, CardTitle } from '@aircall/ds';
|
|
98
|
+
import { KpiCard, KpiValue } from '@aircall/blocks';
|
|
83
99
|
|
|
84
100
|
function AnsweredTile() {
|
|
85
101
|
return (
|
|
86
|
-
<
|
|
102
|
+
<KpiCard className="min-w-36">
|
|
87
103
|
<CardHeader>
|
|
88
|
-
<CardTitle
|
|
104
|
+
<CardTitle>Answered</CardTitle>
|
|
89
105
|
</CardHeader>
|
|
90
106
|
<CardContent>
|
|
91
|
-
<
|
|
107
|
+
<KpiValue value={123} kind="number" />
|
|
92
108
|
</CardContent>
|
|
93
|
-
</
|
|
109
|
+
</KpiCard>
|
|
94
110
|
);
|
|
95
111
|
}
|
|
96
112
|
```
|
|
97
113
|
|
|
98
114
|
Key changes:
|
|
99
|
-
- `Tile` → `
|
|
100
|
-
- `TileHeader label` → `CardTitle` inside `CardHeader`.
|
|
101
|
-
- `TileBody` → `CardContent`.
|
|
102
|
-
- `TileValue` →
|
|
115
|
+
- `Tile` → `KpiCard`. Optional `minWidth` (default 144 px) becomes `className="min-w-36"`.
|
|
116
|
+
- `TileHeader label` → `CardTitle` inside `CardHeader`.
|
|
117
|
+
- `TileBody` → `CardContent`.
|
|
118
|
+
- `TileValue` → `KpiValue` (formatting + typography owned by the block).
|
|
103
119
|
|
|
104
120
|
### 3b. Tile with loading state
|
|
105
121
|
|
|
@@ -119,26 +135,27 @@ function LoadingTile() {
|
|
|
119
135
|
}
|
|
120
136
|
```
|
|
121
137
|
|
|
122
|
-
**After
|
|
138
|
+
**After:**
|
|
123
139
|
```tsx
|
|
124
|
-
import {
|
|
140
|
+
import { CardContent, CardHeader, CardTitle } from '@aircall/ds';
|
|
141
|
+
import { KpiCard, KpiValueSkeleton } from '@aircall/blocks';
|
|
125
142
|
|
|
126
143
|
function LoadingTile() {
|
|
127
144
|
return (
|
|
128
|
-
<
|
|
145
|
+
<KpiCard className="min-w-36">
|
|
129
146
|
<CardHeader>
|
|
130
|
-
<CardTitle
|
|
147
|
+
<CardTitle>Answered</CardTitle>
|
|
131
148
|
</CardHeader>
|
|
132
|
-
<CardContent
|
|
133
|
-
<
|
|
149
|
+
<CardContent>
|
|
150
|
+
<KpiValueSkeleton />
|
|
134
151
|
</CardContent>
|
|
135
|
-
</
|
|
152
|
+
</KpiCard>
|
|
136
153
|
);
|
|
137
154
|
}
|
|
138
155
|
```
|
|
139
156
|
|
|
140
|
-
|
|
141
|
-
|
|
157
|
+
When a comparison line also loads, pair with `KpiDescriptionSkeleton` inside
|
|
158
|
+
`CardContent className="flex flex-col gap-1"`.
|
|
142
159
|
|
|
143
160
|
### 3c. Tile with tooltip on header
|
|
144
161
|
|
|
@@ -158,10 +175,9 @@ function InfoTile() {
|
|
|
158
175
|
}
|
|
159
176
|
```
|
|
160
177
|
|
|
161
|
-
**After
|
|
178
|
+
**After:**
|
|
162
179
|
```tsx
|
|
163
180
|
import {
|
|
164
|
-
Card,
|
|
165
181
|
CardContent,
|
|
166
182
|
CardHeader,
|
|
167
183
|
CardTitle,
|
|
@@ -171,16 +187,19 @@ import {
|
|
|
171
187
|
TooltipTrigger
|
|
172
188
|
} from '@aircall/ds';
|
|
173
189
|
import { InformationCircleIcon } from '@aircall/react-icons';
|
|
190
|
+
import { KpiCard, KpiValue } from '@aircall/blocks';
|
|
174
191
|
|
|
175
192
|
function InfoTile() {
|
|
176
193
|
return (
|
|
177
|
-
<
|
|
194
|
+
<KpiCard className="min-w-36">
|
|
178
195
|
<CardHeader>
|
|
179
|
-
<CardTitle className="flex items-center gap-1
|
|
196
|
+
<CardTitle className="flex items-center gap-1">
|
|
180
197
|
Answered
|
|
181
198
|
<TooltipProvider>
|
|
182
199
|
<Tooltip>
|
|
183
|
-
<TooltipTrigger
|
|
200
|
+
<TooltipTrigger
|
|
201
|
+
render={<button type="button" className="inline-flex" aria-label="More info" />}
|
|
202
|
+
>
|
|
184
203
|
<InformationCircleIcon className="h-3 w-3 cursor-default" aria-hidden />
|
|
185
204
|
</TooltipTrigger>
|
|
186
205
|
<TooltipContent>Calls answered in the period</TooltipContent>
|
|
@@ -189,17 +208,14 @@ function InfoTile() {
|
|
|
189
208
|
</CardTitle>
|
|
190
209
|
</CardHeader>
|
|
191
210
|
<CardContent>
|
|
192
|
-
<
|
|
211
|
+
<KpiValue value={123} />
|
|
193
212
|
</CardContent>
|
|
194
|
-
</
|
|
213
|
+
</KpiCard>
|
|
195
214
|
);
|
|
196
215
|
}
|
|
197
216
|
```
|
|
198
217
|
|
|
199
|
-
|
|
200
|
-
`Tooltip` is hover-driven by default — no extra state needed.
|
|
201
|
-
|
|
202
|
-
### 3d. Tile with actions (TileActions)
|
|
218
|
+
### 3d. Tile with actions + clickable value
|
|
203
219
|
|
|
204
220
|
**Before (`@dashboard/library`):**
|
|
205
221
|
```tsx
|
|
@@ -225,50 +241,37 @@ function ActionTile() {
|
|
|
225
241
|
}
|
|
226
242
|
```
|
|
227
243
|
|
|
228
|
-
**After
|
|
244
|
+
**After:**
|
|
229
245
|
```tsx
|
|
230
|
-
import {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
CardAction,
|
|
234
|
-
CardContent,
|
|
235
|
-
CardHeader,
|
|
236
|
-
CardTitle
|
|
237
|
-
} from '@aircall/ds';
|
|
238
|
-
import { SettingsIcon, MoreVerticalIcon } from '@aircall/react-icons';
|
|
246
|
+
import { Button, CardAction, CardContent, CardHeader, CardTitle } from '@aircall/ds';
|
|
247
|
+
import { EllipsisVertical, Settings } from '@aircall/react-icons';
|
|
248
|
+
import { KpiCard, KpiValue } from '@aircall/blocks';
|
|
239
249
|
|
|
240
250
|
function ActionTile() {
|
|
241
251
|
return (
|
|
242
|
-
<
|
|
252
|
+
<KpiCard className="min-w-36">
|
|
243
253
|
<CardHeader>
|
|
244
|
-
<CardTitle
|
|
254
|
+
<CardTitle>Answered</CardTitle>
|
|
245
255
|
<CardAction className="flex items-center gap-1">
|
|
246
|
-
<Button variant="ghost" size="icon" aria-label="Settings" onClick={() => null}>
|
|
247
|
-
<
|
|
256
|
+
<Button variant="ghost" size="icon-sm" aria-label="Settings" onClick={() => null}>
|
|
257
|
+
<Settings />
|
|
248
258
|
</Button>
|
|
249
|
-
<Button variant="ghost" size="icon" aria-label="More options" onClick={() => null}>
|
|
250
|
-
<
|
|
259
|
+
<Button variant="ghost" size="icon-sm" aria-label="More options" onClick={() => null}>
|
|
260
|
+
<EllipsisVertical />
|
|
251
261
|
</Button>
|
|
252
262
|
</CardAction>
|
|
253
263
|
</CardHeader>
|
|
254
264
|
<CardContent>
|
|
255
|
-
<
|
|
256
|
-
type="button"
|
|
257
|
-
className="text-2xl font-bold tabular-nums underline decoration-dashed"
|
|
258
|
-
onClick={() => null}
|
|
259
|
-
>
|
|
260
|
-
123
|
|
261
|
-
</button>
|
|
265
|
+
<KpiValue value={123} onClick={() => null} />
|
|
262
266
|
</CardContent>
|
|
263
|
-
</
|
|
267
|
+
</KpiCard>
|
|
264
268
|
);
|
|
265
269
|
}
|
|
266
270
|
```
|
|
267
271
|
|
|
268
272
|
Key changes:
|
|
269
|
-
- `TileActions` → `CardAction` inside `CardHeader`.
|
|
270
|
-
- `
|
|
271
|
-
- `TileValue onClick` (clickable value) → plain `<button>` element styled to match.
|
|
273
|
+
- `TileActions` → `CardAction` inside `CardHeader`.
|
|
274
|
+
- `TileValue onClick` → `KpiValue onClick` (dotted underline; `active` for solid underline).
|
|
272
275
|
|
|
273
276
|
### 3e. Disabled tile
|
|
274
277
|
|
|
@@ -279,7 +282,7 @@ import { Tile, TileBody, TileHeader, TileValue } from '@dashboard/library';
|
|
|
279
282
|
function DisabledTile() {
|
|
280
283
|
return (
|
|
281
284
|
<Tile disabled>
|
|
282
|
-
<TileHeader label="Answered"
|
|
285
|
+
<TileHeader label="Answered" />
|
|
283
286
|
<TileBody>
|
|
284
287
|
<TileValue onClick={() => null} value={123} />
|
|
285
288
|
</TileBody>
|
|
@@ -288,142 +291,189 @@ function DisabledTile() {
|
|
|
288
291
|
}
|
|
289
292
|
```
|
|
290
293
|
|
|
291
|
-
**After
|
|
294
|
+
**After:**
|
|
292
295
|
```tsx
|
|
293
|
-
import {
|
|
296
|
+
import { CardContent, CardHeader, CardTitle } from '@aircall/ds';
|
|
297
|
+
import { KpiCard, KpiValue } from '@aircall/blocks';
|
|
294
298
|
|
|
295
299
|
function DisabledTile() {
|
|
296
300
|
return (
|
|
297
|
-
<
|
|
301
|
+
<KpiCard disabled className="min-w-36">
|
|
298
302
|
<CardHeader>
|
|
299
|
-
<CardTitle
|
|
303
|
+
<CardTitle>Answered</CardTitle>
|
|
300
304
|
</CardHeader>
|
|
301
305
|
<CardContent>
|
|
302
|
-
<
|
|
306
|
+
<KpiValue value={123} onClick={() => null} />
|
|
307
|
+
</CardContent>
|
|
308
|
+
</KpiCard>
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
`KpiCard disabled` applies `opacity-50` + `cursor-not-allowed` and cascades so nested
|
|
314
|
+
`KpiValue` buttons cannot be activated — no need to strip `onClick` or style the value by hand.
|
|
315
|
+
|
|
316
|
+
### 3f. Comparison / trend line (TileExtra)
|
|
317
|
+
|
|
318
|
+
**Before:** `TileExtra` with `secondaryText` + `trend`.
|
|
319
|
+
|
|
320
|
+
**After:**
|
|
321
|
+
```tsx
|
|
322
|
+
import { CardContent, CardHeader, CardTitle } from '@aircall/ds';
|
|
323
|
+
import { KpiCard, KpiDescription, KpiValue } from '@aircall/blocks';
|
|
324
|
+
|
|
325
|
+
function OutboundCallsTile() {
|
|
326
|
+
return (
|
|
327
|
+
<KpiCard className="min-w-36">
|
|
328
|
+
<CardHeader>
|
|
329
|
+
<CardTitle>Outbound calls</CardTitle>
|
|
330
|
+
</CardHeader>
|
|
331
|
+
<CardContent className="flex flex-col gap-1">
|
|
332
|
+
<KpiValue value={87} onClick={() => null} />
|
|
333
|
+
<KpiDescription trend="up" tone="positive">
|
|
334
|
+
+16.3% vs previous period
|
|
335
|
+
</KpiDescription>
|
|
303
336
|
</CardContent>
|
|
304
|
-
</
|
|
337
|
+
</KpiCard>
|
|
305
338
|
);
|
|
306
339
|
}
|
|
307
340
|
```
|
|
308
341
|
|
|
309
|
-
`
|
|
310
|
-
|
|
311
|
-
|
|
342
|
+
`trend` is icon direction only; `tone` is color only — an upward change that is bad uses
|
|
343
|
+
`trend="up" tone="negative"`.
|
|
344
|
+
|
|
345
|
+
### 3g. Duration values
|
|
346
|
+
|
|
347
|
+
```tsx
|
|
348
|
+
{/* Long: "1h 2min 3s" */}
|
|
349
|
+
<KpiValue value={3723} kind="duration" />
|
|
350
|
+
|
|
351
|
+
{/* Clock hours: "01:38 hr/min" */}
|
|
352
|
+
<KpiValue value={5880} kind="duration" durationStyle="clock" durationScale="hours" />
|
|
353
|
+
|
|
354
|
+
{/* Clock minutes: "05:04 min/sec" */}
|
|
355
|
+
<KpiValue value={304} kind="duration" durationStyle="clock" durationScale="minutes" />
|
|
356
|
+
|
|
357
|
+
{/* Duration + unit label */}
|
|
358
|
+
<KpiValue value={112} kind="duration" unit="avg" />
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Do not reimplement `formatSecondsToDuration` in the app — `KpiValue` owns formatting.
|
|
312
362
|
|
|
313
363
|
---
|
|
314
364
|
|
|
315
365
|
## 4. Common mistakes
|
|
316
366
|
|
|
317
|
-
### Mistake 1 —
|
|
367
|
+
### Mistake 1 — Using raw DS `Card` instead of `KpiCard`
|
|
318
368
|
|
|
319
369
|
```tsx
|
|
320
|
-
// ❌ Wrong —
|
|
321
|
-
<Card
|
|
322
|
-
<
|
|
370
|
+
// ❌ Wrong — loses disabled cascade and KPI defaults (size="sm", gap)
|
|
371
|
+
<Card size="sm">
|
|
372
|
+
<CardHeader>
|
|
373
|
+
<CardTitle>Answered</CardTitle>
|
|
374
|
+
</CardHeader>
|
|
375
|
+
<CardContent>
|
|
376
|
+
<KpiValue value={123} />
|
|
377
|
+
</CardContent>
|
|
323
378
|
</Card>
|
|
324
379
|
|
|
325
|
-
// ✅ Correct
|
|
326
|
-
<
|
|
327
|
-
<
|
|
328
|
-
</
|
|
380
|
+
// ✅ Correct
|
|
381
|
+
<KpiCard>
|
|
382
|
+
<CardHeader>
|
|
383
|
+
<CardTitle>Answered</CardTitle>
|
|
384
|
+
</CardHeader>
|
|
385
|
+
<CardContent>
|
|
386
|
+
<KpiValue value={123} />
|
|
387
|
+
</CardContent>
|
|
388
|
+
</KpiCard>
|
|
329
389
|
```
|
|
330
390
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
391
|
+
### Mistake 2 — Hand-formatting values or using raw `Skeleton`
|
|
392
|
+
|
|
393
|
+
```tsx
|
|
394
|
+
// ❌ Wrong
|
|
395
|
+
<span className="text-3xl font-bold">{formatDuration(123)}</span>
|
|
396
|
+
<Skeleton className="h-6 w-18" />
|
|
397
|
+
|
|
398
|
+
// ✅ Correct
|
|
399
|
+
<KpiValue value={123} kind="duration" />
|
|
400
|
+
<KpiValueSkeleton />
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Mistake 3 — Passing Tile/TileBody flex props to KpiCard / CardContent
|
|
404
|
+
|
|
405
|
+
```tsx
|
|
406
|
+
// ❌ Wrong — tractor FlexProps on the card
|
|
407
|
+
<KpiCard minWidth="228px" px="xxs" py="xxs">
|
|
408
|
+
<CardContent alignItems="center" gap="xxxs">…</CardContent>
|
|
409
|
+
</KpiCard>
|
|
334
410
|
|
|
335
|
-
|
|
411
|
+
// ✅ Correct — Tailwind via className
|
|
412
|
+
<KpiCard className="min-w-[228px]">
|
|
413
|
+
<CardContent className="flex flex-col gap-1">…</CardContent>
|
|
414
|
+
</KpiCard>
|
|
415
|
+
```
|
|
336
416
|
|
|
337
|
-
### Mistake
|
|
417
|
+
### Mistake 4 — Placing CardAction outside CardHeader
|
|
338
418
|
|
|
339
419
|
```tsx
|
|
340
|
-
// ❌ Wrong
|
|
341
|
-
<
|
|
342
|
-
<CardAction>
|
|
343
|
-
<Button variant="ghost" size="icon">…</Button>
|
|
344
|
-
</CardAction>
|
|
420
|
+
// ❌ Wrong
|
|
421
|
+
<KpiCard>
|
|
422
|
+
<CardAction>…</CardAction>
|
|
345
423
|
<CardHeader>
|
|
346
424
|
<CardTitle>Answered</CardTitle>
|
|
347
425
|
</CardHeader>
|
|
348
426
|
<CardContent>…</CardContent>
|
|
349
|
-
</
|
|
427
|
+
</KpiCard>
|
|
350
428
|
|
|
351
429
|
// ✅ Correct — CardAction must be a child of CardHeader
|
|
352
|
-
<
|
|
430
|
+
<KpiCard>
|
|
353
431
|
<CardHeader>
|
|
354
432
|
<CardTitle>Answered</CardTitle>
|
|
355
|
-
<CardAction>
|
|
356
|
-
<Button variant="ghost" size="icon">…</Button>
|
|
357
|
-
</CardAction>
|
|
433
|
+
<CardAction>…</CardAction>
|
|
358
434
|
</CardHeader>
|
|
359
435
|
<CardContent>…</CardContent>
|
|
360
|
-
</
|
|
436
|
+
</KpiCard>
|
|
361
437
|
```
|
|
362
438
|
|
|
363
|
-
|
|
364
|
-
`CardAction` carries `data-slot="card-action"` and sits at `col-start-2 row-span-2`. Outside
|
|
365
|
-
`CardHeader` there is no grid parent; the action renders in document flow at full width.
|
|
366
|
-
|
|
367
|
-
Source: `packages/ds/src/components/card.tsx`
|
|
368
|
-
|
|
369
|
-
### Mistake 3 — Importing TILE_VALUE enum or TileValue from @dashboard/library after migration
|
|
439
|
+
### Mistake 5 — Keeping TILE_VALUE / TileValue imports after migration
|
|
370
440
|
|
|
371
441
|
```tsx
|
|
372
|
-
// ❌ Wrong
|
|
442
|
+
// ❌ Wrong
|
|
373
443
|
import { TILE_VALUE, Tile, TileHeader, TileValue } from '@dashboard/library';
|
|
374
|
-
|
|
375
444
|
<TileValue type={TILE_VALUE.DURATION} value={123} />
|
|
376
445
|
|
|
377
|
-
// ✅ Correct
|
|
378
|
-
import {
|
|
379
|
-
|
|
380
|
-
// Duration formatting (seconds → "2m 03s") — implement inline or extract to a util
|
|
381
|
-
function formatDuration(seconds: number): string {
|
|
382
|
-
const m = Math.floor(seconds / 60);
|
|
383
|
-
const s = seconds % 60;
|
|
384
|
-
return `${m}m ${String(s).padStart(2, '0')}s`;
|
|
385
|
-
}
|
|
446
|
+
// ✅ Correct
|
|
447
|
+
import { KpiCard, KpiValue } from '@aircall/blocks';
|
|
448
|
+
import { CardContent, CardHeader, CardTitle } from '@aircall/ds';
|
|
386
449
|
|
|
387
|
-
<
|
|
450
|
+
<KpiCard>
|
|
388
451
|
<CardHeader>
|
|
389
|
-
<CardTitle
|
|
452
|
+
<CardTitle>AHT</CardTitle>
|
|
390
453
|
</CardHeader>
|
|
391
454
|
<CardContent>
|
|
392
|
-
<
|
|
455
|
+
<KpiValue value={123} kind="duration" />
|
|
393
456
|
</CardContent>
|
|
394
|
-
</
|
|
457
|
+
</KpiCard>
|
|
395
458
|
```
|
|
396
459
|
|
|
397
|
-
|
|
398
|
-
seconds via an internal `formatSecondsToDuration` helper. That helper is not exported from
|
|
399
|
-
`@dashboard/library`'s public API. Extract the formatting logic to a local utility function.
|
|
400
|
-
|
|
401
|
-
Source: `packages/ds/src/components/card.tsx`
|
|
402
|
-
|
|
403
|
-
### Mistake 4 — Using CardTitle for the metric value instead of the label
|
|
460
|
+
### Mistake 6 — Using CardTitle for the metric value instead of the label
|
|
404
461
|
|
|
405
462
|
```tsx
|
|
406
|
-
// ❌ Wrong — CardTitle used for the big number
|
|
407
|
-
<
|
|
463
|
+
// ❌ Wrong — CardTitle used for the big number
|
|
464
|
+
<KpiCard>
|
|
408
465
|
<CardHeader>
|
|
409
466
|
<CardTitle>123</CardTitle>
|
|
410
467
|
</CardHeader>
|
|
411
|
-
</
|
|
468
|
+
</KpiCard>
|
|
412
469
|
|
|
413
|
-
// ✅ Correct — CardTitle
|
|
414
|
-
<
|
|
470
|
+
// ✅ Correct — CardTitle = label; KpiValue = metric
|
|
471
|
+
<KpiCard>
|
|
415
472
|
<CardHeader>
|
|
416
|
-
<CardTitle
|
|
473
|
+
<CardTitle>Answered</CardTitle>
|
|
417
474
|
</CardHeader>
|
|
418
475
|
<CardContent>
|
|
419
|
-
<
|
|
476
|
+
<KpiValue value={123} />
|
|
420
477
|
</CardContent>
|
|
421
|
-
</
|
|
478
|
+
</KpiCard>
|
|
422
479
|
```
|
|
423
|
-
|
|
424
|
-
In `@dashboard/library`, `TileHeader` rendered the human-readable label ("Answered") and
|
|
425
|
-
`TileValue` rendered the metric number. In the DS mapping, `CardTitle` takes the label and
|
|
426
|
-
`CardContent` holds the value element. Swapping them produces an inverted visual hierarchy
|
|
427
|
-
and breaks assistive-technology reading order.
|
|
428
|
-
|
|
429
|
-
Source: `packages/ds/src/components/card.tsx`
|
|
@@ -9,7 +9,6 @@ description: >
|
|
|
9
9
|
consumers where the host already loads DS globals.
|
|
10
10
|
type: core
|
|
11
11
|
library: aircall-blocks
|
|
12
|
-
library_version: "0.5.1"
|
|
13
12
|
requires:
|
|
14
13
|
- aircall-ds/setup
|
|
15
14
|
sources:
|
|
@@ -58,6 +57,28 @@ import '@aircall/ds/globals.css';
|
|
|
58
57
|
import '@aircall/blocks/globals.css';
|
|
59
58
|
```
|
|
60
59
|
|
|
60
|
+
#### Single-compilation via `theme.css` (recommended for DS + blocks)
|
|
61
|
+
|
|
62
|
+
The two precompiled bundles above each re-emit the same Tailwind utilities into `@layer
|
|
63
|
+
utilities`; at equal specificity the last-loaded bundle can clobber the other's responsive
|
|
64
|
+
variants (e.g. blocks' plain `.text-base` beating DS's `md:text-sm` on `Textarea`), and **no
|
|
65
|
+
import order fixes it**. To avoid this, import the directive-preserving `theme.css` artifacts
|
|
66
|
+
(tokens + dark variant + reset + keyframes only — **no** Preflight, **no** utilities) and let a
|
|
67
|
+
single Tailwind pass generate the utilities by `@source`-scanning the library dist:
|
|
68
|
+
|
|
69
|
+
```css
|
|
70
|
+
/* style.css */
|
|
71
|
+
@import 'tailwindcss'; /* consumer owns the single Preflight + engine */
|
|
72
|
+
@import '@aircall/ds/theme.css';
|
|
73
|
+
@import '@aircall/blocks/theme.css';
|
|
74
|
+
@source '../node_modules/@aircall/ds/dist/index.js';
|
|
75
|
+
@source '../node_modules/@aircall/blocks/dist/index.js';
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Here the full `@import 'tailwindcss'` is **correct** (unlike the `globals.css` case) because
|
|
79
|
+
`theme.css` ships no Preflight/utilities. `globals.css` still ships unchanged for precompiled-
|
|
80
|
+
bundle consumers. Full rationale: `packages/ds/docs/single-compilation-theme-css.md`.
|
|
81
|
+
|
|
61
82
|
### Module-federation consumer (host already owns the Preflight)
|
|
62
83
|
|
|
63
84
|
When your app is a remote loaded inside a host that already imports DS globals
|