@antadesign/anta 0.3.18 → 0.3.20

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.
Files changed (92) hide show
  1. package/README.md +38 -242
  2. package/dist/anta_helpers.d.ts +2 -2
  3. package/dist/components/Banner.d.ts +3 -3
  4. package/dist/components/Button.d.ts +1 -1
  5. package/dist/components/Calendar.d.ts +11 -12
  6. package/dist/components/Card.d.ts +1 -1
  7. package/dist/components/Card.js +1 -1
  8. package/dist/components/Checkbox.d.ts +8 -9
  9. package/dist/components/Dialog.d.ts +1 -1
  10. package/dist/components/Expander.d.ts +5 -6
  11. package/dist/components/Input.d.ts +6 -3
  12. package/dist/components/Input.js +3 -1
  13. package/dist/components/InputAutocomplete.d.ts +8 -8
  14. package/dist/components/InputDate.css +1 -0
  15. package/dist/components/InputDate.d.ts +1 -0
  16. package/dist/components/InputDate.js +5 -5
  17. package/dist/components/InputTime.d.ts +3 -3
  18. package/dist/components/InputTime.js +1 -1
  19. package/dist/components/Menu.d.ts +7 -3
  20. package/dist/components/Menu.js +3 -2
  21. package/dist/components/MenuItem.d.ts +1 -1
  22. package/dist/components/MenuItem.js +1 -1
  23. package/dist/components/Progress.d.ts +1 -1
  24. package/dist/components/RadioGroup.d.ts +12 -13
  25. package/dist/components/Select.d.ts +32 -38
  26. package/dist/components/Select.js +9 -13
  27. package/dist/components/SelectFaceted.d.ts +28 -33
  28. package/dist/components/SelectFaceted.js +29 -23
  29. package/dist/components/Slider.d.ts +5 -5
  30. package/dist/components/Switch.d.ts +4 -4
  31. package/dist/components/Tabs.d.ts +7 -7
  32. package/dist/components/Title.d.ts +1 -1
  33. package/dist/components/Toaster.d.ts +1 -1
  34. package/dist/components/Tooltip.d.ts +1 -1
  35. package/dist/components/select-parts.css +1 -0
  36. package/dist/elements/a-card.css +1 -1
  37. package/dist/elements/a-dialog.css +1 -1
  38. package/dist/elements/a-expander.css +1 -1
  39. package/dist/elements/a-expander.d.ts +1 -0
  40. package/dist/elements/a-expander.js +12 -2
  41. package/dist/elements/a-input.css +1 -1
  42. package/dist/elements/a-menu.js +12 -0
  43. package/dist/elements/a-progress.css +1 -1
  44. package/dist/elements/a-slider.css +1 -1
  45. package/dist/elements/a-tabs.css +1 -1
  46. package/dist/elements/a-tabs.d.ts +3 -0
  47. package/dist/elements/a-tabs.js +40 -1
  48. package/dist/elements/a-tooltip.css +1 -1
  49. package/dist/elements/a-tooltip.d.ts +1 -1
  50. package/dist/elements/a-tooltip.js +1 -1
  51. package/dist/elements/index.d.ts +1 -1
  52. package/dist/general_types.d.ts +24 -22
  53. package/dist/index.d.ts +3 -3
  54. package/dist/jsx-runtime.d.ts +9 -0
  55. package/docs/accessibility.md +34 -0
  56. package/docs/changelog.md +561 -0
  57. package/docs/colors.md +42 -0
  58. package/docs/comparison.md +28 -0
  59. package/docs/components/banner.md +267 -0
  60. package/docs/components/button.md +603 -0
  61. package/docs/components/card.md +321 -0
  62. package/docs/components/checkbox.md +296 -0
  63. package/docs/components/dialog.md +314 -0
  64. package/docs/components/expander.md +526 -0
  65. package/docs/components/icon.md +155 -0
  66. package/docs/components/input-autocomplete.md +100 -0
  67. package/docs/components/input-date.md +401 -0
  68. package/docs/components/input-time.md +174 -0
  69. package/docs/components/input.md +610 -0
  70. package/docs/components/loader.md +139 -0
  71. package/docs/components/menu.md +511 -0
  72. package/docs/components/progress.md +182 -0
  73. package/docs/components/radio.md +368 -0
  74. package/docs/components/select-faceted.md +451 -0
  75. package/docs/components/select.md +926 -0
  76. package/docs/components/slider.md +147 -0
  77. package/docs/components/switch.md +198 -0
  78. package/docs/components/tabs.md +471 -0
  79. package/docs/components/tag.md +185 -0
  80. package/docs/components/text.md +148 -0
  81. package/docs/components/title.md +146 -0
  82. package/docs/components/toaster.md +313 -0
  83. package/docs/components/tooltip.md +177 -0
  84. package/docs/credits.md +18 -0
  85. package/docs/index.md +52 -0
  86. package/docs/install-config.md +240 -0
  87. package/docs/normalization.md +102 -0
  88. package/docs/overview.md +10 -0
  89. package/docs/packages/stickers.md +187 -0
  90. package/docs/packages/table.md +153 -0
  91. package/docs/theming.md +13 -0
  92. package/package.json +10 -6
