alinea 2.0.0-preview.13 → 2.0.0-preview.14

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 (323) hide show
  1. package/dist/.init/content/pages/welcome.json +1 -1
  2. package/dist/.mcp/later/dev.json +1 -1
  3. package/dist/.mcp/online/dev.json +1 -1
  4. package/dist/adapter/next/cms.js +6 -2
  5. package/dist/adapter/next/context.js +1 -4
  6. package/dist/adapter/next/handler.js +12 -1
  7. package/dist/backend/CommitEntries.d.ts +17 -0
  8. package/dist/backend/CommitEntries.js +39 -0
  9. package/dist/backend/Handler.d.ts +9 -0
  10. package/dist/backend/Handler.js +80 -20
  11. package/dist/backend/api/BasicAuth.js +5 -1
  12. package/dist/backend/api/CreateBackend.js +2 -2
  13. package/dist/backend/api/DatabaseApi.js +18 -5
  14. package/dist/backend/api/GithubApi.d.ts +13 -0
  15. package/dist/backend/api/GithubApi.js +170 -30
  16. package/dist/backend/api/database.js +2 -2
  17. package/dist/backend/util/Syncable.d.ts +6 -0
  18. package/dist/backend/util/Syncable.js +3 -1
  19. package/dist/bundled.d.ts +911 -151
  20. package/dist/chunks/{chunk-5H4D5JQL.js → chunk-2PP7WJUJ.js} +1 -11
  21. package/dist/chunks/{chunk-QHHMKJUO.js → chunk-7DDPSWRY.js} +465 -2
  22. package/dist/chunks/{chunk-HOETF6HN.js → chunk-PMGIIYDD.js} +1 -0
  23. package/dist/chunks/{chunk-3QFSGTGF.js → chunk-SCV3TEXM.js} +1 -1
  24. package/dist/chunks/{chunk-UPY2RUZF.js → chunk-TWBREZWC.js} +30 -0
  25. package/dist/cli/Generate.js +20 -7
  26. package/dist/cli/Mcp.js +1 -1
  27. package/dist/cli/Serve.js +1 -1
  28. package/dist/cli/bin.js +1 -1
  29. package/dist/cli/build/BuildEmitter.d.ts +5 -1
  30. package/dist/cli/build/BuildEmitter.js +8 -3
  31. package/dist/cli/generate/CompileConfig.js +10 -6
  32. package/dist/cli/generate/DevDB.d.ts +4 -1
  33. package/dist/cli/generate/DevDB.js +15 -7
  34. package/dist/cli/generate/GenerateDashboard.js +1 -0
  35. package/dist/cli/generate/MediaLocations.d.ts +18 -0
  36. package/dist/cli/generate/MediaLocations.js +69 -0
  37. package/dist/cli/serve/CreateLocalServer.js +1 -0
  38. package/dist/cli/serve/mcp/DevMcp.js +1 -1
  39. package/dist/cloud/CloudRemote.js +21 -7
  40. package/dist/cms.d.ts +4 -2
  41. package/dist/components/Alert.js +2 -2
  42. package/dist/components/AppShell.js +2 -2
  43. package/dist/components/AsyncList.d.ts +52 -0
  44. package/dist/components/AsyncList.js +69 -0
  45. package/dist/components/Breadcrumbs.js +6 -6
  46. package/dist/components/Button.js +4 -4
  47. package/dist/components/Calendar.js +18 -18
  48. package/dist/components/Checkbox.js +10 -10
  49. package/dist/components/CheckboxGroup.js +2 -2
  50. package/dist/components/Collapsible.js +5 -5
  51. package/dist/components/ColorSwatch.js +2 -2
  52. package/dist/components/ColorSwatchPicker.js +4 -4
  53. package/dist/components/ComboBox.d.ts +27 -9
  54. package/dist/components/ComboBox.js +40 -9
  55. package/dist/components/Command.js +13 -13
  56. package/dist/components/ContentCard.js +28 -28
  57. package/dist/components/ContentGrid.js +11 -11
  58. package/dist/components/DataList.js +1 -1
  59. package/dist/components/DateField.d.ts +4 -0
  60. package/dist/components/DateField.js +2 -2
  61. package/dist/components/DatePicker.js +2 -2
  62. package/dist/components/DateRangePicker.js +2 -2
  63. package/dist/components/Dialog.d.ts +14 -3
  64. package/dist/components/Dialog.js +38 -11
  65. package/dist/components/DropZone.js +6 -6
  66. package/dist/components/DropdownMenu.js +8 -8
  67. package/dist/components/Empty.js +4 -4
  68. package/dist/components/Field.d.ts +1 -1
  69. package/dist/components/Field.js +18 -12
  70. package/dist/components/Form.d.ts +23 -0
  71. package/dist/components/Form.js +64 -0
  72. package/dist/components/Link.d.ts +8 -1
  73. package/dist/components/List.js +27 -27
  74. package/dist/components/MediaPreview.js +5 -5
  75. package/dist/components/MultipleSelect.d.ts +13 -8
  76. package/dist/components/MultipleSelect.js +25 -25
  77. package/dist/components/NavRail.js +7 -7
  78. package/dist/components/NumberField.d.ts +5 -1
  79. package/dist/components/NumberField.js +6 -4
  80. package/dist/components/Page.js +6 -6
  81. package/dist/components/Popover.js +2 -2
  82. package/dist/components/PreviewFrame.js +8 -8
  83. package/dist/components/RadioGroup.js +10 -10
  84. package/dist/components/Resizable.js +1 -1
  85. package/dist/components/SearchField.js +8 -8
  86. package/dist/components/Select.d.ts +13 -10
  87. package/dist/components/Select.js +18 -32
  88. package/dist/components/Sheet.js +6 -6
  89. package/dist/components/Sidebar.js +5 -5
  90. package/dist/components/SortableList.js +12 -12
  91. package/dist/components/Spinner.js +4 -4
  92. package/dist/components/Stack.d.ts +20 -0
  93. package/dist/components/Stack.js +45 -0
  94. package/dist/components/Surface.js +1 -1
  95. package/dist/components/Switch.js +5 -5
  96. package/dist/components/Table.d.ts +11 -1
  97. package/dist/components/Table.js +81 -53
  98. package/dist/components/Tabs.js +6 -6
  99. package/dist/components/TagGroup.js +10 -10
  100. package/dist/components/TextField.d.ts +7 -1
  101. package/dist/components/TextField.js +20 -11
  102. package/dist/components/TimeField.d.ts +6 -1
  103. package/dist/components/TimeField.js +51 -26
  104. package/dist/components/Timestamp.js +1 -1
  105. package/dist/components/ToggleGroup.js +2 -2
  106. package/dist/components/Toolbar.js +3 -3
  107. package/dist/components/Tooltip.js +5 -4
  108. package/dist/components/Tree.js +27 -27
  109. package/dist/components/internal/DateControl.js +11 -11
  110. package/dist/components/internal/FormScope.d.ts +2 -0
  111. package/dist/components/internal/FormScope.js +8 -0
  112. package/dist/components/internal/ListBoxGroup.d.ts +12 -0
  113. package/dist/components/internal/ListBoxGroup.js +46 -0
  114. package/dist/components/internal/ListBoxOption.d.ts +2 -2
  115. package/dist/components/internal/ListBoxOption.js +5 -5
  116. package/dist/components/internal/Locale.js +2 -2
  117. package/dist/components/internal/PopoverSurface.js +5 -4
  118. package/dist/components/internal/SelectionCheckbox.js +5 -5
  119. package/dist/components/internal/Slot.js +1 -1
  120. package/dist/components/internal/ToggleButton.js +4 -4
  121. package/dist/components/internal/Trigger.js +2 -2
  122. package/dist/components/internal/useDragDrop.js +2 -2
  123. package/dist/components/types.d.ts +4 -0
  124. package/dist/components.d.ts +3 -0
  125. package/dist/components.js +3 -0
  126. package/dist/core/Client.js +12 -1
  127. package/dist/core/Config.d.ts +6 -0
  128. package/dist/core/Config.js +12 -0
  129. package/dist/core/DashboardEntry.d.ts +19 -0
  130. package/dist/core/DashboardEntry.js +0 -0
  131. package/dist/core/Document.d.ts +2 -2
  132. package/dist/core/Document.js +10 -3
  133. package/dist/core/EntryFields.d.ts +8 -2
  134. package/dist/core/Field.d.ts +5 -0
  135. package/dist/core/Filter.d.ts +19 -2
  136. package/dist/core/Filter.js +5 -0
  137. package/dist/core/Graph.d.ts +10 -2
  138. package/dist/core/Overview.d.ts +74 -5
  139. package/dist/core/Overview.js +1 -0
  140. package/dist/core/Root.js +1 -1
  141. package/dist/core/Scope.d.ts +5 -0
  142. package/dist/core/Scope.js +22 -5
  143. package/dist/core/Type.d.ts +25 -6
  144. package/dist/core/Type.js +25 -7
  145. package/dist/core/db/LocalStore.d.ts +7 -0
  146. package/dist/core/field/CreateField.d.ts +1 -2
  147. package/dist/core/source/Source.d.ts +2 -0
  148. package/dist/core/source/Source.js +11 -0
  149. package/dist/core/util/Async.d.ts +2 -0
  150. package/dist/core/util/Async.js +12 -1
  151. package/dist/core/util/Checks.js +1 -1
  152. package/dist/core/util/Slugs.js +4 -3
  153. package/dist/dashboard/App.css +13 -11
  154. package/dist/dashboard/App.d.ts +1 -0
  155. package/dist/dashboard/App.js +22 -6
  156. package/dist/dashboard/Href.d.ts +37 -0
  157. package/dist/dashboard/Href.js +46 -0
  158. package/dist/dashboard/app/ActivityStatus.js +34 -12
  159. package/dist/dashboard/app/AuthView.js +2 -2
  160. package/dist/dashboard/app/BlockSettings.d.ts +9 -0
  161. package/dist/dashboard/app/BlockSettings.js +16 -0
  162. package/dist/dashboard/app/BlockSheet.js +3 -3
  163. package/dist/dashboard/app/BlockSheetStoryFrame.js +2 -2
  164. package/dist/dashboard/app/CompactField.js +15 -15
  165. package/dist/dashboard/app/CroppedImage.js +2 -2
  166. package/dist/dashboard/app/DashboardLayout.d.ts +1 -1
  167. package/dist/dashboard/app/DashboardLayout.js +12 -0
  168. package/dist/dashboard/app/EntryActions.d.ts +11 -0
  169. package/dist/dashboard/app/EntryActions.js +30 -0
  170. package/dist/dashboard/app/EntryFields.d.ts +15 -2
  171. package/dist/dashboard/app/EntryFields.js +26 -5
  172. package/dist/dashboard/app/EntryHeader.js +17 -7
  173. package/dist/dashboard/app/EntryReferences.js +2 -2
  174. package/dist/dashboard/app/EntrySidebar.js +25 -23
  175. package/dist/dashboard/app/EntrySidebarPreview.js +2 -2
  176. package/dist/dashboard/app/EntryValidationModal.js +3 -3
  177. package/dist/dashboard/app/Explorer.js +84 -45
  178. package/dist/dashboard/app/ExplorerBatchActions.js +4 -4
  179. package/dist/dashboard/app/ExplorerControls.js +5 -5
  180. package/dist/dashboard/app/ExplorerList.js +2 -2
  181. package/dist/dashboard/app/ExplorerModal.js +3 -3
  182. package/dist/dashboard/app/ExplorerOverviewHeader.d.ts +6 -0
  183. package/dist/dashboard/app/ExplorerOverviewHeader.js +28 -0
  184. package/dist/dashboard/app/ExplorerTable.js +7 -7
  185. package/dist/dashboard/app/ExplorerTabs.d.ts +12 -0
  186. package/dist/dashboard/app/ExplorerTabs.js +42 -0
  187. package/dist/dashboard/app/ImageEditor.js +31 -31
  188. package/dist/dashboard/app/LinkPicker.js +3 -3
  189. package/dist/dashboard/app/LocaleMenu.js +9 -9
  190. package/dist/dashboard/app/MediaPreviewCell.js +5 -5
  191. package/dist/dashboard/app/OverviewCell.js +12 -10
  192. package/dist/dashboard/app/RemoveDialog.js +5 -0
  193. package/dist/dashboard/app/SearchBar.js +4 -4
  194. package/dist/dashboard/app/SettingsTab.d.ts +6 -0
  195. package/dist/dashboard/app/SettingsTab.js +27 -0
  196. package/dist/dashboard/app/SidebarTree.js +19 -19
  197. package/dist/dashboard/app/WorkspaceMenu.js +6 -6
  198. package/dist/dashboard/app/WorkspaceRoots.js +102 -98
  199. package/dist/dashboard/app/editor/FileEditor.js +18 -18
  200. package/dist/dashboard/app/pages/EntryPage.js +42 -39
  201. package/dist/dashboard/app/pages/RootPage.js +3 -3
  202. package/dist/dashboard/app/pages/SplashPage.js +94 -79
  203. package/dist/dashboard/app/pages/UsersPage.js +9 -9
  204. package/dist/dashboard/app/ui/DashboardModal.js +14 -14
  205. package/dist/dashboard/atoms/activity.d.ts +1 -0
  206. package/dist/dashboard/atoms/activity.js +8 -5
  207. package/dist/dashboard/atoms/config.d.ts +2 -0
  208. package/dist/dashboard/atoms/dashboard.d.ts +11 -0
  209. package/dist/dashboard/atoms/dashboard.js +8 -0
  210. package/dist/dashboard/atoms/editor.d.ts +7 -1
  211. package/dist/dashboard/atoms/editor.js +14 -2
  212. package/dist/dashboard/atoms/explorer.d.ts +15 -0
  213. package/dist/dashboard/atoms/explorer.js +43 -14
  214. package/dist/dashboard/atoms/graph.d.ts +5 -0
  215. package/dist/dashboard/atoms/graph.js +26 -1
  216. package/dist/dashboard/atoms/nav.d.ts +16 -10
  217. package/dist/dashboard/atoms/nav.js +96 -36
  218. package/dist/dashboard/atoms/overview.d.ts +26 -4
  219. package/dist/dashboard/atoms/overview.js +41 -12
  220. package/dist/dashboard/atoms/root.js +7 -1
  221. package/dist/dashboard/boot/ActivityEvent.d.ts +1 -1
  222. package/dist/dashboard/boot/Boot.js +6 -0
  223. package/dist/dashboard/boot/DashboardWorker.d.ts +6 -0
  224. package/dist/dashboard/boot/DashboardWorker.js +89 -8
  225. package/dist/dashboard/boot/LoadWorker.js +3 -0
  226. package/dist/dashboard/boot/SyncEvent.d.ts +7 -0
  227. package/dist/dashboard/boot/SyncEvent.js +14 -0
  228. package/dist/dashboard/boot/WorkerDB.d.ts +1 -0
  229. package/dist/dashboard/boot/WorkerDB.js +3 -0
  230. package/dist/dashboard/cms/EntryTable.d.ts +4 -3
  231. package/dist/dashboard/cms/EntryTable.js +2 -2
  232. package/dist/dashboard/cms/ViewProps.d.ts +3 -2
  233. package/dist/dashboard/cms/hooks.d.ts +34 -18
  234. package/dist/dashboard/cms/hooks.js +43 -18
  235. package/dist/dashboard/contexts.d.ts +3 -2
  236. package/dist/dashboard/contexts.js +3 -2
  237. package/dist/dashboard/hook/UseEntryEditor.d.ts +1 -1
  238. package/dist/dashboard/hook/UseEntryEditor.js +7 -3
  239. package/dist/dashboard/icons.d.ts +1 -0
  240. package/dist/dashboard/icons.js +15 -0
  241. package/dist/database/BrowserEntryStore.js +35 -13
  242. package/dist/database/DatabaseTables.js +1 -1
  243. package/dist/database/EntryDatabase.d.ts +10 -1
  244. package/dist/database/EntryDatabase.js +31 -10
  245. package/dist/database/EntryStore.js +1 -11
  246. package/dist/database/entry/EntryTable.d.ts +5 -3
  247. package/dist/database/entry/EntryTable.js +25 -8
  248. package/dist/database/query/Condition.d.ts +4 -1
  249. package/dist/database/query/Condition.js +16 -5
  250. package/dist/database/query/EntryQuery.js +59 -7
  251. package/dist/database/query/Relation.d.ts +13 -1
  252. package/dist/database/query/Relation.js +19 -1
  253. package/dist/database/query/Search.d.ts +7 -1
  254. package/dist/database/query/Search.js +54 -9
  255. package/dist/database/sync/EntrySyncer.d.ts +2 -0
  256. package/dist/database/sync/EntrySyncer.js +4 -2
  257. package/dist/database/sync/Ingest.d.ts +2 -1
  258. package/dist/database/sync/Ingest.js +20 -6
  259. package/dist/field/code/CodeField.view.js +12 -12
  260. package/dist/field/link/EntryLink.d.ts +4 -5
  261. package/dist/field/link/FileLink.d.ts +2 -2
  262. package/dist/field/link/ImageLink.d.ts +2 -5
  263. package/dist/field/link/ImageLink.js +0 -1
  264. package/dist/field/link/LinkField.view.js +18 -15
  265. package/dist/field/list/ListField.view.js +50 -35
  266. package/dist/field/metadata/MetadataField.view.js +14 -14
  267. package/dist/field/richtext/RichTextBlock.js +39 -35
  268. package/dist/field/richtext/RichTextExtensions.js +23 -23
  269. package/dist/field/richtext/RichTextField.view.js +6 -6
  270. package/dist/field/richtext/RichTextInsertMenu.js +5 -5
  271. package/dist/field/richtext/RichTextToolbar.js +26 -1
  272. package/dist/field/richtext/Toolbar.d.ts +9 -1
  273. package/dist/field/richtext/Toolbar.js +12 -2
  274. package/dist/field/select/SelectField.view.js +1 -0
  275. package/dist/index.css +174 -23
  276. package/dist/index.d.ts +1 -0
  277. package/dist/index.js +3 -1
  278. package/dist/next.d.ts +2 -0
  279. package/dist/next.edge.d.ts +2 -0
  280. package/dist/picker/entry/EntryPicker.d.ts +3 -1
  281. package/dist/picker/entry/EntryPicker.js +15 -1
  282. package/dist/preview/react.js +3 -0
  283. package/dist/query.d.ts +26 -14
  284. package/dist/query.js +4 -0
  285. package/dist/types.d.ts +8 -5
  286. package/dist/ui/Stack.d.ts +4 -2
  287. package/docs/ai-setup.md +3 -3
  288. package/docs/cli.md +4 -2
  289. package/docs/components/combo-box.md +105 -16
  290. package/docs/components/date-field.md +1 -1
  291. package/docs/components/dialog.md +51 -0
  292. package/docs/components/form.md +67 -0
  293. package/docs/components/link.md +10 -0
  294. package/docs/components/multiple-select.md +9 -17
  295. package/docs/components/number-field.md +5 -1
  296. package/docs/components/select.md +9 -20
  297. package/docs/components/stack.md +42 -0
  298. package/docs/components/table.md +83 -0
  299. package/docs/components/text-field.md +9 -1
  300. package/docs/components/time-field.md +5 -1
  301. package/docs/components.md +39 -1
  302. package/docs/configuration.md +3 -1
  303. package/docs/custom-fields.md +135 -7
  304. package/docs/deploy/self-host.md +2 -0
  305. package/docs/deploy.md +7 -4
  306. package/docs/fields/image.md +0 -1
  307. package/docs/fields/list.md +19 -0
  308. package/docs/fields/object.md +5 -3
  309. package/docs/fields/rich-text.md +31 -0
  310. package/docs/index.md +8 -6
  311. package/docs/instant-publishing.md +23 -2
  312. package/docs/overviews.md +128 -4
  313. package/docs/query/filtering.md +2 -0
  314. package/docs/query/related.md +58 -9
  315. package/docs/query.md +52 -1
  316. package/docs/quickstart.md +5 -5
  317. package/docs/schema/document.md +2 -0
  318. package/docs/schema/type.md +3 -1
  319. package/docs/upgrading.md +100 -19
  320. package/llms-full.txt +1022 -125
  321. package/package.json +1 -1
  322. package/readme.md +2 -2
  323. package/dist/chunks/{chunk-4ZA6KZHS.js → chunk-6FQ5IMNA.js} +3 -3
