themelia-ui 3.1.0 → 3.2.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 (178) hide show
  1. package/.agents/skills/themelia-ui/SKILL.md +38 -9
  2. package/.agents/skills/themelia-ui/manifest.json +23 -0
  3. package/.agents/skills/themelia-ui/references/api-compatibility.md +3 -2
  4. package/.agents/skills/themelia-ui/references/blocks.md +123 -0
  5. package/.agents/skills/themelia-ui/references/composition-ladder.md +8 -8
  6. package/.agents/skills/themelia-ui/references/composition.md +98 -1
  7. package/.agents/skills/themelia-ui/references/forms.md +23 -3
  8. package/.agents/skills/themelia-ui/references/framework-wiring.md +34 -6
  9. package/.agents/skills/themelia-ui/references/i18n.md +4 -2
  10. package/.agents/skills/themelia-ui/references/imports.md +1 -1
  11. package/.agents/skills/themelia-ui/references/installation.md +5 -4
  12. package/.agents/skills/themelia-ui/references/migration.md +22 -2
  13. package/.agents/skills/themelia-ui/references/provider-and-scoping.md +27 -9
  14. package/.agents/skills/themelia-ui/references/reference-workflows.md +43 -0
  15. package/.agents/skills/themelia-ui/references/structure-and-spacing.md +147 -0
  16. package/.agents/skills/themelia-ui/references/theming.md +6 -2
  17. package/.agents/skills/themelia-ui/references/troubleshooting.md +28 -5
  18. package/.agents/skills/themelia-ui/references/verification.md +58 -2
  19. package/CHANGELOG.md +25 -0
  20. package/README.md +3 -0
  21. package/dist/{alert-dialog-Bmo1YrTm.js → alert-dialog-Cg2HhuBe.js} +1 -1
  22. package/dist/base/alert-dialog.js +1 -1
  23. package/dist/base/cards.js +1 -1
  24. package/dist/base/choice-inputs.js +1 -1
  25. package/dist/base/combobox.js +1 -1
  26. package/dist/base/command.js +1 -1
  27. package/dist/base/date-pickers.js +1 -1
  28. package/dist/base/dialog.js +1 -1
  29. package/dist/base/display.js +1 -1
  30. package/dist/base/forms-numeric.js +1 -1
  31. package/dist/base/forms.js +1 -1
  32. package/dist/base/item.js +1 -1
  33. package/dist/base/navigation.js +1 -1
  34. package/dist/base/otp-input.js +1 -1
  35. package/dist/base/overlay.js +1 -1
  36. package/dist/base/popover-menu.js +1 -1
  37. package/dist/base/repeaters.js +1 -1
  38. package/dist/base/sheet.js +1 -1
  39. package/dist/base/sidebar.js +1 -1
  40. package/dist/base/skeleton.js +1 -1
  41. package/dist/base/structure.js +1 -1
  42. package/dist/base/timeline.js +1 -1
  43. package/dist/base/upload.js +1 -1
  44. package/dist/base/value-inputs.js +1 -1
  45. package/dist/blocks/admin/access.js +2 -2
  46. package/dist/blocks/admin/commerce.js +9 -9
  47. package/dist/blocks/analytics.js +3 -3
  48. package/dist/blocks/onboarding.js +1 -1
  49. package/dist/blocks/timelines.js +1 -1
  50. package/dist/{cards-DdiICdMM.js → cards-Ddhz43Bz.js} +2 -2
  51. package/dist/{choice-inputs-CCw-0gD6.js → choice-inputs-NwRxvmZN.js} +2 -2
  52. package/dist/combobox-519ubijc.js +292 -0
  53. package/dist/{command-CnB_IZSo.js → command-JjApYFds.js} +2 -2
  54. package/dist/{comments-C6Y_ddMt.js → comments-CCQ2QRaU.js} +4 -4
  55. package/dist/components/base/display/icon-badge.d.ts +2 -2
  56. package/dist/components/base/forms/form-field.d.ts +2 -2
  57. package/dist/components/base/structure/bleed.d.ts +1 -0
  58. package/dist/components/base/structure/grid.d.ts +8 -2
  59. package/dist/components/base/structure/split.d.ts +1 -0
  60. package/dist/components/features/combobox/fetching-combobox.d.ts +2 -1
  61. package/dist/components/features/combobox/resource-combobox.d.ts +3 -0
  62. package/dist/components/features/combobox/use-suggestions.d.ts +7 -0
  63. package/dist/css/structure.css +1 -1
  64. package/dist/{date-pickers-C2HgjpFU.js → date-pickers-BwCwKIVM.js} +1 -1
  65. package/dist/{dialog-0nRFg5Zs.js → dialog-CAnwRNrm.js} +1 -1
  66. package/dist/{display-BGN834xV.js → display-a7AoX_u7.js} +1 -1
  67. package/dist/features/actions.js +1 -1
  68. package/dist/features/activities.js +3 -3
  69. package/dist/features/ai-chat.js +2 -2
  70. package/dist/features/combobox.js +285 -273
  71. package/dist/features/comments.js +1 -1
  72. package/dist/features/data-view.js +5 -5
  73. package/dist/features/event-calendar.js +3 -3
  74. package/dist/features/filters.js +1 -1
  75. package/dist/features/global-search.js +4 -4
  76. package/dist/features/map.js +2 -2
  77. package/dist/features/media-library.js +8 -8
  78. package/dist/features/mentions.js +1 -1
  79. package/dist/features/overlays.js +1 -1
  80. package/dist/features/products.js +9 -9
  81. package/dist/features/resource-assignment.js +3 -3
  82. package/dist/features/resource.js +3 -3
  83. package/dist/features/schema-form.js +6 -6
  84. package/dist/features/sync.js +2 -2
  85. package/dist/features/table.js +1 -1
  86. package/dist/features/theme-tweaker.js +10 -10
  87. package/dist/{filters-geLuSlFL.js → filters-BhsTkWpZ.js} +9 -9
  88. package/dist/{forms-NxbJK4Mh.js → forms-C0WeNPFF.js} +1 -1
  89. package/dist/{forms-numeric-C6NMdj6G.js → forms-numeric-IwQ1ltNf.js} +1 -1
  90. package/dist/hooks/use-native-dialog.d.ts +1 -1
  91. package/dist/{item-CuUJyETl.js → item-M2pmpWXh.js} +1 -1
  92. package/dist/layout/app-shell.js +1 -1
  93. package/dist/layout/auth.js +1 -1
  94. package/dist/layout/header.js +3 -3
  95. package/dist/layout/navigation.js +1 -1
  96. package/dist/layout/page.js +2 -2
  97. package/dist/layout/settings.js +2 -2
  98. package/dist/layout/sidebar.js +10 -6
  99. package/dist/layout/workspace.js +3 -3
  100. package/dist/lib/ui-provider/ui-scope.d.ts +1 -1
  101. package/dist/{mentions-oeYNv_j1.js → mentions-BOxbigwp.js} +2 -2
  102. package/dist/{navigation-BTX5Ti0Z.js → navigation-Bgu7Vc0M.js} +2 -2
  103. package/dist/{navigation-WYKjDhZq.js → navigation-DiYACqpE.js} +2 -2
  104. package/dist/{overlay-C8RAgi2_.js → overlay-BuQOl8aP.js} +5 -5
  105. package/dist/{overlays-9BxwFTou.js → overlays-1sULyytH.js} +5 -5
  106. package/dist/{popover-menu-DcLeiw9r.js → popover-menu-QXgQeWeG.js} +1 -1
  107. package/dist/{repeaters-DfptvbGA.js → repeaters-CeH20dvr.js} +4 -4
  108. package/dist/{sheet-D-DVZH_b.js → sheet-CJ1-OAla.js} +1 -1
  109. package/dist/{sidebar-DXQOy_Cb.js → sidebar-D8uGqxio.js} +4 -4
  110. package/dist/{skeleton-CubvHel9.js → skeleton-BTKNKTdj.js} +1 -1
  111. package/dist/{structure-B9Y5NkP_.js → structure-CynEZ-OY.js} +1 -2
  112. package/dist/style.css +1 -1
  113. package/dist/styles/TOKENS.md +4 -3
  114. package/dist/{table-DT_20ity.js → table-B7dtIFUx.js} +4 -4
  115. package/dist/{timeline-BRZbSdhf.js → timeline-BR_jNgvz.js} +1 -1
  116. package/dist/{timelines-DczZKW5p.js → timelines-DXG1BfIN.js} +1 -1
  117. package/dist/{upload-C_6mlbfl.js → upload-CYuoJykZ.js} +1 -1
  118. package/dist/{value-inputs-CnO6RZeT.js → value-inputs-ng8aVJJZ.js} +4 -4
  119. package/docs/README.md +11 -1
  120. package/docs/build/browser-composition-fixture.ts +28 -0
  121. package/docs/build/recipes.md +398 -1
  122. package/docs/generated/component-index.json +1993 -862
  123. package/docs/generated/components/base--display.md +2 -2
  124. package/docs/generated/components/base--forms.md +72 -1
  125. package/docs/generated/components/base--structure.md +56 -5
  126. package/docs/generated/components/base--typography.md +38 -0
  127. package/docs/generated/components/blocks--admin--access.md +10 -0
  128. package/docs/generated/components/blocks--analytics.md +44 -0
  129. package/docs/generated/components/features--combobox.md +67 -0
  130. package/docs/generated/components/features--data-view.md +59 -3
  131. package/docs/generated/components/features--filters.md +10 -0
  132. package/docs/generated/components/features--media-library.md +68 -0
  133. package/docs/generated/components/features--overlays.md +54 -0
  134. package/docs/generated/components/features--rich-text-editor.md +38 -0
  135. package/docs/generated/components/layout--app-shell.md +31 -0
  136. package/docs/generated/components/layout--containers.md +40 -0
  137. package/docs/generated/components/layout--page.md +30 -1
  138. package/docs/generated/components/layout--sidebar.md +41 -0
  139. package/docs/generated/components/ui-provider.md +1 -1
  140. package/docs/generated/composition-ladder.md +8 -8
  141. package/docs/generated/consumer-contract.json +5736 -0
  142. package/docs/generated/imports.md +1 -1
  143. package/docs/generated/recipes.json +171 -1
  144. package/docs/generated/workflows/communications-overview/analytics/communications-reference.tsx +19 -0
  145. package/docs/generated/workflows/index.json +691 -0
  146. package/docs/generated/workflows/media-picking/media-library/media-picking-reference.tsx +63 -0
  147. package/docs/generated/workflows/media-picking/media-library/reference-data.ts +3 -0
  148. package/docs/generated/workflows/multi-section-form/form-workflow/multi-section-reference.tsx +46 -0
  149. package/docs/generated/workflows/nested-action-flow/action-overlays/nested-action-reference.tsx +39 -0
  150. package/docs/generated/workflows/record-detail/page-layout/record-detail-reference.tsx +24 -0
  151. package/docs/generated/workflows/resource-index/data-view/resource-index-reference.tsx +51 -0
  152. package/docs/generated/workflows/scoped-resource-selection/combobox/scoped-resource-reference.tsx +50 -0
  153. package/docs/generated/workflows/website-content-editor/media-library/reference-data.ts +3 -0
  154. package/docs/generated/workflows/website-content-editor/rich-text-editor/website-content-reference.tsx +33 -0
  155. package/docs/generated/workflows/workspace-navigation/app-shell/workspace-navigation-reference.tsx +26 -0
  156. package/docs/learn/api-compatibility.md +3 -2
  157. package/docs/learn/blocks.md +123 -0
  158. package/docs/learn/composition.md +98 -1
  159. package/docs/learn/forms.md +23 -3
  160. package/docs/learn/framework-wiring.md +34 -6
  161. package/docs/learn/i18n.md +4 -2
  162. package/docs/learn/installation.md +5 -4
  163. package/docs/learn/migration.md +22 -2
  164. package/docs/learn/provider-and-scoping.md +27 -9
  165. package/docs/learn/reference-workflows.md +43 -0
  166. package/docs/learn/structure-and-spacing.md +147 -0
  167. package/docs/learn/theming.md +6 -2
  168. package/docs/learn/troubleshooting.md +28 -5
  169. package/docs/learn/verification.md +58 -2
  170. package/package.json +4 -1
  171. package/scripts/consumer/codemod.mjs +3 -24
  172. package/scripts/consumer/contract-reader.mjs +42 -0
  173. package/scripts/consumer/css-scan.mjs +51 -0
  174. package/scripts/consumer/diagnose.mjs +136 -0
  175. package/scripts/consumer/find-component.mjs +28 -13
  176. package/scripts/consumer/install-skill.mjs +93 -11
  177. package/src/styles/TOKENS.md +4 -3
  178. package/dist/combobox-Byu8N-Oe.js +0 -274