@@ -0,0 +1,451 @@
1
+ # SelectFaceted
2
+
3
+ A faceted filter lets people filter a result set by several categories. Anta
4
+ calls each category a **facet**. The trigger opens a menu of facets, and each
5
+ facet opens a control for choosing its value.
6
+
7
+ The value is a `Record<facetKey, value>`. This keeps values separate when two
8
+ facets use the same option. For example, `assignee` and `owner` below are
9
+ application-defined facet keys, not Anta terms. Each facet has one of four
10
+ `kind` values: `single`, `multiple`, `text`, or `custom`.
11
+
12
+ `SelectFaceted` uses [Menu](./menu.md), [Select](./select.md)'s option model,
13
+ [Input](./input.md), and [Tag](./tag.md). It does not create a custom element.
14
+ Use `value` with `onValueChange` to control it, or use `defaultValue` for an
15
+ uncontrolled filter.
16
+
17
+ Pass `placement` to control where the root facet menu opens relative to its
18
+ trigger. It uses the same values as `Menu` and still flips or clamps when space
19
+ is limited. `offset` sets the gap in pixels between the trigger and the menu:
20
+
21
+ ```tsx
22
+ <SelectFaceted placement="top-end" offset={8} facets={FACETS} />
23
+ ```
24
+
25
+ ```tsx
26
+ const FACETS = [
27
+ { key: 'assignee', label: 'Assignee', kind: 'multiple', filter: true, options: people },
28
+ { key: 'owner', label: 'Owner', kind: 'single', filter: true, options: people },
29
+ { key: 'status', label: 'Status', kind: 'single', options: [{ value: 'open', label: 'Open' }, /* … */] },
30
+ { key: 'title', label: 'Title contains', kind: 'text' },
31
+ { key: 'duration', label: 'Min duration', kind: 'custom', summary: (v) => `≥ ${v.min}s`, render: /* … */ },
32
+ ]
33
+
34
+ const isSet = (v) => !(v == null || v === '' || (Array.isArray(v) && v.length === 0))
35
+
36
+ function Demo() {
37
+ const [value, setValue] = useState({ assignee: ['Alice Nguyen'], status: 'open', title: 'crash' })
38
+ const setFacet = (key, v) =>
39
+ setValue((p) => (isSet(v) ? { ...p, [key]: v } : (({ [key]: _, ...rest }) => rest)(p)))
40
+
41
+ // Keep a text/custom chip mounted while it's focused, even when emptied — so
42
+ // backspacing to empty doesn't yank the field (only Clear or blur-when-empty does).
43
+ const [focusedKey, setFocusedKey] = useState(null)
44
+ const active = FACETS.filter((f) => isSet(value[f.key]) || f.key === focusedKey)
45
+
46
+ return (
47
+ <div style={{
48
+ display: 'flex',
49
+ flexDirection: 'row',
50
+ flexWrap: 'wrap',
51
+ gap: 12,
52
+ alignItems: 'center',
53
+ width: '100%',
54
+ }}>
55
+ <SelectFaceted facets={FACETS} value={value} onValueChange={setValue} searchable />
56
+
57
+ {active.length > 0 && (
58
+ <div style={{ display: 'flex', flexDirection: 'row', flexWrap: 'wrap', gap: 8, alignItems: 'center' }}>
59
+ {/* Show one editable control for each active facet. Its label becomes
60
+ the control's `leading` prefix. */}
61
+ {active.map((facet) => {
62
+ // Options facets → an editable Select (single shows the trailing check;
63
+ // both are filterable + have a clearable footer).
64
+ if (facet.kind === 'single' || facet.kind === 'multiple')
65
+ return (
66
+ <Select
67
+ key={facet.key}
68
+ selection={facet.kind}
69
+ indicator={facet.kind === 'single' ? 'check' : undefined}
70
+ options={facet.options}
71
+ value={value[facet.key]}
72
+ onValueChange={(v) => setFacet(facet.key, v)}
73
+ leading={`${facet.label}:`}
74
+ filter={facet.filter}
75
+ clearable
76
+ // Chip sizes to its value and caps at a max-width (ellipsizing past it).
77
+ style={{ width: 'fit-content', maxWidth: '240px' }}
78
+ />
79
+ )
80
+
81
+ // Free-form text / custom → an editable Input.
82
+ const val = facet.kind === 'text' ? value[facet.key] : value[facet.key]?.min
83
+ const write = (raw) =>
84
+ setFacet(facet.key, facet.kind === 'text' ? raw || undefined : raw ? { min: raw } : undefined)
85
+ return (
86
+ <Input
87
+ key={facet.key}
88
+ value={val ?? ''}
89
+ leading={`${facet.label}:`}
90
+ clearable
91
+ dimActions
92
+ onFocus={() => setFocusedKey(facet.key)}
93
+ onBlur={() => setFocusedKey(null)}
94
+ onInput={(e) => write(e.currentTarget.value)}
95
+ onClearInput={() => { setFocusedKey(null); setFacet(facet.key, undefined) }}
96
+ // Size the chip to its value, capped at a max-width.
97
+ style={{ width: 'fit-content', maxWidth: '240px' }}
98
+ />
99
+ )
100
+ })}
101
+ {/* Once more than one result is showing, a single control resets them
102
+ all — pushed to the right of the last chip. */}
103
+ {active.length > 1 && (
104
+ <Button
105
+ priority="tertiary"
106
+ icon="filter-x"
107
+ label="Clear all"
108
+ style={{ marginLeft: 'auto' }}
109
+ onClick={() => { setFocusedKey(null); setValue({}) }}
110
+ />
111
+ )}
112
+ </div>
113
+ )}
114
+ </div>
115
+ )
116
+ }
117
+ ```
118
+
119
+ The trigger opens the facet menu. The example renders one editable
120
+ [Select](./select.md) below it for each active facet. Each Select receives the
121
+ facet label through `leading`, which displays it as a prefix.
122
+
123
+ ### Value and changes
124
+
125
+ The value is a record keyed by facet key: `{ [facetKey]: value }`. Its shape
126
+ depends on `kind`: an option value for `single`, an array for `multiple`, a
127
+ string for `text`, and a value of type `V` for `custom`. A cleared facet is
128
+ absent from the record, rather than set to `undefined`. The trigger badge counts
129
+ the keys that remain. Use `value` with `onValueChange` to control the filter, or
130
+ use `defaultValue` for the uncontrolled case.
131
+
132
+ Because one control mixes facets that hold different value types, the record is typed
133
+ `Record<string, unknown>`; narrow per facet when you read it.
134
+
135
+ **`onValueChange(value, attrs)`** fires after any change:
136
+
137
+ - **`value`** — the whole new record, e.g. `{ assignee: ['Alice'], status: 'open', title: 'crash' }`.
138
+ - **`attrs`** — what changed: `{ facet, kind, value }` for a single facet edit (the
139
+ facet's `key`, its `kind`, and its new value, `undefined` when that facet was cleared),
140
+ or `{ all: true }` for the **Clear all** row. Narrow on `'all' in attrs` before reading
141
+ `facet`.
142
+
143
+ ```tsx
144
+ <SelectFaceted
145
+ facets={FACETS}
146
+ onValueChange={(value, attrs) => {
147
+ if ('all' in attrs) return clearFilters() // the Clear all row
148
+ // `value` is the full record; `attrs.facet` / `attrs.value` is the one that changed
149
+ applyFilter(attrs.facet, attrs.value)
150
+ }}
151
+ />
152
+ ```
153
+
154
+ ### Value types
155
+
156
+ `single` and `multiple` facets use [Select](./select.md#value-and-changes) option
157
+ values: `string`, `number`, or `boolean` (`OptionValue`). Anta compares them
158
+ with `===` and returns them unchanged. For a non-primitive value, use a stable
159
+ primitive key such as a date's ISO string or a record ID. Look up the full value
160
+ with that key. Use a `custom` facet when the facet value is an object, such as a
161
+ date range or numeric comparison. Its editor can return any value of type `V`.
162
+
163
+ ### Custom facet
164
+
165
+ A `custom` facet has no option list. Provide its editor and choose the value it
166
+ returns: a number, date, range, or any other object. In addition to `key`,
167
+ `label`, and `icon`, provide two functions:
168
+
169
+ - **`render(ctx)`** renders the editor. `ctx.value` is the current value.
170
+ `ctx.onChange(next)` sets it, and `undefined` clears the facet. `ctx.close()`
171
+ closes the menu. Anta stores the value passed to `onChange` at `value[key]`
172
+ without changing it.
173
+ - **`summary(value)`** renders the chip on the facet row when a value is set.
174
+ It does not change the stored value. It can return any node or `null`. Long
175
+ summaries are truncated so that they do not widen the menu.
176
+
177
+ `SelectFaceted` does not filter application data. It stores the facet values and
178
+ reports them through `onValueChange`. Filter the application's rows from that
179
+ value. Type the facet as `SelectFacetCustom<V>` so `ctx.value` and `summary`
180
+ use the same value type.
181
+
182
+ For a minimum-duration filter, the editor is one `Input`. Because that Input
183
+ does not open a popup, it appears directly in the facet menu.
184
+
185
+ ```tsx
186
+ import type { SelectFacetCustom } from '@antadesign/anta'
187
+
188
+ const duration: SelectFacetCustom<string> = {
189
+ key: 'duration',
190
+ label: 'Min duration',
191
+ kind: 'custom',
192
+ render: (ctx) => (
193
+ <Input
194
+ // Controlled numeric field: type="text" + inputMode, not type="number"
195
+ // (which sanitizes "1." / "1.0" to empty). Keep the raw string; parse it below.
196
+ type="text"
197
+ inputMode="decimal"
198
+ size="small"
199
+ placeholder="seconds"
200
+ value={ctx.value ?? ''}
201
+ onInput={(e) => ctx.onChange(e.currentTarget.value || undefined)}
202
+ />
203
+ ),
204
+ summary: (v) => `≥ ${v}s`,
205
+ }
206
+
207
+ <SelectFaceted facets={[duration]} value={value} onValueChange={setValue} />
208
+
209
+ // user types 1.5 → value.duration === "1.5" (the raw string, returned unchanged)
210
+ // parse it where you apply the filter, so partial input ("1.", "1.0") types cleanly:
211
+ rows.filter((r) => value.duration == null || r.seconds >= Number(value.duration))
212
+ ```
213
+
214
+ For dates, the example converts between the Calendar's ISO strings and a stored
215
+ `Date`. Editors that open a menu, including `InputDate` and `Select`, also work
216
+ inside a custom facet. Their menu opens above the facet menu, which remains open.
217
+
218
+ ```tsx
219
+ import type { SelectFacetCustom } from '@antadesign/anta'
220
+
221
+ const toISO = (d: Date) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` // Date → "2026-01-01"
222
+ const fromISO = (s: string | null) => (s ? new Date(`${s}T00:00:00`) : undefined) // ISO → Date | undefined
223
+
224
+ const since: SelectFacetCustom<Date> = {
225
+ key: 'since',
226
+ label: 'Created after',
227
+ kind: 'custom',
228
+ icon: 'calendar',
229
+ render: (ctx) => (
230
+ <Calendar
231
+ size="small"
232
+ value={ctx.value ? toISO(ctx.value) : ''}
233
+ // Controlled: apply the pick in onStateChange (onValueChange fires only after
234
+ // value changes, so it can't drive a controlled Calendar).
235
+ onStateChange={(_e, { next }) => ctx.onChange(fromISO(next))}
236
+ />
237
+ ),
238
+ summary: (d) => d.toLocaleDateString(),
239
+ }
240
+
241
+ <SelectFaceted facets={[since]} value={value} onValueChange={setValue} />
242
+
243
+ // user picks a day → value.since is a Date, returned unchanged
244
+ // you apply it:
245
+ rows.filter((r) => value.since == null || r.createdAt >= value.since)
246
+ ```
247
+
248
+ A custom facet stores a `Date` directly. Unlike the value of a `single` or
249
+ `multiple` facet, it is not compared with `===` to find an option.
250
+
251
+ #### A recency filter: presets plus a range
252
+
253
+ This editor combines a `RadioGroup` of presets with a **Custom range** option
254
+ that reveals two `InputDate` fields. Its value is either `{ preset }` or
255
+ `{ from, to }`. The `resolveRange` helper converts either shape into the date
256
+ range used to filter the result set. Each InputDate calendar opens above the
257
+ facet menu without closing it.
258
+
259
+ ```tsx
260
+ type Recency = { preset: 'today' | 'yesterday' | 'last14' | 'last30' } | { from: string; to: string }
261
+
262
+ // Whichever shape the facet holds, resolve it to a concrete range to filter with.
263
+ const resolveRange = (r: Recency): { from: string; to: string } => {
264
+ if ('from' in r) return r
265
+ const today = isoDay(daysAgo(0))
266
+ switch (r.preset) {
267
+ case 'today': return { from: today, to: today }
268
+ case 'yesterday': { const y = isoDay(daysAgo(1)); return { from: y, to: y } }
269
+ case 'last14': return { from: isoDay(daysAgo(13)), to: today }
270
+ case 'last30': return { from: isoDay(daysAgo(29)), to: today }
271
+ }
272
+ }
273
+
274
+ import type { SelectFacetCustom } from '@antadesign/anta'
275
+
276
+ const recency: SelectFacetCustom<Recency> = {
277
+ key: 'recency',
278
+ label: 'Recency',
279
+ kind: 'custom',
280
+ icon: 'calendar',
281
+ summary: (v) => ('preset' in v ? PRESET_LABELS[v.preset] : `${v.from} → ${v.to}`),
282
+ render: ({ value, onChange }) => {
283
+ const mode = value == null ? '' : 'preset' in value ? value.preset : 'custom'
284
+ const range = value && 'from' in value ? value : { from: '', to: '' }
285
+ return (
286
+ <div style={{ display: 'grid', gap: 8 }}>
287
+ <RadioGroup
288
+ size="small"
289
+ options={PRESETS} // Today / Yesterday / Last 14 / Last 30 / Custom range
290
+ value={mode}
291
+ // Controlled group: apply the pick in onStateChange (onValueChange fires
292
+ // only after `value` changes, so it can't drive a controlled RadioGroup).
293
+ onStateChange={(_e, { next }) => onChange(next === 'custom' ? range : { preset: next })}
294
+ />
295
+ {mode === 'custom' && (
296
+ <>
297
+ <InputDate size="small" label="From" value={range.from}
298
+ onValueChange={(from) => onChange({ from, to: range.to })} />
299
+ <InputDate size="small" label="To" value={range.to} min={range.from || undefined}
300
+ onValueChange={(to) => onChange({ from: range.from, to })} />
301
+ </>
302
+ )}
303
+ </div>
304
+ )
305
+ },
306
+ }
307
+
308
+ <SelectFaceted facets={[recency]} value={value} onValueChange={setValue} />
309
+ // value.recency is { preset: 'last14' } or { from, to } — apply resolveRange(value.recency) to your rows
310
+ ```
311
+
312
+ #### Menu-item presets, calendar on the right
313
+
314
+ This version renders the presets as `MenuItem` rows and marks the selected row
315
+ with a check. **Custom day** reveals one `InputDate`. Its calendar opens to the
316
+ right with `placement="right-start"`, which is useful when the trigger is on the
317
+ left side of a wide layout. `placement` and `offset` are passed to the
318
+ calendar's Menu.
319
+
320
+ ```tsx
321
+ type Recency = { preset: 'today' | 'yesterday' | 'last14' | 'last30' } | { day: string }
322
+
323
+ const recency: SelectFacetCustom<Recency> = {
324
+ key: 'recency',
325
+ label: 'Recency',
326
+ kind: 'custom',
327
+ icon: 'calendar',
328
+ summary: (v) => ('preset' in v ? PRESET_LABELS[v.preset] : v.day || 'Custom day'),
329
+ render: ({ value, onChange }) => {
330
+ const mode = value == null ? '' : 'preset' in value ? value.preset : 'custom'
331
+ const day = value && 'day' in value ? value.day : ''
332
+ return (
333
+ <>
334
+ {DAY_PRESETS.map((p) => ( // Today / Yesterday / Last 14 / Last 30 / Custom day
335
+ <MenuItem
336
+ key={p.value}
337
+ label={p.label}
338
+ selectionIndicator="check"
339
+ selected={mode === p.value}
340
+ data-menu-open
341
+ onSelect={() => onChange(p.value === 'custom' ? { day } : { preset: p.value })}
342
+ />
343
+ ))}
344
+ {mode === 'custom' && (
345
+ <div data-menu-open style={{ padding: 8 }}>
346
+ {/* opens the calendar to the right of the field */}
347
+ <InputDate size="small" label="Day" placement="right-start" value={day}
348
+ onValueChange={(d) => onChange({ day: d })} />
349
+ </div>
350
+ )}
351
+ </>
352
+ )
353
+ },
354
+ }
355
+
356
+ <SelectFaceted facets={[recency]} value={value} onValueChange={setValue} />
357
+ // value.recency is { preset: 'last14' } or { day: '2026-07-20' } — resolve a preset to a
358
+ // concrete date (or take the day as-is) and filter your rows with it
359
+ ```
360
+
361
+ ### Props
362
+
363
+ | Prop | Type | Default | Description |
364
+ |------|------|---------|-------------|
365
+ | `facets` | SelectFacet[] | — | Categories to filter by. Each facet has a `kind` that determines its editor. |
366
+ | `clearable?` | boolean | true | Show the per-facet "Clear" row and the "Clear all" row. |
367
+ | `clearAllLabel?` | string | Clear all | Label for the "Clear all" row. |
368
+ | `defaultValue?` | SelectFacetedValue | — | Initial value for uncontrolled use. |
369
+ | `disabled?` | boolean | — | Disable the whole control. |
370
+ | `icon?` | IconShape | filter | Default trigger's leading icon. |
371
+ | `label?` | string | Filter | Default trigger's button label. |
372
+ | `offset?` | number | 4 | Gap in pixels between the trigger and the filter menu. |
373
+ | `onValueChange?` | (value, attrs) => void | — | Fires after any facet changes. `value` is the whole new record (a facet key →
374
+ that facet's value; a cleared facet is absent). `attrs` says what changed:
375
+ `{ facet, kind, value }` for a single facet edit, or `{ all: true }` for the
376
+ "Clear all" row — narrow on `'all' in attrs` before reading `facet`. |
377
+ | `placement?` | 'left' \| 'right' \| 'bottom' \| 'top' \| 'bottom-start' \| 'bottom-end' \| 'top-start' \| 'top-end' \| 'right-start' \| 'right-end' \| 'left-start' \| 'left-end' | bottom-start | Preferred placement of the root filter menu relative to its trigger. The
378
+ menu auto-flips vertically and clamps horizontally when needed. |
379
+ | `priority?` | 'primary' \| 'secondary' | secondary | Default trigger's button priority. |
380
+ | `renderTrigger?` | (state) => ReactNode | — | Replaces the default `Button` with a trigger returned from this function.
381
+ Receives a `SelectFacetedTriggerState`. Return exactly one focusable element:
382
+ the menu is positioned relative to that element and opens when it is clicked.
383
+ Add `aria-haspopup="menu"` and `aria-expanded={state.open}` to the returned
384
+ element. `className`, `style`, and other trigger props apply only to the
385
+ default Button, so add styling and attributes to the returned element. |
386
+ | `searchable?` | boolean | — | Adds a search field at the top of the root menu. It searches the options of
387
+ every `single` and `multiple` facet in one list. For example, "alice" can
388
+ appear under application-defined Assignee and Owner facets. `text` and
389
+ `custom` facets remain available when the search is empty. Each facet uses
390
+ its `filter` function when supplied, or the built-in substring match. |
391
+ | `searchPlaceholder?` | string | Filter… | Placeholder for the global search field. |
392
+ | `size?` | 'small' \| 'medium' \| 'large' | medium | Default trigger's button size. |
393
+ | `toneSelected?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | — | Tone applied to a selected option row in the facet flyouts (label, selected
394
+ tint, and the check / checkbox indicator). A named tone or a custom CSS
395
+ color, matching `Select`'s `toneSelected`. Defaults to a neutral selection. |
396
+ | `value?` | SelectFacetedValue | — | Controlled value record, keyed by facet. When provided, update it through
397
+ `onValueChange`. Leave it undefined for uncontrolled use. |
398
+
399
+ ### The `SelectFacet` type
400
+
401
+ `facets` takes a `SelectFacet[]`. Every facet has a `key`, `label`, and optional
402
+ `icon`. The `kind` field selects one of the shapes below.
403
+
404
+ `single` and `multiple` facets use the same `options`, `filter`, and `selectAll`
405
+ properties as [Select](./select.md). They use `kind` in place of Select's
406
+ `selection`. `text` and `custom` do not use Select options. A `single` facet
407
+ returns one option value; a `multiple` facet returns an array of option values.
408
+
409
+ | Field | Type | Description |
410
+ |---|---|---|
411
+ | `key` | `string` | The key for this facet's value in the record. `attrs.facet` returns it. It must be unique in `facets`. |
412
+ | `label` | `string` | The facet's row label in the menu. |
413
+ | `icon` | `IconShape` | Leading icon on the facet's row. |
414
+
415
+ **`single`** (`SelectFacetSingle`) selects one option. Its value is the selected
416
+ option's `value`, or is absent when cleared. Selecting the current option again
417
+ clears it.
418
+
419
+ | Field | Type | Description |
420
+ |---|---|---|
421
+ | `kind` | `'single'` | Discriminant. |
422
+ | `options` | `SelectItem[]` | The choices — bare strings or `SelectOption`s (groups / submenus flatten to their leaves). Option `value`s may be `string` / `number` / `boolean`. |
423
+ | `filter` | `FacetFilter` | Search field over this facet's options: `true` for the built-in substring match, or `(option, query) => boolean`. |
424
+
425
+ **`multiple`** (`SelectFacetMultiple`) selects any number of options. Its value
426
+ is an array of the selected option values. An empty array clears it.
427
+
428
+ | Field | Type | Description |
429
+ |---|---|---|
430
+ | `kind` | `'multiple'` | Discriminant. |
431
+ | `options` | `SelectItem[]` | As `single`. |
432
+ | `filter` | `FacetFilter` | As `single`. |
433
+ | `selectAll` | `boolean` | A "Select all" row toggling every visible option. Default `true`. |
434
+ | `selectAllLabel` | `string` | Label for that row. Default `Select all`. |
435
+
436
+ **`text`** (`SelectFacetText`) accepts a free-form string. It applies on Enter or
437
+ blur and is absent when empty.
438
+
439
+ | Field | Type | Description |
440
+ |---|---|---|
441
+ | `kind` | `'text'` | Discriminant. |
442
+ | `placeholder` | `string` | Placeholder for the text field. |
443
+
444
+ **`custom`** (`SelectFacetCustom<V>`) renders the editor supplied by `render`.
445
+ It can store a value of type `V`, including an object such as a date range.
446
+
447
+ | Field | Type | Description |
448
+ |---|---|---|
449
+ | `kind` | `'custom'` | Discriminant. |
450
+ | `render` | `(ctx) => ReactNode` | Renders the editor. `ctx.value` is the current `V`; `ctx.onChange(next)` sets it, and `undefined` clears the facet. `ctx.close()` closes the menu. |
451
+ | `summary` | `(value: V) => ReactNode` | The chip on the facet's row while a value is set. |