@@ -18,40 +18,129 @@ export function ComboBoxExample() {
18
18
  }
19
19
  ```
20
20
 
21
+ ## Groups and descriptions
22
+
23
+ Group items under a label with ComboBoxGroup. Like SelectItem, a ComboBoxItem can show an `icon` and a `description` below its label. Values are strings or numbers: `onValueChange` receives the `value` of the picked item, typed like the `value` you pass.
24
+
25
+ Example: ComboBox: groups
26
+ ```tsx
27
+ import {ComboBox, ComboBoxGroup, ComboBoxItem} from 'alinea/components'
28
+ import {
29
+ IcRoundDescription,
30
+ IcRoundFeed,
31
+ IcRoundImage
32
+ } from 'alinea/dashboard/icons'
33
+
34
+ export function ComboBoxGroupsExample() {
35
+ return (
36
+ <ComboBox label="Link to" style={{width: 280}}>
37
+ <ComboBoxGroup label="Pages">
38
+ <ComboBoxItem value="about" icon={IcRoundDescription}>
39
+ About us
40
+ </ComboBoxItem>
41
+ <ComboBoxItem
42
+ value="collection"
43
+ icon={IcRoundImage}
44
+ description="Products of the summer collection"
45
+ >
46
+ Summer collection
47
+ </ComboBoxItem>
48
+ </ComboBoxGroup>
49
+ <ComboBoxGroup label="Blog">
50
+ <ComboBoxItem
51
+ value="care"
52
+ icon={IcRoundFeed}
53
+ description="Published last week"
54
+ >
55
+ Caring for linen
56
+ </ComboBoxItem>
57
+ </ComboBoxGroup>
58
+ </ComboBox>
59
+ )
60
+ }
61
+ ```
62
+
63
+ ## Load items as you type
64
+
65
+ To search on a server, pass `filter={false}` so every item you render is shown, and `loading` while a request runs: it shows a spinner in the input, and in the list while it is empty. `useAsyncList` loads the items for the text in the input and cancels a request when a newer one replaces it. A function as `filter` replaces the default matching instead: `(textValue, inputValue) => boolean`.
66
+
67
+ Example: ComboBox: async
68
+ ```tsx
69
+ import {ComboBox, ComboBoxItem, useAsyncList} from 'alinea/components'
70
+
71
+ const products = [
72
+ {id: 'shirt', title: 'Linen shirt'},
73
+ {id: 'chair', title: 'Oak dining chair'},
74
+ {id: 'throw', title: 'Wool throw'},
75
+ {id: 'table', title: 'Walnut side table'},
76
+ {id: 'lamp', title: 'Ceramic table lamp'}
77
+ ]
78
+
79
+ // Stands in for a search request to your own API
80
+ async function searchProducts(text: string) {
81
+ await new Promise(resolve => setTimeout(resolve, 400))
82
+ const query = text.toLowerCase()
83
+ return products.filter(product => product.title.toLowerCase().includes(query))
84
+ }
85
+
86
+ export function ComboBoxAsyncExample() {
87
+ const list = useAsyncList({
88
+ async load({filterText}) {
89
+ return {items: await searchProducts(filterText)}
90
+ }
91
+ })
92
+ return (
93
+ <ComboBox
94
+ label="Related product"
95
+ inputValue={list.filterText}
96
+ onInputValueChange={list.setFilterText}
97
+ filter={false}
98
+ loading={list.loading}
99
+ emptyMessage="No products found"
100
+ style={{width: 280}}
101
+ >
102
+ {list.items.map(product => (
103
+ <ComboBoxItem key={product.id} value={product.id}>
104
+ {product.title}
105
+ </ComboBoxItem>
106
+ ))}
107
+ </ComboBox>
108
+ )
109
+ }
110
+ ```
111
+
21
112
  ## Props
22
113
 
23
114
  `ComboBox`
24
115
  A labelled text input that filters a list of options to pick from
25
116
  | Prop | Type | Default | Description |
26
117
  | --- | --- | --- | --- |
27
- | value | `string \| null` | | |
28
- | defaultValue | `string \| null` | | |
29
- | onValueChange | `(value: string \| null) => void` | | Called with the selected value, or null when the selection is cleared |
118
+ | value | `V \| null` | | The value of the selected ComboBoxItem |
119
+ | defaultValue | `V \| null` | | |
120
+ | onValueChange | `(value: V \| null) => void` | | Called with the selected value, or null when the selection is cleared |
30
121
  | inputValue | `string` | | |
31
122
  | defaultInputValue | `string` | | |
32
123
  | onInputValueChange | `(value: string) => void` | | |
33
124
  | placeholder | `string` | | |
34
125
  | allowsCustomValue | `boolean` | | Keep text that does not match an item instead of reverting it on blur |
35
126
  | emptyMessage | `ReactNode` | | Shown in the list when no item matches, the list stays closed if unset |
127
+ | filter | `false \| ((textValue: string, inputValue: string) => boolean)` | | Whether an item is shown for the typed text, defaults to a case and accent insensitive match anywhere in its text. Pass false to show every item, eg. when the server already filtered them. |
128
+ | loading | `boolean` | | Shows a spinner, eg. while the items for the typed text load |
36
129
  | name | `string` | | Name of the hidden form input carrying the value |
37
130
  | autoFocus | `boolean` | | |
38
- | label | `ReactNode` | | |
39
- | description | `ReactNode` | | |
40
- | error | `ReactNode` | | |
41
- | required | `boolean` | | |
42
- | disabled | `boolean` | | |
43
- | readOnly | `boolean` | | |
44
- | icon | `IconType \| ReactElement` | | |
45
- | shared | `boolean` | | Marks the field as shared between translations |
46
- | open | `boolean` | | |
47
- | defaultOpen | `boolean` | | |
48
- | onOpenChange | `(open: boolean) => void` | | |
49
- Also accepts `className` and `style`, `id` and `aria-*` labelling attributes, `data-*` attributes.
50
131
 
51
132
  `ComboBoxItem`
52
133
  | Prop | Type | Default | Description |
53
134
  | --- | --- | --- | --- |
54
- | value (required) | `string` | | |
135
+ | value (required) | `Key` | | |
55
136
  | textValue | `string` | | Text used for filtering and the input, defaults to string children |
56
137
  | disabled | `boolean` | | |
138
+ | icon | `IconType \| ReactElement` | | Icon shown before the label in the list |
139
+ | description | `ReactNode` | | Secondary text shown below the label in the list |
57
140
  Also accepts `className` and `style`.
141
+
142
+ `ComboBoxGroup`
143
+ | Prop | Type | Default | Description |
144
+ | --- | --- | --- | --- |
145
+ | label | `ReactNode` | | |
146
+ Also accepts `id` and `aria-*` labelling attributes.
@@ -22,7 +22,7 @@ export function DateFieldExample() {
22
22
  `DateField`
23
23
  | Prop | Type | Default | Description |
24
24
  | --- | --- | --- | --- |
25
- | locale | `string; /** The date, `YYYY-MM-DD` */ value?: string \| null; defaultValue?: string \| null; onValueChange?: (value: string \| null) => void; /** The earliest valid date, `YYYY-MM-DD` */ min?: string; /** The latest valid date, `YYYY-MM-DD` */ max?: string; name?: string; autoFocus?: boolean;` | | BCP 47 locale used to format dates, eg. `en-GB`, defaults to the user's locale |
25
+ | locale | `string; /** The date, `YYYY-MM-DD` */ value?: string \| null; defaultValue?: string \| null; onValueChange?: (value: string \| null) => void; /** The earliest valid date, `YYYY-MM-DD` */ min?: string; /** The latest valid date, `YYYY-MM-DD` */ max?: string; /** * Name of the hidden form input, which submits the date like a native * date input: `YYYY-MM-DD`, or an empty string when no date is set */ name?: string; autoFocus?: boolean;` | | BCP 47 locale used to format dates, eg. `en-GB`, defaults to the user's locale |
26
26
  | label | `ReactNode` | | |
27
27
  | description | `ReactNode` | | |
28
28
  | error | `ReactNode` | | |
@@ -134,11 +134,60 @@ export function DialogFormExample() {
134
134
  }
135
135
  ```