@@ -207,8 +207,8 @@ MetadataListStrings
207
207
 
208
208
  Kind: callable.
209
209
 
210
- A glyph in a tinted medallion. Its size comes from `--_icon-badge-size`, so a caller
211
- needing a smaller mark re-points the variable instead of redrawing the badge.
210
+ A glyph in a tinted medallion. `tone` coordinates fill and glyph; `shape` chooses
211
+ rounded or circular geometry. Internal sizing variables are not consumer overrides.
212
212
 
213
213
  ```text
214
214
  ({ icon, children, tone, shape, solid, bordered, className, ...props }: IconBadgeProps) => import("react").JSX.Element
@@ -14,7 +14,7 @@ Assembling a form's visible structure — label, control, support text, sections
14
14
  ## Import
15
15
 
16
16
  ```tsx fragment — import-only example
17
- import { DirtyStateBanner } from "themelia-ui/base/forms"
17
+ import { FormField, FormSection } from "themelia-ui/base/forms"
18
18
  ```
19
19
 
20
20
  ## Composition
@@ -135,6 +135,16 @@ Several fields sharing one label and one supporting line — a date range, a nam
135
135
  two. A real `<fieldset>` and `<legend>`, so the legend is announced when focus enters the
136
136
  group.
137
137
 
138
+ **Use when:** Related controls sharing an announced fieldset legend.
139
+
140
+ **Avoid when:** A whole independently titled subject surface.
141
+
142
+ **Compose:** Use one legend for the group and distinct FormField labels for its controls.
143
+
144
+ **Capabilities:** semantic fieldset legend; related editable controls.
145
+
146
+ **Alternatives:** FormSection.
147
+
138
148
  ```text
