torch-glare 2.3.0 → 2.4.1

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 (264) hide show
  1. package/README.md +8 -0
  2. package/apps/lib/components/ActionButton.tsx +7 -12
  3. package/apps/lib/components/ActionsGroup.tsx +4 -15
  4. package/apps/lib/components/AlertDialog.tsx +173 -191
  5. package/apps/lib/components/Avatar.tsx +13 -16
  6. package/apps/lib/components/Badge.tsx +7 -3
  7. package/apps/lib/components/BadgeField.tsx +76 -71
  8. package/apps/lib/components/Breadcrumb.tsx +49 -85
  9. package/apps/lib/components/Button.tsx +20 -19
  10. package/apps/lib/components/ButtonGroup.tsx +31 -41
  11. package/apps/lib/components/Calendar.tsx +51 -29
  12. package/apps/lib/components/Card.tsx +2 -13
  13. package/apps/lib/components/ChartBlockTool.ts +17 -52
  14. package/apps/lib/components/Checkbox.tsx +9 -16
  15. package/apps/lib/components/ContextMenu.tsx +110 -106
  16. package/apps/lib/components/CountBadge.tsx +4 -6
  17. package/apps/lib/components/DataTable.tsx +196 -216
  18. package/apps/lib/components/DataViews/DataViewRadio.tsx +10 -8
  19. package/apps/lib/components/DataViews/DataViewsConfigPanel.tsx +21 -55
  20. package/apps/lib/components/DataViews/DataViewsHeader.tsx +1 -3
  21. package/apps/lib/components/DataViews/DataViewsLayout.tsx +5 -12
  22. package/apps/lib/components/DataViews/FilterPanel.tsx +173 -150
  23. package/apps/lib/components/DataViews/HeaderSearch.tsx +1 -5
  24. package/apps/lib/components/DataViews/InboxView.tsx +22 -68
  25. package/apps/lib/components/DataViews/InboxViewCard.tsx +68 -77
  26. package/apps/lib/components/DataViews/KanbanView.tsx +8 -25
  27. package/apps/lib/components/DataViews/PanelControls.tsx +3 -13
  28. package/apps/lib/components/DataViews/SettingsPanel.tsx +83 -89
  29. package/apps/lib/components/DataViews/TableView.tsx +8 -28
  30. package/apps/lib/components/DataViews/TreeView.tsx +12 -40
  31. package/apps/lib/components/DataViews/badgeAdapter.ts +32 -28
  32. package/apps/lib/components/DataViews/fieldRenderers.tsx +106 -141
  33. package/apps/lib/components/DataViews/filters/DatePickerRangeFilter.tsx +87 -0
  34. package/apps/lib/components/DataViews/filters/DateRangePopover.tsx +39 -32
  35. package/apps/lib/components/DataViews/filters/PresetChips.tsx +15 -15
  36. package/apps/lib/components/DataViews/filters/RangeSliderWithInputs.tsx +69 -58
  37. package/apps/lib/components/DataViews/index.ts +26 -26
  38. package/apps/lib/components/DataViews/tree/TreeDrawer.tsx +12 -16
  39. package/apps/lib/components/DataViews/tree/TreeSidebar.tsx +28 -31
  40. package/apps/lib/components/DataViews/types.ts +111 -114
  41. package/apps/lib/components/DatePicker.tsx +198 -191
  42. package/apps/lib/components/Dialog.tsx +92 -105
  43. package/apps/lib/components/Divider.tsx +21 -24
  44. package/apps/lib/components/Drawer.tsx +79 -139
  45. package/apps/lib/components/DropdownMenu.tsx +144 -181
  46. package/apps/lib/components/FieldHint.tsx +4 -11
  47. package/apps/lib/components/Form.tsx +146 -155
  48. package/apps/lib/components/FormStepper.tsx +9 -39
  49. package/apps/lib/components/HeaderBar.tsx +119 -0
  50. package/apps/lib/components/ImageAttachment.tsx +111 -73
  51. package/apps/lib/components/InnerLabelField.tsx +20 -16
  52. package/apps/lib/components/Input.tsx +171 -172
  53. package/apps/lib/components/InputField.tsx +44 -45
  54. package/apps/lib/components/InputOTP.tsx +68 -69
  55. package/apps/lib/components/Label.tsx +31 -35
  56. package/apps/lib/components/LabelField.tsx +4 -5
  57. package/apps/lib/components/LabeledCheckBox.tsx +8 -12
  58. package/apps/lib/components/LabeledRadio.tsx +7 -5
  59. package/apps/lib/components/LinkButton.tsx +2 -9
  60. package/apps/lib/components/LoginButton.tsx +4 -9
  61. package/apps/lib/components/PasswordLevel.tsx +5 -5
  62. package/apps/lib/components/Popover.tsx +15 -33
  63. package/apps/lib/components/ProfileMenu.tsx +67 -72
  64. package/apps/lib/components/Radio.tsx +1 -5
  65. package/apps/lib/components/RadioCard.tsx +9 -28
  66. package/apps/lib/components/ScrollArea.tsx +45 -43
  67. package/apps/lib/components/SearchField.tsx +49 -39
  68. package/apps/lib/components/SearchableSelect.tsx +46 -52
  69. package/apps/lib/components/SearchableTable.tsx +199 -219
  70. package/apps/lib/components/SearchableTree.tsx +469 -0
  71. package/apps/lib/components/SearchableTreeDialog.tsx +493 -0
  72. package/apps/lib/components/SectionBlock.tsx +4 -16
  73. package/apps/lib/components/Select.tsx +53 -80
  74. package/apps/lib/components/SimpleSelect.tsx +196 -187
  75. package/apps/lib/components/Skeleton.tsx +9 -12
  76. package/apps/lib/components/SlideDatePicker.tsx +215 -166
  77. package/apps/lib/components/SpinLoading.tsx +33 -49
  78. package/apps/lib/components/Stepper.tsx +13 -56
  79. package/apps/lib/components/Switch.tsx +8 -10
  80. package/apps/lib/components/TabFormItem.tsx +3 -12
  81. package/apps/lib/components/TabSwitch.tsx +7 -34
  82. package/apps/lib/components/Table.tsx +60 -75
  83. package/apps/lib/components/TableDnDWrapper.ts +15 -48
  84. package/apps/lib/components/TextEditor.tsx +93 -104
  85. package/apps/lib/components/Textarea.tsx +8 -12
  86. package/apps/lib/components/Timeline.tsx +21 -41
  87. package/apps/lib/components/Toast.tsx +83 -83
  88. package/apps/lib/components/Toggle.tsx +112 -114
  89. package/apps/lib/components/ToggleButton.tsx +1 -1
  90. package/apps/lib/components/Tooltip.tsx +24 -19
  91. package/apps/lib/components/TransparentLabel.tsx +61 -62
  92. package/apps/lib/components/TreeDropDown.tsx +86 -52
  93. package/apps/lib/components/TreeFolder/TreeFolder.tsx +15 -48
  94. package/apps/lib/components/TreeFolder/TreeFolderBreadcrumb.tsx +16 -21
  95. package/apps/lib/components/TreeFolder/TreeFolderRow.tsx +9 -20
  96. package/apps/lib/components/TreeFolder/TreeFolderStyles.tsx +3 -3
  97. package/apps/lib/components/TreeFolder/icons.tsx +14 -17
  98. package/apps/lib/components/TreeFolder/index.ts +15 -8
  99. package/apps/lib/components/TreeFolder/treeFolderUtils.ts +47 -52
  100. package/apps/lib/components/TreeFolder/types.ts +32 -32
  101. package/apps/lib/components/TreeFolder/useTreeFolderDnD.ts +117 -123
  102. package/apps/lib/hooks/useActiveTreeItem.ts +52 -55
  103. package/apps/lib/hooks/useClickOutside.ts +21 -20
  104. package/apps/lib/hooks/useDataViewsState.ts +86 -80
  105. package/apps/lib/hooks/useIsMobile.ts +12 -12
  106. package/apps/lib/hooks/useResize.ts +54 -65
  107. package/apps/lib/hooks/useTagSelection.ts +201 -201
  108. package/apps/lib/layouts/CNLayout.tsx +181 -140
  109. package/apps/lib/layouts/DataViewCard.tsx +4 -18
  110. package/apps/lib/layouts/FieldSection.tsx +2 -6
  111. package/apps/lib/layouts/TreeSubLayout.tsx +24 -38
  112. package/apps/lib/providers/ThemeProvider.tsx +8 -23
  113. package/apps/lib/registry.json +1004 -0
  114. package/apps/lib/tsconfig.json +9 -0
  115. package/apps/lib/utils/cn.ts +1 -1
  116. package/apps/lib/utils/dataViews/columnUtils.ts +46 -44
  117. package/apps/lib/utils/dataViews/fieldUtils.ts +70 -71
  118. package/apps/lib/utils/dataViews/nestedDataUtils.tsx +129 -122
  119. package/apps/lib/utils/dataViews/pathUtils.ts +74 -67
  120. package/apps/lib/utils/dataViews/rangeUtils.ts +121 -112
  121. package/apps/lib/utils/dataViews/treeUtils.ts +204 -211
  122. package/apps/lib/utils/dateFormat.ts +64 -54
  123. package/apps/lib/utils/markdownParser.ts +2 -7
  124. package/apps/lib/utils/resize.ts +21 -25
  125. package/apps/lib/utils/types.ts +13 -13
  126. package/dist/bin/index.js +8 -2
  127. package/dist/bin/index.js.map +1 -1
  128. package/dist/src/shared/getAvailableFiles.d.ts.map +1 -1
  129. package/dist/src/shared/getAvailableFiles.js +6 -1
  130. package/dist/src/shared/getAvailableFiles.js.map +1 -1
  131. package/dist/src/shared/loadRegistry.d.ts +7 -0
  132. package/dist/src/shared/loadRegistry.d.ts.map +1 -0
  133. package/dist/src/shared/loadRegistry.js +31 -0
  134. package/dist/src/shared/loadRegistry.js.map +1 -0
  135. package/dist/src/shared/resolveInstallPlan.d.ts +12 -0
  136. package/dist/src/shared/resolveInstallPlan.d.ts.map +1 -0
  137. package/dist/src/shared/resolveInstallPlan.js +39 -0
  138. package/dist/src/shared/resolveInstallPlan.js.map +1 -0
  139. package/dist/src/types/main.d.ts +24 -0
  140. package/dist/src/types/main.d.ts.map +1 -1
  141. package/docs/README.md +10 -2
  142. package/docs/components/action-button.md +30 -24
  143. package/docs/components/actions-group.md +26 -20
  144. package/docs/components/alert-dialog.md +45 -45
  145. package/docs/components/avatar.md +6 -2
  146. package/docs/components/badge-field.md +6 -2
  147. package/docs/components/badge.md +7 -1
  148. package/docs/components/breadcrumb.md +15 -40
  149. package/docs/components/button-group.md +13 -10
  150. package/docs/components/button.md +58 -33
  151. package/docs/components/calendar.md +26 -19
  152. package/docs/components/card.md +19 -16
  153. package/docs/components/chart-block-tool.md +128 -0
  154. package/docs/components/checkbox.md +16 -8
  155. package/docs/components/cn-layout.md +7 -1
  156. package/docs/components/context-menu.md +47 -24
  157. package/docs/components/count-badge.md +7 -1
  158. package/docs/components/data-table.md +45 -33
  159. package/docs/components/data-views-config-panel.md +18 -14
  160. package/docs/components/data-views-layout.md +20 -17
  161. package/docs/components/date-picker.md +30 -23
  162. package/docs/components/dialog.md +43 -42
  163. package/docs/components/divider.md +10 -4
  164. package/docs/components/drawer.md +8 -33
  165. package/docs/components/dropdown-menu.md +51 -32
  166. package/docs/components/field-hint.md +37 -25
  167. package/docs/components/field-section.md +9 -3
  168. package/docs/components/form-stepper.md +6 -0
  169. package/docs/components/form.md +17 -20
  170. package/docs/components/header-bar.md +187 -0
  171. package/docs/components/image-attachment.md +33 -41
  172. package/docs/components/inbox-view.md +16 -5
  173. package/docs/components/inner-label-field.md +34 -24
  174. package/docs/components/input-field.md +18 -10
  175. package/docs/components/input-otp.md +14 -11
  176. package/docs/components/input.md +22 -16
  177. package/docs/components/kanban-view.md +15 -5
  178. package/docs/components/label-field.md +32 -22
  179. package/docs/components/label.md +43 -32
  180. package/docs/components/labeled-check-box.md +16 -8
  181. package/docs/components/labeled-radio.md +18 -11
  182. package/docs/components/link-button.md +7 -1
  183. package/docs/components/login-button.md +8 -2
  184. package/docs/components/password-level.md +37 -24
  185. package/docs/components/popover.md +26 -23
  186. package/docs/components/profile-menu.md +38 -27
  187. package/docs/components/radio-card.md +16 -9
  188. package/docs/components/radio.md +16 -8
  189. package/docs/components/scroll-area.md +6 -2
  190. package/docs/components/search-field.md +14 -6
  191. package/docs/components/searchable-select.md +19 -14
  192. package/docs/components/searchable-table.md +60 -41
  193. package/docs/components/searchable-tree-dialog.md +190 -0
  194. package/docs/components/searchable-tree.md +200 -0
  195. package/docs/components/section-block.md +130 -4
  196. package/docs/components/select.md +23 -18
  197. package/docs/components/simple-select.md +14 -10
  198. package/docs/components/skeleton.md +43 -33
  199. package/docs/components/slide-date-picker.md +37 -29
  200. package/docs/components/spin-loading.md +32 -23
  201. package/docs/components/stepper.md +6 -0
  202. package/docs/components/switch.md +14 -6
  203. package/docs/components/tab-form-item.md +7 -1
  204. package/docs/components/tab-switch.md +13 -6
  205. package/docs/components/table-dnd-wrapper.md +105 -0
  206. package/docs/components/table-view.md +15 -5
  207. package/docs/components/table.md +28 -28
  208. package/docs/components/text-editor.md +19 -13
  209. package/docs/components/textarea.md +15 -7
  210. package/docs/components/timeline.md +6 -0
  211. package/docs/components/toast.md +35 -26
  212. package/docs/components/toggle-button.md +24 -21
  213. package/docs/components/toggle.md +23 -15
  214. package/docs/components/tooltip.md +42 -30
  215. package/docs/components/transparent-label.md +29 -20
  216. package/docs/components/tree-drop-down.md +31 -23
  217. package/docs/components/tree-sub-layout.md +6 -2
  218. package/docs/components/tree-view.md +16 -5
  219. package/docs/explanation/architecture.md +54 -0
  220. package/docs/explanation/design-system.md +52 -0
  221. package/docs/how-to/data-views-from-backend-response.md +7 -4
  222. package/docs/how-to/guides.md +24 -18
  223. package/docs/migration/changelog.md +38 -0
  224. package/docs/reference/cli.md +87 -0
  225. package/docs/reference/components.md +11 -12
  226. package/docs/reference/hooks.md +8 -8
  227. package/docs/reference/providers.md +22 -22
  228. package/docs/reference/tailwind-plugins.md +2 -2
  229. package/docs/reference/theme.md +85 -0
  230. package/docs/reference/types.md +4 -4
  231. package/docs/reference/utilities.md +19 -26
  232. package/docs/tutorials/building-first-form.md +20 -13
  233. package/docs/tutorials/component-composition.md +34 -50
  234. package/docs/tutorials/getting-started.md +2 -2
  235. package/docs/tutorials/theming-basics.md +13 -10
  236. package/package.json +31 -3
  237. package/dist/src/commands/block.d.ts +0 -5
  238. package/dist/src/commands/block.d.ts.map +0 -1
  239. package/dist/src/commands/block.js +0 -255
  240. package/dist/src/commands/block.js.map +0 -1
  241. package/dist/src/shared/configureFonts.d.ts +0 -6
  242. package/dist/src/shared/configureFonts.d.ts.map +0 -1
  243. package/dist/src/shared/configureFonts.js +0 -106
  244. package/dist/src/shared/configureFonts.js.map +0 -1
  245. package/dist/src/shared/configureGlobalCss.d.ts +0 -6
  246. package/dist/src/shared/configureGlobalCss.d.ts.map +0 -1
  247. package/dist/src/shared/configureGlobalCss.js +0 -154
  248. package/dist/src/shared/configureGlobalCss.js.map +0 -1
  249. package/dist/src/shared/configureTailwind.d.ts +0 -7
  250. package/dist/src/shared/configureTailwind.d.ts.map +0 -1
  251. package/dist/src/shared/configureTailwind.js +0 -163
  252. package/dist/src/shared/configureTailwind.js.map +0 -1
  253. package/dist/src/shared/detectFramework.d.ts +0 -23
  254. package/dist/src/shared/detectFramework.d.ts.map +0 -1
  255. package/dist/src/shared/detectFramework.js +0 -119
  256. package/dist/src/shared/detectFramework.js.map +0 -1
  257. package/dist/src/shared/installBaseUtils.d.ts +0 -5
  258. package/dist/src/shared/installBaseUtils.d.ts.map +0 -1
  259. package/dist/src/shared/installBaseUtils.js +0 -79
  260. package/dist/src/shared/installBaseUtils.js.map +0 -1
  261. package/dist/src/shared/resolveAliases.d.ts +0 -24
  262. package/dist/src/shared/resolveAliases.d.ts.map +0 -1
  263. package/dist/src/shared/resolveAliases.js +0 -98
  264. package/dist/src/shared/resolveAliases.js.map +0 -1
