@aircall/blocks 0.5.1 → 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.
- package/README.md +43 -0
- package/dist/globals.css +1 -1
- package/dist/index.d.ts +81 -24
- package/dist/index.js +493 -113
- package/package.json +20 -4
- 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,474 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: aircall-blocks/migrate-dashboard/list
|
|
3
|
+
description: >
|
|
4
|
+
Migrate @dashboard/library List, ListItem, ListPaginated, and ListItemActions to
|
|
5
|
+
@aircall/ds Item primitives (ItemGroup, Item, ItemMedia, ItemContent, ItemActions,
|
|
6
|
+
ItemTitle, ItemDescription). Load when a file imports List, ListItem, ListPaginated,
|
|
7
|
+
or ListItemActions from @dashboard/library.
|
|
8
|
+
type: sub-skill
|
|
9
|
+
library: aircall-blocks
|
|
10
|
+
library_version: "0.5.1"
|
|
11
|
+
requires:
|
|
12
|
+
- aircall-blocks/setup
|
|
13
|
+
- aircall-blocks/migrate-dashboard
|
|
14
|
+
sources:
|
|
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 | @aircall/ds |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| `List` (container) | `ItemGroup` |
|
|
25
|
+
| `List` `hasScroll` prop | `ScrollArea` wrapping `ItemGroup` |
|
|
26
|
+
| `List` `stickyFooter` prop | Sibling `div` below `ItemGroup`; no DS equivalent |
|
|
27
|
+
| `List` `isLoadingMoreItems` + `customLoader` | Inline `Spinner` below `ItemGroup`; no DS equivalent |
|
|
28
|
+
| `List` `overLoading` | Overlay `Spinner` positioned absolutely over `ItemGroup` |
|
|
29
|
+
| `List` `renderHeader` | Sibling element above `ItemGroup` |
|
|
30
|
+
| `ListItem` (row) | `Item` |
|
|
31
|
+
| `ListItem` `avatar` prop | `ItemMedia` with `variant="image"` inside `Item` |
|
|
32
|
+
| `ListItem` `primaryText` prop | `ItemTitle` inside `ItemContent` inside `Item` |
|
|
33
|
+
| `ListItem` `secondaryText` prop | `ItemDescription` inside `ItemContent` inside `Item` |
|
|
34
|
+
| `ListItem` `dropdownOptions` prop | `DropdownMenu` with `DropdownMenuItem` entries inside `ItemActions` inside `Item` |
|
|
35
|
+
| `ListItem` `banner` prop | Sibling element below `Item` (no direct DS slot) |
|
|
36
|
+
| `ListItem` arbitrary `FlexProps` (px, py, h…) | `className` on `Item`; use Tailwind utilities |
|
|
37
|
+
| `ListItemActions` (wrapper with action menu) | `Item` + `ItemActions` + `DropdownMenu` from `@aircall/ds` |
|
|
38
|
+
| `ListPaginated` | `ItemGroup` + `Pagination` (and its parts) from `@aircall/ds` |
|
|
39
|
+
|
|
40
|
+
`@dashboard/library` `List` wrapped items in `<ul>/<li>` and auto-added dividers via CSS.
|
|
41
|
+
`ItemGroup` uses `role="list"` and a `stackedItems` prop — set `stackedItems` to replicate the
|
|
42
|
+
no-gap, border-between-items visual style.
|
|
43
|
+
|
|
44
|
+
`ListItem` accepted raw `FlexProps` from `@aircall/tractor` (spacing tokens, color tokens). `Item`
|
|
45
|
+
extends `React.ComponentProps<'div'>` — use `className` with Tailwind utilities instead.
|
|
46
|
+
|
|
47
|
+
## 2. Imports
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
// DS primitives — item / list family
|
|
51
|
+
import {
|
|
52
|
+
Item,
|
|
53
|
+
ItemActions,
|
|
54
|
+
ItemContent,
|
|
55
|
+
ItemDescription,
|
|
56
|
+
ItemGroup,
|
|
57
|
+
ItemMedia,
|
|
58
|
+
ItemTitle
|
|
59
|
+
} from '@aircall/ds';
|
|
60
|
+
|
|
61
|
+
// DS pagination (for ListPaginated)
|
|
62
|
+
import {
|
|
63
|
+
Pagination,
|
|
64
|
+
PaginationContent,
|
|
65
|
+
PaginationItem,
|
|
66
|
+
PaginationLink,
|
|
67
|
+
PaginationNext,
|
|
68
|
+
PaginationPrevious
|
|
69
|
+
} from '@aircall/ds';
|
|
70
|
+
|
|
71
|
+
// DS dropdown (for dropdownOptions)
|
|
72
|
+
import {
|
|
73
|
+
DropdownMenu,
|
|
74
|
+
DropdownMenuContent,
|
|
75
|
+
DropdownMenuItem,
|
|
76
|
+
DropdownMenuTrigger
|
|
77
|
+
} from '@aircall/ds';
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
No `@aircall/blocks` imports are needed for this migration — `Item*` and `ItemGroup` live entirely
|
|
81
|
+
in `@aircall/ds`.
|
|
82
|
+
|
|
83
|
+
## 3. Before / After
|
|
84
|
+
|
|
85
|
+
### 3a. Basic List with avatar + text items
|
|
86
|
+
|
|
87
|
+
**Before (`@dashboard/library`):**
|
|
88
|
+
```tsx
|
|
89
|
+
import { List, ListItem } from '@dashboard/library';
|
|
90
|
+
|
|
91
|
+
function UserList({ users }: { users: { id: string; name: string; role: string; avatar: React.ReactElement }[] }) {
|
|
92
|
+
return (
|
|
93
|
+
<List>
|
|
94
|
+
{users.map((u) => (
|
|
95
|
+
<ListItem
|
|
96
|
+
key={u.id}
|
|
97
|
+
avatar={u.avatar}
|
|
98
|
+
primaryText={u.name}
|
|
99
|
+
secondaryText={u.role}
|
|
100
|
+
/>
|
|
101
|
+
))}
|
|
102
|
+
</List>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**After (`@aircall/ds`):**
|
|
108
|
+
```tsx
|
|
109
|
+
import { Item, ItemContent, ItemDescription, ItemGroup, ItemMedia, ItemTitle } from '@aircall/ds';
|
|
110
|
+
|
|
111
|
+
function UserList({ users }: { users: { id: string; name: string; role: string; avatar: React.ReactElement }[] }) {
|
|
112
|
+
return (
|
|
113
|
+
<ItemGroup stackedItems>
|
|
114
|
+
{users.map((u) => (
|
|
115
|
+
<Item key={u.id} variant="outline">
|
|
116
|
+
<ItemMedia variant="image">{u.avatar}</ItemMedia>
|
|
117
|
+
<ItemContent>
|
|
118
|
+
<ItemTitle>{u.name}</ItemTitle>
|
|
119
|
+
<ItemDescription>{u.role}</ItemDescription>
|
|
120
|
+
</ItemContent>
|
|
121
|
+
</Item>
|
|
122
|
+
))}
|
|
123
|
+
</ItemGroup>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Key changes:
|
|
129
|
+
- `List` → `ItemGroup` with `stackedItems` to preserve the stacked-row layout with
|
|
130
|
+
collapsed borders between items.
|
|
131
|
+
- `ListItem` → `Item` with `variant="outline"` (gives each row a visible border).
|
|
132
|
+
- `avatar` → `ItemMedia variant="image"` as first child of `Item`.
|
|
133
|
+
- `primaryText` → `ItemTitle` inside `ItemContent`.
|
|
134
|
+
- `secondaryText` → `ItemDescription` inside `ItemContent`.
|
|
135
|
+
- Drop all `FlexProps` token spreads; use `className` with Tailwind utilities instead.
|
|
136
|
+
|
|
137
|
+
### 3b. ListItem with custom children and dropdown actions
|
|
138
|
+
|
|
139
|
+
**Before (`@dashboard/library`):**
|
|
140
|
+
```tsx
|
|
141
|
+
import { List, ListItem } from '@dashboard/library';
|
|
142
|
+
|
|
143
|
+
function IntegrationList({ items }: { items: { id: string; name: string }[] }) {
|
|
144
|
+
return (
|
|
145
|
+
<List>
|
|
146
|
+
{items.map((item) => (
|
|
147
|
+
<ListItem
|
|
148
|
+
key={item.id}
|
|
149
|
+
primaryText={item.name}
|
|
150
|
+
dropdownOptions={[
|
|
151
|
+
{ title: 'Edit', callback: () => onEdit(item.id) },
|
|
152
|
+
{ title: 'Delete', callback: () => onDelete(item.id) }
|
|
153
|
+
]}
|
|
154
|
+
/>
|
|
155
|
+
))}
|
|
156
|
+
</List>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**After (`@aircall/ds`):**
|
|
162
|
+
```tsx
|
|
163
|
+
import {
|
|
164
|
+
DropdownMenu,
|
|
165
|
+
DropdownMenuContent,
|
|
166
|
+
DropdownMenuItem,
|
|
167
|
+
DropdownMenuTrigger,
|
|
168
|
+
Item,
|
|
169
|
+
ItemActions,
|
|
170
|
+
ItemContent,
|
|
171
|
+
ItemGroup,
|
|
172
|
+
ItemTitle
|
|
173
|
+
} from '@aircall/ds';
|
|
174
|
+
import { Button } from '@aircall/ds';
|
|
175
|
+
import { EllipsisVertical } from '@aircall/react-icons';
|
|
176
|
+
|
|
177
|
+
function IntegrationList({ items }: { items: { id: string; name: string }[] }) {
|
|
178
|
+
return (
|
|
179
|
+
<ItemGroup stackedItems>
|
|
180
|
+
{items.map((item) => (
|
|
181
|
+
<Item key={item.id} variant="outline">
|
|
182
|
+
<ItemContent>
|
|
183
|
+
<ItemTitle>{item.name}</ItemTitle>
|
|
184
|
+
</ItemContent>
|
|
185
|
+
<ItemActions>
|
|
186
|
+
<DropdownMenu>
|
|
187
|
+
<DropdownMenuTrigger asChild>
|
|
188
|
+
<Button variant="ghost" size="icon" aria-label="More actions">
|
|
189
|
+
<EllipsisVertical />
|
|
190
|
+
</Button>
|
|
191
|
+
</DropdownMenuTrigger>
|
|
192
|
+
<DropdownMenuContent align="end">
|
|
193
|
+
<DropdownMenuItem onClick={() => onEdit(item.id)}>Edit</DropdownMenuItem>
|
|
194
|
+
<DropdownMenuItem onClick={() => onDelete(item.id)}>Delete</DropdownMenuItem>
|
|
195
|
+
</DropdownMenuContent>
|
|
196
|
+
</DropdownMenu>
|
|
197
|
+
</ItemActions>
|
|
198
|
+
</Item>
|
|
199
|
+
))}
|
|
200
|
+
</ItemGroup>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Key changes:
|
|
206
|
+
- `dropdownOptions` prop → explicit `DropdownMenu` composition inside `ItemActions`.
|
|
207
|
+
- `ItemActions` becomes the last child of `Item`; the flex layout positions it to the right
|
|
208
|
+
automatically.
|
|
209
|
+
- Icons: import from `@aircall/react-icons`, not `lucide-react` directly.
|
|
210
|
+
|
|
211
|
+
### 3c. ListItemActions (card-style item with action menu)
|
|
212
|
+
|
|
213
|
+
**Before (`@dashboard/library`):**
|
|
214
|
+
```tsx
|
|
215
|
+
import { ListItemActions } from '@dashboard/library';
|
|
216
|
+
|
|
217
|
+
function PhoneNumberRow({ number, onEdit, onDelete }: { number: string; onEdit: () => void; onDelete: () => void }) {
|
|
218
|
+
return (
|
|
219
|
+
<ListItemActions
|
|
220
|
+
actions={[
|
|
221
|
+
{ name: 'Edit', onClick: onEdit },
|
|
222
|
+
{ name: 'Delete', onClick: onDelete }
|
|
223
|
+
]}
|
|
224
|
+
>
|
|
225
|
+
<span>{number}</span>
|
|
226
|
+
</ListItemActions>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**After (`@aircall/ds`):**
|
|
232
|
+
```tsx
|
|
233
|
+
import {
|
|
234
|
+
DropdownMenu,
|
|
235
|
+
DropdownMenuContent,
|
|
236
|
+
DropdownMenuItem,
|
|
237
|
+
DropdownMenuTrigger,
|
|
238
|
+
Item,
|
|
239
|
+
ItemActions,
|
|
240
|
+
ItemContent,
|
|
241
|
+
ItemTitle
|
|
242
|
+
} from '@aircall/ds';
|
|
243
|
+
import { Button } from '@aircall/ds';
|
|
244
|
+
import { EllipsisVertical } from '@aircall/react-icons';
|
|
245
|
+
|
|
246
|
+
function PhoneNumberRow({ number, onEdit, onDelete }: { number: string; onEdit: () => void; onDelete: () => void }) {
|
|
247
|
+
return (
|
|
248
|
+
<Item variant="outline">
|
|
249
|
+
<ItemContent>
|
|
250
|
+
<ItemTitle>{number}</ItemTitle>
|
|
251
|
+
</ItemContent>
|
|
252
|
+
<ItemActions>
|
|
253
|
+
<DropdownMenu>
|
|
254
|
+
<DropdownMenuTrigger asChild>
|
|
255
|
+
<Button variant="ghost" size="icon" aria-label="More actions">
|
|
256
|
+
<EllipsisVertical />
|
|
257
|
+
</Button>
|
|
258
|
+
</DropdownMenuTrigger>
|
|
259
|
+
<DropdownMenuContent align="end">
|
|
260
|
+
<DropdownMenuItem onClick={onEdit}>Edit</DropdownMenuItem>
|
|
261
|
+
<DropdownMenuItem onClick={onDelete}>Delete</DropdownMenuItem>
|
|
262
|
+
</DropdownMenuContent>
|
|
263
|
+
</DropdownMenu>
|
|
264
|
+
</ItemActions>
|
|
265
|
+
</Item>
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Key changes:
|
|
271
|
+
- `ListItemActions` `actions` prop → `DropdownMenu` composition inside `ItemActions`.
|
|
272
|
+
- `ListItemActions` `children` → `ItemContent` / `ItemTitle` inside `Item`.
|
|
273
|
+
- The absolute-positioned action menu is replaced by `ItemActions` flex layout.
|
|
274
|
+
|
|
275
|
+
### 3d. ListPaginated
|
|
276
|
+
|
|
277
|
+
**Before (`@dashboard/library`):**
|
|
278
|
+
```tsx
|
|
279
|
+
import { ListPaginated, ListItem } from '@dashboard/library';
|
|
280
|
+
|
|
281
|
+
function PaginatedUsers({ users, page, onPageChange, onPageSizeChange, total, pageSize }) {
|
|
282
|
+
return (
|
|
283
|
+
<ListPaginated
|
|
284
|
+
activePage={page}
|
|
285
|
+
onPageChange={onPageChange}
|
|
286
|
+
onPageSizeChange={onPageSizeChange}
|
|
287
|
+
pageSize={pageSize}
|
|
288
|
+
recordsTotalCount={total}
|
|
289
|
+
>
|
|
290
|
+
{users.map((u) => (
|
|
291
|
+
<ListItem key={u.id} primaryText={u.name} />
|
|
292
|
+
))}
|
|
293
|
+
</ListPaginated>
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
**After (`@aircall/ds`):**
|
|
299
|
+
```tsx
|
|
300
|
+
import {
|
|
301
|
+
Item,
|
|
302
|
+
ItemContent,
|
|
303
|
+
ItemGroup,
|
|
304
|
+
ItemTitle,
|
|
305
|
+
Pagination,
|
|
306
|
+
PaginationContent,
|
|
307
|
+
PaginationItem,
|
|
308
|
+
PaginationLink,
|
|
309
|
+
PaginationNext,
|
|
310
|
+
PaginationPrevious
|
|
311
|
+
} from '@aircall/ds';
|
|
312
|
+
|
|
313
|
+
function PaginatedUsers({ users, page, onPageChange, total, pageSize }) {
|
|
314
|
+
const totalPages = Math.ceil(total / pageSize);
|
|
315
|
+
|
|
316
|
+
return (
|
|
317
|
+
<div className="flex flex-col gap-3">
|
|
318
|
+
<ItemGroup stackedItems>
|
|
319
|
+
{users.map((u) => (
|
|
320
|
+
<Item key={u.id} variant="outline">
|
|
321
|
+
<ItemContent>
|
|
322
|
+
<ItemTitle>{u.name}</ItemTitle>
|
|
323
|
+
</ItemContent>
|
|
324
|
+
</Item>
|
|
325
|
+
))}
|
|
326
|
+
</ItemGroup>
|
|
327
|
+
<Pagination>
|
|
328
|
+
<PaginationContent>
|
|
329
|
+
<PaginationItem>
|
|
330
|
+
<PaginationPrevious
|
|
331
|
+
onClick={() => onPageChange(page - 1)}
|
|
332
|
+
aria-disabled={page <= 1}
|
|
333
|
+
/>
|
|
334
|
+
</PaginationItem>
|
|
335
|
+
{Array.from({ length: totalPages }, (_, i) => (
|
|
336
|
+
<PaginationItem key={i + 1}>
|
|
337
|
+
<PaginationLink isActive={page === i + 1} onClick={() => onPageChange(i + 1)}>
|
|
338
|
+
{i + 1}
|
|
339
|
+
</PaginationLink>
|
|
340
|
+
</PaginationItem>
|
|
341
|
+
))}
|
|
342
|
+
<PaginationItem>
|
|
343
|
+
<PaginationNext
|
|
344
|
+
onClick={() => onPageChange(page + 1)}
|
|
345
|
+
aria-disabled={page >= totalPages}
|
|
346
|
+
/>
|
|
347
|
+
</PaginationItem>
|
|
348
|
+
</PaginationContent>
|
|
349
|
+
</Pagination>
|
|
350
|
+
</div>
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Key changes:
|
|
356
|
+
- `ListPaginated` has no DS equivalent — decompose into `ItemGroup` + `Pagination` parts.
|
|
357
|
+
- `@aircall/ds` `Pagination` is a composable primitive; build page links explicitly.
|
|
358
|
+
- `onPageSizeChange` has no DS equivalent — implement with a `Select` from `@aircall/ds` if needed.
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## 4. Common mistakes
|
|
363
|
+
|
|
364
|
+
### Mistake 1 — Missing stackedItems on ItemGroup
|
|
365
|
+
|
|
366
|
+
```tsx
|
|
367
|
+
// ❌ Wrong — gaps between items, no stacked border collapse
|
|
368
|
+
<ItemGroup>
|
|
369
|
+
<Item variant="outline">…</Item>
|
|
370
|
+
<Item variant="outline">…</Item>
|
|
371
|
+
</ItemGroup>
|
|
372
|
+
|
|
373
|
+
// ✅ Correct — stacked rows with collapsed borders (matches List visual style)
|
|
374
|
+
<ItemGroup stackedItems>
|
|
375
|
+
<Item variant="outline">…</Item>
|
|
376
|
+
<Item variant="outline">…</Item>
|
|
377
|
+
</ItemGroup>
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Without `stackedItems`, `ItemGroup` applies `gap-4` between items (card-grid style). With
|
|
381
|
+
`stackedItems`, it collapses borders between adjacent `Item` rows and removes gap, replicating
|
|
382
|
+
the `List` border-separated row appearance.
|
|
383
|
+
|
|
384
|
+
Source: `packages/ds/src/components/item.tsx`
|
|
385
|
+
|
|
386
|
+
### Mistake 2 — Spreading tractor FlexProps onto Item
|
|
387
|
+
|
|
388
|
+
```tsx
|
|
389
|
+
// ❌ Wrong — tractor token props (px, py, h, color) do not exist on Item
|
|
390
|
+
<Item px="s" py="xxs" h="xl" color="neutral-600">
|
|
391
|
+
<ItemContent>…</ItemContent>
|
|
392
|
+
</Item>
|
|
393
|
+
|
|
394
|
+
// ✅ Correct — use className with Tailwind utilities
|
|
395
|
+
<Item className="px-3 py-1.5 min-h-10">
|
|
396
|
+
<ItemContent>…</ItemContent>
|
|
397
|
+
</Item>
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
`ListItem` accepted arbitrary `FlexProps` from `@aircall/tractor` (design tokens). `Item`
|
|
401
|
+
extends `React.ComponentProps<'div'>` — only standard HTML attributes and `className` are
|
|
402
|
+
accepted. Passing token strings results in React warnings for unknown DOM attributes.
|
|
403
|
+
|
|
404
|
+
Source: `packages/ds/src/components/item.tsx`
|
|
405
|
+
|
|
406
|
+
### Mistake 3 — Placing ItemActions outside Item
|
|
407
|
+
|
|
408
|
+
```tsx
|
|
409
|
+
// ❌ Wrong — ItemActions outside Item has no flex parent to pin it right
|
|
410
|
+
<ItemGroup stackedItems>
|
|
411
|
+
<Item variant="outline">
|
|
412
|
+
<ItemContent><ItemTitle>Name</ItemTitle></ItemContent>
|
|
413
|
+
</Item>
|
|
414
|
+
<ItemActions>
|
|
415
|
+
<Button>Edit</Button>
|
|
416
|
+
</ItemActions>
|
|
417
|
+
</ItemGroup>
|
|
418
|
+
|
|
419
|
+
// ✅ Correct — ItemActions is the last child inside Item
|
|
420
|
+
<ItemGroup stackedItems>
|
|
421
|
+
<Item variant="outline">
|
|
422
|
+
<ItemContent><ItemTitle>Name</ItemTitle></ItemContent>
|
|
423
|
+
<ItemActions>
|
|
424
|
+
<Button>Edit</Button>
|
|
425
|
+
</ItemActions>
|
|
426
|
+
</Item>
|
|
427
|
+
</ItemGroup>
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
`Item` is a flex row (`flex items-center`). `ItemActions` carries `data-slot="item-actions"` and
|
|
431
|
+
relies on being a flex child to align right. Outside `Item`, it renders in document flow with no
|
|
432
|
+
flex context.
|
|
433
|
+
|
|
434
|
+
Source: `packages/ds/src/components/item.tsx`
|
|
435
|
+
|
|
436
|
+
### Mistake 4 — Using ListItemActions children directly as ItemTitle text
|
|
437
|
+
|
|
438
|
+
```tsx
|
|
439
|
+
// ❌ Wrong — bare string child of Item is not styled as a title
|
|
440
|
+
<Item variant="outline">
|
|
441
|
+
{number}
|
|
442
|
+
<ItemActions>…</ItemActions>
|
|
443
|
+
</Item>
|
|
444
|
+
|
|
445
|
+
// ✅ Correct — wrap in ItemContent > ItemTitle for correct typography and layout slot
|
|
446
|
+
<Item variant="outline">
|
|
447
|
+
<ItemContent>
|
|
448
|
+
<ItemTitle>{number}</ItemTitle>
|
|
449
|
+
</ItemContent>
|
|
450
|
+
<ItemActions>…</ItemActions>
|
|
451
|
+
</Item>
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
`ListItemActions` accepted `children` as a raw React node and rendered it freely. `Item` is
|
|
455
|
+
a flex row — raw text nodes do not receive the `ItemTitle` typography or the `flex-1` grow
|
|
456
|
+
behaviour that `ItemContent` provides. Without `ItemContent`, `ItemActions` may not push to
|
|
457
|
+
the right end of the row.
|
|
458
|
+
|
|
459
|
+
Source: `packages/ds/src/components/item.tsx`
|
|
460
|
+
|
|
461
|
+
### Mistake 5 — Importing icons from lucide-react directly
|
|
462
|
+
|
|
463
|
+
```tsx
|
|
464
|
+
// ❌ Wrong — imports lucide directly, bypassing the Aircall icon layer
|
|
465
|
+
import { MoreVertical } from 'lucide-react';
|
|
466
|
+
|
|
467
|
+
// ✅ Correct — route all icons through @aircall/react-icons
|
|
468
|
+
import { EllipsisVertical } from '@aircall/react-icons';
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
`@aircall/react-icons` re-exports all lucide icons plus Aircall custom icons. Direct lucide
|
|
472
|
+
imports bypass the single source of truth and prevent Aircall icon overrides.
|
|
473
|
+
|
|
474
|
+
Source: `packages/ds/src/components/item.tsx`
|