139
149
  ({ legend, description, className, children, ...props }: FieldGroupProps) => React.JSX.Element
140
150
  ```
@@ -230,6 +240,16 @@ Kind: callable.
230
240
 
231
241
  A titled group of fields, on a card's surface.
232
242
 
243
+ **Use when:** Editable form sections, including groups inside an existing Card.
244
+
245
+ **Avoid when:** Read-only facts or a semantic fieldset with one shared legend.
246
+
247
+ **Compose:** Use flat inside a surface; flat retains Card region insets. Let its title name the section and FormField name each field.
248
+
249
+ **Capabilities:** editable form sections; existing surface grouping.
250
+
251
+ **Alternatives:** ContentBlock, FieldGroup.
252
+
233
253
  ```text
234
254
  ({ title, description, actions, children, footer, surface, className, }: FormSectionProps) => import("react").JSX.Element
235
255
  ```
@@ -391,6 +411,57 @@ export default function FieldGroupExample() {
391
411
  }
392
412
  ```
393
413
 
414
+ ### Multi-section form reference
415
+
416
+ ```tsx fragment — excerpt from the live preview; surrounding values are supplied by the application
417
+ import { useRef, useState } from "react"
418
+ import { Card } from "themelia-ui/base/cards"
419
+ import { ContentBlock } from "themelia-ui/base/display"
420
+ import { FieldGroup, FormField, FormSection, FormActionsBar, SubmitStateButton } from "themelia-ui/base/forms"
421
+ import { Stack } from "themelia-ui/base/structure"
422
+ import { Input } from "themelia-ui/base/text-inputs"
423
+ import { Text } from "themelia-ui/base/typography"
424
+ import { useFormFieldBinding, useStateFormControl, type FormControl } from "themelia-ui/forms"
425
+
426
+ type Draft = { firstName: string; lastName: string; company: string }
427
+ function BoundField({ control, name, label, error }: { control: FormControl<Draft>; name: keyof Draft; label: string; error?: string }) {
428
+ const field = useFormFieldBinding<string, Draft>({ control, name })
429
+ return <FormField label={label} error={error} hint={name === "company" ? "The name shown on invoices." : undefined}><Input required value={field.value ?? ""} onChange={event => field.onValueChange(event.target.value)} onBlur={field.onBlur} /></FormField>
430
+ }
431
+
432
+ export default function MultiSectionReference() {
433
+ const control = useStateFormControl<Draft>({ firstName: "Alex", lastName: "Example", company: "Example team" })
434
+ const [pending, setPending] = useState(false)
435
+ const [error, setError] = useState<string>()
436
+ const [rootError, setRootError] = useState<string>()
437
+ const [saved, setSaved] = useState(0)
438
+ const running = useRef(false)
439
+ return <Stack>
440
+ <Card title="Account details" description="One outer surface, editable sections with retained region insets.">
441
+ <form onSubmit={async event => {
442
+ event.preventDefault(); if (running.current) return
443
+ running.current = true; setPending(true); setError(undefined); setRootError(undefined)
444
+ try {
445
+ await new Promise(resolve => setTimeout(resolve, 250))
446
+ if (control.values.company === "offline") throw new Error("Request failed. Your values remain here.")
447
+ if (control.values.company.length < 3) { setError("Company must have at least three characters."); return }
448
+ setSaved(count => count + 1)
449
+ } catch (failure) { setRootError((failure as Error).message) }
450
+ finally { running.current = false; setPending(false) }
451
+ }}>
452
+ <Stack gap="none">
453
+ {rootError && <Text role="alert" type="error">{rootError}</Text>}
454
+ <FormSection surface="flat" title="Contact"><FieldGroup legend="Legal name" disabled={pending}><BoundField control={control} name="firstName" label="First name" /><BoundField control={control} name="lastName" label="Last name" /></FieldGroup></FormSection>
455
+ <FormSection surface="flat" title="Billing"><fieldset disabled={pending} style={{ border: 0, padding: 0, margin: 0 }}><BoundField control={control} name="company" label="Company" error={error} /></fieldset></FormSection>
456
+ <FormActionsBar leading={<Text role="status">Successful submissions {saved}</Text>}><SubmitStateButton type="submit" state={pending ? "submitting" : "idle"} strings={{ idle: "Save account" }} /></FormActionsBar>
457
+ </Stack>
458
+ </form>
459
+ </Card>
460
+ <Card title="Preferences" description="A sibling subject owns its own surface."><ContentBlock surface="plain" title="Delivery instructions"><Text>Use ContentBlock for supporting prose; FieldGroup supplies fieldset semantics for related controls.</Text></ContentBlock></Card>
461
+ </Stack>
462
+ }
463
+ ```
464
+
394
465
  ### FormSection
