@acronis-platform/ui-react 3.0.0 → 4.0.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.
Files changed (219) hide show
  1. package/dist/components/ui/accordion-container/accordion-container.js +14 -14
  2. package/dist/components/ui/accordion-container/accordion-container.js.map +1 -1
  3. package/dist/components/ui/app-shell-chat/app-shell-chat.js.map +1 -1
  4. package/dist/components/ui/area-chart/area-chart.js +141 -139
  5. package/dist/components/ui/area-chart/area-chart.js.map +1 -1
  6. package/dist/components/ui/bar-chart/bar-chart.js +295 -293
  7. package/dist/components/ui/bar-chart/bar-chart.js.map +1 -1
  8. package/dist/components/ui/category-bar/category-bar.js +109 -93
  9. package/dist/components/ui/category-bar/category-bar.js.map +1 -1
  10. package/dist/components/ui/chart/chart-palette.js +113 -0
  11. package/dist/components/ui/chart/chart-palette.js.map +1 -0
  12. package/dist/components/ui/chart/chart.js +176 -129
  13. package/dist/components/ui/chart/chart.js.map +1 -1
  14. package/dist/components/ui/chart-state/chart-state-illustrations.js +310 -0
  15. package/dist/components/ui/chart-state/chart-state-illustrations.js.map +1 -0
  16. package/dist/components/ui/chart-state/chart-state.js +48 -34
  17. package/dist/components/ui/chart-state/chart-state.js.map +1 -1
  18. package/dist/components/ui/chart-widget/chart-widget.js +87 -0
  19. package/dist/components/ui/chart-widget/chart-widget.js.map +1 -0
  20. package/dist/components/ui/composed-chart/composed-chart.js +117 -115
  21. package/dist/components/ui/composed-chart/composed-chart.js.map +1 -1
  22. package/dist/components/ui/confidence-cone/confidence-cone.js +166 -158
  23. package/dist/components/ui/confidence-cone/confidence-cone.js.map +1 -1
  24. package/dist/components/ui/funnel-chart/funnel-chart.js +162 -161
  25. package/dist/components/ui/funnel-chart/funnel-chart.js.map +1 -1
  26. package/dist/components/ui/histogram/histogram.js +94 -92
  27. package/dist/components/ui/histogram/histogram.js.map +1 -1
  28. package/dist/components/ui/input/input.js.map +1 -1
  29. package/dist/components/ui/label/label.js +2 -9
  30. package/dist/components/ui/label/label.js.map +1 -1
  31. package/dist/components/ui/line-chart/line-chart.js +143 -141
  32. package/dist/components/ui/line-chart/line-chart.js.map +1 -1
  33. package/dist/components/ui/metric/metric.js +74 -185
  34. package/dist/components/ui/metric/metric.js.map +1 -1
  35. package/dist/components/ui/page-content/page-content.js.map +1 -1
  36. package/dist/components/ui/pie-chart/pie-chart.js +201 -195
  37. package/dist/components/ui/pie-chart/pie-chart.js.map +1 -1
  38. package/dist/components/ui/radar-chart/radar-chart.js +133 -131
  39. package/dist/components/ui/radar-chart/radar-chart.js.map +1 -1
  40. package/dist/components/ui/radial-bar-chart/radial-bar-chart.js +297 -265
  41. package/dist/components/ui/radial-bar-chart/radial-bar-chart.js.map +1 -1
  42. package/dist/components/ui/sankey-chart/sankey-chart.js +123 -101
  43. package/dist/components/ui/sankey-chart/sankey-chart.js.map +1 -1
  44. package/dist/components/ui/scatter-chart/scatter-chart.js +76 -74
  45. package/dist/components/ui/scatter-chart/scatter-chart.js.map +1 -1
  46. package/dist/components/ui/section/section.js +157 -31
  47. package/dist/components/ui/section/section.js.map +1 -1
  48. package/dist/components/ui/stepper/stepper.js +24 -24
  49. package/dist/components/ui/stepper/stepper.js.map +1 -1
  50. package/dist/components/ui/stepper-item/stepper-item.js +43 -24
  51. package/dist/components/ui/stepper-item/stepper-item.js.map +1 -1
  52. package/dist/components/ui/treemap/treemap.js +133 -124
  53. package/dist/components/ui/treemap/treemap.js.map +1 -1
  54. package/dist/components/ui/trend-indicator/trend-indicator.js +34 -69
  55. package/dist/components/ui/trend-indicator/trend-indicator.js.map +1 -1
  56. package/dist/components/ui/wizard/wizard.js +59 -0
  57. package/dist/components/ui/wizard/wizard.js.map +1 -0
  58. package/dist/hooks/use-wizard.js +75 -0
  59. package/dist/hooks/use-wizard.js.map +1 -0
  60. package/dist/index.js +502 -497
  61. package/dist/index.js.map +1 -1
  62. package/dist/llms/accordion-container.md +181 -0
  63. package/dist/llms/accordion.md +124 -0
  64. package/dist/llms/alert.md +225 -0
  65. package/dist/llms/app-shell-chat.md +415 -0
  66. package/dist/llms/area-chart.md +355 -0
  67. package/dist/llms/avatar.md +208 -0
  68. package/dist/llms/bar-chart.md +406 -0
  69. package/dist/llms/breadcrumb.md +326 -0
  70. package/dist/llms/button-group.md +298 -0
  71. package/dist/llms/button-icon-input.md +142 -0
  72. package/dist/llms/button-icon-menu.md +209 -0
  73. package/dist/llms/button-icon.md +128 -0
  74. package/dist/llms/button-menu.md +172 -0
  75. package/dist/llms/button.md +185 -0
  76. package/dist/llms/calendar-panel.md +733 -0
  77. package/dist/llms/calendar.md +188 -0
  78. package/dist/llms/card-filter.md +214 -0
  79. package/dist/llms/card-section.md +293 -0
  80. package/dist/llms/card.md +447 -0
  81. package/dist/llms/carousel-dialog.md +156 -0
  82. package/dist/llms/category-bar.md +193 -0
  83. package/dist/llms/chart-state.md +190 -0
  84. package/dist/llms/chart-widget.md +209 -0
  85. package/dist/llms/chart.md +182 -0
  86. package/dist/llms/checkbox.md +257 -0
  87. package/dist/llms/chip.md +195 -0
  88. package/dist/llms/combobox.md +164 -0
  89. package/dist/llms/composed-chart.md +508 -0
  90. package/dist/llms/confidence-cone.md +277 -0
  91. package/dist/llms/data-table.md +474 -0
  92. package/dist/llms/date-range-picker.md +272 -0
  93. package/dist/llms/description-list.md +131 -0
  94. package/dist/llms/dialog-footer-carousel.md +147 -0
  95. package/dist/llms/dialog-footer-default.md +142 -0
  96. package/dist/llms/dialog-root.md +246 -0
  97. package/dist/llms/dialog-welcome.md +223 -0
  98. package/dist/llms/dialog.md +403 -0
  99. package/dist/llms/dropdown-menu.md +193 -0
  100. package/dist/llms/empty-overlay.md +146 -0
  101. package/dist/llms/empty.md +156 -0
  102. package/dist/llms/field.md +185 -0
  103. package/dist/llms/filter-cards.md +157 -0
  104. package/dist/llms/filter-chips.md +323 -0
  105. package/dist/llms/filter-search.md +512 -0
  106. package/dist/llms/form.md +154 -0
  107. package/dist/llms/funnel-chart.md +333 -0
  108. package/dist/llms/grid.md +80 -0
  109. package/dist/llms/histogram.md +190 -0
  110. package/dist/llms/input-date-picker.md +203 -0
  111. package/dist/llms/input-otp.md +173 -0
  112. package/dist/llms/input-password.md +197 -0
  113. package/dist/llms/input-search.md +170 -0
  114. package/dist/llms/input-select.md +278 -0
  115. package/dist/llms/input-text-area.md +271 -0
  116. package/dist/llms/input-text.md +192 -0
  117. package/dist/llms/line-chart.md +369 -0
  118. package/dist/llms/link.md +209 -0
  119. package/dist/llms/loading.md +130 -0
  120. package/dist/llms/meter.md +177 -0
  121. package/dist/llms/metric.md +204 -0
  122. package/dist/llms/number-field.md +122 -0
  123. package/dist/llms/page-content.md +69 -0
  124. package/dist/llms/page-header.md +182 -0
  125. package/dist/llms/pagination.md +133 -0
  126. package/dist/llms/pie-chart.md +307 -0
  127. package/dist/llms/popover.md +252 -0
  128. package/dist/llms/progress-circle.md +126 -0
  129. package/dist/llms/progress.md +111 -0
  130. package/dist/llms/radar-chart.md +316 -0
  131. package/dist/llms/radial-bar-chart.md +319 -0
  132. package/dist/llms/radio.md +255 -0
  133. package/dist/llms/resizable.md +195 -0
  134. package/dist/llms/sankey-chart.md +208 -0
  135. package/dist/llms/scatter-chart.md +239 -0
  136. package/dist/llms/scroll-area.md +183 -0
  137. package/dist/llms/section.md +401 -0
  138. package/dist/llms/select.md +210 -0
  139. package/dist/llms/separator.md +87 -0
  140. package/dist/llms/sheet.md +202 -0
  141. package/dist/llms/sidebar-primary.md +360 -0
  142. package/dist/llms/sidebar-secondary.md +417 -0
  143. package/dist/llms/skeleton.md +81 -0
  144. package/dist/llms/slider.md +97 -0
  145. package/dist/llms/spinner.md +100 -0
  146. package/dist/llms/stack.md +80 -0
  147. package/dist/llms/stepper-item.md +309 -0
  148. package/dist/llms/stepper.md +251 -0
  149. package/dist/llms/switch.md +140 -0
  150. package/dist/llms/table.md +436 -0
  151. package/dist/llms/tabs.md +197 -0
  152. package/dist/llms/tag.md +139 -0
  153. package/dist/llms/timeline.md +361 -0
  154. package/dist/llms/timer.md +259 -0
  155. package/dist/llms/toast.md +241 -0
  156. package/dist/llms/toggle-group.md +121 -0
  157. package/dist/llms/toolbar.md +453 -0
  158. package/dist/llms/tooltip.md +164 -0
  159. package/dist/llms/treemap.md +254 -0
  160. package/dist/llms/trend-indicator.md +132 -0
  161. package/dist/llms/widget-placeholder.md +169 -0
  162. package/dist/llms/wizard.md +412 -0
  163. package/dist/llms.txt +142 -0
  164. package/dist/react.js +502 -497
  165. package/dist/react.js.map +1 -1
  166. package/dist/src/components/ui/area-chart/area-chart.d.ts +9 -4
  167. package/dist/src/components/ui/avatar/avatar.d.ts +1 -1
  168. package/dist/src/components/ui/bar-chart/bar-chart.d.ts +9 -4
  169. package/dist/src/components/ui/category-bar/category-bar.d.ts +10 -4
  170. package/dist/src/components/ui/chart/__stories__/palette-control.d.ts +48 -0
  171. package/dist/src/components/ui/chart/__tests__/chart-palette.test.d.ts +1 -0
  172. package/dist/src/components/ui/chart/chart-palette.d.ts +128 -0
  173. package/dist/src/components/ui/chart/chart.d.ts +66 -15
  174. package/dist/src/components/ui/chart/index.d.ts +4 -2
  175. package/dist/src/components/ui/chart-state/chart-state-illustrations.d.ts +6 -0
  176. package/dist/src/components/ui/chart-state/chart-state.d.ts +13 -2
  177. package/dist/src/components/ui/chart-state/index.d.ts +1 -0
  178. package/dist/src/components/ui/chart-widget/chart-widget.d.ts +64 -0
  179. package/dist/src/components/ui/chart-widget/index.d.ts +1 -0
  180. package/dist/src/components/ui/composed-chart/composed-chart.d.ts +12 -7
  181. package/dist/src/components/ui/confidence-cone/confidence-cone.d.ts +10 -5
  182. package/dist/src/components/ui/empty-overlay/empty-overlay.d.ts +1 -1
  183. package/dist/src/components/ui/funnel-chart/funnel-chart.d.ts +11 -5
  184. package/dist/src/components/ui/histogram/histogram.d.ts +10 -4
  185. package/dist/src/components/ui/label/index.d.ts +1 -1
  186. package/dist/src/components/ui/label/label.d.ts +1 -5
  187. package/dist/src/components/ui/line-chart/line-chart.d.ts +9 -4
  188. package/dist/src/components/ui/metric/metric.d.ts +13 -14
  189. package/dist/src/components/ui/pie-chart/pie-chart.d.ts +17 -8
  190. package/dist/src/components/ui/radar-chart/radar-chart.d.ts +10 -5
  191. package/dist/src/components/ui/radial-bar-chart/radial-bar-chart.d.ts +17 -6
  192. package/dist/src/components/ui/sankey-chart/sankey-chart.d.ts +10 -5
  193. package/dist/src/components/ui/scatter-chart/scatter-chart.d.ts +10 -5
  194. package/dist/src/components/ui/section/index.d.ts +1 -1
  195. package/dist/src/components/ui/section/section.d.ts +75 -6
  196. package/dist/src/components/ui/stepper-item/stepper-item.d.ts +6 -5
  197. package/dist/src/components/ui/tag/tag.d.ts +1 -1
  198. package/dist/src/components/ui/treemap/treemap.d.ts +11 -5
  199. package/dist/src/components/ui/trend-indicator/trend-indicator.d.ts +3 -7
  200. package/dist/src/components/ui/wizard/index.d.ts +1 -0
  201. package/dist/src/components/ui/wizard/wizard.d.ts +6 -0
  202. package/dist/src/hooks/index.d.ts +1 -0
  203. package/dist/src/hooks/use-wizard.d.ts +80 -0
  204. package/dist/src/index.d.ts +2 -3
  205. package/dist/ui-react.css +1 -1
  206. package/package.json +9 -3
  207. package/dist/components/ui/app-shell/app-shell.js +0 -75
  208. package/dist/components/ui/app-shell/app-shell.js.map +0 -1
  209. package/dist/components/ui/auth-layout/auth-layout.js +0 -56
  210. package/dist/components/ui/auth-layout/auth-layout.js.map +0 -1
  211. package/dist/components/ui/collapsible/collapsible.js +0 -22
  212. package/dist/components/ui/collapsible/collapsible.js.map +0 -1
  213. package/dist/src/components/ui/app-shell/app-shell.d.ts +0 -8
  214. package/dist/src/components/ui/app-shell/index.d.ts +0 -1
  215. package/dist/src/components/ui/auth-layout/auth-layout.d.ts +0 -6
  216. package/dist/src/components/ui/auth-layout/index.d.ts +0 -1
  217. package/dist/src/components/ui/collapsible/collapsible.d.ts +0 -5
  218. package/dist/src/components/ui/collapsible/index.d.ts +0 -1
  219. package/dist/src/components/ui/label/label.docs.d.ts +0 -10