136
136
 
137
+ ## Submit a form
138
+
139
+ Pass `onSubmit` to DialogContent to render its content as a [Form](form.md): a Button with `type="submit"` submits it with the values of the named fields. The dialog closes once `onSubmit` resolves without errors. Errors it returns, keyed by field name, show on the fields and keep the dialog open. `onClose` on Dialog runs whenever the dialog closes, after `onOpenChange(false)`, for example to reset state. The form takes no room of its own (`display: contents`): put spacing such as a `gap` on DialogContent.
140
+
141
+ Example: Dialog: submit
142
+ ```tsx
143
+ import {
144
+ Button,
145
+ Dialog,
146
+ DialogClose,
147
+ DialogContent,
148
+ DialogDescription,
149
+ DialogFooter,
150
+ DialogHeader,
151
+ DialogTitle,
152
+ DialogTrigger,
153
+ TextField
154
+ } from 'alinea/components'
155
+
156
+ // Stands in for a request to your own API, which returns errors by field name
157
+ async function movePage(data: FormData) {
158
+ await new Promise(resolve => setTimeout(resolve, 300))
159
+ if (data.get('path') === 'about') return {path: 'This path is taken'}
160
+ return undefined
161
+ }
162
+
163
+ export function DialogSubmitExample() {
164
+ return (
165
+ <Dialog>
166
+ <DialogTrigger variant="outline">Change path</DialogTrigger>
167
+ <DialogContent onSubmit={movePage}>
168
+ <DialogHeader>
169
+ <DialogTitle>Change path</DialogTitle>
170
+ <DialogDescription>Try “about” to see an error.</DialogDescription>
171
+ </DialogHeader>
172
+ <TextField label="Path" name="path" defaultValue="about" autoFocus />
173
+ <DialogFooter>
174
+ <DialogClose variant="ghost">Cancel</DialogClose>
175
+ <Button type="submit" color="primary">
176
+ Save
177
+ </Button>
178
+ </DialogFooter>
179
+ </DialogContent>
180
+ </Dialog>
181
+ )
182
+ }
183
+ ```
184
+
137
185
  ## Props