@@ -11,17 +11,22 @@ keywords: [searchable-select, combobox, select, search, async, infinite-scroll,
11
11
 
12
12
  ## Installation
13
13
 
14
+ TORCH Glare is a copy-in library: the CLI copies this component's source into your project
15
+ (you do **not** install it from the npm package). Run `init` once, then `add`:
16
+
14
17
  ```bash
15
- npm install @radix-ui/react-popover
18
+ npx torch-glare@latest init
19
+ npx torch-glare@latest add SearchableSelect
16
20
  ```
17
21
 
18
- SearchableSelect is built on the TORCH `Popover` component (which wraps `@radix-ui/react-popover`) and reuses the `Input`, `Button`, and `DropdownMenu` item styles. All imports come from `@torch-ui/components`.
22
+ `add` also copies any components, hooks, and utilities that `SearchableSelect` depends on.
19
23
 
20
24
  ## Import
21
25
 
22
- ```typescript
23
- import { SearchableSelect } from '@torch-ui/components'
24
- import type { SearchableSelectOption } from '@torch-ui/components'
26
+ Import from your project's local path — the alias configured in `glare.json` (e.g. `@/*`):
27
+
28
+ ```tsx
29
+ import { SearchableSelect } from "@/components/SearchableSelect";
25
30
  ```
26
31
 
27
32
  ## Quick Examples
@@ -31,8 +36,8 @@ import type { SearchableSelectOption } from '@torch-ui/components'
31
36
  Pass a static `options` array and control the selection with `value` / `onValueChange`. Options may include an `icon`. Filtering happens locally by default.
32
37
 
33
38
  ```typescript
34
- import { SearchableSelect } from '@torch-ui/components'
35
- import type { SearchableSelectOption } from '@torch-ui/components'
39
+ import { SearchableSelect } from "@/components/SearchableSelect";
40
+ import type { SearchableSelectOption } from "@/components/SearchableSelect";
36
41
  import { useState } from 'react'
37
42
 
38
43
  const options: SearchableSelectOption[] = [
@@ -62,8 +67,8 @@ function FrameworkPicker() {
62
67
  For large or remote datasets, set `filterClientSide={false}` and drive everything from controlled props: refetch in `onSearchChange` (debounced), append pages in `onLoadMore`, and report `hasMore` / `loading`. Options are rendered as-is — the component does not filter them locally in this mode.
63
68
 
64
69
  ```typescript
65
- import { SearchableSelect } from '@torch-ui/components'
66
- import type { SearchableSelectOption } from '@torch-ui/components'
70
+ import { SearchableSelect } from "@/components/SearchableSelect";
71
+ import type { SearchableSelectOption } from "@/components/SearchableSelect";
67
72
  import { useEffect, useState } from 'react'
68
73
 
69
74
  const PAGE_SIZE = 20
@@ -125,7 +130,7 @@ function UserPicker() {
125
130
  The list shows up to `maxVisibleItems` rows (default `5`) before it scrolls internally.
126
131
 
127
132
  ```typescript
128
- import { SearchableSelect } from '@torch-ui/components'
133
+ import { SearchableSelect } from "@/components/SearchableSelect";
129
134
 
130
135
  function CompactList({ options, value, onValueChange }) {
131
136
  return (
@@ -144,7 +149,7 @@ function CompactList({ options, value, onValueChange }) {
144
149
  Pass `dir="rtl"` to lay out the input, chevron, and dropdown right-to-left.
145
150
 
146
151
  ```typescript
147
- import { SearchableSelect } from '@torch-ui/components'
152
+ import { SearchableSelect } from "@/components/SearchableSelect";
148
153
 
149
154
  function ArabicPicker({ options, value, onValueChange }) {
150
155
  return (
@@ -236,8 +241,8 @@ export function SearchableSelect(props: SearchableSelectProps): JSX.Element
236
241
  React Query's `useInfiniteQuery` maps cleanly onto the controlled async props. Track the debounced query in state, flatten pages into `options`, and wire `fetchNextPage`/`hasNextPage`/`isFetching` to `onLoadMore`/`hasMore`/`loading`.
237
242
 
238
243
  ```typescript
239
- import { SearchableSelect } from '@torch-ui/components'
240
- import type { SearchableSelectOption } from '@torch-ui/components'
244
+ import { SearchableSelect } from "@/components/SearchableSelect";
245
+ import type { SearchableSelectOption } from "@/components/SearchableSelect";
241
246
  import { useInfiniteQuery } from '@tanstack/react-query'
242
247
  import { useMemo, useState } from 'react'
243
248
 
@@ -283,7 +288,7 @@ function UserPicker() {
283
288
  The same shape works with SWR's `useSWRInfinite` — increment `size` in `onLoadMore`, flatten the page array into `options`, and derive `hasMore` from the last page length.
284
289
 
285
290
  ```typescript
286
- import { SearchableSelect } from '@torch-ui/components'
291
+ import { SearchableSelect } from "@/components/SearchableSelect";
287
292
  import useSWRInfinite from 'swr/infinite'
288
293
  import { useState } from 'react'
289
294
 
@@ -1,27 +1,32 @@
1
1
  ---
2
2
  title: SearchableTable
3
- description: A searchable combobox whose dropdown renders a real Table, with single-select, client- or server-side search, and infinite-scroll pagination
3
+ description: A field that opens a modal dialog to pick a row from a real Table, with single-select, client- or server-side search, and infinite-scroll pagination
4
4
  group: Inputs
5
- keywords: [searchable-table, combobox, table, search, async, infinite-scroll, pagination, select]
5
+ keywords: [searchable-table, dialog, table, search, async, infinite-scroll, pagination, select, picker]
6
6
  ---
7
7
 
8
8
  # SearchableTable
9
9
 
10
- > A searchable combobox that renders its options as a real, multi-column `Table` inside a `Popover`. Type to filter rows (client-side) or refetch them from a backend (server-side), click a row to single-select it, and scroll to the bottom to lazy-load more pages. Generic over your row type `T`.
10
+ > A field that opens a modal `Dialog` to pick a row from a real, multi-column `Table`. The trigger shows a placeholder until something is selected, then the selected row's label. Click it to open the dialog (a search input + the data table); type to filter rows (client-side) or refetch them from a backend (server-side), click a row to single-select it and close the dialog, and scroll to the bottom to lazy-load more pages. Generic over your row type `T`.
11
11
 
12
12
  ## Installation
13
13
 
14
- `SearchableTable` is part of the TORCH Glare component library. It composes the [Popover](./popover.md) (built on Radix Popover) and the [Table](./table.md) component internally, so both must be available — they ship with the library.
14
+ TORCH Glare is a copy-in library: the CLI copies this component's source into your project
15
+ (you do **not** install it from the npm package). Run `init` once, then `add`:
15
16
 
16
17
  ```bash
17
- npm install @torch-ui/components @radix-ui/react-popover
18
+ npx torch-glare@latest init
19
+ npx torch-glare@latest add SearchableTable
18
20
  ```
19
21
 
22
+ `add` also copies any components, hooks, and utilities that `SearchableTable` depends on.
23
+
20
24
  ## Import
21
25
 
22
- ```typescript
23
- import { SearchableTable } from '@torch-ui/components'
24
- import type { SearchableTableColumn } from '@torch-ui/components'
26
+ Import from your project's local path — the alias configured in `glare.json` (e.g. `@/*`):
27
+
28
+ ```tsx
29
+ import { SearchableTable } from "@/components/SearchableTable";
25
30
  ```
26
31
 
27
32
  ## Quick Examples
@@ -31,8 +36,8 @@ import type { SearchableTableColumn } from '@torch-ui/components'
31
36
  Provide a `columns` config and a `rows` array. Single-select is controlled via `value` / `onSelect`. Use `getLabel` to control the text shown in the input after selection, and `getRowId` to give each row a stable key.
32
37
 
33
38
  ```typescript
34
- import { SearchableTable } from '@torch-ui/components'
35
- import type { SearchableTableColumn } from '@torch-ui/components'
39
+ import { SearchableTable } from "@/components/SearchableTable";
40
+ import type { SearchableTableColumn } from "@/components/SearchableTable";
36
41
  import { useState } from 'react'
37
42
 
38
43
  type User = { id: string; name: string; role: string; email: string }
@@ -60,13 +65,15 @@ function Example() {
60
65
  onSelect={setSelected}
61
66
  getLabel={(row) => row.name}
62
67
  getRowId={(row) => row.id}
63
- placeholder="Search users…"
68
+ icon={<i className="ri-user-line" />}
69
+ placeholder="Select a user…"
70
+ title="Select a user"
64
71
  />
65
72
  )
66
73
  }
67
74
  ```
68
75
 
69
- The input opens its dropdown on focus. As you type, rows are filtered locally by every column key. Clicking a row selects it (single-select), closes the dropdown, and shows `getLabel(row)` in the input.
76
+ Clicking the field opens a modal dialog with a search input and the data table. As you type, rows are filtered locally by every column key. Clicking a row selects it (single-select), closes the dialog, and shows `getLabel(row)` on the trigger. Use `placeholder` for the trigger text, `searchPlaceholder` for the in-dialog search input, and `title` for the dialog's search-field label (also the accessible dialog title).
70
77
 
71
78
  ### Custom cell rendering
72
79
 
@@ -95,8 +102,8 @@ const columns: SearchableTableColumn<User>[] = [
95
102
  Set `filterClientSide={false}` and treat `rows` as already-filtered server data. `onSearchChange` fires (debounced by `searchDebounceMs`, default 300ms) whenever the query settles — refetch there. As the list nears the bottom, `onLoadMore` fires while `hasMore && !loading`; a loading row renders whenever `loading` is true.
96
103
 
97
104
  ```typescript
98
- import { SearchableTable } from '@torch-ui/components'
99
- import type { SearchableTableColumn } from '@torch-ui/components'
105
+ import { SearchableTable } from "@/components/SearchableTable";
106
+ import type { SearchableTableColumn } from "@/components/SearchableTable";
100
107
  import { useEffect, useState } from 'react'
101
108
 
102
109
  type User = { id: string; name: string; role: string; email: string }
@@ -159,26 +166,35 @@ function AsyncExample() {
159
166
  hasMore={hasMore}
160
167
  loading={loading}
161
168
  onLoadMore={loadMore}
162
- placeholder="Search users…"
169
+ icon={<i className="ri-user-line" />}
170
+ placeholder="Select a user…"
171
+ title="Select a user"
172
+ searchPlaceholder="Search users… (scroll to load more)"
163
173
  />
164
174
  )
165
175
  }
166
176
  ```
167
177
 
168
- ### Capping the visible height
178
+ ### Dialog labels (trigger, title, search)
169
179
 
170
- `maxVisibleRows` (default `6`) caps how many rows are visible before the dropdown scrolls vertically. The remaining rows stay reachable via scroll, which is also what drives infinite-scroll loading.
180
+ Three props control the dialog's text. `placeholder` is the trigger text shown until a row is selected; `title` labels the dialog's search field (and is the accessible dialog title); `searchPlaceholder` is the placeholder of the in-dialog search input.
171
181
 
172
182
  ```typescript
173
183
  <SearchableTable<User>
174
184
  columns={columns}
175
185
  rows={users}
186
+ value={selected}
176
187
  onSelect={setSelected}
177
188
  getRowId={(row) => row.id}
178
- maxVisibleRows={4}
189
+ icon={<i className="ri-user-line" />}
190
+ placeholder="Select a user…"
191
+ title="Select a user"
192
+ searchPlaceholder="Search by name or role…"
179
193
  />
180
194
  ```
181
195
 
196
+ The data table inside the dialog scrolls vertically once it exceeds `~55vh`; the remaining rows stay reachable via scroll, which is also what drives infinite-scroll loading.
197
+
182
198
  ## API Reference
183
199
 
184
200
  ### SearchableTable&lt;T&gt;
@@ -188,24 +204,25 @@ function AsyncExample() {
188
204
  | `columns` | `SearchableTableColumn<T>[]` | Required | Column config — header, the row key to read, and an optional cell renderer. |
189
205
  | `rows` | `T[]` | Required | The data rows. In server mode these are rendered as-is (already filtered upstream). |
190
206
  | `value` | `T \| null` | - | Controlled selected row. The matching row is highlighted in the table. |
191
- | `onSelect` | `(row: T) => void` | - | Called when a row is clicked. The dropdown closes and the input shows the row's label. |
192
- | `getLabel` | `(row: T) => string` | First column's value | Text shown in the input after selection. |
207
+ | `onSelect` | `(row: T) => void` | - | Called when a row is clicked. The dialog closes and the trigger shows the row's label. |
208
+ | `getLabel` | `(row: T) => string` | First column's value | Text shown on the trigger after selection. |
193
209
  | `getRowId` | `(row: T) => string` | `JSON.stringify(row)` | Stable id/key per row, used for keys and selection matching. |
194
210
  | `searchKeys` | `(keyof T & string)[]` | Every column `key` | Which fields client-side search matches against. |
195
- | `placeholder` | `string` | `'Search…'` | Input placeholder text. |
196
- | `size` | `'XS' \| 'S' \| 'M'` | `'M'` | Input size. (`XS` maps the underlying Group to `S` with a tighter input height.) |
197
- | `variant` | `'PresentationStyle'` | `'PresentationStyle'` | Visual style of the input and dropdown surface. |
198
- | `icon` | `ReactNode` | - | Optional leading icon rendered inside the input. |
211
+ | `placeholder` | `string` | `'Select…'` | Trigger placeholder shown until a row is selected. |
212
+ | `searchPlaceholder` | `string` | `'Search…'` | Placeholder for the search input inside the dialog. |
213
+ | `title` | `string` | `'Select an item'` | Label shown on the dialog's search field (also used as the accessible dialog title). |
214
+ | `size` | `'XS' \| 'S' \| 'M'` | `'M'` | Trigger size. (`XS` maps the underlying Group to `S` with a tighter input height.) |
215
+ | `variant` | `'PresentationStyle'` | `'PresentationStyle'` | Visual style of the trigger field. |
216
+ | `icon` | `ReactNode` | - | Optional leading icon rendered inside the trigger. |
199
217
  | `theme` | `'dark' \| 'light' \| 'default'` | - | Theme variant, applied via `data-theme`. |
200
- | `dir` | `string` | - | Text direction (e.g. `'rtl'`), applied to the input group and dropdown. |
201
- | `className` | `string` | - | Additional classes merged onto the input group. |
218
+ | `dir` | `string` | - | Text direction (e.g. `'rtl'`), applied to the trigger and dialog. |
219
+ | `className` | `string` | - | Additional classes merged onto the trigger group. |
202
220
  | `filterClientSide` | `boolean` | `true` | When `true`, filter `rows` locally by `searchKeys`. Set `false` for server-side search. |
203
221
  | `onSearchChange` | `(query: string) => void` | - | Debounced query callback — refetch your data here in server mode. |
204
222
  | `searchDebounceMs` | `number` | `300` | Debounce delay (ms) for `onSearchChange`. |
205
223
  | `hasMore` | `boolean` | `false` | Whether more pages are available; gates the infinite-scroll loader. |
206
224
  | `loading` | `boolean` | `false` | Whether a fetch is in flight; renders a loading row and blocks `onLoadMore`. |
207
225
  | `onLoadMore` | `() => void` | - | Called when the list nears the bottom and `hasMore && !loading`. |
208
- | `maxVisibleRows` | `number` | `6` | Max rows visible before the list scrolls vertically. |
209
226
 
210
227
  ### SearchableTableColumn&lt;T&gt;
211
228
 
@@ -232,7 +249,7 @@ export interface SearchableTableColumn<T> {
232
249
  ### Component signature
233
250
 
234
251
  ```typescript
235
- import { Themes } from '@torch-ui/components'
252
+ import { Themes } from "@/utils/types";
236
253
 
237
254
  interface SearchableTableProps<T> {
238
255
  columns: SearchableTableColumn<T>[]
@@ -242,7 +259,9 @@ interface SearchableTableProps<T> {
242
259
  getLabel?: (row: T) => string
243
260
  getRowId?: (row: T) => string
244
261
  searchKeys?: (keyof T & string)[]
245
- placeholder?: string
262
+ placeholder?: string // trigger text until selection (default 'Select…')
263
+ searchPlaceholder?: string // in-dialog search input (default 'Search…')
264
+ title?: string // dialog search-field label / a11y title (default 'Select an item')
246
265
  size?: 'XS' | 'S' | 'M'
247
266
  variant?: 'PresentationStyle'
248
267
  icon?: React.ReactNode
@@ -256,7 +275,6 @@ interface SearchableTableProps<T> {
256
275
  hasMore?: boolean
257
276
  loading?: boolean
258
277
  onLoadMore?: () => void
259
- maxVisibleRows?: number
260
278
  }
261
279
 
262
280
  // Generic function component, constrained so rows are object-shaped:
@@ -274,8 +292,8 @@ Always pass the type argument explicitly (`<SearchableTable<User> … />`) so `c
274
292
  Wire pagination through `useInfiniteQuery`, flatten the pages into `rows`, and map the query's state onto `hasMore` / `loading` / `onLoadMore`.
275
293
 
276
294
  ```typescript
277
- import { SearchableTable } from '@torch-ui/components'
278
- import type { SearchableTableColumn } from '@torch-ui/components'
295
+ import { SearchableTable } from "@/components/SearchableTable";
296
+ import type { SearchableTableColumn } from "@/components/SearchableTable";
279
297
  import { useInfiniteQuery } from '@tanstack/react-query'
280
298
  import { useState } from 'react'
281
299
 
@@ -341,7 +359,7 @@ function UserPicker() {
341
359
  ### SWR — `useSWRInfinite`
342
360
 
343
361
  ```typescript
344
- import { SearchableTable } from '@torch-ui/components'
362
+ import { SearchableTable } from "@/components/SearchableTable";
345
363
  import useSWRInfinite from 'swr/infinite'
346
364
  import { useState } from 'react'
347
365
 
@@ -395,25 +413,26 @@ function toUser(api: ApiUser): User {
395
413
 
396
414
  ## Accessibility
397
415
 
398
- - The trigger is a standard text `<input>`, so it is focusable and typeable by keyboard. Focusing it opens the dropdown and starts a fresh search.
399
- - The chevron is a boxed icon button with `aria-label` toggling between `"Open"` and `"Close"`; it is `tabIndex={-1}` so keyboard focus stays on the input, and it uses `onMouseDown` with `preventDefault` to avoid the input blur/focus race.
400
- - Clicking outside the input group or the dropdown closes it (handled via `useClickOutside`).
401
- - The dropdown surface does not steal focus on open (`onOpenAutoFocus` is prevented), keeping the caret in the input while results update.
402
- - Provide a descriptive `placeholder` and a meaningful `getLabel` so screen-reader users hear a clear value after selection. Prefer human-readable headers in `columns`.
416
+ - The trigger is a focusable group with `role="button"` and `tabIndex={0}`; activating it opens a modal dialog (Radix Dialog), which traps focus and closes on `Esc` or outside click.
417
+ - On open, focus moves to the in-dialog search input (`onOpenAutoFocus` is intercepted to focus the search field rather than the first row), so users can type to filter immediately.
418
+ - The dialog always renders a `DialogTitle` (visually hidden, sourced from `title`) so the modal has an accessible name even though the heading is not shown.
419
+ - Clicking a row selects it and closes the dialog; the selected row is marked with the Table's `state="selected"`.
420
+ - Provide a descriptive `placeholder`, a meaningful `title`, and a `getLabel` so screen-reader users hear a clear value after selection. Prefer human-readable headers in `columns`.
403
421
 
404
422
  ## Best Practices
405
423
 
406
424
  1. **Always supply `getRowId`.** The default key is `JSON.stringify(row)`, which is slow and brittle for large or nested rows. A stable id keeps keys and selection matching cheap.
407
- 2. **Set `getLabel` for the selected display.** Otherwise the input shows the first column's raw value, which may not be the most descriptive field.
425
+ 2. **Set `getLabel` for the selected display.** Otherwise the trigger shows the first column's raw value, which may not be the most descriptive field.
408
426
  3. **Pick the right search mode.** Keep `filterClientSide` (default) for small, in-memory datasets. Switch to `filterClientSide={false}` + `onSearchChange` for backend-driven search so you never filter a partial page.
409
427
  4. **Gate pagination correctly.** `onLoadMore` only fires while `hasMore && !loading` — keep `loading` accurate so a single scroll doesn't trigger duplicate fetches, and flip `hasMore` to `false` on the last page.
410
- 5. **The table scrolls in both directions.** Columns keep their natural width, so wide tables scroll horizontally inside the dropdown, while `maxVisibleRows` caps the vertical height and the rest scrolls vertically (which is what drives infinite scroll). Keep column count and content reasonable so horizontal scroll stays usable.
428
+ 5. **The table scrolls in both directions.** Columns keep their natural width, so wide tables scroll horizontally inside the dialog, while the table caps its vertical height (~`55vh`) and the rest scrolls vertically (which is what drives infinite scroll). Keep column count and content reasonable so horizontal scroll stays usable.
411
429
  6. **Tune `searchDebounceMs` to your backend.** The 300ms default suits most APIs; raise it for slow or rate-limited endpoints.
412
430
  7. **Match `searchKeys` to visible columns.** In client mode, search defaults to every column key — narrow `searchKeys` if some columns hold non-searchable data (ids, formatted dates).
413
431
 
414
432
  ## Related Components
415
433
 
416
434
  - [SearchableSelect](./searchable-select.md) - Single-column searchable combobox
417
- - [Table](./table.md) - The table primitive rendered inside the dropdown
435
+ - [Table](./table.md) - The table primitive rendered inside the dialog
436
+ - [Dialog](./dialog.md) - The modal surface the picker opens in
418
437
  - [DataTable](./data-table.md) - Full-featured data grid for page-level tables
419
438
  - [Select](./select.md) - Standard form select field
@@ -0,0 +1,190 @@
1
+ ---
2
+ title: SearchableTreeDialog
3
+ description: Searchable tree-select presented in a modal dialog — a trigger opens a dialog containing a searchable hierarchical tree, with client or server-side search
4
+ group: Inputs
5
+ keywords: [searchable-tree-dialog, tree, tree-select, dialog, modal, hierarchy, search, async]
6
+ ---
7
+
8
+ # SearchableTreeDialog
9
+
10
+ > A searchable tree-select that opens in a modal **Dialog** instead of a popover. A trigger
11
+ > field opens a dialog containing a search input and a hierarchical tree of nodes, filtered as
12
+ > you type. Accepts nested data (via `getNodeChildren`) or a flat list it nests for you (via
13
+ > `parentIdKey`). Supports local filtering or debounced server-side search, optional folder
14
+ > selection, and controlled selection. Use it instead of [SearchableTree](./searchable-tree.md)
15
+ > when the tree is large or you want a focused, full-screen-friendly selection surface.
16
+
17
+ ## Installation
18
+
19
+ TORCH Glare is a copy-in library: the CLI copies this component's source into your project
20
+ (you do **not** install it from the npm package). Run `init` once, then `add`:
21
+
22
+ ```bash
23
+ npx torch-glare@latest init
24
+ npx torch-glare@latest add SearchableTreeDialog
25
+ ```
26
+
27
+ The CLI also copies its internal dependencies: `Button`, `Dialog`, `Input`, and the
28
+ `cn`/`types` utils.
29
+
30
+ ## Import
31
+
32
+ ```typescript
33
+ import { SearchableTreeDialog } from "@/components/SearchableTreeDialog";
34
+ ```
35
+
36
+ ## Quick Examples
37
+
38
+ ### Basic Usage (nested data)
39
+
40
+ ```tsx
41
+ import { SearchableTreeDialog } from "@/components/SearchableTreeDialog";
42
+
43
+ type Node = { id: string; name: string; children?: Node[] };
44
+
45
+ const data: Node[] = [
46
+ {
47
+ id: "eng",
48
+ name: "Engineering",
49
+ children: [
50
+ { id: "fe", name: "Frontend" },
51
+ { id: "be", name: "Backend" },
52
+ ],
53
+ },
54
+ { id: "design", name: "Design" },
55
+ ];
56
+
57
+ export function DepartmentPicker() {
58
+ const [value, setValue] = useState<Node | null>(null);
59
+
60
+ return (
61
+ <SearchableTreeDialog
62
+ nodes={data}
63
+ getNodeId={(n) => n.id}
64
+ getNodeLabel={(n) => n.name}
65
+ getNodeChildren={(n) => n.children}
66
+ value={value}
67
+ onSelect={setValue}
68
+ placeholder="Select a department"
69
+ searchPlaceholder="Search departments…"
70
+ title="Departments"
71
+ size="M"
72
+ />
73
+ );
74
+ }
75
+ ```
76
+
77
+ ### Flat data (built from a parent id)
78
+
79
+ ```tsx
80
+ type Row = { id: string; label: string; parentId: string | null };
81
+
82
+ const rows: Row[] = [
83
+ { id: "1", label: "Company", parentId: null },
84
+ { id: "2", label: "Sales", parentId: "1" },
85
+ { id: "3", label: "EMEA", parentId: "2" },
86
+ ];
87
+
88
+ <SearchableTreeDialog
89
+ nodes={rows}
90
+ parentIdKey="parentId"
91
+ getNodeId={(r) => r.id}
92
+ getNodeLabel={(r) => r.label}
93
+ onSelect={(r) => console.log(r.id)}
94
+ />;
95
+ ```
96
+
97
+ ### Server-side search
98
+
99
+ ```tsx
100
+ <SearchableTreeDialog
101
+ nodes={results}
102
+ getNodeId={(n) => n.id}
103
+ getNodeLabel={(n) => n.name}
104
+ getNodeChildren={(n) => n.children}
105
+ filterClientSide={false}
106
+ onSearchChange={(query) => fetchNodes(query)}
107
+ loading={isFetching}
108
+ searchDebounceMs={300}
109
+ />
110
+ ```
111
+
112
+ ## API Reference
113
+
114
+ ### Props
115
+
116
+ | Prop | Type | Default | Description |
117
+ |------|------|---------|-------------|
118
+ | `nodes` | `T[]` | — | Tree data. Nested by default; treated as a flat list when `parentIdKey` is set. |
119
+ | `getNodeId` | `(node: T) => string` | — | Returns a stable unique id for a node. |
120
+ | `getNodeLabel` | `(node: T) => ReactNode` | — | Returns a node's display label. |
121
+ | `getNodeChildren` | `(node: T) => T[] \| undefined` | — | Returns a node's children (nested mode). Ignored when `parentIdKey` is set. |
122
+ | `parentIdKey` | `keyof T & string` | — | When set, `nodes` is a FLAT list and the tree is built from this parent-id key. |
123
+ | `value` | `T \| null` | — | Controlled selected node. |
124
+ | `onSelect` | `(node: T) => void` | — | Called when a node is selected. |
125
+ | `getSearchText` | `(node: T) => string` | `String(getNodeLabel(node))` | Plain text used for search matching. |
126
+ | `placeholder` | `string` | — | Trigger placeholder shown until a node is selected. |
127
+ | `searchPlaceholder` | `string` | — | Placeholder for the search input inside the dialog. |
128
+ | `title` | `string` | — | Label shown on the dialog's search field. |
129
+ | `size` | `"XS" \| "S" \| "M"` | `"M"` | Trigger size. |
130
+ | `variant` | `"SystemStyle" \| "PresentationStyle"` | `"PresentationStyle"` | Visual style. `SystemStyle` is reserved for internal library system surfaces. |
131
+ | `icon` | `ReactNode` | — | Leading icon in the trigger. |
132
+ | `theme` | `Themes` | — | `"dark" \| "light" \| "default"`. |
133
+ | `dir` | `string` | — | Text direction. |
134
+ | `className` | `string` | — | Extra classes for the trigger. |
135
+ | `defaultExpanded` | `boolean` | `true` | Expand every node when the dialog opens. |
136
+ | `selectableFolders` | `boolean` | `false` | When true, folder nodes are selectable too; otherwise folders only expand/collapse. |
137
+ | `filterClientSide` | `boolean` | `true` | Filter locally. Set `false` for server-side search. |
138
+ | `onSearchChange` | `(query: string) => void` | — | Debounced as the user types — refetch your data here (server search). |
139
+ | `searchDebounceMs` | `number` | `300` | Debounce for `onSearchChange`. |
140
+ | `loading` | `boolean` | `false` | Whether a fetch is in flight; shows a loading indicator. |
141
+
142
+ ### TypeScript
143
+
144
+ ```typescript
145
+ import { ReactNode } from "react";
146
+ import { Themes } from "@/utils/types";
147
+
148
+ interface SearchableTreeDialogProps<T> {
149
+ nodes: T[];
150
+ getNodeId: (node: T) => string;
151
+ getNodeLabel: (node: T) => ReactNode;
152
+ getNodeChildren?: (node: T) => T[] | undefined;
153
+ parentIdKey?: keyof T & string;
154
+ value?: T | null;
155
+ onSelect?: (node: T) => void;
156
+ getSearchText?: (node: T) => string;
157
+ placeholder?: string;
158
+ searchPlaceholder?: string;
159
+ title?: string;
160
+ size?: "XS" | "S" | "M";
161
+ variant?: "SystemStyle" | "PresentationStyle";
162
+ icon?: ReactNode;
163
+ theme?: Themes;
164
+ dir?: string;
165
+ className?: string;
166
+ defaultExpanded?: boolean;
167
+ selectableFolders?: boolean;
168
+ filterClientSide?: boolean;
169
+ onSearchChange?: (query: string) => void;
170
+ searchDebounceMs?: number;
171
+ loading?: boolean;
172
+ }
173
+
174
+ export function SearchableTreeDialog<T>(props: SearchableTreeDialogProps<T>): JSX.Element;
175
+ ```
176
+
177
+ ## Common Patterns
178
+
179
+ - **Dialog vs popover:** use `SearchableTreeDialog` for large trees or a focused modal
180
+ selection; use [SearchableTree](./searchable-tree.md) for an inline popover field.
181
+ - **Nested vs flat data:** provide `getNodeChildren` for nested data, or `parentIdKey` to nest
182
+ a flat list. Do not use both.
183
+ - **Async search:** set `filterClientSide={false}`, handle `onSearchChange` to fetch, and pass
184
+ `loading` while the request is in flight.
185
+
186
+ ## Related Components
187
+
188
+ - [SearchableTree](./searchable-tree.md) — the same tree-select on an inline Popover.
189
+ - [SearchableSelect](./searchable-select.md) — flat searchable single-select combobox.
190
+ - [Dialog](./dialog.md) — the modal surface this component builds on.