@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,260 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: aircall-blocks/migrate-dashboard/loading
|
|
3
|
+
description: >
|
|
4
|
+
Migrate @dashboard/library Loading, Loader, and Spinner to @aircall/ds
|
|
5
|
+
Spinner (animated spinner) and Skeleton (content placeholder). Load when a
|
|
6
|
+
file imports Loading, Loader, or Spinner 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. Component mapping
|
|
20
|
+
|
|
21
|
+
| `@dashboard/library` | `@aircall/ds` | Notes |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| `Loading` | `Spinner` | Full-area centered spinner — replace the flex wrapper with a parent `div` using Tailwind layout classes |
|
|
24
|
+
| `Loader` | `Spinner` | Inline spinning icon — direct 1-to-1 replacement |
|
|
25
|
+
| `Spinner` | `Spinner` | Already a spinner — direct 1-to-1 replacement |
|
|
26
|
+
|
|
27
|
+
**Content placeholders:** when `Loading` was used while async content loads (e.g. inside a card or list panel), prefer `Skeleton` over `Spinner` — it better communicates that content is incoming rather than a blocking operation.
|
|
28
|
+
|
|
29
|
+
## 2. Verified DS exports (`packages/ds/src/index.ts`)
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
Spinner, spinnerVariants
|
|
33
|
+
Skeleton
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 3. Prop mapping
|
|
37
|
+
|
|
38
|
+
### `Loading` → `Spinner`
|
|
39
|
+
|
|
40
|
+
| `@dashboard/library` prop | DS equivalent | Action |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| `size` (number, e.g. `40`) | `size` (`"sm"` \| `"default"` \| `"lg"` \| `"xl"`) | Map to the nearest token (see table below) |
|
|
43
|
+
| `color` (tractor color token, e.g. `"primary-500"`) | `className` | Use a Tailwind text-color class |
|
|
44
|
+
| `data-test` | `data-test` | Pass through as-is |
|
|
45
|
+
| Flex layout (`FlexProps`) | Tailwind parent | Wrap `Spinner` in a container `<div className="flex h-full w-full items-center justify-center">` |
|
|
46
|
+
|
|
47
|
+
**Size mapping:**
|
|
48
|
+
|
|
49
|
+
| Old `size` (px) | New `size` token |
|
|
50
|
+
| --- | --- |
|
|
51
|
+
| ≤ 12 | `"sm"` (12 px) |
|
|
52
|
+
| 13–16 | `"default"` (16 px) |
|
|
53
|
+
| 17–20 | `"lg"` (20 px) |
|
|
54
|
+
| ≥ 21 | `"xl"` (24 px) |
|
|
55
|
+
|
|
56
|
+
When `size` was not set, `Loading` defaulted to `40` and `Spinner` defaulted to `30` — both map to `"xl"`.
|
|
57
|
+
|
|
58
|
+
### `Loader` → `Spinner`
|
|
59
|
+
|
|
60
|
+
| `@dashboard/library` prop | DS equivalent | Action |
|
|
61
|
+
| --- | --- | --- |
|
|
62
|
+
| `size` (number) | `size` (token string) | Map using the table above |
|
|
63
|
+
| `color` | `className` | Use a Tailwind text-color class |
|
|
64
|
+
| `data-test` | `data-test` | Pass through as-is |
|
|
65
|
+
|
|
66
|
+
### `Skeleton`
|
|
67
|
+
|
|
68
|
+
`Skeleton` has no direct `@dashboard/library` predecessor — it is introduced as a
|
|
69
|
+
replacement for `Loading` when used during content fetch. It accepts a plain `<div>`
|
|
70
|
+
`className` for sizing and shape.
|
|
71
|
+
|
|
72
|
+
## 4. Before / After examples
|
|
73
|
+
|
|
74
|
+
### 4a. `Loading` (full-area centered spinner)
|
|
75
|
+
|
|
76
|
+
**Before (`@dashboard/library`):**
|
|
77
|
+
```tsx
|
|
78
|
+
import { Loading } from '@dashboard/library';
|
|
79
|
+
|
|
80
|
+
<Loading data-test="reports-loading" size={40} color="primary-500" />
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**After (`@aircall/ds`):**
|
|
84
|
+
```tsx
|
|
85
|
+
import { Spinner } from '@aircall/ds';
|
|
86
|
+
|
|
87
|
+
<div
|
|
88
|
+
data-test="reports-loading"
|
|
89
|
+
className="flex h-full w-full items-center justify-center"
|
|
90
|
+
>
|
|
91
|
+
<Spinner size="xl" className="text-primary" />
|
|
92
|
+
</div>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Key changes:
|
|
96
|
+
- The flex-center wrapper that `Loading` rendered internally must now be an explicit parent `<div>`.
|
|
97
|
+
- Numeric `size` becomes a size token string.
|
|
98
|
+
- Tractor color token (`"primary-500"`) becomes a Tailwind text-color class (`"text-primary"`).
|
|
99
|
+
|
|
100
|
+
### 4b. `Loader` (inline spinner icon)
|
|
101
|
+
|
|
102
|
+
**Before (`@dashboard/library`):**
|
|
103
|
+
```tsx
|
|
104
|
+
import { Loader } from '@dashboard/library';
|
|
105
|
+
|
|
106
|
+
<Loader size={20} color="neutral-700" />
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**After (`@aircall/ds`):**
|
|
110
|
+
```tsx
|
|
111
|
+
import { Spinner } from '@aircall/ds';
|
|
112
|
+
|
|
113
|
+
<Spinner size="lg" className="text-neutral-700" />
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`Loader` was already a bare spinning icon with no layout wrapper — `Spinner` is the direct replacement.
|
|
117
|
+
|
|
118
|
+
### 4c. `Spinner` (bare spinning icon)
|
|
119
|
+
|
|
120
|
+
**Before (`@dashboard/library`):**
|
|
121
|
+
```tsx
|
|
122
|
+
import { Spinner } from '@dashboard/library';
|
|
123
|
+
|
|
124
|
+
<Spinner size={30} />
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**After (`@aircall/ds`):**
|
|
128
|
+
```tsx
|
|
129
|
+
import { Spinner } from '@aircall/ds';
|
|
130
|
+
|
|
131
|
+
<Spinner size="xl" />
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The API is nearly identical; only the `size` type changes from a numeric pixel value to a string token.
|
|
135
|
+
|
|
136
|
+
### 4d. `Loading` as a content placeholder (prefer `Skeleton`)
|
|
137
|
+
|
|
138
|
+
**Before (`@dashboard/library`):**
|
|
139
|
+
```tsx
|
|
140
|
+
import { Loading } from '@dashboard/library';
|
|
141
|
+
|
|
142
|
+
function StatsCard({ isLoading, value }) {
|
|
143
|
+
return (
|
|
144
|
+
<div className="p-4">
|
|
145
|
+
{isLoading ? <Loading size={24} /> : <span>{value}</span>}
|
|
146
|
+
</div>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**After (`@aircall/ds`):**
|
|
152
|
+
```tsx
|
|
153
|
+
import { Skeleton } from '@aircall/ds';
|
|
154
|
+
|
|
155
|
+
function StatsCard({ isLoading, value }) {
|
|
156
|
+
return (
|
|
157
|
+
<div className="p-4">
|
|
158
|
+
{isLoading ? <Skeleton className="h-6 w-24" /> : <span>{value}</span>}
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Use `Skeleton` when the spinner was filling the space of specific content that will
|
|
165
|
+
appear once data loads. `Skeleton` communicates the shape of the incoming content, which
|
|
166
|
+
reduces layout shift and improves perceived performance compared to a generic spinner.
|
|
167
|
+
|
|
168
|
+
## 5. Common Mistakes
|
|
169
|
+
|
|
170
|
+
### Mistake 1 — Keeping the `Loading` flex wrapper implicit
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
// WRONG — Spinner has no built-in flex-center layout; it renders as an inline SVG
|
|
174
|
+
import { Spinner } from '@aircall/ds';
|
|
175
|
+
|
|
176
|
+
<Spinner size="xl" className="h-full w-full" />
|
|
177
|
+
|
|
178
|
+
// CORRECT — wrap in an explicit flex container to center in the available area
|
|
179
|
+
import { Spinner } from '@aircall/ds';
|
|
180
|
+
|
|
181
|
+
<div className="flex h-full w-full items-center justify-center">
|
|
182
|
+
<Spinner size="xl" />
|
|
183
|
+
</div>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`Loading` rendered its own `<Flex … alignItems="center" justifyContent="center">` internally. DS `Spinner` is a bare SVG icon; the centering layout must be provided by the caller.
|
|
187
|
+
|
|
188
|
+
Source: `packages/ds/src/components/spinner.tsx`
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
### Mistake 2 — Passing a numeric pixel value to `size`
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
// WRONG — size accepts only the four named tokens; a number is a type error
|
|
196
|
+
import { Spinner } from '@aircall/ds';
|
|
197
|
+
|
|
198
|
+
<Spinner size={30} />
|
|
199
|
+
|
|
200
|
+
// CORRECT — use the nearest token string
|
|
201
|
+
import { Spinner } from '@aircall/ds';
|
|
202
|
+
|
|
203
|
+
<Spinner size="xl" />
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
`Spinner.Props` extends `VariantProps<typeof spinnerVariants>` where `size` is
|
|
207
|
+
`"sm" | "default" | "lg" | "xl" | null | undefined`. Passing a number does not satisfy
|
|
208
|
+
the type and will error at compile time.
|
|
209
|
+
|
|
210
|
+
Source: `packages/ds/src/components/spinner.tsx`
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
### Mistake 3 — Passing a Tractor color token to `className`
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
// WRONG — Tractor color tokens are not valid Tailwind classes and produce no styling
|
|
218
|
+
import { Spinner } from '@aircall/ds';
|
|
219
|
+
|
|
220
|
+
<Spinner className="primary-500" />
|
|
221
|
+
|
|
222
|
+
// CORRECT — use a Tailwind text-color utility class
|
|
223
|
+
import { Spinner } from '@aircall/ds';
|
|
224
|
+
|
|
225
|
+
<Spinner className="text-primary" />
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`Spinner` renders a `Loader2Icon` SVG that inherits its color from the CSS `currentColor`
|
|
229
|
+
(the `text-current` Tailwind class is baked in via `spinnerVariants`). To tint it, apply
|
|
230
|
+
a `text-*` Tailwind class — a bare Tractor token string like `"primary-500"` is not a
|
|
231
|
+
recognized utility and has no effect.
|
|
232
|
+
|
|
233
|
+
Source: `packages/ds/src/components/spinner.tsx`
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
### Mistake 4 — Using `Spinner` for content-area placeholders instead of `Skeleton`
|
|
238
|
+
|
|
239
|
+
```tsx
|
|
240
|
+
// WRONG — a spinner implies an active blocking operation, not an inline content gap
|
|
241
|
+
import { Spinner } from '@aircall/ds';
|
|
242
|
+
|
|
243
|
+
function UserName({ isLoading, name }) {
|
|
244
|
+
return isLoading ? <Spinner size="sm" /> : <span>{name}</span>;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// CORRECT — use Skeleton to represent the shape of the incoming content
|
|
248
|
+
import { Skeleton } from '@aircall/ds';
|
|
249
|
+
|
|
250
|
+
function UserName({ isLoading, name }) {
|
|
251
|
+
return isLoading ? <Skeleton className="h-4 w-32" /> : <span>{name}</span>;
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
`Skeleton` (an animated pulse `<div>`) is semantically and visually correct for
|
|
256
|
+
inline content placeholders because it mirrors the dimensions of the element it replaces.
|
|
257
|
+
`Spinner` is intended for full-area or button loading states where the action source is
|
|
258
|
+
explicit.
|
|
259
|
+
|
|
260
|
+
Source: `packages/ds/src/components/skeleton.tsx`
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: aircall-blocks/migrate-dashboard/page-header
|
|
3
|
+
description: >
|
|
4
|
+
Migrate @dashboard/library PageHeader, PageHeaderLink, and GaramondTitleTypography
|
|
5
|
+
to @aircall/blocks DashboardPageHeader and its sub-components (DashboardPageHeaderTitle,
|
|
6
|
+
DashboardPageHeaderActions, DashboardPageHeaderAction, DashboardPageHeaderNav,
|
|
7
|
+
DashboardPageHeaderNavBack, DashboardPageHeaderPrefix, DashboardPageHeaderTitleGroup,
|
|
8
|
+
DashboardPageHeaderSubtitle, DashboardPageHeaderDescription). Load when a file
|
|
9
|
+
imports PageHeader or PageHeaderLink from @dashboard/library.
|
|
10
|
+
type: sub-skill
|
|
11
|
+
library: aircall-blocks
|
|
12
|
+
library_version: "0.5.1"
|
|
13
|
+
requires:
|
|
14
|
+
- aircall-blocks/setup
|
|
15
|
+
- aircall-blocks/migrate-dashboard
|
|
16
|
+
sources:
|
|
17
|
+
- "aircall/hydra:packages/blocks/src/index.ts"
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
This skill builds on aircall-blocks/migrate-dashboard.
|
|
21
|
+
|
|
22
|
+
## 1. Component mapping
|
|
23
|
+
|
|
24
|
+
| @dashboard/library | @aircall/blocks |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| `PageHeader` (root container) | `DashboardPageHeader` |
|
|
27
|
+
| `PageHeader` `title` prop (small heading) | `DashboardPageHeaderTitle size="sm"` |
|
|
28
|
+
| `PageHeader` `largeTitle` prop (large heading) | `DashboardPageHeaderTitle size="lg"` |
|
|
29
|
+
| `PageHeader` `subtitle` prop | `DashboardPageHeaderDescription` inside `DashboardPageHeaderSubtitle` inside `DashboardPageHeaderTitleGroup` |
|
|
30
|
+
| `PageHeader` `renderTitleRight` render prop | `DashboardPageHeaderActions` + `DashboardPageHeaderAction` |
|
|
31
|
+
| `PageHeader` `renderExtra` render prop | Extra children appended directly inside `DashboardPageHeader` |
|
|
32
|
+
| `PageHeader` `renderSubLeft` / `renderSubRight` render props | Children inside `DashboardPageHeaderSubtitle` |
|
|
33
|
+
| `PageHeader` `gobackLinkRoute` + `gobackLinkText` props | `DashboardPageHeaderNav` + `DashboardPageHeaderNavBack` |
|
|
34
|
+
| `PageHeaderLink` (standalone back-link component) | `DashboardPageHeaderNavBack` inside `DashboardPageHeaderNav` |
|
|
35
|
+
| `PageHeader` `icon` prop (tractor `Icon` component) | `DashboardPageHeaderPrefix` (put icon/flag/avatar inside) |
|
|
36
|
+
| `PageHeader` `avatarSrc` / `avatarInitials` / `renderAvatar` props | `DashboardPageHeaderPrefix` (put `Avatar` or equivalent inside) |
|
|
37
|
+
| `PageHeader` `activeTabId` / `onTabChange` / `Tab.*` children | No direct equivalent — implement tabs separately below the header |
|
|
38
|
+
| `GaramondTitleTypography` / `useGaramondFont` prop | Not reproduced — use `DashboardPageHeaderTitle size="lg"` (system font) |
|
|
39
|
+
| `PageHeader` `titleProps` (BoxProps spread) | Remove — `DashboardPageHeaderTitleGroup` handles layout |
|
|
40
|
+
|
|
41
|
+
`DashboardPageHeader` is a flat-children composition: sub-components self-place via CSS
|
|
42
|
+
grid areas — declaration order determines render order within each area. You do **not** pass
|
|
43
|
+
data via props on the root; every piece of content is a named sub-component child.
|
|
44
|
+
|
|
45
|
+
## 2. Imports
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
// All DashboardPageHeader parts from @aircall/blocks
|
|
49
|
+
import {
|
|
50
|
+
DashboardPageHeader,
|
|
51
|
+
DashboardPageHeaderTitle,
|
|
52
|
+
DashboardPageHeaderTitleGroup,
|
|
53
|
+
DashboardPageHeaderSubtitle,
|
|
54
|
+
DashboardPageHeaderDescription,
|
|
55
|
+
DashboardPageHeaderActions,
|
|
56
|
+
DashboardPageHeaderAction,
|
|
57
|
+
DashboardPageHeaderNav,
|
|
58
|
+
DashboardPageHeaderNavBack,
|
|
59
|
+
DashboardPageHeaderPrefix,
|
|
60
|
+
} from '@aircall/blocks';
|
|
61
|
+
|
|
62
|
+
// DS primitives used alongside (Tooltip, DropdownMenu, etc.)
|
|
63
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@aircall/ds';
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## 3. Before / After
|
|
67
|
+
|
|
68
|
+
### 3a. Simple title-only header
|
|
69
|
+
|
|
70
|
+
**Before (`@dashboard/library`):**
|
|
71
|
+
```tsx
|
|
72
|
+
import { PageHeader } from '@dashboard/library';
|
|
73
|
+
|
|
74
|
+
function AnalyticsHeader() {
|
|
75
|
+
return <PageHeader largeTitle="Analytics" />;
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**After (`@aircall/blocks`):**
|
|
80
|
+
```tsx
|
|
81
|
+
import { DashboardPageHeader, DashboardPageHeaderTitle } from '@aircall/blocks';
|
|
82
|
+
|
|
83
|
+
function AnalyticsHeader() {
|
|
84
|
+
return (
|
|
85
|
+
<DashboardPageHeader>
|
|
86
|
+
<DashboardPageHeaderTitle size="lg">Analytics</DashboardPageHeaderTitle>
|
|
87
|
+
</DashboardPageHeader>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Key changes:
|
|
93
|
+
- `largeTitle` string prop → `DashboardPageHeaderTitle size="lg"` child with the string as children.
|
|
94
|
+
- `title` string prop → `DashboardPageHeaderTitle size="sm"` (level-2 pages that have a nav row).
|
|
95
|
+
- No `data-test` prop — set `data-test` directly on `DashboardPageHeader` if needed.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
### 3b. Title with actions (renderTitleRight)
|
|
100
|
+
|
|
101
|
+
**Before (`@dashboard/library`):**
|
|
102
|
+
```tsx
|
|
103
|
+
import { PageHeader } from '@dashboard/library';
|
|
104
|
+
import { Button } from '@aircall/tractor';
|
|
105
|
+
|
|
106
|
+
function CampaignsHeader() {
|
|
107
|
+
return (
|
|
108
|
+
<PageHeader
|
|
109
|
+
largeTitle="Campaigns"
|
|
110
|
+
renderTitleRight={() => (
|
|
111
|
+
<Button variant="primary" size="regular">Create campaign</Button>
|
|
112
|
+
)}
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**After (`@aircall/blocks`):**
|
|
119
|
+
```tsx
|
|
120
|
+
import {
|
|
121
|
+
DashboardPageHeader,
|
|
122
|
+
DashboardPageHeaderTitle,
|
|
123
|
+
DashboardPageHeaderActions,
|
|
124
|
+
DashboardPageHeaderAction,
|
|
125
|
+
} from '@aircall/blocks';
|
|
126
|
+
|
|
127
|
+
function CampaignsHeader() {
|
|
128
|
+
return (
|
|
129
|
+
<DashboardPageHeader>
|
|
130
|
+
<DashboardPageHeaderTitle size="lg">Campaigns</DashboardPageHeaderTitle>
|
|
131
|
+
<DashboardPageHeaderActions>
|
|
132
|
+
<DashboardPageHeaderAction variant="default">Create campaign</DashboardPageHeaderAction>
|
|
133
|
+
</DashboardPageHeaderActions>
|
|
134
|
+
</DashboardPageHeader>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Key changes:
|
|
140
|
+
- `renderTitleRight` render prop → `DashboardPageHeaderActions` child containing `DashboardPageHeaderAction` elements.
|
|
141
|
+
- Tractor `Button variant="primary"` → `DashboardPageHeaderAction variant="default"` (size is fixed by the component).
|
|
142
|
+
- Icon-only action buttons: `DashboardPageHeaderAction variant="outline" size="icon-lg"`.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
### 3c. Back navigation (gobackLinkRoute / gobackLinkText)
|
|
147
|
+
|
|
148
|
+
**Before (`@dashboard/library`):**
|
|
149
|
+
```tsx
|
|
150
|
+
import { PageHeader } from '@dashboard/library';
|
|
151
|
+
|
|
152
|
+
function NumberDetailHeader() {
|
|
153
|
+
return (
|
|
154
|
+
<PageHeader
|
|
155
|
+
title="Number settings"
|
|
156
|
+
gobackLinkRoute="/numbers"
|
|
157
|
+
gobackLinkText="Back to numbers"
|
|
158
|
+
renderTitleRight={() => <button>Save</button>}
|
|
159
|
+
/>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**After (`@aircall/blocks`):**
|
|
165
|
+
```tsx
|
|
166
|
+
import {
|
|
167
|
+
DashboardPageHeader,
|
|
168
|
+
DashboardPageHeaderNav,
|
|
169
|
+
DashboardPageHeaderNavBack,
|
|
170
|
+
DashboardPageHeaderTitle,
|
|
171
|
+
DashboardPageHeaderActions,
|
|
172
|
+
DashboardPageHeaderAction,
|
|
173
|
+
} from '@aircall/blocks';
|
|
174
|
+
import { Link } from 'react-router-dom';
|
|
175
|
+
|
|
176
|
+
function NumberDetailHeader() {
|
|
177
|
+
return (
|
|
178
|
+
<DashboardPageHeader>
|
|
179
|
+
<DashboardPageHeaderNav>
|
|
180
|
+
<DashboardPageHeaderNavBack render={<Link to="/numbers" />}>
|
|
181
|
+
Back to numbers
|
|
182
|
+
</DashboardPageHeaderNavBack>
|
|
183
|
+
</DashboardPageHeaderNav>
|
|
184
|
+
<DashboardPageHeaderTitle size="sm">Number settings</DashboardPageHeaderTitle>
|
|
185
|
+
<DashboardPageHeaderActions>
|
|
186
|
+
<DashboardPageHeaderAction variant="default">Save</DashboardPageHeaderAction>
|
|
187
|
+
</DashboardPageHeaderActions>
|
|
188
|
+
</DashboardPageHeader>
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Key changes:
|
|
194
|
+
- `gobackLinkRoute` + `gobackLinkText` props → `DashboardPageHeaderNav` > `DashboardPageHeaderNavBack` with `render={<Link to="…" />}` (Base UI render prop — avoids a nested `<a><button>` violation).
|
|
195
|
+
- When nav is present, use `size="sm"` on `DashboardPageHeaderTitle` (level-2 pattern).
|
|
196
|
+
- The `PageHeaderLink` standalone export is replaced the same way.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
### 3d. Header with prefix (icon / avatar) and subtitle
|
|
201
|
+
|
|
202
|
+
**Before (`@dashboard/library`):**
|
|
203
|
+
```tsx
|
|
204
|
+
import { PageHeader } from '@dashboard/library';
|
|
205
|
+
import { PhoneOutlined } from '@aircall/icons';
|
|
206
|
+
|
|
207
|
+
function NumberHeader({ number, description }) {
|
|
208
|
+
return (
|
|
209
|
+
<PageHeader
|
|
210
|
+
largeTitle={number.name}
|
|
211
|
+
subtitle={description}
|
|
212
|
+
icon={PhoneOutlined}
|
|
213
|
+
renderSubRight={() => <span>{number.countryCode}</span>}
|
|
214
|
+
/>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**After (`@aircall/blocks`):**
|
|
220
|
+
```tsx
|
|
221
|
+
import {
|
|
222
|
+
DashboardPageHeader,
|
|
223
|
+
DashboardPageHeaderPrefix,
|
|
224
|
+
DashboardPageHeaderTitleGroup,
|
|
225
|
+
DashboardPageHeaderTitle,
|
|
226
|
+
DashboardPageHeaderSubtitle,
|
|
227
|
+
DashboardPageHeaderDescription,
|
|
228
|
+
} from '@aircall/blocks';
|
|
229
|
+
import { Phone } from '@aircall/react-icons';
|
|
230
|
+
|
|
231
|
+
function NumberHeader({ number, description }) {
|
|
232
|
+
return (
|
|
233
|
+
<DashboardPageHeader>
|
|
234
|
+
<DashboardPageHeaderPrefix>
|
|
235
|
+
<Phone className="size-5 text-muted-foreground" />
|
|
236
|
+
</DashboardPageHeaderPrefix>
|
|
237
|
+
<DashboardPageHeaderTitleGroup>
|
|
238
|
+
<DashboardPageHeaderTitle size="lg">{number.name}</DashboardPageHeaderTitle>
|
|
239
|
+
<DashboardPageHeaderSubtitle>
|
|
240
|
+
<DashboardPageHeaderDescription>{description}</DashboardPageHeaderDescription>
|
|
241
|
+
<span className="text-sm text-muted-foreground">{number.countryCode}</span>
|
|
242
|
+
</DashboardPageHeaderSubtitle>
|
|
243
|
+
</DashboardPageHeaderTitleGroup>
|
|
244
|
+
</DashboardPageHeader>
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Key changes:
|
|
250
|
+
- `icon` prop → `DashboardPageHeaderPrefix` containing the icon element; icons come from `@aircall/react-icons`, not `@aircall/icons`.
|
|
251
|
+
- `subtitle` prop → `DashboardPageHeaderDescription` inside `DashboardPageHeaderSubtitle` inside `DashboardPageHeaderTitleGroup`.
|
|
252
|
+
- `renderSubLeft` / `renderSubRight` → siblings of `DashboardPageHeaderDescription` inside `DashboardPageHeaderSubtitle`.
|
|
253
|
+
- When `DashboardPageHeaderPrefix` is present, `DashboardPageHeaderTitleGroup` resets its left padding automatically (CSS group selector).
|
|
254
|
+
- `avatarSrc` / `avatarInitials` / `renderAvatar` → render your avatar element inside `DashboardPageHeaderPrefix`.
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
### 3e. Tabs (activeTabId / onTabChange / Tab.Menu children)
|
|
259
|
+
|
|
260
|
+
`PageHeader` accepted `Tab.Menu` / `Tab.Content` children and wired them into a
|
|
261
|
+
`Tab.Container`. `DashboardPageHeader` has no built-in tab support.
|
|
262
|
+
|
|
263
|
+
Pattern: render a DS `Tabs` compound below `DashboardPageHeader`, outside of it.
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
// Before
|
|
267
|
+
import { PageHeader, Tab } from '@dashboard/library';
|
|
268
|
+
<PageHeader largeTitle="Reports" activeTabId="overview" onTabChange={setTab}>
|
|
269
|
+
<Tab.Menu tabId="overview">Overview</Tab.Menu>
|
|
270
|
+
<Tab.Menu tabId="calls">Calls</Tab.Menu>
|
|
271
|
+
<Tab.Content tabId="overview"><Overview /></Tab.Content>
|
|
272
|
+
<Tab.Content tabId="calls"><Calls /></Tab.Content>
|
|
273
|
+
</PageHeader>
|
|
274
|
+
|
|
275
|
+
// After — use DS Tabs below the header
|
|
276
|
+
import { DashboardPageHeader, DashboardPageHeaderTitle } from '@aircall/blocks';
|
|
277
|
+
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@aircall/ds';
|
|
278
|
+
|
|
279
|
+
<DashboardPageHeader>
|
|
280
|
+
<DashboardPageHeaderTitle size="lg">Reports</DashboardPageHeaderTitle>
|
|
281
|
+
</DashboardPageHeader>
|
|
282
|
+
<Tabs value={tab} onValueChange={setTab}>
|
|
283
|
+
<TabsList>
|
|
284
|
+
<TabsTrigger value="overview">Overview</TabsTrigger>
|
|
285
|
+
<TabsTrigger value="calls">Calls</TabsTrigger>
|
|
286
|
+
</TabsList>
|
|
287
|
+
<TabsContent value="overview"><Overview /></TabsContent>
|
|
288
|
+
<TabsContent value="calls"><Calls /></TabsContent>
|
|
289
|
+
</Tabs>
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## 4. Common mistakes
|
|
295
|
+
|
|
296
|
+
### Mistake 1 — Passing title/largeTitle as props instead of children
|
|
297
|
+
|
|
298
|
+
```tsx
|
|
299
|
+
// ❌ Wrong — PageHeader prop API; DashboardPageHeader ignores unknown props
|
|
300
|
+
<DashboardPageHeader largeTitle="Analytics" title="Overview" />
|
|
301
|
+
|
|
302
|
+
// ✅ Correct — title is a child component
|
|
303
|
+
<DashboardPageHeader>
|
|
304
|
+
<DashboardPageHeaderTitle size="lg">Analytics</DashboardPageHeaderTitle>
|
|
305
|
+
</DashboardPageHeader>
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
`DashboardPageHeader` extends `React.ComponentProps<'div'>` — it has no `title`, `largeTitle`, or `subtitle` props. Passing them results in unknown HTML attributes forwarded to the DOM and no visible heading rendered.
|
|
309
|
+
|
|
310
|
+
Source: `packages/blocks/src/components/dashboard-page-header.tsx`
|
|
311
|
+
|
|
312
|
+
### Mistake 2 — Using a Router Link directly instead of the `render` prop on DashboardPageHeaderNavBack
|
|
313
|
+
|
|
314
|
+
```tsx
|
|
315
|
+
// ❌ Wrong — nests <a> inside <button>, invalid HTML
|
|
316
|
+
<DashboardPageHeaderNavBack>
|
|
317
|
+
<Link to="/numbers">Back to numbers</Link>
|
|
318
|
+
</DashboardPageHeaderNavBack>
|
|
319
|
+
|
|
320
|
+
// ✅ Correct — render prop replaces the <button> element with the Link
|
|
321
|
+
<DashboardPageHeaderNavBack render={<Link to="/numbers" />}>
|
|
322
|
+
Back to numbers
|
|
323
|
+
</DashboardPageHeaderNavBack>
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
`DashboardPageHeaderNavBack` renders a `Button` (which renders a `<button>`). Nesting a
|
|
327
|
+
`<Link>` (`<a>`) inside a `<button>` is invalid HTML and breaks keyboard navigation.
|
|
328
|
+
The `render` prop (Base UI pattern) polymorphically replaces the root element while
|
|
329
|
+
keeping all button behavior.
|
|
330
|
+
|
|
331
|
+
Source: `packages/blocks/src/components/dashboard-page-header.tsx`
|
|
332
|
+
|
|
333
|
+
### Mistake 3 — Placing DashboardPageHeaderActions outside DashboardPageHeader
|
|
334
|
+
|
|
335
|
+
```tsx
|
|
336
|
+
// ❌ Wrong — outside the @container context; grid-area:actions has no effect
|
|
337
|
+
<DashboardPageHeader>
|
|
338
|
+
<DashboardPageHeaderTitle size="lg">Analytics</DashboardPageHeaderTitle>
|
|
339
|
+
</DashboardPageHeader>
|
|
340
|
+
<DashboardPageHeaderActions>
|
|
341
|
+
<DashboardPageHeaderAction variant="default">Export</DashboardPageHeaderAction>
|
|
342
|
+
</DashboardPageHeaderActions>
|
|
343
|
+
|
|
344
|
+
// ✅ Correct — all sub-components are children of DashboardPageHeader
|
|
345
|
+
<DashboardPageHeader>
|
|
346
|
+
<DashboardPageHeaderTitle size="lg">Analytics</DashboardPageHeaderTitle>
|
|
347
|
+
<DashboardPageHeaderActions>
|
|
348
|
+
<DashboardPageHeaderAction variant="default">Export</DashboardPageHeaderAction>
|
|
349
|
+
</DashboardPageHeaderActions>
|
|
350
|
+
</DashboardPageHeader>
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Sub-components use `[grid-area:actions]` Tailwind classes that only resolve inside the
|
|
354
|
+
CSS grid established by `DashboardPageHeader`'s inner grid `div`. Rendered outside, they
|
|
355
|
+
appear in normal document flow and lose their placement entirely.
|
|
356
|
+
|
|
357
|
+
Source: `packages/blocks/src/components/dashboard-page-header.tsx`
|
|
358
|
+
|
|
359
|
+
### Mistake 4 — Importing icons from @aircall/icons instead of @aircall/react-icons
|
|
360
|
+
|
|
361
|
+
```tsx
|
|
362
|
+
// ❌ Wrong — @aircall/icons is the old tractor-era icon package
|
|
363
|
+
import { PhoneOutlined } from '@aircall/icons';
|
|
364
|
+
|
|
365
|
+
// ✅ Correct — @aircall/react-icons is the current icon package
|
|
366
|
+
import { Phone } from '@aircall/react-icons';
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
`DashboardPageHeader` and the DS components it composes (`Button`, etc.) are built for
|
|
370
|
+
`@aircall/react-icons` (lucide-based). The `@aircall/icons` package ships SVG components
|
|
371
|
+
with a different size/color API (`size` / `color` props vs `className`).
|
|
372
|
+
|
|
373
|
+
Source: `packages/blocks/src/components/dashboard-page-header.tsx`
|
|
374
|
+
|
|
375
|
+
### Mistake 5 — Using GaramondTitleTypography / useGaramondFont in the new stack
|
|
376
|
+
|
|
377
|
+
```tsx
|
|
378
|
+
// ❌ Wrong — GaramondTitleTypography and useGaramondFont are @dashboard/library internals
|
|
379
|
+
import { GaramondTitleTypography } from '@dashboard/library';
|
|
380
|
+
<GaramondTitleTypography>{title}</GaramondTitleTypography>
|
|
381
|
+
|
|
382
|
+
// ❌ Wrong — no equivalent prop on DashboardPageHeaderTitle
|
|
383
|
+
<DashboardPageHeaderTitle size="lg" useGaramondFont>Analytics</DashboardPageHeaderTitle>
|
|
384
|
+
|
|
385
|
+
// ✅ Correct — use DashboardPageHeaderTitle with size="lg" (system font, trimmed baseline)
|
|
386
|
+
<DashboardPageHeaderTitle size="lg">Analytics</DashboardPageHeaderTitle>
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
`GaramondTitleTypography` and `useGaramondFont` were `@dashboard/library`-only overrides
|
|
390
|
+
for the ITC Garamond Narrow font. `DashboardPageHeaderTitle size="lg"` renders `text-3xl
|
|
391
|
+
font-bold` in the system font stack with `leading-10`, matching the design spec without
|
|
392
|
+
a custom typeface dependency.
|
|
393
|
+
|
|
394
|
+
Source: `packages/blocks/src/components/dashboard-page-header.tsx`
|