395
466
 
396
467
  ```tsx fragment — excerpt from the live preview; surrounding values are supplied by the application
@@ -30,6 +30,16 @@ outside the package and arrives through the public props, callbacks, slots, or a
30
30
 
31
31
  Kind: const.
32
32
 
33
+ **Use when:** Equal tracks should be added as minimum-width columns fit the available panel.
34
+
35
+ **Avoid when:** The design requires an explicit column count or a fixed summary rail.
36
+
37
+ **Compose:** Set minColumnWidth and shared gaps. Column count follows available width; responsive gap and alignment use viewport steps.
38
+
39
+ **Capabilities:** auto fit columns; minimum column width; responsive panel grid.
40
+
41
+ **Alternatives:** Grid, Split.
42
+
33
43
  ```text
34
44
  React.ForwardRefExoticComponent<Omit<AdaptiveGridProps, "ref"> & React.RefAttributes<HTMLDivElement>>
35
45
  ```
@@ -58,6 +68,16 @@ Extends: `React.ComponentProps<"div">`.
58
68
 
59
69
  Kind: const.
60
70
 
71
+ **Use when:** Intentional media or a rule must escape a known parent inset.
72
+
73
+ **Avoid when:** Removing duplicate wrapper padding or hiding accidental overflow.
74
+
75
+ **Compose:** Match amount to the actual inset. Shipped padding and gap pairs match; check custom themes and focus visibility.
76
+
77
+ **Capabilities:** edge to edge inset escape; inline block negative margins.
78
+
79
+ **Alternatives:** ContentBlock.
80
+
61
81
  ```text
62
82
  React.ForwardRefExoticComponent<Omit<BleedProps, "ref"> & React.RefAttributes<HTMLDivElement>>
63
83
  ```
@@ -81,7 +101,7 @@ Extends: `React.ComponentProps<"div">`.
81
101
  | member | required | type | description / documented default |
82
102
  | --- | :-: | --- | --- |
83
103
  | `amount` | no | `ResponsiveValue<StructureGap>` | How far to escape: one of the two gaps, which equal the two paddings. Match it to the<br>padding being cancelled — a surface padded by `--padding-sm` bleeds `sm` — so the two<br>move together under a density change. @default "none" |
84
- | `axis` | no | `BleedAxis` | Which way to escape; `both` covers every direction. Sideways is the common case: an<br>image bleeds across and keeps its vertical rhythm. |
104
+ | `axis` | no | `BleedAxis` | Which way to escape; `both` covers every direction. Sideways is the common case: an<br>image bleeds across and keeps its vertical rhythm. @default "inline" |
85
105
 
86
106
  ### `CssLength`
87
107
 
@@ -97,6 +117,16 @@ A CSS length: a number with a unit or a percentage, or a math or `var()` express
97
117
 
98
118
  Kind: const.
99
119
 
120
+ **Use when:** Passive siblings need an explicit responsive column count.
121
+
122
+ **Avoid when:** The column count should follow how many minimum-width tracks fit, or a feature already arranges its children.
123
+
124
+ **Compose:** Grid owns gaps, not child insets. It uses nearest-container thresholds when containment exists, otherwise viewport thresholds.
125
+
126
+ **Capabilities:** responsive columns; grid row column gaps; nearest container width.
127
+
128
+ **Alternatives:** AdaptiveGrid, Stack.
129
+
100
130
  ```text
101
131
  React.ForwardRefExoticComponent<Omit<GridProps, "ref"> & React.RefAttributes<HTMLDivElement>>
102
132
  ```
@@ -105,6 +135,16 @@ React.ForwardRefExoticComponent<Omit<GridProps, "ref"> & React.RefAttributes<HTM
105
135
 
106
136
  Kind: const.
107
137
 
138
+ **Use when:** A direct Grid child needs a numeric span or a full row.
139
+
140
+ **Avoid when:** The parent is not a Grid, or a span would exceed its current column count.
141
+
142
+ **Compose:** Coordinate numeric spans with the parent columns. GridCell spans use viewport steps even when Grid uses container steps.
143
+
144
+ **Capabilities:** responsive cell span; full row span.
145
+
146
+ **Alternatives:** Stack.
147
+
108
148
  ```text
109
149
  React.ForwardRefExoticComponent<Omit<GridCellProps, "ref"> & React.RefAttributes<HTMLDivElement>>
110
150
  ```
@@ -117,7 +157,7 @@ Extends: `React.ComponentProps<"div">`.
117
157
 
118
158
  | member | required | type | description / documented default |
119
159
  | --- | :-: | --- | --- |
120
- | `span` | no | `ResponsiveValue<GridSpan>` | Columns occupied. `full` spans whatever the grid currently has. |
160
+ | `span` | no | `ResponsiveValue<GridSpan>` | Columns occupied. `full` spans whatever the grid currently has, including in a<br>responsive object. Span breakpoints follow the viewport, not Grid's container. |
121
161
 
122
162
  ### `GridColumns`
123
163
 
@@ -135,7 +175,7 @@ Extends: `React.ComponentProps<"div">`.
135
175
 
136
176
  | member | required | type | description / documented default |
137
177
  | --- | :-: | --- | --- |
138
- | `columns` | no | `ResponsiveValue<GridColumns>` | Defaults to one column, then two from the md breakpoint. |
178
+ | `columns` | no | `ResponsiveValue<GridColumns>` | Defaults to one column, then two from md. Uses the nearest inline-size query<br>container when present (md = 32rem), otherwise the viewport (md = 48rem). |
139
179
  | `gap` | no | `ResponsiveValue<StructureGap>` | |
140
180
  | `rowGap` | no | `ResponsiveValue<StructureGap>` | |
141
181
  | `columnGap` | no | `ResponsiveValue<StructureGap>` | |
@@ -164,6 +204,16 @@ T | Partial<Record<Breakpoint, T>>
164
204
 
165
205
  Kind: const.
166
206
 
207
+ **Use when:** A fluid main region sits beside a constrained rail that collapses at a chosen viewport step.
208
+
209
+ **Avoid when:** Needing equal tracks or the standard semantic main/aside layout.
210
+
211
+ **Compose:** Supply main first and side second. side changes visual placement, not DOM order. Width is a rail maximum, with collapseBelow controlling stacking.
212
+
213
+ **Capabilities:** fluid main fixed rail; responsive two columns; collapse breakpoint.
214
+
215
+ **Alternatives:** TwoColumnLayout, Grid.
216
+
167
217
  ```text