@@ -0,0 +1,512 @@
1
+ # FilterSearch
2
+
3
+ > A composable toolbar for data tables: arranges a search field, optional filters button, optional tenant/scope switcher, and trailing action buttons in a single horizontal row. Pure layout — no dedicated token tier; child components (Search, ButtonMenu, Select) bring their own tokens. `FilterSearchFilters` (the filter popover: trigger + form + Reset/Cancel/ Apply) is still a design-pending addition — no Figma node yet, unlike the FilterSearch node itself (see `figma:` below, which covers only the root toolbar layout). `FilterSearchAppliedFilters` (the removable-chip row + top-level "Reset filters") is the data-driven wrapper over `FilterChips`, which owns that row's design — see the FilterChips spec.
4
+
5
+ **Status:** ready · **Category:** layout · **Since:** v0.55.0
6
+
7
+ ## Import
8
+
9
+ ```tsx
10
+ import { FilterSearch, FilterSearchActions, FilterSearchFilters, FilterSearchAppliedFilters, useFilterSearchFilters } from '@acronis-platform/ui-react'
11
+ ```
12
+
13
+ ## Quick example
14
+
15
+ ```tsx
16
+ function StatusField() {
17
+ const { filters, setFilter } = useFilterSearchFilters();
18
+ return (
19
+ <InputSelect
20
+ value={(filters.status as string) ?? null}
21
+ onValueChange={(v) => setFilter('status', v)}
22
+ >
23
+ {/* … */}
24
+ </InputSelect>
25
+ );
26
+ }
27
+
28
+ function Toolbar() {
29
+ const [filters, setFilters] = React.useState<Record<string, unknown>>({});
30
+ return (
31
+ <div className="flex flex-col gap-3">
32
+ <FilterSearch>
33
+ <Search placeholder="Search table" aria-label="Search" className="w-56" />
34
+ <FilterSearchFilters value={filters} onValueChange={setFilters} label="Table filters">
35
+ <StatusField />
36
+ </FilterSearchFilters>
37
+ <FilterSearchActions>
38
+ <Button>Export</Button>
39
+ </FilterSearchActions>
40
+ </FilterSearch>
41
+ <FilterSearchAppliedFilters filters={filters} onValueChange={setFilters} />
42
+ </div>
43
+ );
44
+ }
45
+ ```
46
+
47
+ A composable toolbar for data tables that arranges a search field, optional
48
+ filter controls, an optional scope/tenant switcher, and trailing action buttons
49
+ in a single horizontal row.
50
+
51
+ `FilterSearchFilters` (the filter popover — trigger + form + Reset/Cancel/
52
+ Apply) and `FilterSearchAppliedFilters` (the removable-chip row + a top-level
53
+ "Reset filters" that clears immediately) are the real, functioning filter
54
+ building blocks — compose `FilterSearchFilters` in place of a plain filter
55
+ trigger, and render `FilterSearchAppliedFilters` as a sibling row below
56
+ `FilterSearch`. Ported from the standalone `MultiSearch` component (retired in
57
+ favor of living here, next to the toolbar it always appeared alongside).
58
+
59
+ > **Design-pending.** Unlike `FilterSearch`/`FilterSearchActions` (which have a
60
+ > real Figma node — see `figma:` in `index.yaml`), `FilterSearchFilters` and
61
+ > `FilterSearchAppliedFilters` have no Figma node yet. They theme entirely from
62
+ > shared semantic tokens plus their composed components' own tiers (Button,
63
+ > Chip, Popover) — no new component token tier. Reconcile against the real
64
+ > design later with `/figma-component FilterSearchFilters <url> --update`.
65
+
66
+ ## When to Use
67
+
68
+ - Above a data table to provide search, filtering, and bulk action controls.
69
+ - When the table needs a consistent toolbar layout across views.
70
+
71
+ ## When NOT to Use
72
+
73
+ - **Form layouts** — use a form component, not a toolbar.
74
+ - **Navigation bars** — use a dedicated nav component.
75
+
76
+ ## Related Components
77
+
78
+ - **DataTableToolbar** — a differently-designed toolbar that is part of the
79
+ `DataTable` compound component. Use `FilterSearch` when you need a standalone
80
+ toolbar above any data table; use `DataTableToolbar` when working within the
81
+ `DataTable` composition.
82
+
83
+ ## Filter fields via the context hook
84
+
85
+ Filter fields are **plain children** (no render prop). Each field wires its
86
+ own value/onChange to the popover draft through the `useFilterSearchFilters()`
87
+ context hook, which returns the current draft `filters` map and a per-key
88
+ `setFilter` setter. Apply commits the draft; Cancel / dismiss reverts it.
89
+
90
+ ```tsx
91
+ import {
92
+ FilterSearchFilters,
93
+ useFilterSearchFilters,
94
+ } from '@acronis-platform/ui-react';
95
+
96
+ function StatusField() {
97
+ const { filters, setFilter } = useFilterSearchFilters();
98
+ return (
99
+ <InputSelect
100
+ value={(filters.status as string) ?? null}
101
+ onValueChange={(v) => setFilter('status', v)}
102
+ >
103
+ {/* … */}
104
+ </InputSelect>
105
+ );
106
+ }
107
+ ```
108
+
109
+ Group fields with a `Separator` between them as a layout convention.
110
+
111
+ ## Parts
112
+
113
+ | Part | Element / component | Role |
114
+ | ---------------------------- | ------------------------------- | ---------------------------------------------------------- |
115
+ | `FilterSearch` | `<div>` | Root flex container (16px gap, center-aligned) |
116
+ | `FilterSearchActions` | `<div>` | Right-aligned area for action buttons |
117
+ | `FilterSearchFilters` | `Popover` + `Button` trigger | The filter popover: fields + Reset/Cancel/Apply footer |
118
+ | `FilterSearchAppliedFilters` | `<div>` with `Chip`s + `Button` | Removable applied-filter chips + a top-level Reset filters |
119
+
120
+ ## Behavior at a glance
121
+
122
+ - Opening `FilterSearchFilters`'s popover snapshots `value` into an editable draft.
123
+ - **Apply** commits the draft → `onValueChange` (+ `onApply`) and closes.
124
+ Disabled until the draft differs from the last-applied `value`.
125
+ - **Cancel**, outside-press, and **Escape** revert the draft and close.
126
+ - The footer's **Reset filters** clears the draft to empty (disabled when already empty).
127
+ - `FilterSearchAppliedFilters` renders nothing when `filters` is empty; its own
128
+ **Reset filters** clears all filters immediately (no popover involved).
129
+ - `FilterSearchFilters`'s popover is a `Popover`/`PopoverContent` under the
130
+ hood, so it inherits that component's constrained-container behavior: for
131
+ an isolated portal container (e.g. a shadow root), pass `portalContainer`
132
+ so the popup inherits that scope's styles and switches to `fixed`
133
+ positioning so it isn't clipped at the container's own edge. See
134
+ `Popover`'s own spec for details.
135
+
136
+ ## Quick Examples
137
+
138
+ ### React
139
+
140
+ ```tsx
141
+ import {
142
+ FilterSearch,
143
+ FilterSearchActions,
144
+ FilterSearchFilters,
145
+ FilterSearchAppliedFilters,
146
+ useFilterSearchFilters,
147
+ } from '@acronis-platform/ui-react';
148
+ import { Search, Button } from '@acronis-platform/ui-react';
149
+
150
+ function StatusField() {
151
+ const { filters, setFilter } = useFilterSearchFilters();
152
+ return (
153
+ <InputSelect
154
+ value={(filters.status as string) ?? null}
155
+ onValueChange={(v) => setFilter('status', v)}
156
+ >
157
+ {/* … */}
158
+ </InputSelect>
159
+ );
160
+ }
161
+
162
+ function TableToolbar() {
163
+ const [filters, setFilters] = React.useState<Record<string, unknown>>({});
164
+ return (
165
+ <div className="flex flex-col gap-3">
166
+ <FilterSearch>
167
+ <Search
168
+ placeholder="Search table"
169
+ aria-label="Search"
170
+ className="w-56"
171
+ />
172
+ <FilterSearchFilters
173
+ value={filters}
174
+ onValueChange={setFilters}
175
+ label="Table filters"
176
+ >
177
+ <StatusField />
178
+ </FilterSearchFilters>
179
+ <FilterSearchActions>
180
+ <Button>Export</Button>
181
+ </FilterSearchActions>
182
+ </FilterSearch>
183
+ <FilterSearchAppliedFilters
184
+ filters={filters}
185
+ onValueChange={setFilters}
186
+ />
187
+ </div>
188
+ );
189
+ }
190
+ ```
191
+
192
+ ### With tenant switcher
193
+
194
+ ```tsx
195
+ <FilterSearch>
196
+ <Select defaultValue="all">
197
+ <SelectTrigger className="w-56">
198
+ <SelectValue placeholder="All customers" />
199
+ </SelectTrigger>
200
+ <SelectContent>
201
+ <SelectItem value="all">All customers</SelectItem>
202
+ </SelectContent>
203
+ </Select>
204
+ <Search placeholder="Search table" aria-label="Search" className="w-56" />
205
+ <FilterSearchFilters value={filters} onValueChange={setFilters}>
206
+ <StatusField />
207
+ </FilterSearchFilters>
208
+ </FilterSearch>
209
+ ```
210
+
211
+ ## Spec Files
212
+
213
+ | File | Contents |
214
+ | ------------------ | --------------------------------------------------------------- |
215
+ | `index.yaml` | Identity, status, category, dependencies, Figma link |
216
+ | `anatomy.yaml` | Root, parts, layout, prop + internal states |
217
+ | `api.yaml` | Framework-agnostic contract + framework adapters |
218
+ | `tokens.yaml` | Directly-referenced `--ui-*` token references |
219
+ | `behavior.md` | Given/When/Then behavior scenarios |
220
+ | `accessibility.md` | ARIA guidance (delegated to child components; own popover a11y) |
221
+
222
+ ## Props
223
+
224
+ | Prop | Type | Default | Required | Description |
225
+ |------|------|---------|----------|-------------|
226
+ | `className` | `string` | `undefined` | no | Additional CSS classes. On `FilterSearch` and `FilterSearchAppliedFilters`, the root container; on `FilterSearchFilters`, the trigger button. |
227
+ | `value` | `Record<string, unknown>` | — | yes | `FilterSearchFilters` only. The applied filter values, keyed by an arbitrary consumer-chosen id. |
228
+ | `onValueChange` | `(next: Record<string, unknown>) => void` | — | yes | `FilterSearchFilters` only. Called with the committed filters when Apply is pressed. `FilterSearchAppliedFilters` also has an `onValueChange` — called with the remaining filters when a chip is removed or "Reset filters" clears all. |
229
+ | `onApply` | `(filters: Record<string, unknown>) => void` | `undefined` | no | `FilterSearchFilters` only. Called with the newly applied filters when Apply is pressed (fires alongside `onValueChange`). Synchronous — no loading/applying state in v1. |
230
+ | `label` | `ReactNode` | `'Filters'` | no | `FilterSearchFilters` only. Label for the filter trigger button. |
231
+ | `resetFiltersLabel` | `ReactNode` | `'Reset filters'` | no | `FilterSearchFilters` and `FilterSearchAppliedFilters`. Label for the "Reset filters" action (the popover footer action on `FilterSearchFilters`; the top-level clear-all action on `FilterSearchAppliedFilters`). Override to localize. |
232
+ | `cancelLabel` | `ReactNode` | `'Cancel'` | no | `FilterSearchFilters` only. Label for the popover footer's "Cancel" action. Override to localize. |
233
+ | `applyLabel` | `ReactNode` | `'Apply'` | no | `FilterSearchFilters` only. Label for the popover footer's "Apply" action. Override to localize. |
234
+ | `contentClassName` | `string` | `undefined` | no | `FilterSearchFilters` only. Additional CSS classes for the popover content. |
235
+ | `side` | `'top' | 'bottom' | 'left' | 'right' | 'inline-start' | 'inline-end'` | `'bottom'` | no | `FilterSearchFilters` only. Which side of the trigger to render the popover on. |
236
+ | `align` | `'start' | 'center' | 'end'` | `'start'` | no | `FilterSearchFilters` only. Alignment of the popover along the chosen side. |
237
+ | `sideOffset` | `number` | `4` | no | `FilterSearchFilters` only. Distance in px between the trigger and the popover. |
238
+ | `portalContainer` | `Element | ShadowRoot | null | RefObject<Element | ShadowRoot | null>` | `undefined` | no | `FilterSearchFilters` only. React/DOM escape hatch, not part of the framework-agnostic contract (see the `react` adapter below). Forwarded to the underlying `Popover`'s `PopoverContent`; defaults to the nearest `PortalContainerProvider`, or `document.body` if none is set. |
239
+ | `collisionBoundary` | `Element | Element[] | Rect | 'clipping-ancestors'` | `undefined` | no | `FilterSearchFilters` only. React/DOM escape hatch, not part of the framework-agnostic contract (see the `react` adapter below). Forwarded to `PopoverContent`; left at the platform default (clipping ancestors) unless overridden. |
240
+ | `positionMethod` | `'absolute' | 'fixed'` | `undefined` | no | `FilterSearchFilters` only. React/DOM escape hatch, not part of the framework-agnostic contract (see the `react` adapter below). Forwarded to `PopoverContent`; defaults to `'fixed'` whenever a custom `portalContainer` is resolved, so the popover isn't clipped at a constrained MFE/Shadow DOM container's own edge. |
241
+ | `filters` | `Record<string, unknown>` | — | yes | `FilterSearchAppliedFilters` only. The currently applied filter values, keyed by an arbitrary consumer-chosen id. Renders nothing when empty. |
242
+ | `getFilterChipLabel` | `(key: string, value: unknown) => ReactNode` | `defaults to "<key>: <value>"` | no | `FilterSearchAppliedFilters` only. Format an applied-filter chip's label. Defaults to `"<key>: <value>"` (array values joined with ", "). |
243
+ | `getRemoveFilterLabel` | `(key: string) => string` | `defaults to "Remove <key> filter"` | no | `FilterSearchAppliedFilters` only. Format a filter chip's remove-button accessible label. Override to localize. |
244
+
245
+ ## Content slots
246
+
247
+ | Slot | Description |
248
+ |------|-------------|
249
+ | `default` | The composed toolbar. Place Search, Select, FilterSearchFilters, and FilterSearchActions as direct children. The order of children determines the visual order. |
250
+ | `FilterSearchFilters children` | Filter field children rendered inside `FilterSearchFilters`'s popover form. Plain children — wire each field to the draft via `useFilterSearchFilters`. Group fields with a `Separator` as a layout convention if desired. |
251
+
252
+ ## Behavior
253
+
254
+ ### Structure
255
+
256
+ #### Renders a horizontal toolbar
257
+
258
+ **Given** a FilterSearch wrapping child components
259
+ **When** the component renders
260
+ **Then** the root is a `<div>` with a horizontal flex layout and 16px gap
261
+ **And** children are vertically centered
262
+
263
+ #### Renders with search only
264
+
265
+ **Given** a FilterSearch containing only a Search component
266
+ **When** it renders
267
+ **Then** only the search field is shown in the toolbar
268
+
269
+ #### Renders with tenant switcher and filters
270
+
271
+ **Given** a FilterSearch containing a Select, Search, and ButtonMenu
272
+ **When** it renders
273
+ **Then** all three appear in order: select, search, filters button
274
+
275
+ ---
276
+
277
+ ### Actions Area
278
+
279
+ #### Pushes action buttons to the trailing edge
280
+
281
+ **Given** a FilterSearch with a FilterSearchActions containing buttons
282
+ **When** it renders
283
+ **Then** the actions area takes remaining space (`flex-1`)
284
+ **And** its content is right-aligned
285
+
286
+ #### Renders without actions
287
+
288
+ **Given** a FilterSearch with no FilterSearchActions
289
+ **When** it renders
290
+ **Then** children are laid out from the leading edge with no trailing spacer
291
+
292
+ ---
293
+
294
+ ### Composition
295
+
296
+ #### Supports arbitrary children
297
+
298
+ **Given** a FilterSearch with custom children (not just Search/Select/ButtonMenu)
299
+ **When** it renders
300
+ **Then** all children appear in source order within the flex container
301
+
302
+ #### Merges custom className
303
+
304
+ **Given** a FilterSearch with a custom className
305
+ **When** it renders
306
+ **Then** the custom class is merged onto the root element
307
+
308
+ ---
309
+
310
+ ### FilterSearchFilters (the filter popover)
311
+
312
+ #### Opens the popover from the trigger
313
+
314
+ **Given** a FilterSearchFilters whose popover is closed
315
+ **When** the filter trigger is clicked
316
+ **Then** the popover opens showing the field children and the footer (Reset filters / Cancel / Apply)
317
+ **And** the trigger is visually highlighted while open
318
+
319
+ #### Applies the drafted filters on Apply
320
+
321
+ **Given** the popover is open and a field child has set a draft value (e.g. `status: "active"`)
322
+ **When** Apply is pressed
323
+ **Then** `onValueChange` fires with the committed filters (`{ status: "active" }`)
324
+ **And** `onApply` fires with the same applied filters
325
+ **And** the popover closes
326
+
327
+ #### Reverts the draft on Cancel without applying
328
+
329
+ **Given** the popover is open and a field child has changed the draft
330
+ **When** Cancel is pressed
331
+ **Then** `onValueChange` does **not** fire
332
+ **And** the popover closes
333
+ **And** reopening the popover shows a reverted (last-applied) draft
334
+
335
+ > Dismissing the popover by outside-press or Escape reverts the draft the same
336
+ > way Cancel does — an un-applied edit never leaks out.
337
+
338
+ #### Reset filters clears the draft (disabled when empty)
339
+
340
+ **Given** the popover is open with applied filters present
341
+ **When** the footer's "Reset filters" is pressed
342
+ **Then** the draft is cleared to empty
343
+
344
+ **Given** the popover is open with no filters set
345
+ **When** it renders
346
+ **Then** the footer's "Reset filters" is disabled
347
+
348
+ #### Apply is disabled until the draft actually changes
349
+
350
+ **Given** the popover is opened fresh (no edits made)
351
+ **When** it renders
352
+ **Then** "Apply" is disabled, since the draft equals the last-applied `value`
353
+
354
+ **Given** a field child changes the draft to a value different from `value`
355
+ **When** it renders
356
+ **Then** "Apply" becomes enabled
357
+
358
+ #### Fields scroll once they exceed the popover's max height
359
+
360
+ **Given** more filter field children than fit in the popover's height
361
+ **When** the popover is open
362
+ **Then** the fields region scrolls internally (`max-h-80 overflow-y-auto`)
363
+ **And** the footer stays pinned below it
364
+
365
+ #### Footer action labels are overridable
366
+
367
+ **Given** a FilterSearchFilters rendered with `resetFiltersLabel`, `cancelLabel`,
368
+ and/or `applyLabel`
369
+ **When** the popover is open
370
+ **Then** the footer renders the given labels instead of the English defaults
371
+ ("Reset filters" / "Cancel" / "Apply")
372
+
373
+ #### The popover renders inside a constrained portal container without clipping
374
+
375
+ **Given** a FilterSearchFilters rendered inside a `PortalContainerProvider`
376
+ whose `container` is a small or offset element (e.g. an MFE/Shadow DOM mount)
377
+ **When** the filter trigger is clicked
378
+ **Then** the popover content mounts inside that container
379
+ **And** it is not clipped at the container's own edge — it switches to `fixed`
380
+ positioning by default, which (for a plain overflow-clipping container) keeps
381
+ the platform's own collision boundary resolved against the real viewport
382
+
383
+ **Given** the same constrained-container setup
384
+ **When** a consumer passes an explicit `collisionBoundary`, `positionMethod`,
385
+ `side`, `align`, `sideOffset`, `portalContainer`, or `contentClassName`
386
+ **Then** that value overrides the computed default and is forwarded to the
387
+ underlying popover
388
+
389
+ ---
390
+
391
+ ### FilterSearchAppliedFilters (the applied-filter chip row)
392
+
393
+ #### Renders nothing when there are no applied filters
394
+
395
+ **Given** a FilterSearchAppliedFilters with an empty `filters` object
396
+ **When** it renders
397
+ **Then** nothing is rendered
398
+
399
+ #### Renders one removable chip per applied filter plus a Reset filters action
400
+
401
+ **Given** a FilterSearchAppliedFilters with applied filters (e.g. `status: "active"`, `type: "server"`)
402
+ **When** it renders
403
+ **Then** one removable chip is shown per applied filter (`"status: active"`, `"type: server"`)
404
+ **And** a "Reset filters" action is shown alongside the chips
405
+
406
+ > Chip labels default to `"<key>: <value>"`; a `getFilterChipLabel(key, value)`
407
+ > callback overrides the label.
408
+
409
+ #### Removes a single filter when its chip is removed
410
+
411
+ **Given** a FilterSearchAppliedFilters with applied filters
412
+ **When** a chip's remove control is clicked (e.g. "Remove status filter")
413
+ **Then** `onValueChange` fires with that key dropped
414
+
415
+ #### The remove-filter label is overridable
416
+
417
+ **Given** a FilterSearchAppliedFilters rendered with a `getRemoveFilterLabel`
418
+ **When** it renders
419
+ **Then** each chip's remove control uses the given label instead of the
420
+ English default `"Remove <key> filter"`
421
+
422
+ #### Clears all filters when Reset filters is pressed
423
+
424
+ **Given** a FilterSearchAppliedFilters with one or more applied filters
425
+ **When** its "Reset filters" action is pressed
426
+ **Then** `onValueChange` fires with an empty filters object — immediately, with no popover involved
427
+
428
+ #### The Reset filters label is overridable
429
+
430
+ **Given** a FilterSearchAppliedFilters rendered with a `resetFiltersLabel`
431
+ **When** it renders
432
+ **Then** the clear-all action shows the given label instead of the English
433
+ default "Reset filters"
434
+
435
+ ## Accessibility
436
+
437
+ `FilterSearch` itself is a pure layout wrapper — accessibility concerns are
438
+ delegated to its composed children (Search, ButtonMenu, Select, Button).
439
+ `FilterSearchFilters` and `FilterSearchAppliedFilters` own their own
440
+ interaction and are covered below.
441
+
442
+ ## ARIA Roles and Attributes
443
+
444
+ ### Root (`div`)
445
+
446
+ The root element has **no semantic role** — it is a presentational flex
447
+ container. If the consumer needs a landmark, they can add `role="toolbar"`
448
+ and an `aria-label` via props.
449
+
450
+ | Attribute | Value | Reason |
451
+ | ------------ | -------- | ----------------------------------------------- |
452
+ | No `role` | — | Pure layout; no inherent semantics |
453
+ | `aria-label` | (opt-in) | Consumer may set this if using `role="toolbar"` |
454
+
455
+ ---
456
+
457
+ ## Keyboard Navigation
458
+
459
+ Keyboard interaction is handled by the child components:
460
+
461
+ | Component | Key | Action |
462
+ | ---------------------------- | ----------- | --------------------------------------------- |
463
+ | Search | Tab | Focuses the search input |
464
+ | ButtonMenu | Enter | Opens/closes the filter menu |
465
+ | Select | Enter | Opens the select dropdown |
466
+ | Button | Enter | Activates the action |
467
+ | `FilterSearchFilters` | Enter/Space | Opens/closes the filter popover |
468
+ | `FilterSearchFilters` | Escape | Closes the popover and reverts the draft |
469
+ | `FilterSearchAppliedFilters` | Tab | Moves between chips' remove buttons and Reset |
470
+
471
+ The toolbar itself does not manage focus — children follow their own focus
472
+ patterns. If `role="toolbar"` is applied, arrow-key navigation between
473
+ children is the consumer's responsibility.
474
+
475
+ `FilterSearchFilters`'s popover uses Base UI's `Popover` primitive, which
476
+ provides standard dialog-like focus trapping and restores focus to the
477
+ trigger on close (Apply, Cancel, outside-press, or Escape).
478
+
479
+ ---
480
+
481
+ ## Screen Reader Requirements
482
+
483
+ 1. Each child component announces itself according to its own spec.
484
+ 2. The search field should have an `aria-label` (e.g. "Search table").
485
+ 3. Action buttons should have descriptive labels.
486
+ 4. `FilterSearchFilters`'s trigger announces its expanded state
487
+ (`aria-haspopup`/`aria-expanded`, from Base UI's `PopoverTrigger`).
488
+ 5. Each removable chip's remove control has an explicit `aria-label`
489
+ (`"Remove <key> filter"` by default, overridable via
490
+ `getRemoveFilterLabel(key)`), so a screen reader announces which filter a
491
+ given remove button drops.
492
+
493
+ ---
494
+
495
+ ## Color and Contrast
496
+
497
+ All contrast requirements are owned by the child components and their
498
+ respective token tiers, including `Button`, `Chip`, and `Popover`'s own specs
499
+ for `FilterSearchFilters`/`FilterSearchAppliedFilters`.
500
+
501
+ ---
502
+
503
+ ## Testing Checklist
504
+
505
+ - [ ] Root renders as a `<div>` with no unexpected ARIA roles
506
+ - [ ] All child components are reachable via Tab
507
+ - [ ] Search field has an accessible label
508
+ - [ ] ButtonMenu announces its label and expanded state
509
+ - [ ] Action buttons have accessible names
510
+ - [ ] `FilterSearchFilters`'s trigger announces expanded state and toggles via Enter/Space
511
+ - [ ] Escape closes the open popover and reverts the draft
512
+ - [ ] Each applied-filter chip's remove control has a descriptive `aria-label`
@@ -0,0 +1,154 @@
1
+ # Form
2
+
3
+ > A native form element with consolidated validation, built on Base UI's Form. It collects values by each Field's name, runs the Fields' validation on submit (or per validationMode), surfaces server errors keyed by field name, and calls onFormSubmit with the values when every field is valid. Ported from the legacy shadcn-uikit `form` (which wrapped react-hook-form); this version drops that dependency and composes the ui-react Field directly.
4
+
5
+ **Status:** draft · **Category:** forms · **Since:** v0.48.0
6
+
7
+ ## Import
8
+
9
+ ```tsx
10
+ import { Form } from '@acronis-platform/ui-react'
11
+ ```
12
+
13
+ ## Quick example
14
+
15
+ ```tsx
16
+ <Form onFormSubmit={(values) => save(values)} errors={serverErrors}>
17
+ <Field name="email">
18
+ <FieldLabel>Email</FieldLabel>
19
+ <FieldControl render={<InputBox type="email" required />} />
20
+ <FieldError />
21
+ </Field>
22
+ <Button type="submit">Save</Button>
23
+ </Form>
24
+ ```
25
+
26
+ A native `<form>` with consolidated validation, built on
27
+ [Base UI's Form](https://base-ui.com/react/components/form). It collects values by
28
+ each `Field`'s `name`, validates the fields on submit (or per `validationMode`),
29
+ surfaces server `errors` keyed by field name, and calls `onFormSubmit(values)`
30
+ when every field is valid.
31
+
32
+ > **Design-pending v1.** Ported from the legacy shadcn-uikit `form`, which wrapped
33
+ > [react-hook-form](https://react-hook-form.com). This version **drops that
34
+ > dependency** and composes the ui-react `Field` directly on Base UI's validation.
35
+ > If you need react-hook-form / TanStack Form, use them around plain `Field`s —
36
+ > Form is the lightweight, dependency-free default.
37
+
38
+ ## When to use
39
+
40
+ - Any short-to-medium form where Base UI's built-in validation is enough.
41
+ - You want native submit semantics with per-field errors and no extra form-state
42
+ library.
43
+
44
+ ## When not to use
45
+
46
+ - Complex form state (arrays, wizards, cross-field logic) better served by a
47
+ dedicated form library — compose that with the `Field` parts directly.
48
+
49
+ ## Parts
50
+
51
+ Form is a single component. Compose it with the `Field` family:
52
+
53
+ | Export | Purpose |
54
+ | ------ | ---------------------------------------------------------------- |
55
+ | `Form` | The `<form>` wrapper that coordinates validation and submission. |
56
+
57
+ ## Example
58
+
59
+ ```tsx
60
+ import {
61
+ Form,
62
+ Field,
63
+ FieldLabel,
64
+ FieldControl,
65
+ FieldError,
66
+ InputBox,
67
+ Button,
68
+ } from '@acronis-platform/ui-react';
69
+
70
+ <Form onFormSubmit={(values) => save(values)} errors={serverErrors}>
71
+ <Field name="email">
72
+ <FieldLabel>Email</FieldLabel>
73
+ <FieldControl render={<InputBox type="email" required />} />
74
+ <FieldError />
75
+ </Field>
76
+ <Button type="submit">Save</Button>
77
+ </Form>;
78
+ ```
79
+
80
+ ## Props
81
+
82
+ | Prop | Type | Default | Required | Description |
83
+ |------|------|---------|----------|-------------|
84
+ | `validationMode` | `'onSubmit' | 'onBlur' | 'onChange'` | `'onSubmit'` | no | When the fields validate. A Field's own validationMode takes precedence. |
85
+ | `errors` | `Record<string, string | string[]>` | — | no | Externally supplied errors (e.g. from a server) keyed by each Field's name. Shown on the matching field's FieldError. |
86
+ | `actionsRef` | `RefObject<{ validate: (fieldName?: string) => void }>` | — | no | Imperative handle to validate all fields (or one) on demand. |
87
+
88
+ ## Events
89
+
90
+ | Event | Payload | Description |
91
+ |-------|---------|-------------|
92
+ | `onFormSubmit` | `(values: Record<string, unknown>, details) => void` | Fired on submit once every field is valid; receives the collected field values keyed by name. preventDefault is called on the native event. |
93
+
94
+ ## Behavior
95
+
96
+ Form is built on Base UI's Form. It coordinates the ui-react `Field`s it contains
97
+ — no react-hook-form. Each Field carries a `name`; Form collects their values by
98
+ that name, validates them, and reports errors.
99
+
100
+ ```gherkin
101
+ Scenario: Submit collects values by field name
102
+ Given a Form with Fields named "email" and "password"
103
+ And the user fills them in
104
+ When the form is submitted
105
+ Then onFormSubmit is called once with { email, password }
106
+ And the native submit event is preventDefault-ed
107
+ ```
108
+
109
+ ```gherkin
110
+ Scenario: Validation gates submit
111
+ Given a Field with a required control left empty
112
+ When the form is submitted
113
+ Then onFormSubmit is NOT called
114
+ And the field is marked invalid (aria-invalid) and shows its FieldError
115
+ ```
116
+
117
+ ```gherkin
118
+ Scenario: Validation mode
119
+ Given validationMode="onChange"
120
+ Then each field validates on every change instead of only on submit
121
+ ```
122
+
123
+ ```gherkin
124
+ Scenario: Server errors
125
+ Given errors={{ email: "That email is taken." }} (e.g. after a failed submit)
126
+ Then the Field named "email" shows that message in its FieldError
127
+ And the field is marked invalid
128
+ ```
129
+
130
+ ```gherkin
131
+ Scenario: Imperative validation
132
+ Given an actionsRef on the Form
133
+ When actionsRef.current.validate() is called
134
+ Then all fields validate (or a single field when a name is passed)
135
+ ```
136
+
137
+ ## Accessibility
138
+
139
+ - Renders a native `<form>`, so browser submit semantics (Enter to submit, the
140
+ submit button, native required validation) work as expected.
141
+ - Accessibility of the individual controls is owned by `Field` — label
142
+ associations, `aria-describedby`, and `aria-invalid` are wired there. Form's job
143
+ is to coordinate validation and surface errors on the right field.
144
+ - On a failed submit, focus management and the per-field `FieldError`
145
+ (`role="alert"` semantics) announce what went wrong; don't rely on color alone —
146
+ the error text states the problem.
147
+ - Give the form an accessible name when there are multiple forms on a page
148
+ (`aria-label` / `aria-labelledby` referencing a heading).
149
+
150
+ ## Contrast
151
+
152
+ Form adds no color of its own; contrast is governed by the Fields and controls it
153
+ composes (see the Field spec). Error text there uses `--ui-text-on-status-danger`,
154
+ which meets contrast in light and dark.