138
186
 
139
187
  `Dialog`
140
188
  | Prop | Type | Default | Description |
141
189
  | --- | --- | --- | --- |
190
+ | onClose | `() => void` | | Called when the dialog closes, after `onOpenChange(false)` |
142
191
  | open | `boolean` | | |
143
192
  | defaultOpen | `boolean` | | |
144
193
  | onOpenChange | `(open: boolean) => void` | | |
@@ -153,6 +202,8 @@ Accepts every prop of `Button`.
153
202
  | size | `'default' \| 'lg' \| 'full'` | `'default'` | `default` fits its content up to a narrow width, `lg` is a fixed wide dialog and `full` fills the viewport (minus a margin), defaults to `default` |
154
203
  | dismissable | `boolean` | `true` | Close the dialog when clicking outside of it, defaults to true |
155
204
  | showCloseButton | `boolean` | `true` | Defaults to true |
205
+ | onSubmit | `FormProps['onSubmit']` | | Renders the content as a Form, submitted by a `type="submit"` Button. The dialog closes once this resolves without returning errors, which are shown on the fields otherwise. |
206
+ | validationErrors | `ValidationErrors` | | Errors keyed by field name, when the content is a form |
156
207
  Also accepts `className` and `style`, `id` and `aria-*` labelling attributes, `data-*` attributes.