168
218
  React.ForwardRefExoticComponent<Omit<SplitProps, "ref"> & React.RefAttributes<HTMLDivElement>>
169
219
  ```
@@ -179,7 +229,7 @@ Extends: `React.ComponentProps<"div">`.
179
229
  | `side` | no | `SplitSide` | Which visual column is the fixed one. The DOM order never changes with it: the first<br>child is the main content and the second the side on both settings, so a reader<br>tabbing through reaches the content first either way. |
180
230
  | `sideWidth` | no | `ResponsiveValue<StructureWidth \| CssLength>` | The fixed column's width — a step or any CSS length. A maximum rather than an exact<br>size, so a narrow viewport shrinks the rail instead of squeezing the content beside it<br>to nothing. @default "18rem" |
181
231
  | `gap` | no | `ResponsiveValue<StructureGap>` | Space between the two columns. @default "default" |
182
- | `collapseBelow` | no | `"sm" \| "md" \| "lg" \| "xl" \| "never"` | Below this breakpoint the two columns become one. `never` keeps them side by side at<br>every width. |
232
+ | `collapseBelow` | no | `"sm" \| "md" \| "lg" \| "xl" \| "never"` | Below this breakpoint the two columns become one. `never` keeps them side by side at<br>every width. @default "md" |
183
233
 
184
234
  ### `SplitSide`
185
235
 
@@ -199,7 +249,7 @@ Kind: const.
199
249
 
200
250
  **Avoid when:** An existing component already owns the layout.
201
251
 
202
- **Compose:** Use shared gap and inset props; do not pin local pixel spacing.
252
+ **Compose:** Use gap, direction and alignment props; Stack adds no inset. Let child surfaces own padding.
203
253
 
204
254
  **Capabilities:** vertical horizontal gap spacing; responsive arrangement.
205
255
 
@@ -751,6 +801,7 @@ export default function GridExample() {
751
801
  <GridCell><Box>three</Box></GridCell>
752
802
  <GridCell span={{ base: 1, md: 2 }}><Box>span 2 from md</Box></GridCell>
753
803
  <GridCell><Box>four</Box></GridCell>
804
+ <GridCell span={{ base: 1, md: "full", xl: 2 }}><Box>responsive full row</Box></GridCell>
754
805
  </Grid>
755
806
  )
756
807
  }
@@ -427,6 +427,44 @@ export default function RolesBlueprint() {
427
427
  }
428
428
  ```
429
429
 
430
+ ### Website content reference
431
+
432
+ ```tsx fragment — excerpt from the live preview; surrounding values are supplied by the application
433
+ import { useState } from "react"
434
+ import { Button } from "themelia-ui/base/buttons"
435
+ import { Card } from "themelia-ui/base/cards"
436
+ import { PillRadioGroup } from "themelia-ui/base/choice-inputs"
437
+ import { FormField } from "themelia-ui/base/forms"
438
+ import { Stack } from "themelia-ui/base/structure"
439
+ import { Input } from "themelia-ui/base/text-inputs"
440
+ import { RichText, Text } from "themelia-ui/base/typography"
441
+ import { MediaLibraryDialog, type MediaLibraryItem } from "themelia-ui/features/media-library"
442
+ import { RichTextEditor } from "themelia-ui/features/rich-text-editor"
443
+ import { SAMPLE_MEDIA } from "../media-library/reference-data"
444
+
445
+ export default function WebsiteContentReference() {
446
+ const [locale, setLocale] = useState("en")
447
+ const [drafts, setDrafts] = useState<Record<string, string>>({ en: "<p>Welcome to our workspace.</p>", ar: "<p>مرحباً بكم</p>" })
448
+ const [mediaOpen, setMediaOpen] = useState(false)
449
+ const [image, setImage] = useState<MediaLibraryItem>()
450
+ const [alt, setAlt] = useState("")
451
+ const [failed, setFailed] = useState(true)
452
+ const [pending, setPending] = useState(false)
453
+ const [message, setMessage] = useState("Drafts are local until saved")
454
+ return <Card title="Website content" description="Each locale owns a retained draft. Preview sanitizes HTML; the application also validates and sanitizes persisted content.">
455
+ <Stack>
456
+ <PillRadioGroup aria-label="Content locale" name="reference-content-locale" value={locale} onValueChange={value => value && setLocale(value)} options={[{ value: "en", label: "English" }, { value: "ar", label: "العربية" }]} />
457
+ <div dir={locale === "ar" ? "rtl" : "ltr"}><RichTextEditor value={drafts[locale] ?? ""} onValueChange={html => setDrafts(current => ({ ...current, [locale]: html }))} /></div>
458
+ <MediaLibraryDialog open={mediaOpen} trigger={<Button tone="neutral" appearance="outline">Choose hero media</Button>} onOpenChange={setMediaOpen} items={SAMPLE_MEDIA} selectionMode="single" confirmOnSelect allowUpload={false} onConfirm={items => { const item = items[0]; setImage(item); setAlt(item?.alt ?? ""); setMediaOpen(false) }} />
459
+ {image && <FormField label="Hero image alternative text" hint={image.name}><Input value={alt} onChange={event => setAlt(event.target.value)} /></FormField>}
460
+ <Stack direction="horizontal" wrap gap="sm"><Button disabled={pending} onClick={async () => { if (pending) return; setPending(true); await new Promise(resolve => setTimeout(resolve, 250)); setMessage(failed ? "Save failed. All locale drafts and image text remain here." : "Mock website save completed"); setPending(false) }}>Save website content</Button><Button tone="neutral" appearance="outline" onClick={() => setFailed(value => !value)}>Toggle save failure</Button></Stack>
461
+ <Text role="status">{message}</Text>
462
+ <RichText html={drafts[locale] ?? ""} />
463
+ </Stack>
464
+ </Card>
465
+ }
466
+ ```
467
+
430
468
  ### The editor and what comes out
431
469
 
