@aircall/ds 0.13.0 → 0.15.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 +31 -0
- package/dist/globals.css +1 -1
- package/dist/index.d.ts +94 -33
- package/dist/index.js +292 -42
- package/package.json +16 -3
- package/skills/aircall-ds/migrate-icons/SKILL.md +346 -0
- package/skills/aircall-ds/migrate-tractor/SKILL.md +314 -0
- package/skills/aircall-ds/migrate-tractor/accordion/SKILL.md +276 -0
- package/skills/aircall-ds/migrate-tractor/alert/SKILL.md +225 -0
- package/skills/aircall-ds/migrate-tractor/avatar/SKILL.md +272 -0
- package/skills/aircall-ds/migrate-tractor/badge/SKILL.md +274 -0
- package/skills/aircall-ds/migrate-tractor/button/SKILL.md +277 -0
- package/skills/aircall-ds/migrate-tractor/card/SKILL.md +278 -0
- package/skills/aircall-ds/migrate-tractor/combobox/SKILL.md +346 -0
- package/skills/aircall-ds/migrate-tractor/data-table/SKILL.md +333 -0
- package/skills/aircall-ds/migrate-tractor/dialog/SKILL.md +206 -0
- package/skills/aircall-ds/migrate-tractor/divider/SKILL.md +226 -0
- package/skills/aircall-ds/migrate-tractor/dropdown-menu/SKILL.md +266 -0
- package/skills/aircall-ds/migrate-tractor/dropzone/SKILL.md +338 -0
- package/skills/aircall-ds/migrate-tractor/form-and-field/SKILL.md +325 -0
- package/skills/aircall-ds/migrate-tractor/gauge/SKILL.md +248 -0
- package/skills/aircall-ds/migrate-tractor/input/SKILL.md +261 -0
- package/skills/aircall-ds/migrate-tractor/item/SKILL.md +298 -0
- package/skills/aircall-ds/migrate-tractor/link/SKILL.md +263 -0
- package/skills/aircall-ds/migrate-tractor/popover/SKILL.md +214 -0
- package/skills/aircall-ds/migrate-tractor/select/SKILL.md +245 -0
- package/skills/aircall-ds/migrate-tractor/sheet-vs-drawer/SKILL.md +272 -0
- package/skills/aircall-ds/migrate-tractor/skeleton/SKILL.md +190 -0
- package/skills/aircall-ds/migrate-tractor/styling/SKILL.md +421 -0
- package/skills/aircall-ds/migrate-tractor/tabs/SKILL.md +250 -0
- package/skills/aircall-ds/migrate-tractor/toast/SKILL.md +322 -0
- package/skills/aircall-ds/migrate-tractor/tooltip/SKILL.md +204 -0
- package/skills/aircall-ds/migrate-tractor/tree/SKILL.md +346 -0
- package/skills/aircall-ds/setup/SKILL.md +347 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: aircall-ds/migrate-tractor/popover
|
|
3
|
+
description: >
|
|
4
|
+
Migrate Tractor Popover (single-component, content + placement props) to the
|
|
5
|
+
@aircall/ds Popover compound (Popover, PopoverTrigger, PopoverContent, PopoverHeader,
|
|
6
|
+
PopoverTitle, PopoverDescription). Load when a file imports Popover from @aircall/tractor.
|
|
7
|
+
type: sub-skill
|
|
8
|
+
library: aircall-ds
|
|
9
|
+
library_version: "0.13.0"
|
|
10
|
+
requires:
|
|
11
|
+
- aircall-ds/setup
|
|
12
|
+
- aircall-ds/migrate-tractor
|
|
13
|
+
sources:
|
|
14
|
+
- "aircall/hydra:docs/migration-guides/tractor-to-ds/recipes/popover.md"
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
This skill builds on aircall-ds/migrate-tractor. Apply all cross-cutting rules from that skill (prop renames, `render` prop, data attributes) before the popover-specific steps below.
|
|
18
|
+
|
|
19
|
+
## 1. Component mapping
|
|
20
|
+
|
|
21
|
+
Tractor `Popover` was a single component: you passed a `content` prop (the panel) and a `placement` prop (positioning) alongside the trigger child. DS uses a Base UI compound where each concern is a named part.
|
|
22
|
+
|
|
23
|
+
| Tractor | @aircall/ds | Role |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| `<Popover>` | `<Popover>` | State owner (open, onOpenChange) |
|
|
26
|
+
| _(trigger child of `<Popover>`)_ | `<PopoverTrigger>` | The anchor element that opens the panel |
|
|
27
|
+
| `content={…}` prop | `<PopoverContent>` | The floating panel; children replace the prop |
|
|
28
|
+
| `placement="bottom-start"` | `side="bottom" align="start"` on `<PopoverContent>` | Split into two props |
|
|
29
|
+
| _(no equivalent)_ | `<PopoverHeader>` | Optional structured header wrapper |
|
|
30
|
+
| _(no equivalent)_ | `<PopoverTitle>` | Accessible title inside the panel |
|
|
31
|
+
| _(no equivalent)_ | `<PopoverDescription>` | Accessible description inside the panel |
|
|
32
|
+
|
|
33
|
+
## 2. Verified DS exports (`packages/ds/src/index.ts`)
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
Popover, PopoverContent, PopoverDescription,
|
|
37
|
+
PopoverHeader, PopoverTitle, PopoverTrigger
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 3. Imports
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import {
|
|
44
|
+
Popover,
|
|
45
|
+
PopoverContent,
|
|
46
|
+
PopoverDescription,
|
|
47
|
+
PopoverHeader,
|
|
48
|
+
PopoverTitle,
|
|
49
|
+
PopoverTrigger,
|
|
50
|
+
Button,
|
|
51
|
+
} from '@aircall/ds';
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 4. Before / After
|
|
55
|
+
|
|
56
|
+
### Uncontrolled (most common)
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
// Before
|
|
60
|
+
import { Popover, Button } from '@aircall/tractor';
|
|
61
|
+
|
|
62
|
+
<Popover
|
|
63
|
+
placement="bottom-start"
|
|
64
|
+
content={
|
|
65
|
+
<div>
|
|
66
|
+
<strong>Title</strong>
|
|
67
|
+
<p>A short description.</p>
|
|
68
|
+
<p>Body content.</p>
|
|
69
|
+
</div>
|
|
70
|
+
}
|
|
71
|
+
>
|
|
72
|
+
<Button variant="secondary" mode="outline">Open</Button>
|
|
73
|
+
</Popover>
|
|
74
|
+
|
|
75
|
+
// After
|
|
76
|
+
import { Popover, PopoverContent, PopoverHeader, PopoverTitle, PopoverDescription, PopoverTrigger, Button } from '@aircall/ds';
|
|
77
|
+
|
|
78
|
+
<Popover>
|
|
79
|
+
<PopoverTrigger render={<Button variant="outline" />}>Open</PopoverTrigger>
|
|
80
|
+
<PopoverContent side="bottom" align="start">
|
|
81
|
+
<PopoverHeader>
|
|
82
|
+
<PopoverTitle>Title</PopoverTitle>
|
|
83
|
+
<PopoverDescription>A short description.</PopoverDescription>
|
|
84
|
+
</PopoverHeader>
|
|
85
|
+
<p>Body content.</p>
|
|
86
|
+
</PopoverContent>
|
|
87
|
+
</Popover>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Controlled (replaces `isOpen` / `onClose`)
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
// Before
|
|
94
|
+
import { Popover, Button } from '@aircall/tractor';
|
|
95
|
+
|
|
96
|
+
<Popover
|
|
97
|
+
isOpen={isOpen}
|
|
98
|
+
onClose={() => setIsOpen(false)}
|
|
99
|
+
placement="top"
|
|
100
|
+
content={<p>Settings panel</p>}
|
|
101
|
+
>
|
|
102
|
+
<Button variant="primary">Settings</Button>
|
|
103
|
+
</Popover>
|
|
104
|
+
|
|
105
|
+
// After
|
|
106
|
+
import { Popover, PopoverContent, PopoverTrigger, Button } from '@aircall/ds';
|
|
107
|
+
|
|
108
|
+
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
|
109
|
+
<PopoverTrigger render={<Button variant="default" />}>Settings</PopoverTrigger>
|
|
110
|
+
<PopoverContent side="top">
|
|
111
|
+
<p>Settings panel</p>
|
|
112
|
+
</PopoverContent>
|
|
113
|
+
</Popover>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Key changes:
|
|
117
|
+
- `isOpen` → `open`; `onClose` → `onOpenChange` (receives the new boolean)
|
|
118
|
+
- `content={…}` prop is removed — panel content is children of `PopoverContent`
|
|
119
|
+
- Tractor's trigger child becomes `<PopoverTrigger render={<Button />}>` — label goes on `PopoverTrigger`, not inside the `render` element
|
|
120
|
+
- `placement="bottom-start"` splits into `side="bottom" align="start"` on `PopoverContent`
|
|
121
|
+
|
|
122
|
+
## 5. Positioning
|
|
123
|
+
|
|
124
|
+
Tractor's single `placement` string maps to two props on `PopoverContent`:
|
|
125
|
+
|
|
126
|
+
| Tractor `placement` | DS `side` | DS `align` |
|
|
127
|
+
| --- | --- | --- |
|
|
128
|
+
| `"bottom"` | `"bottom"` | `"center"` (default) |
|
|
129
|
+
| `"bottom-start"` | `"bottom"` | `"start"` |
|
|
130
|
+
| `"bottom-end"` | `"bottom"` | `"end"` |
|
|
131
|
+
| `"top"` | `"top"` | `"center"` (default) |
|
|
132
|
+
| `"top-start"` | `"top"` | `"start"` |
|
|
133
|
+
| `"top-end"` | `"top"` | `"end"` |
|
|
134
|
+
| `"left"` | `"left"` | `"center"` (default) |
|
|
135
|
+
| `"right"` | `"right"` | `"center"` (default) |
|
|
136
|
+
|
|
137
|
+
Defaults when omitted: `side="bottom"`, `align="center"`, `sideOffset={4}`. Fine-tune gap with `sideOffset` / `alignOffset`.
|
|
138
|
+
|
|
139
|
+
## 6. Panel surface
|
|
140
|
+
|
|
141
|
+
`PopoverContent` ships its own surface styles — `rounded-md`, `ring-1`, `bg-popover`, `p-4`, `shadow-md`. Do not re-add a wrapper `<div>` with manual padding, background, or border inside `PopoverContent`; it will double the spacing and styling.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## 7. Common mistakes
|
|
146
|
+
|
|
147
|
+
### Mistake 1 — Keeping `content` as a prop instead of children
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
// Wrong — content prop does not exist on DS Popover
|
|
151
|
+
<Popover content={<p>Body content.</p>}>
|
|
152
|
+
<PopoverTrigger render={<Button variant="outline" />}>Open</PopoverTrigger>
|
|
153
|
+
</Popover>
|
|
154
|
+
|
|
155
|
+
// Correct — panel content is children of PopoverContent
|
|
156
|
+
<Popover>
|
|
157
|
+
<PopoverTrigger render={<Button variant="outline" />}>Open</PopoverTrigger>
|
|
158
|
+
<PopoverContent>
|
|
159
|
+
<p>Body content.</p>
|
|
160
|
+
</PopoverContent>
|
|
161
|
+
</Popover>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The DS compound has no `content` prop on `Popover`. Passing it silently does nothing — the popover renders with an empty panel. Move the JSX into children of `PopoverContent`.
|
|
165
|
+
|
|
166
|
+
Source: `packages/ds/src/components/popover.tsx`
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
### Mistake 2 — Putting the label inside the `render` Button instead of `PopoverTrigger`
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
// Wrong — label ends up inside Button's children prop, not managed by PopoverTrigger
|
|
174
|
+
<PopoverTrigger render={<Button variant="outline">Open</Button>} />
|
|
175
|
+
|
|
176
|
+
// Correct — label is a child of PopoverTrigger; it is merged into the rendered Button
|
|
177
|
+
<PopoverTrigger render={<Button variant="outline" />}>Open</PopoverTrigger>
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
`PopoverTrigger` uses the Base UI `render` prop pattern: it clones the element passed as `render` and merges its own open/close handler. The visible label must be the child of `PopoverTrigger`, not of the inner Button — otherwise it is ignored and the button renders empty.
|
|
181
|
+
|
|
182
|
+
Source: `packages/ds/src/components/popover.tsx`
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
### Mistake 3 — Passing a single `placement` string instead of `side` + `align`
|
|
187
|
+
|
|
188
|
+
```tsx
|
|
189
|
+
// Wrong — placement is a Tractor prop; DS PopoverContent does not accept it
|
|
190
|
+
<PopoverContent placement="bottom-start">…</PopoverContent>
|
|
191
|
+
|
|
192
|
+
// Correct — split into side and align
|
|
193
|
+
<PopoverContent side="bottom" align="start">…</PopoverContent>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The DS `PopoverContent` accepts `side` and `align` as separate props (sourced from `PopoverPrimitive.Positioner`). A `placement` string is silently ignored, and the panel falls back to the default `side="bottom" align="center"` — which may look correct until the layout shifts.
|
|
197
|
+
|
|
198
|
+
Source: `packages/ds/src/components/popover.tsx`
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
### Mistake 4 — Using `isOpen` / `onClose` instead of `open` / `onOpenChange`
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
// Wrong — isOpen and onClose are Tractor props; DS Popover ignores them
|
|
206
|
+
<Popover isOpen={isOpen} onClose={() => setIsOpen(false)}>…</Popover>
|
|
207
|
+
|
|
208
|
+
// Correct — controlled via open and onOpenChange
|
|
209
|
+
<Popover open={isOpen} onOpenChange={setIsOpen}>…</Popover>
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
DS `Popover` follows the Base UI controlled pattern: `open` (boolean) and `onOpenChange` (callback receiving the new boolean). `onClose` is not called — the popover stays open permanently when `onClose` is the only handler provided.
|
|
213
|
+
|
|
214
|
+
Source: `packages/ds/src/components/popover.tsx`
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: aircall-ds/migrate-tractor/select
|
|
3
|
+
description: >
|
|
4
|
+
Migrate Tractor Select to the @aircall/ds Select compound. Load when a file
|
|
5
|
+
imports Select from @aircall/tractor. Covers the compound parts, the nullable
|
|
6
|
+
onValueChange, and the Group-wraps-Label rule.
|
|
7
|
+
type: sub-skill
|
|
8
|
+
library: aircall-ds
|
|
9
|
+
library_version: "0.13.0"
|
|
10
|
+
requires:
|
|
11
|
+
- aircall-ds/setup
|
|
12
|
+
- aircall-ds/migrate-tractor
|
|
13
|
+
sources:
|
|
14
|
+
- "aircall/hydra:docs/migration-guides/tractor-to-ds/recipes/select.md"
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
This skill builds on aircall-ds/migrate-tractor. Apply all cross-cutting rules from that skill (prop renames, `render` prop, data attributes) before the select-specific steps below.
|
|
18
|
+
|
|
19
|
+
## 1. Component mapping
|
|
20
|
+
|
|
21
|
+
Tractor `Select` was a flat component with `SelectOption` children. DS uses a compound structure where each visual slot is a named part:
|
|
22
|
+
|
|
23
|
+
| Tractor | DS compound part | Role |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| `<Select>` | `<Select>` | State owner (value, open, disabled) |
|
|
26
|
+
| _(no equivalent)_ | `<SelectTrigger>` | Visible field / button |
|
|
27
|
+
| _(no equivalent)_ | `<SelectValue>` | Renders current label or placeholder |
|
|
28
|
+
| _(no equivalent)_ | `<SelectContent>` | Dropdown container |
|
|
29
|
+
| `<SelectOption>` | `<SelectItem>` | Individual option |
|
|
30
|
+
| _(no equivalent)_ | `<SelectGroup>` | Required wrapper around a labelled section |
|
|
31
|
+
| _(no equivalent)_ | `<SelectLabel>` | Section heading inside a `SelectGroup` |
|
|
32
|
+
|
|
33
|
+
## 2. Verified DS exports (`packages/ds/src/index.ts`)
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
Select, SelectContent, SelectGroup, SelectItem,
|
|
37
|
+
SelectLabel, SelectTrigger, SelectValue,
|
|
38
|
+
SelectScrollDownButton, SelectScrollUpButton, SelectSeparator,
|
|
39
|
+
selectTriggerVariants
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
All names listed in the Before/After section below exist in the published public API.
|
|
43
|
+
|
|
44
|
+
## 3. Imports
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
import {
|
|
48
|
+
Select,
|
|
49
|
+
SelectContent,
|
|
50
|
+
SelectGroup,
|
|
51
|
+
SelectItem,
|
|
52
|
+
SelectLabel,
|
|
53
|
+
SelectTrigger,
|
|
54
|
+
SelectValue,
|
|
55
|
+
} from '@aircall/ds';
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 4. Prop renames
|
|
59
|
+
|
|
60
|
+
| Tractor prop | DS prop | Notes |
|
|
61
|
+
| --- | --- | --- |
|
|
62
|
+
| `onChange` | `onValueChange` | Signature change — see Mistake 1 below |
|
|
63
|
+
| `placeholder` on `<Select>` | `placeholder` on `<SelectValue>` | Moved to the value part |
|
|
64
|
+
| `selectedOption` / `value` | `value` | Unchanged name, same position |
|
|
65
|
+
|
|
66
|
+
## 5. Before / After
|
|
67
|
+
|
|
68
|
+
### Before (Tractor)
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { Select, SelectOption } from '@aircall/tractor';
|
|
72
|
+
|
|
73
|
+
<Select
|
|
74
|
+
value={channelType}
|
|
75
|
+
onChange={(value) => setChannelType(value)}
|
|
76
|
+
placeholder="Pick a channel"
|
|
77
|
+
>
|
|
78
|
+
<SelectOption value="phone">Phone</SelectOption>
|
|
79
|
+
<SelectOption value="email">Email</SelectOption>
|
|
80
|
+
<SelectOption value="sms">SMS</SelectOption>
|
|
81
|
+
</Select>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### After (DS)
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
import {
|
|
88
|
+
Select,
|
|
89
|
+
SelectContent,
|
|
90
|
+
SelectItem,
|
|
91
|
+
SelectTrigger,
|
|
92
|
+
SelectValue,
|
|
93
|
+
} from '@aircall/ds';
|
|
94
|
+
|
|
95
|
+
<Select
|
|
96
|
+
value={channelType}
|
|
97
|
+
onValueChange={(value: string | null) => {
|
|
98
|
+
if (value !== null) setChannelType(value);
|
|
99
|
+
}}
|
|
100
|
+
>
|
|
101
|
+
<SelectTrigger>
|
|
102
|
+
<SelectValue placeholder="Pick a channel" />
|
|
103
|
+
</SelectTrigger>
|
|
104
|
+
<SelectContent>
|
|
105
|
+
<SelectItem value="phone">Phone</SelectItem>
|
|
106
|
+
<SelectItem value="email">Email</SelectItem>
|
|
107
|
+
<SelectItem value="sms">SMS</SelectItem>
|
|
108
|
+
</SelectContent>
|
|
109
|
+
</Select>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## 6. With sections (Labels)
|
|
113
|
+
|
|
114
|
+
When grouping options under a label, `SelectLabel` **must** be inside a `SelectGroup` along with its items. Direct children of `SelectContent` are not accessibility-correct in Base UI.
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
import {
|
|
118
|
+
Select,
|
|
119
|
+
SelectContent,
|
|
120
|
+
SelectGroup,
|
|
121
|
+
SelectItem,
|
|
122
|
+
SelectLabel,
|
|
123
|
+
SelectTrigger,
|
|
124
|
+
SelectValue,
|
|
125
|
+
} from '@aircall/ds';
|
|
126
|
+
|
|
127
|
+
<Select value={value} onValueChange={setValue}>
|
|
128
|
+
<SelectTrigger>
|
|
129
|
+
<SelectValue placeholder="Pick a channel" />
|
|
130
|
+
</SelectTrigger>
|
|
131
|
+
<SelectContent>
|
|
132
|
+
<SelectGroup>
|
|
133
|
+
<SelectLabel>Voice</SelectLabel>
|
|
134
|
+
<SelectItem value="phone">Phone</SelectItem>
|
|
135
|
+
<SelectItem value="sms">SMS</SelectItem>
|
|
136
|
+
</SelectGroup>
|
|
137
|
+
<SelectGroup>
|
|
138
|
+
<SelectLabel>Written</SelectLabel>
|
|
139
|
+
<SelectItem value="email">Email</SelectItem>
|
|
140
|
+
</SelectGroup>
|
|
141
|
+
</SelectContent>
|
|
142
|
+
</Select>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## 7. Size
|
|
146
|
+
|
|
147
|
+
`SelectTrigger` accepts a `size` prop:
|
|
148
|
+
|
|
149
|
+
| Value | Height | Use for |
|
|
150
|
+
| --- | --- | --- |
|
|
151
|
+
| `"default"` | 40px | Any Tractor `regular` or `small` size |
|
|
152
|
+
| `"sm"` | 32px | Compact layouts |
|
|
153
|
+
|
|
154
|
+
## 8. When to use Combobox instead
|
|
155
|
+
|
|
156
|
+
If users need to **search or filter** the options list, use `Combobox` rather than `Select`. Plain `Select` is for short, fixed-enum lists where no search is needed.
|
|
157
|
+
|
|
158
|
+
## 9. Common Mistakes
|
|
159
|
+
|
|
160
|
+
### Mistake 1 — Not widening the `onValueChange` handler for `null`
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
// Wrong
|
|
164
|
+
<Select onValueChange={(value: string) => setChannelType(value)}>
|
|
165
|
+
...
|
|
166
|
+
</Select>
|
|
167
|
+
|
|
168
|
+
// Correct
|
|
169
|
+
<Select
|
|
170
|
+
onValueChange={(value: string | null) => {
|
|
171
|
+
if (value !== null) setChannelType(value);
|
|
172
|
+
}}
|
|
173
|
+
>
|
|
174
|
+
...
|
|
175
|
+
</Select>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The DS `onValueChange` callback receives `string | null` — `null` is emitted when the selection is cleared. Typing the parameter as `string` causes a TypeScript error; omitting the null guard causes a runtime `null` to propagate into state that expects a string.
|
|
179
|
+
Source: `packages/ds/src/components/select.tsx`
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
### Mistake 2 — Placing `SelectLabel` as a direct child of `SelectContent`
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
// Wrong
|
|
187
|
+
<SelectContent>
|
|
188
|
+
<SelectLabel>Voice</SelectLabel> {/* label outside a SelectGroup */}
|
|
189
|
+
<SelectItem value="phone">Phone</SelectItem>
|
|
190
|
+
</SelectContent>
|
|
191
|
+
|
|
192
|
+
// Correct
|
|
193
|
+
<SelectContent>
|
|
194
|
+
<SelectGroup>
|
|
195
|
+
<SelectLabel>Voice</SelectLabel> {/* label always inside a SelectGroup */}
|
|
196
|
+
<SelectItem value="phone">Phone</SelectItem>
|
|
197
|
+
</SelectGroup>
|
|
198
|
+
</SelectContent>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
In Base UI, `SelectLabel` must be a sibling of its items inside a `SelectGroup`. A bare `SelectLabel` in `SelectContent` is not accessibility-correct — the group association that screen readers rely on is missing, and the label may render incorrectly.
|
|
202
|
+
Source: `packages/ds/src/components/select.tsx`
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
### Mistake 3 — Keeping `placeholder` on `<Select>` instead of `<SelectValue>`
|
|
207
|
+
|
|
208
|
+
```tsx
|
|
209
|
+
// Wrong
|
|
210
|
+
<Select placeholder="Pick a channel">
|
|
211
|
+
<SelectTrigger>
|
|
212
|
+
<SelectValue />
|
|
213
|
+
</SelectTrigger>
|
|
214
|
+
...
|
|
215
|
+
</Select>
|
|
216
|
+
|
|
217
|
+
// Correct
|
|
218
|
+
<Select>
|
|
219
|
+
<SelectTrigger>
|
|
220
|
+
<SelectValue placeholder="Pick a channel" />
|
|
221
|
+
</SelectTrigger>
|
|
222
|
+
...
|
|
223
|
+
</Select>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Tractor accepted `placeholder` on the root `<Select>`. In the DS compound, the `placeholder` prop lives on `<SelectValue>` — it is silently ignored when placed on `<Select>`, so no placeholder text appears in the trigger.
|
|
227
|
+
Source: `packages/ds/src/components/select.tsx`
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
### Mistake 4 — Using `SelectOption` instead of `SelectItem`
|
|
232
|
+
|
|
233
|
+
```tsx
|
|
234
|
+
// Wrong
|
|
235
|
+
import { Select, SelectOption } from '@aircall/tractor';
|
|
236
|
+
// SelectOption does not exist in @aircall/ds
|
|
237
|
+
|
|
238
|
+
// Correct
|
|
239
|
+
import { Select, SelectItem } from '@aircall/ds';
|
|
240
|
+
|
|
241
|
+
<SelectItem value="phone">Phone</SelectItem>
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Tractor used `SelectOption` for each choice. The DS compound uses `SelectItem`. `SelectOption` is not exported from `@aircall/ds` — importing it will cause a build error.
|
|
245
|
+
Source: `packages/ds/src/components/select.tsx`
|