157
208
 
158
209
  `DialogHeader`
@@ -0,0 +1,67 @@
1
+ # Form
2
+
3
+ A form whose fields show their validation errors. `onSubmit` receives the `FormData` of the named fields instead of the browser submitting the form. It can return errors keyed by field name, also from a promise, which show on those fields until they are edited.
4
+
5
+ Example: Form
6
+ ```tsx
7
+ import {
8
+ Button,
9
+ Form,
10
+ Select,
11
+ SelectItem,
12
+ Stack,
13
+ Text,
14
+ TextField
15
+ } from 'alinea/components'
16
+ import {useState} from 'react'
17
+
18
+ // Stands in for a request to your own API, which returns errors by field name
19
+ async function createPage(data: FormData) {
20
+ await new Promise(resolve => setTimeout(resolve, 300))
21
+ if (data.get('path') === 'about') return {path: 'This path is taken'}
22
+ return undefined
23
+ }
24
+
25
+ export function FormExample() {
26
+ const [created, setCreated] = useState<string>()
27
+ return (
28
+ <Form
29
+ style={{width: 300}}
30
+ onSubmit={async data => {
31
+ const errors = await createPage(data)
32
+ if (!errors) setCreated(String(data.get('title')))
33
+ return errors
34
+ }}
35
+ >
36
+ <Stack gap={16}>
37
+ <TextField label="Title" name="title" required />
38
+ <TextField label="Path" name="path" defaultValue="about" />
39
+ <Select label="Type" name="type" defaultValue="page">
40
+ <SelectItem value="page">Page</SelectItem>
41
+ <SelectItem value="post">Blog post</SelectItem>
42
+ </Select>
43
+ <Button type="submit" color="primary">
44
+ Create page
45
+ </Button>
46
+ {created && <Text size="sm">Created “{created}”</Text>}
47
+ </Stack>
48
+ </Form>
49
+ )
50
+ }
51
+ ```
52
+
53
+ ## Validation
54
+
55
+ Native constraints are checked when the form is submitted: `required`, and `pattern`, `minLength` and `maxLength` on a TextField. `validationErrors` shows errors you already have, for example from a server, keyed by field name like the ones `onSubmit` returns. An `error` set on a field itself takes precedence.
56
+
57
+ The form controls of `alinea/components` take a `name` to be submitted under: text, number and select controls submit their value as a string, dates and times as ISO strings (`2026-09-23`, `14:30`) and MultipleSelect one entry per picked value. To submit a form from a dialog, pass `onSubmit` to [DialogContent](dialog.md#submit-a-form): the dialog closes once it succeeds.
58
+
59
+ ## Props
60
+
61
+ `Form`
62
+ A form whose fields show validation errors: native constraints such as `required` once the form is submitted, and `validationErrors` or the errors returned by `onSubmit` by field name. An `error` set on a field itself takes precedence.
63
+ | Prop | Type | Default | Description |
64
+ | --- | --- | --- | --- |
65
+ | validationErrors | `ValidationErrors` | | Errors keyed by field name, eg. from the server. A field shows its error until it is edited. |
66
+ | onSubmit | `(data: FormData) => FormSubmitResult \| Promise<FormSubmitResult>` | | Called with the form data instead of submitting the form natively. Errors it returns are shown on the fields, next to `validationErrors`. |
67
+ Also accepts `className` and `style`, `id` and `aria-*` labelling attributes, `data-*` attributes.
@@ -19,6 +19,10 @@ export function LinkExample() {
19
19
  }
20
20
  ```
21
21
 
22
+ ## Attributes
23
+
24
+ Besides `href`, the link passes `target`, `rel`, `download`, `title`, `tabIndex`, `lang`, `hrefLang`, `referrerPolicy` and `aria-current` to its `<a>`. Mark the link to the page that is shown with `aria-current="page"`.
25
+
22
26
  ## Props
23
27
 
24
28
  `Link`
@@ -28,6 +32,12 @@ export function LinkExample() {
28
32
  | target | `string` | | |
29
33
  | rel | `string` | | |
30
34
  | download | `boolean \| string` | | |
35
+ | title | `string` | | Tooltip text |
36
+ | tabIndex | `number` | | |
37
+ | lang | `string` | | |
38
+ | hrefLang | `string` | | |
39
+ | referrerPolicy | `HTMLAttributeReferrerPolicy` | | |
40
+ | aria-current | `boolean \| 'page' \| 'step' \| 'location' \| 'date' \| 'time'` | | |
31
41
  | variant | `'plain' \| 'underline'` | `'plain'` | `plain` underlines on hover, `underline` is always underlined |
32
42
  | disabled | `boolean` | | |
33
43
  | onClick | `(event: MouseEvent<HTMLAnchorElement>) => void` | | |
@@ -22,35 +22,27 @@ export function MultipleSelectExample() {
22
22
  }
23
23
  ```
24
24
 
25
+ ## Values
26
+
27
+ The value is an array of the `value`s of the picked items, strings or numbers. With a `name` the field submits one form entry per value, like a native multiple select: read them with `formData.getAll(name)`.
28
+
25
29
  ## Props
26
30
 
27
31
  `MultipleSelect`
28
32
  A labelled field to pick several values from a searchable list, the selected values are shown as removable tags.
29
33
  | Prop | Type | Default | Description |
30
34
  | --- | --- | --- | --- |
31
- | value | `Array<string>` | | |
32
- | defaultValue | `Array<string>` | | |
33
- | onValueChange | `(value: Array<string>) => void` | | |
35
+ | value | `Array<V>` | | The values of the selected MultipleSelectItems |
36
+ | defaultValue | `Array<V>` | | |
37
+ | onValueChange | `(value: Array<V>) => void` | | |
34
38
  | placeholder | `string` | | |
35
39
  | emptyMessage | `ReactNode` | `'No options'` | Shown in the list when no item matches the search, defaults to "No options" |
36
- | name | `string` | | Name of the hidden form input carrying the values |
37
- | label | `ReactNode` | | |
38
- | description | `ReactNode` | | |
39
- | error | `ReactNode` | | |
40
- | required | `boolean` | | |
41
- | disabled | `boolean` | | |
42
- | readOnly | `boolean` | | |
43
- | icon | `IconType \| ReactElement` | | |
44
- | shared | `boolean` | | Marks the field as shared between translations |
45
- | open | `boolean` | | |
46
- | defaultOpen | `boolean` | | |
47
- | onOpenChange | `(open: boolean) => void` | | |
48
- Also accepts `className` and `style`, `id` and `aria-*` labelling attributes, `data-*` attributes.
40
+ | name | `string` | | Name of the hidden form input carrying the values, it submits one entry per selected value like a native multiple select: read them with `formData.getAll(name)` |
49
41
 
50
42
  `MultipleSelectItem`
51
43
  | Prop | Type | Default | Description |
52
44
  | --- | --- | --- | --- |
53
- | value (required) | `string` | | |
45
+ | value (required) | `Key` | | |
54
46
  | textValue | `string` | | Text used for searching and the tag, defaults to string children |
55
47
  | disabled | `boolean` | | |
56
48
  Also accepts `className` and `style`.
@@ -19,6 +19,10 @@ export function NumberFieldExample() {
19
19
  }
20
20
  ```
21
21
 
22
+ ## Formatting
23
+
24
+ `formatOptions` takes the options of `Intl.NumberFormat`, such as a currency or a unit, and the number is shown and parsed in the locale of the dashboard. Digits are not grouped by default, since number fields often hold years or ids: pass `formatOptions={{useGrouping: true}}` to show `1,250` rather than `1250`. The value is a number, or `null` when the field is empty.
25
+
22
26
  ## Props
23
27
 
24
28
  `NumberField`
@@ -30,7 +34,7 @@ export function NumberFieldExample() {
30
34
  | min | `number` | | |
31
35
  | max | `number` | | |
32
36
  | step | `number` | | |
33
- | formatOptions | `Intl.NumberFormatOptions` | | |
37
+ | formatOptions | `Intl.NumberFormatOptions` | | How the number is shown and parsed. Digits are not grouped unless `useGrouping` is set: number fields often hold years and ids |
34
38
  | placeholder | `string` | | |
35
39
  | steppers | `boolean` | `true` | Show increment and decrement buttons, defaults to true |
36
40
  | name | `string` | | |
@@ -1,6 +1,6 @@
1
1
  # Select
2
2
 
3
- Picks a single value from a list of options. The value is the `value` of the chosen SelectItem, and editors can clear it unless the field is required.
3
+ Picks a single value from a list of options. The value is the `value` of the chosen SelectItem, and `onValueChange` receives it, typed like the `value` you pass: values are strings or numbers. Set `clearable` to show a button that clears the value again, `onValueChange` then receives `null`.
4
4
 
5
5
  Example: Select
6
6
  ```tsx
@@ -63,7 +63,7 @@ export function SelectGroupsExample() {
63
63
 
64
64
  ## States
65
65
 
66
- A placeholder shows while nothing is selected. Like the other form controls, a select takes `description`, `error`, `required`, `disabled` and `readOnly`.
66
+ A placeholder shows while nothing is selected, and `clearable` adds a button to clear the selection. Like the other form controls, a select takes `description`, `error`, `required`, `disabled` and `readOnly`.
67
67
 
68
68
  Example: Select: states
69
69
  ```tsx
@@ -72,7 +72,7 @@ import {Select, SelectItem} from 'alinea/components'
72
72
  export function SelectStatesExample() {
73
73
  return (
74
74
  <div style={{display: 'grid', gap: 16, width: 280}}>
75
- <Select label="Category" placeholder="Choose a category">
75
+ <Select label="Category" placeholder="Choose a category" clearable>
76
76
  <SelectItem value="bedroom">Bedroom</SelectItem>
77
77
  <SelectItem value="dining">Dining</SelectItem>
78
78
  </Select>
@@ -92,33 +92,22 @@ export function SelectStatesExample() {
92
92
  ## Props
93
93
 
94
94
  `Select`
95
- A labelled field to pick a single value from a list. The value can be cleared unless the field is required.
95
+ A labelled field to pick a single value from a list. Set `clearable` to let the value be cleared again.
96
96
  | Prop | Type | Default | Description |
97
97
  | --- | --- | --- | --- |
98
- | value | `string \| null` | | |
99
- | defaultValue | `string \| null` | | |
100
- | onValueChange | `(value: string \| null) => void` | | Called with the selected value, or null when the value is cleared |
98
+ | value | `V \| null` | | The value of the selected SelectItem |
99
+ | defaultValue | `V \| null` | | |
100
+ | onValueChange | `(value: V \| null) => void` | | Called with the selected value, or null when the value is cleared |
101
101
  | placeholder | `string` | | |
102
+ | clearable | `boolean` | | Show a button that clears the value |
102
103
  | name | `string` | | Name of the hidden form input carrying the value |
103
104
  | autoFocus | `boolean` | | |
104
105
  | size | `'default' \| 'sm'` | `'default'` | `sm` is as tall as a default Button, defaults to `default` |
105
- | label | `ReactNode` | | |
106
- | description | `ReactNode` | | |
107
- | error | `ReactNode` | | |
108
- | required | `boolean` | | |
109
- | disabled | `boolean` | | |
110
- | readOnly | `boolean` | | |
111
- | icon | `IconType \| ReactElement` | | |
112
- | shared | `boolean` | | Marks the field as shared between translations |
113
- | open | `boolean` | | |
114
- | defaultOpen | `boolean` | | |
115
- | onOpenChange | `(open: boolean) => void` | | |
116
- Also accepts `className` and `style`, `id` and `aria-*` labelling attributes, `data-*` attributes.
117
106
 
118
107
  `SelectItem`
119
108
  | Prop | Type | Default | Description |
120
109
  | --- | --- | --- | --- |
121
- | value (required) | `string` | | |
110
+ | value (required) | `Key` | | |
122
111
  | disabled | `boolean` | | |
123
112
  | icon | `IconType \| ReactElement` | | |
124
113
  | textValue | `string` | | Text used for typeahead and the value, defaults to string children |
@@ -0,0 +1,42 @@
1
+ # Stack
2
+
3
+ Lays out its children in a column, or a row with `direction="row"`, with a `gap` between them: a number is in pixels, a string any CSS length. `align` positions the items across the stack (they stretch by default), `justify` along it, and `wrap` lets a row continue on the next line.
4
+
5
+ Example: Stack
6
+ ```tsx
7
+ import {Badge, Button, Stack, TextField} from 'alinea/components'
8
+
9
+ export function StackExample() {
10
+ return (
11
+ <Stack gap={16} style={{width: 320}}>
12
+ <Stack direction="row" align="center" justify="between">
13
+ <strong>About us</strong>
14
+ <Badge status="draft">Draft</Badge>
15
+ </Stack>
16
+ <TextField label="Title" defaultValue="About us" />
17
+ <TextField label="Path" defaultValue="about" />
18
+ <Stack direction="row" gap={8} justify="end" wrap>
19
+ <Button variant="outline">Cancel</Button>
20
+ <Button color="primary">Save</Button>
21
+ </Stack>
22
+ </Stack>
23
+ )
24
+ }
25
+ ```
26
+
27
+ With `asChild` the stack lays out its single child element instead of rendering a div. `Stack` replaces the deprecated `HStack` and `VStack` of `alinea/ui`: `<Stack direction="row">` is an HStack.
28
+
29
+ ## Props
30
+
31
+ `Stack`
32
+ Lays out its children in a row or column with a gap between them
33
+ | Prop | Type | Default | Description |
34
+ | --- | --- | --- | --- |
35
+ | direction | `'row' \| 'column'` | `'column'` | Defaults to `column` |
36
+ | gap | `number \| string` | | Space between the items, a number is in pixels |
37
+ | align | `'start' \| 'center' \| 'end' \| 'stretch' \| 'baseline'` | | Cross axis alignment of the items, they stretch by default |
38
+ | justify | `'start' \| 'center' \| 'end' \| 'between'` | | Main axis distribution of the items |
39
+ | wrap | `boolean` | | Wrap items onto multiple lines |
40
+ | asChild | `boolean` | | Lay out the single child element instead of rendering a div |
41
+ | ref | `Ref<HTMLDivElement>` | | |
42
+ Also accepts `className` and `style`, `id` and `aria-*` labelling attributes, `data-*` attributes.
@@ -112,6 +112,84 @@ export function TableSelectionExample() {
112
112
  }
113
113
  ```
114
114
 
115
+ ## Load more rows
116
+
117
+ `onLoadMore` is called when the rows are scrolled close to the end, to fetch the next page. While `loading` is set, a spinner shows below the rows, or in place of them while there are none. `useAsyncList` keeps the loaded pages: its `load` function receives the `cursor` the previous page returned, and `loadMore` does nothing once a page returns no cursor. Its `items`, `loading`, `loadMore`, `sortDescriptor` and `sort`, and `selectedKeys` and `setSelectedKeys` plug into the props of the table. `update(key, item)`, `remove(...keys)`, `prepend(...items)` and `append(...items)` change the loaded items without loading them again, for example after a dialog saves a row.
118
+
119
+ Example: Table: async
120
+ ```tsx
121
+ import {
122
+ Table,
123
+ TableCell,
124
+ TableRow,
125
+ TableTitle,
126
+ useAsyncList
127
+ } from 'alinea/components'
128
+
129
+ interface Product {
130
+ id: string
131
+ title: string
132
+ price: number
133
+ }
134
+
135
+ const materials = ['Linen', 'Oak', 'Wool', 'Walnut', 'Ceramic']
136
+ const kinds = ['shirt', 'chair', 'throw', 'stool', 'lamp', 'table']
137
+
138
+ // Stands in for a paged API: 20 products per page, 100 in total
139
+ async function fetchProducts(page: number) {
140
+ await new Promise(resolve => setTimeout(resolve, 500))
141
+ const items: Array<Product> = Array.from({length: 20}, (_, i) => {
142
+ const n = page * 20 + i
143
+ const title = `${materials[n % 5]} ${kinds[n % 6]} ${n + 1}`
144
+ return {id: `product-${n}`, title, price: 40 + ((n * 37) % 300)}
145
+ })
146
+ return {items, cursor: page < 4 ? page + 1 : undefined}
147
+ }
148
+
149
+ export function TableAsyncExample() {
150
+ const list = useAsyncList<Product, number>({
151
+ load: ({cursor = 0}) => fetchProducts(cursor)
152
+ })
153
+ return (
154
+ <div style={{width: 420, height: 260}}>
155
+ <Table
156
+ aria-label="Products"
157
+ items={list.items}
158
+ columns={[
159
+ {id: 'title', header: 'Title', width: '1fr'},
160
+ {id: 'price', header: 'Price', width: 100, align: 'end'}
161
+ ]}
162
+ loading={list.loading}
163
+ onLoadMore={list.loadMore}
164
+ >
165
+ {product => (
166
+ <TableRow id={product.id} textValue={product.title}>
167
+ <TableTitle title={product.title} />
168
+ <TableCell align="end">€{product.price}</TableCell>
169
+ </TableRow>
170
+ )}
171
+ </Table>
172
+ </div>
173
+ )
174
+ }
175
+ ```
176
+
177
+ ```tsx
178
+ import {useAsyncList} from 'alinea/components'
179
+
180
+ const list = useAsyncList({
181
+ async load({cursor, filterText, sort, signal}) {
182
+ const res = await fetch(`/api/products?page=${cursor ?? 1}`, {signal})
183
+ const {items, next} = await res.json()
184
+ // Leave out the cursor, or return null, on the last page
185
+ return {items, cursor: next}
186
+ }
187
+ })
188
+ // list.items, list.loading, list.hasMore, list.error,
189
+ // list.loadMore(), list.reload(), list.setFilterText(text), list.sort(sort),
190
+ // list.update(id, item), list.remove(id), list.prepend(item)
191
+ ```
192
+
115
193
  ## Props
116
194
 
117
195
  `Table`
@@ -132,6 +210,8 @@ export function TableSelectionExample() {
132
210
  | onSortChange | `(descriptor: SortDescriptor) => void` | | |
133
211
  | onRowAction | `(key: Key) => void` | | Called when a row is activated (see `selectionBehavior`), rows can override it with their own `onAction` |
134
212
  | renderEmptyState | `() => ReactNode` | | |
213
+ | loading | `boolean` | | Shows a spinner in place of the rows while there are none, below the rows otherwise |
214
+ | onLoadMore | `() => void` | | Called when the rows are scrolled close to the end, to load the next page (see useAsyncList) |
135
215
  | dependencies | `ReadonlyArray<unknown>` | | Rows are cached per item, list the values `children` reads besides the item to render them again when those change |
136
216
  | children (required) | `(item: T) => ReactElement` | | |
137
217
 
@@ -174,6 +254,9 @@ Also accepts `className` and `style`.
174
254
  | suffix | `ReactNode` | | Shown right after the title, eg. a status icon |
175
255
  Also accepts `className` and `style`.
176
256
 
257
+ `useAsyncList`
258
+ Loads a list of items, with sorting, filtering and paging
259
+
177
260
  ## Accessibility
178
261
 
179
262
  Built on the React Aria grid: the arrow keys move between rows, Space selects a row and Enter runs its action. Sortable column headers announce their sort direction.
@@ -43,7 +43,7 @@ export function TextFieldStatesExample() {
43
43
 
44
44
  ## Multiline
45
45
 
46
- A multiline field starts at `rows` lines and grows as editors type.
46
+ A multiline field starts at `rows` lines and grows as editors type, up to `maxRows` lines when you set it, then it scrolls.
47
47
 
48
48
  Example: TextField: multiline
49
49
  ```tsx
@@ -55,6 +55,7 @@ export function TextFieldMultilineExample() {
55
55
  label="Description"
56
56
  multiline
57
57
  rows={4}
58
+ maxRows={8}
58
59
  defaultValue="A relaxed shirt in washed linen that softens with every wear."
59
60
  style={{width: 300}}
60
61
  />
@@ -95,6 +96,10 @@ export function TextFieldIconsExample() {
95
96
  }
96
97
  ```
97
98
 
99
+ ## Native input attributes
100
+
101
+ `type`, `name`, `autoComplete`, `inputMode`, `pattern`, `minLength` and `maxLength` are passed to the input. `inputMode` picks the virtual keyboard on touch devices, eg. `numeric` for a code, and `pattern` is a regular expression the value must match, checked when the field is in a [Form](form.md).
102
+
98
103
  ## Props
99
104
 
100
105
  `TextField`
@@ -107,6 +112,7 @@ export function TextFieldIconsExample() {
107
112
  | type | `'text' \| 'email' \| 'url' \| 'password' \| 'tel' \| 'search'` | `'text'` | |
108
113
  | multiline | `boolean` | | Render a textarea that grows with its content |
109
114
  | rows | `number` | `1` | Minimum number of rows of a multiline field |
115
+ | maxRows | `number` | | Maximum number of rows a multiline field grows to before it scrolls |
110
116
  | startIcon | `IconType \| ReactElement` | | Icon displayed inside the input, before the text |
111
117
  | endIcon | `IconType \| ReactElement` | | Icon displayed inside the input, after the text |
112
118
  | name | `string` | | |
@@ -114,6 +120,8 @@ export function TextFieldIconsExample() {
114
120
  | autoComplete | `string` | | |
115
121
  | maxLength | `number` | | |
116
122
  | minLength | `number` | | |
123
+ | inputMode | `'none' \| 'text' \| 'tel' \| 'url' \| 'email' \| 'numeric' \| 'decimal' \| 'search'` | | Which virtual keyboard to show, eg. `numeric` for a code |
124
+ | pattern | `string` | | A regular expression the value must match to be valid |
117
125
  | onBlur | `(event: FocusEvent<TextFieldElement>) => void` | | |
118
126
  | onFocus | `(event: FocusEvent<TextFieldElement>) => void` | | |
119
127
  | onKeyDown | `(event: KeyboardEvent<TextFieldElement>) => void` | | |
@@ -18,13 +18,17 @@ export function TimeFieldExample() {
18
18
  }
19
19
  ```
20
20
 
21
+ ## Value
22
+
23
+ The value is an ISO time string: `14:30`, or `14:30:15` with `granularity="second"`. With a `name` the field submits the same string in a form, like a native time input, and an empty string when no time is set. `hourCycle` only changes how the time is shown.
24
+
21
25
  ## Props
22
26
 
23
27
  `TimeField`
24
28
  | Prop | Type | Default | Description |
25
29
  | --- | --- | --- | --- |
26
30
  | locale | `string; /** The time, `HH:mm` or `HH:mm:ss` */ value?: string \| null; defaultValue?: string \| null; /** Receives `HH:mm`, or `HH:mm:ss` when granularity is `second` */ onValueChange?: (value: string \| null) => void; /** The earliest valid time, `HH:mm` or `HH:mm:ss` */ min?: string; /** The latest valid time, `HH:mm` or `HH:mm:ss` */ max?: string; /** The smallest unit that can be edited, defaults to `minute` */ granularity?: 'minute' \| 'second'; /** Defaults to the locale's hour cycle */ hourCycle?: 12 \| 24` | | BCP 47 locale used to format dates, eg. `en-GB`, defaults to the user's locale |
27
- | name | `string` | | |
31
+ | name | `string` | | Name of the hidden form input, which submits the time like a native time input: `HH:mm`, or `HH:mm:ss` when granularity is `second`, and an empty string when no time is set |
28
32
  | autoFocus | `boolean` | | |
29
33
  | label | `ReactNode` | | |
30
34
  | description | `ReactNode` | | |