432
470
  ```tsx fragment — excerpt from the live preview; surrounding values are supplied by the application
@@ -118,6 +118,16 @@ Kind: interface.
118
118
 
119
119
  Kind: callable.
120
120
 
121
+ **Use when:** A read-only role permission summary grouped by product area.
122
+
123
+ **Avoid when:** Editing permission values or enforcing authorization.
124
+
125
+ **Compose:** Supply roleName and grouped permission records. The app computes authorization and owns the edit callback; the block supplies visible and accessible state presentation.
126
+
127
+ **Capabilities:** role permissions; permission summary; granted denied access facts.
128
+
129
+ **Alternatives:** Table, FormSection.
130
+
121
131
  ```text
122
132
  ({ roleName, description, memberCount, groups, onEdit, strings, className, ...props }: RolePermissionsProps) => import("react").JSX.Element
123
133
  ```
@@ -161,6 +161,16 @@ Kind: callable.
161
161
 
162
162
  Kind: callable.
163
163
 
164
+ **Use when:** One quantitative measure needs value hierarchy and optional comparison, trend or progress.
165
+
166
+ **Avoid when:** An ordinary fact or a group that MetricGrid already arranges.
167
+
168
+ **Compose:** Read MetricProps for state and surface variants; they are not shared by every analytics export. The analytics module requires recharts.
169
+
170
+ **Capabilities:** single quantitative measure; metric comparison trend.
171
+
172
+ **Alternatives:** MetricGrid, MetadataList.
173
+
164
174
  ```text
165
175
  ({ data, variant, tone, showSparkline, showChange, showIcon, loading, error, strings, progress, className, ...props }: MetricProps) => import("react").JSX.Element
166
176
  ```
@@ -318,6 +328,16 @@ Four pairs off the categorical ramp. Named for the look, not for a meaning.
318
328
 
319
329
  Kind: callable.
320
330
 
331
+ **Use when:** Several quantitative measures need responsive tiles with comparison or trend.
332
+
333
+ **Avoid when:** Ordinary label/value facts, identifiers or prose summaries.
334
+
335
+ **Compose:** Pass MetricData and the period context. Use null for an unavailable quantity, zero for measured zero. The module requires recharts and already owns tile arrangement.
336
+
337
+ **Capabilities:** quantitative metrics; dashboard figures; responsive metric tiles.
338
+
339
+ **Alternatives:** Metric, MetadataList.
340
+
321
341
  ```text
322
342
  ({ metrics, variant, tone, columns, showSparklines, showChanges, showIcons, loading, className, ...props }: MetricGridProps) => import("react").JSX.Element
323
343
  ```
@@ -569,6 +589,30 @@ Kind: interface.
569
589
 
570
590
  Preview route: Analytics — `/analytics`
571
591
 
592
+ ### Communications overview reference
593
+
594
+ ```tsx fragment — excerpt from the live preview; surrounding values are supplied by the application
595
+ import { useState } from "react"
596
+ import { Button } from "themelia-ui/base/buttons"
597
+ import { Card } from "themelia-ui/base/cards"
598
+ import { MetadataList } from "themelia-ui/base/display"
599
+ import { Stack } from "themelia-ui/base/structure"
600
+ import { Text } from "themelia-ui/base/typography"
601
+ import { MetricGrid } from "themelia-ui/blocks/analytics"
602
+
603
+ export default function CommunicationsReference() {
604
+ const [retried, setRetried] = useState(false)
605
+ return <Stack>
606
+ <MetricGrid metrics={[{ id: "delivered", label: "Messages delivered during the selected reporting period", value: 128, valueType: "number" }, { id: "retry", label: "Messages awaiting retry", value: retried ? 0 : 3, valueType: "number" }]} variant="card" />
607
+ <Card title="Latest communication" headerAction={<Button disabled={retried} onClick={() => setRetried(true)}>Retry delivery</Button>}>
608
+ <MetadataList items={[{ id: "subject", label: "Subject", value: "Workspace invitation" }, { id: "status", label: "Delivery state", value: retried ? "Delivered" : "Retry available" }, { id: "recipient", label: "Recipient", value: "member@example.test" }, { id: "time", label: "Last attempt", value: { kind: "datetime", value: "2026-09-01T09:30:00Z" } }, { id: "opened", label: "Opened", value: "Unknown; tracking unavailable" }]} />
609
+ <Text>The application owns delivery policy and permission checks. Metrics describe quantities; this subject and its delivery facts stay in a record surface.</Text>
610
+ </Card>
611
+ <Text role="status">{retried ? "Mock retry completed" : "Mock delivery is waiting"}</Text>
612
+ </Stack>
613
+ }
614
+ ```
615
+
572
616
  ### Metric bar
573
617
 
574
618
  ```tsx fragment — excerpt from the live preview; surrounding values are supplied by the application
@@ -228,6 +228,16 @@ Browsable from the first keystroke, and preloaded on mount so the list is there
228
228
  failed fetch replaces the list content, as `AsyncCombobox`'s `showListContent` does when
229
229
  off, but keeps the selection and the query, for retry.
230
230
 
231
+ **Use when:** Resource selection with self-owned debounce, abort and scoped requests.
232
+
233
+ **Avoid when:** An existing application query/cache already owns lookup results.
234
+
235
+ **Compose:** Change scopeKey when workspace/tenant/parent changes; preserve query/open state and explicitly clear an invalid controlled selection.
236
+
237
+ **Capabilities:** resource selection; scoped resource lookup; pending empty error retry.
238
+
239
+ **Alternatives:** AsyncCombobox, SuggestionsCombobox.
240
+
231
241
  ```text
232
242
  ({ fetcher, onAfterFetch, onError, getKey, getLabel, limit, debounceMs, preload, minSearchLength, defaultValue, errorMessage, ...props }: ResourceComboboxProps<T>) => import("react").JSX.Element
233
243
  ```
@@ -280,6 +290,7 @@ Extends: `Omit<AsyncComboboxProps<T>, "items" | "selectedValue" | "onSelectedVal
280
290
  | member | required | type | description / documented default |
281
291
  | --- | :-: | --- | --- |
282
292
  | `fetcher` | yes | `ResourceComboboxFetcher<T>` | Fetches the options for a query. Debounce, abort, and the race guard are owned for you,<br>and the query arrives trimmed. Honour `signal`, so an abort does not surface as a failure. |
293
+ | `scopeKey` | no | `string \| number` | Resource scope identity. A change aborts stale lookups and refetches the current<br>eligible query. The application owns clearing a selection invalid in the new scope. |
283
294
  | `value` | no | `T \| null` | Controlled. Omit entirely to let the component own the selection. |
284
295
  | `defaultValue` | no | `T \| null` | The starting selection when uncontrolled. Ignored once `value` is passed. |
285
296
  | `onValueChange` | no | `(value: T \| null) => void` | |
@@ -438,6 +449,7 @@ Kind: interface.
438
449
  | member | required | type | description / documented default |
439
450
  | --- | :-: | --- | --- |
440
451
  | `fetchData` | yes | `(query: string, context?: SuggestionsFetchContext) => MaybePromise<T[]>` | Fetches the suggestions for a query, as an array or a promise of one. Pass<br>`context.signal` to the request: it aborts when a newer query starts. |
452
+ | `scopeKey` | no | `string \| number` | Identity of the resource scope (workspace, tenant or parent record). Changing it<br>aborts previous work, hides its results/errors and immediately refetches an eligible<br>query. Compared with Object.is; callback identity alone never refetches. Selection,<br>query and open state survive: the application clears selections invalid in the new scope. |
441
453
  | `minQueryLength` | no | `number` | Characters required before a request is issued, not counting surrounding whitespace.<br>Defaults to 1. |
442
454
  | `debounceMs` | no | `number` | The quiet period that collapses a burst of typing into one request. Defaults to 300. |
443
455
  | `requestDelay` | no | `number` | A further wait before the fetcher runs, after the loading state is showing (unlike<br>`debounceMs`, which delays deciding to fetch). For rate-limited endpoints. @default 0 |
@@ -475,6 +487,61 @@ Kind: interface.
475
487
 
476
488
  Preview route: Combobox — `/combobox`
477
489
 
490
+ ### Scoped resource selection
491
+
492
+ ```tsx fragment — excerpt from the live preview; surrounding values are supplied by the application
493
+ import { useState } from "react"
494
+ import { Button } from "themelia-ui/base/buttons"
495
+ import { FormField } from "themelia-ui/base/forms"
496
+ import { Overlay, OverlayTrigger, OverlayContent, OverlayHeader, OverlayTitle, OverlayDescription, OverlayBody } from "themelia-ui/base/overlay"
497
+ import { Stack } from "themelia-ui/base/structure"
498
+ import { Text } from "themelia-ui/base/typography"
499
+ import { ResourceCombobox } from "themelia-ui/features/combobox"
500
+
501
+ type Resource = { id: string; label: string }
502
+ type Mode = "results" | "pending" | "empty" | "error"
503
+
504
+ export default function ScopedResourceReference() {
505
+ const [scope, setScope] = useState("A")
506
+ const [mode, setMode] = useState<Mode>("results")
507
+ const [query, setQuery] = useState("")
508
+ const [value, setValue] = useState<Resource | null>({ id: "A-1", label: "A project" })
509
+ const [selections, setSelections] = useState(0)
510
+ const [submissions, setSubmissions] = useState(0)
511
+ const [completions, setCompletions] = useState(0)
512
+ return <Overlay>
513
+ <OverlayTrigger render={<Button />}>Choose scoped resource</OverlayTrigger>
514
+ <OverlayContent>
515
+ <OverlayHeader><OverlayTitle>Assign resource</OverlayTitle><OverlayDescription>Lookup validity belongs to a workspace. The application decides whether a selected resource remains valid.</OverlayDescription></OverlayHeader>
516
+ <OverlayBody>
517
+ <form onSubmit={event => { event.preventDefault(); setSubmissions(count => count + 1) }}>
518
+ <Stack>
519
+ <Stack direction="horizontal" gap="sm" wrap>
520
+ {["A", "B"].map(key => <Button key={key} tone="neutral" appearance="outline" onClick={() => { setScope(key); setValue(null) }}>Workspace {key}</Button>)}
521
+ </Stack>
522
+ <Stack direction="horizontal" gap="sm" wrap>
523
+ {(["results", "pending", "empty", "error"] as const).map(key => <Button key={key} tone="neutral" appearance="ghost" onClick={() => setMode(key)}>{key}</Button>)}
524
+ </Stack>
525
+ <FormField label="Resource" hint={`Searching workspace ${scope}`}>
526
+ <ResourceCombobox<Resource> scopeKey={`${scope}:${mode}`} value={value} onValueChange={next => { setValue(next); setSelections(count => count + 1) }} searchValue={query} onSearchValueChange={setQuery} debounceMs={100} getKey={item => item.id} getLabel={item => item.label}
527
+ fetcher={async ({ query: search, signal }) => {
528
+ // The mock deliberately ignores abort; the picker must suppress its late answer.
529
+ void signal
530
+ await new Promise(resolve => setTimeout(resolve, mode === "pending" ? 2400 : scope === "A" ? 600 : 80))
531
+ if (mode === "error") throw new Error("Lookup unavailable")
532
+ return mode === "empty" || (search && !"project".includes(search.toLowerCase())) ? [] : [{ id: `${scope}-1`, label: `${scope} project` }]
533
+ }} onAfterFetch={() => setCompletions(count => count + 1)} />
534
+ </FormField>
535
+ <Button type="submit">Assign</Button>
536
+ <Text role="status">Selections {selections}; submissions {submissions}; completions {completions}</Text>
537
+ </Stack>
538
+ </form>
539
+ </OverlayBody>
540
+ </OverlayContent>
541
+ </Overlay>
542
+ }
543
+ ```
544
+
478
545
  ### A picker that fetches
479
546
 
480
547
  ```tsx fragment — excerpt from the live preview; surrounding values are supplied by the application
@@ -2,12 +2,12 @@
2
2
 
3
3
  # `features/data-view`
4
4
 
5
- One dataset is presented through switchable views with saved tabs, a toolbar and pagination.
5
+ A resource collection needs search, filters, saved views, table controls and paging.
6
6
 
7
7
  ## Selection
8
8
 
9
- - **Use when:** One dataset is presented through switchable views with saved tabs, a toolbar and pagination.
10
- - **Avoid when:** There is one table and no view switching, or you need the table body alone.
9
+ - **Use when:** A resource collection needs search, filters, saved views, table controls and paging.
10
+ - **Avoid when:** Only tabular behavior is needed inside an existing application-owned browser shell.
11
11
  - **Consider instead:** `features/table`, `base/table`
12
12
  - **Stability:** stable.
13
13
 
@@ -365,6 +365,62 @@ Kind: interface.
365
365
 
366
366
  Preview route: Data view & data table — `/data-view`
367
367
 
368
+ ### Server resource index reference
369
+
370
+ ```tsx fragment — excerpt from the live preview; surrounding values are supplied by the application
371
+ import { useEffect, useRef, useState } from "react"
372
+ import { Button } from "themelia-ui/base/buttons"
373
+ import { Stack } from "themelia-ui/base/structure"
374
+ import { Heading, Text } from "themelia-ui/base/typography"
375
+ import { DataView, type DataViewProps } from "themelia-ui/features/data-view"
376
+ import { FilterType, type ActiveFilter, type FilterConfig } from "themelia-ui/features/filters"
377
+
378
+ type RecordRow = { id: string; name: string; status: string; searchTerms: string }
379
+ const RECORDS: RecordRow[] = Array.from({ length: 6 }, (_, index) => ({ id: `r${index + 1}`, name: `Resource ${index + 1}`, status: index % 2 ? "Closed" : "Open", searchTerms: "hidden reference term" }))
380
+ const COLUMNS: DataViewProps<RecordRow>["columns"] = [{ accessorKey: "name", header: "Resource" }, { accessorKey: "status", header: "Status" }]
381
+ const FILTERS: FilterConfig[] = [{ key: "search", label: "Search hidden metadata and resource names", type: FilterType.SEARCH }, { key: "status", label: "Publication and review status with a long translated label", type: FilterType.SELECT, options: [{ value: "Open", label: "Open" }, { value: "Closed", label: "Closed" }] }]
382
+
383
+ export default function ResourceIndexReference() {
384
+ const [filters, setFilters] = useState<ActiveFilter[]>([])
385
+ const [page, setPage] = useState(0)
386
+ const [rows, setRows] = useState<RecordRow[]>([])
387
+ const [hasNext, setHasNext] = useState(false)
388
+ const [pending, setPending] = useState(true)
389
+ const [error, setError] = useState<string>()
390
+ const [refresh, setRefresh] = useState(0)
391
+ const [selection, setSelection] = useState<Record<string, true>>({})
392
+ const [answeredQuery, setAnsweredQuery] = useState("")
393
+ const [commands, setCommands] = useState("")
394
+ const failNext = useRef(false)
395
+ useEffect(() => {
396
+ let active = true
397
+ async function load() {
398
+ setPending(true); setError(undefined)
399
+ await new Promise(resolve => setTimeout(resolve, 250))
400
+ if (!active) return
401
+ if (failNext.current) { failNext.current = false; setError("Refresh failed. Previous rows remain available."); setPending(false); return }
402
+ const search = filters.find(filter => filter.key === "search")?.value[0]?.toLowerCase() ?? ""
403
+ const status = filters.find(filter => filter.key === "status")?.value[0]
404
+ const matches = RECORDS.filter(row => `${row.name} ${row.searchTerms}`.toLowerCase().includes(search) && (!status || row.status === status))
405
+ setAnsweredQuery(search); setRows(matches.slice(page * 2, page * 2 + 2)); setHasNext(matches.length > page * 2 + 2); setPending(false)
406
+ }
407
+ void load()
408
+ return () => { active = false }
409
+ }, [filters, page, refresh])
410
+ return <div style={{ width: "100%", minWidth: 0, maxWidth: "48rem" }}>
411
+ <Stack>
412
+ <Heading level={3}>Resource index</Heading>
413
+ <Text type="secondary">Server matching includes fields absent from the visible columns. Totals are unknown; selection applies to the current page.</Text>
414
+ {error && <Text role="alert" type="error">{error}</Text>}
415
+ <DataView data={rows} columns={COLUMNS} filtering={{ filters: FILTERS, activeFilters: filters, onFilterChange: next => { setFilters(next); setPage(0); setSelection({}) }, filterRows: ({ data }) => [...data], isFiltering: pending }}
416
+ table={{ enableColumnVisibility: true, enableRowSelection: true, getRowId: row => row.id, rowSelection: selection, onRowSelectionChange: setSelection, bulkActions: ({ selectedRows }) => <Button onClick={() => setCommands(`Selected-page command: ${selectedRows.map(row => row.id).join(", ")}`)}>Archive selected page rows</Button> }}
417
+ slots={{ topbarEnd: <Stack direction="horizontal" gap="sm" wrap><Button tone="neutral" appearance="outline" disabled={pending} onClick={() => setRefresh(count => count + 1)}>Refresh</Button><Button tone="neutral" appearance="outline" onClick={() => { failNext.current = true; setRefresh(count => count + 1) }}>Fail next refresh</Button><Button tone="neutral" appearance="outline" onClick={() => setCommands(`Export current page: ${rows.map(row => row.id).join(", ")}`)}>Export page</Button></Stack>, footer: <Stack direction="horizontal" wrap align="center"><Text>Total unknown; page {page + 1}</Text><Button disabled={pending || page === 0} onClick={() => { setPage(value => value - 1); setSelection({}) }}>Previous page</Button><Button disabled={pending || !hasNext} onClick={() => { setPage(value => value + 1); setSelection({}) }}>Next page</Button></Stack> }} />
418
+ <Text role="status">{pending ? "Loading page" : `${rows.length} rows supplied by server; matched ${answeredQuery || "all"}`}{commands && `; ${commands}`}</Text>
419
+ </Stack>
420
+ </div>
421
+ }
422
+ ```
423
+
368
424
  ### An index
369
425
 
370
426
  ```tsx fragment — excerpt from the live preview; surrounding values are supplied by the application
@@ -292,6 +292,16 @@ Kind: callable.
292
292
  The default bar: saved tabs, search boxes, active pills, always-shown pills, the add button,
293
293
  then Clear. For a custom bar, place the exported parts using `useFilterGroups()`.
294
294
 
295
+ **Use when:** Responsive filter controls shared between a desktop region and mobile sheet.
296
+
297
+ **Avoid when:** A complete resource browser already provides its filter layout, or the controls are page commands.
298
+
299
+ **Compose:** Share controlled filter state across presentations. Place create and export in action slots outside filter controls.
300
+
301
+ **Capabilities:** responsive filters; mobile sheet desktop filter region.
302
+
303
+ **Alternatives:** —.
304
+
295
305
  ```text
296
306
  ({ className, dynamicFilterOptions, loadingFilters, variant, showClearFilters, tabs, mobilePresentation, }: FilterLayoutProps) => import("react").JSX.Element
297
307
  ```