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.
- package/README.md +8 -0
- package/apps/lib/components/ActionButton.tsx +7 -12
- package/apps/lib/components/ActionsGroup.tsx +4 -15
- package/apps/lib/components/AlertDialog.tsx +173 -191
- package/apps/lib/components/Avatar.tsx +13 -16
- package/apps/lib/components/Badge.tsx +7 -3
- package/apps/lib/components/BadgeField.tsx +76 -71
- package/apps/lib/components/Breadcrumb.tsx +49 -85
- package/apps/lib/components/Button.tsx +20 -19
- package/apps/lib/components/ButtonGroup.tsx +31 -41
- package/apps/lib/components/Calendar.tsx +51 -29
- package/apps/lib/components/Card.tsx +2 -13
- package/apps/lib/components/ChartBlockTool.ts +17 -52
- package/apps/lib/components/Checkbox.tsx +9 -16
- package/apps/lib/components/ContextMenu.tsx +110 -106
- package/apps/lib/components/CountBadge.tsx +4 -6
- package/apps/lib/components/DataTable.tsx +196 -216
- package/apps/lib/components/DataViews/DataViewRadio.tsx +10 -8
- package/apps/lib/components/DataViews/DataViewsConfigPanel.tsx +21 -55
- package/apps/lib/components/DataViews/DataViewsHeader.tsx +1 -3
- package/apps/lib/components/DataViews/DataViewsLayout.tsx +5 -12
- package/apps/lib/components/DataViews/FilterPanel.tsx +173 -150
- package/apps/lib/components/DataViews/HeaderSearch.tsx +1 -5
- package/apps/lib/components/DataViews/InboxView.tsx +22 -68
- package/apps/lib/components/DataViews/InboxViewCard.tsx +68 -77
- package/apps/lib/components/DataViews/KanbanView.tsx +8 -25
- package/apps/lib/components/DataViews/PanelControls.tsx +3 -13
- package/apps/lib/components/DataViews/SettingsPanel.tsx +83 -89
- package/apps/lib/components/DataViews/TableView.tsx +8 -28
- package/apps/lib/components/DataViews/TreeView.tsx +12 -40
- package/apps/lib/components/DataViews/badgeAdapter.ts +32 -28
- package/apps/lib/components/DataViews/fieldRenderers.tsx +106 -141
- package/apps/lib/components/DataViews/filters/DatePickerRangeFilter.tsx +87 -0
- package/apps/lib/components/DataViews/filters/DateRangePopover.tsx +39 -32
- package/apps/lib/components/DataViews/filters/PresetChips.tsx +15 -15
- package/apps/lib/components/DataViews/filters/RangeSliderWithInputs.tsx +69 -58
- package/apps/lib/components/DataViews/index.ts +26 -26
- package/apps/lib/components/DataViews/tree/TreeDrawer.tsx +12 -16
- package/apps/lib/components/DataViews/tree/TreeSidebar.tsx +28 -31
- package/apps/lib/components/DataViews/types.ts +111 -114
- package/apps/lib/components/DatePicker.tsx +198 -191
- package/apps/lib/components/Dialog.tsx +92 -105
- package/apps/lib/components/Divider.tsx +21 -24
- package/apps/lib/components/Drawer.tsx +79 -139
- package/apps/lib/components/DropdownMenu.tsx +144 -181
- package/apps/lib/components/FieldHint.tsx +4 -11
- package/apps/lib/components/Form.tsx +146 -155
- package/apps/lib/components/FormStepper.tsx +9 -39
- package/apps/lib/components/HeaderBar.tsx +119 -0
- package/apps/lib/components/ImageAttachment.tsx +111 -73
- package/apps/lib/components/InnerLabelField.tsx +20 -16
- package/apps/lib/components/Input.tsx +171 -172
- package/apps/lib/components/InputField.tsx +44 -45
- package/apps/lib/components/InputOTP.tsx +68 -69
- package/apps/lib/components/Label.tsx +31 -35
- package/apps/lib/components/LabelField.tsx +4 -5
- package/apps/lib/components/LabeledCheckBox.tsx +8 -12
- package/apps/lib/components/LabeledRadio.tsx +7 -5
- package/apps/lib/components/LinkButton.tsx +2 -9
- package/apps/lib/components/LoginButton.tsx +4 -9
- package/apps/lib/components/PasswordLevel.tsx +5 -5
- package/apps/lib/components/Popover.tsx +15 -33
- package/apps/lib/components/ProfileMenu.tsx +67 -72
- package/apps/lib/components/Radio.tsx +1 -5
- package/apps/lib/components/RadioCard.tsx +9 -28
- package/apps/lib/components/ScrollArea.tsx +45 -43
- package/apps/lib/components/SearchField.tsx +49 -39
- package/apps/lib/components/SearchableSelect.tsx +46 -52
- package/apps/lib/components/SearchableTable.tsx +199 -219
- package/apps/lib/components/SearchableTree.tsx +469 -0
- package/apps/lib/components/SearchableTreeDialog.tsx +493 -0
- package/apps/lib/components/SectionBlock.tsx +4 -16
- package/apps/lib/components/Select.tsx +53 -80
- package/apps/lib/components/SimpleSelect.tsx +196 -187
- package/apps/lib/components/Skeleton.tsx +9 -12
- package/apps/lib/components/SlideDatePicker.tsx +215 -166
- package/apps/lib/components/SpinLoading.tsx +33 -49
- package/apps/lib/components/Stepper.tsx +13 -56
- package/apps/lib/components/Switch.tsx +8 -10
- package/apps/lib/components/TabFormItem.tsx +3 -12
- package/apps/lib/components/TabSwitch.tsx +7 -34
- package/apps/lib/components/Table.tsx +60 -75
- package/apps/lib/components/TableDnDWrapper.ts +15 -48
- package/apps/lib/components/TextEditor.tsx +93 -104
- package/apps/lib/components/Textarea.tsx +8 -12
- package/apps/lib/components/Timeline.tsx +21 -41
- package/apps/lib/components/Toast.tsx +83 -83
- package/apps/lib/components/Toggle.tsx +112 -114
- package/apps/lib/components/ToggleButton.tsx +1 -1
- package/apps/lib/components/Tooltip.tsx +24 -19
- package/apps/lib/components/TransparentLabel.tsx +61 -62
- package/apps/lib/components/TreeDropDown.tsx +86 -52
- package/apps/lib/components/TreeFolder/TreeFolder.tsx +15 -48
- package/apps/lib/components/TreeFolder/TreeFolderBreadcrumb.tsx +16 -21
- package/apps/lib/components/TreeFolder/TreeFolderRow.tsx +9 -20
- package/apps/lib/components/TreeFolder/TreeFolderStyles.tsx +3 -3
- package/apps/lib/components/TreeFolder/icons.tsx +14 -17
- package/apps/lib/components/TreeFolder/index.ts +15 -8
- package/apps/lib/components/TreeFolder/treeFolderUtils.ts +47 -52
- package/apps/lib/components/TreeFolder/types.ts +32 -32
- package/apps/lib/components/TreeFolder/useTreeFolderDnD.ts +117 -123
- package/apps/lib/hooks/useActiveTreeItem.ts +52 -55
- package/apps/lib/hooks/useClickOutside.ts +21 -20
- package/apps/lib/hooks/useDataViewsState.ts +86 -80
- package/apps/lib/hooks/useIsMobile.ts +12 -12
- package/apps/lib/hooks/useResize.ts +54 -65
- package/apps/lib/hooks/useTagSelection.ts +201 -201
- package/apps/lib/layouts/CNLayout.tsx +181 -140
- package/apps/lib/layouts/DataViewCard.tsx +4 -18
- package/apps/lib/layouts/FieldSection.tsx +2 -6
- package/apps/lib/layouts/TreeSubLayout.tsx +24 -38
- package/apps/lib/providers/ThemeProvider.tsx +8 -23
- package/apps/lib/registry.json +1004 -0
- package/apps/lib/tsconfig.json +9 -0
- package/apps/lib/utils/cn.ts +1 -1
- package/apps/lib/utils/dataViews/columnUtils.ts +46 -44
- package/apps/lib/utils/dataViews/fieldUtils.ts +70 -71
- package/apps/lib/utils/dataViews/nestedDataUtils.tsx +129 -122
- package/apps/lib/utils/dataViews/pathUtils.ts +74 -67
- package/apps/lib/utils/dataViews/rangeUtils.ts +121 -112
- package/apps/lib/utils/dataViews/treeUtils.ts +204 -211
- package/apps/lib/utils/dateFormat.ts +64 -54
- package/apps/lib/utils/markdownParser.ts +2 -7
- package/apps/lib/utils/resize.ts +21 -25
- package/apps/lib/utils/types.ts +13 -13
- package/dist/bin/index.js +8 -2
- package/dist/bin/index.js.map +1 -1
- package/dist/src/shared/getAvailableFiles.d.ts.map +1 -1
- package/dist/src/shared/getAvailableFiles.js +6 -1
- package/dist/src/shared/getAvailableFiles.js.map +1 -1
- package/dist/src/shared/loadRegistry.d.ts +7 -0
- package/dist/src/shared/loadRegistry.d.ts.map +1 -0
- package/dist/src/shared/loadRegistry.js +31 -0
- package/dist/src/shared/loadRegistry.js.map +1 -0
- package/dist/src/shared/resolveInstallPlan.d.ts +12 -0
- package/dist/src/shared/resolveInstallPlan.d.ts.map +1 -0
- package/dist/src/shared/resolveInstallPlan.js +39 -0
- package/dist/src/shared/resolveInstallPlan.js.map +1 -0
- package/dist/src/types/main.d.ts +24 -0
- package/dist/src/types/main.d.ts.map +1 -1
- package/docs/README.md +10 -2
- package/docs/components/action-button.md +30 -24
- package/docs/components/actions-group.md +26 -20
- package/docs/components/alert-dialog.md +45 -45
- package/docs/components/avatar.md +6 -2
- package/docs/components/badge-field.md +6 -2
- package/docs/components/badge.md +7 -1
- package/docs/components/breadcrumb.md +15 -40
- package/docs/components/button-group.md +13 -10
- package/docs/components/button.md +58 -33
- package/docs/components/calendar.md +26 -19
- package/docs/components/card.md +19 -16
- package/docs/components/chart-block-tool.md +128 -0
- package/docs/components/checkbox.md +16 -8
- package/docs/components/cn-layout.md +7 -1
- package/docs/components/context-menu.md +47 -24
- package/docs/components/count-badge.md +7 -1
- package/docs/components/data-table.md +45 -33
- package/docs/components/data-views-config-panel.md +18 -14
- package/docs/components/data-views-layout.md +20 -17
- package/docs/components/date-picker.md +30 -23
- package/docs/components/dialog.md +43 -42
- package/docs/components/divider.md +10 -4
- package/docs/components/drawer.md +8 -33
- package/docs/components/dropdown-menu.md +51 -32
- package/docs/components/field-hint.md +37 -25
- package/docs/components/field-section.md +9 -3
- package/docs/components/form-stepper.md +6 -0
- package/docs/components/form.md +17 -20
- package/docs/components/header-bar.md +187 -0
- package/docs/components/image-attachment.md +33 -41
- package/docs/components/inbox-view.md +16 -5
- package/docs/components/inner-label-field.md +34 -24
- package/docs/components/input-field.md +18 -10
- package/docs/components/input-otp.md +14 -11
- package/docs/components/input.md +22 -16
- package/docs/components/kanban-view.md +15 -5
- package/docs/components/label-field.md +32 -22
- package/docs/components/label.md +43 -32
- package/docs/components/labeled-check-box.md +16 -8
- package/docs/components/labeled-radio.md +18 -11
- package/docs/components/link-button.md +7 -1
- package/docs/components/login-button.md +8 -2
- package/docs/components/password-level.md +37 -24
- package/docs/components/popover.md +26 -23
- package/docs/components/profile-menu.md +38 -27
- package/docs/components/radio-card.md +16 -9
- package/docs/components/radio.md +16 -8
- package/docs/components/scroll-area.md +6 -2
- package/docs/components/search-field.md +14 -6
- package/docs/components/searchable-select.md +19 -14
- package/docs/components/searchable-table.md +60 -41
- package/docs/components/searchable-tree-dialog.md +190 -0
- package/docs/components/searchable-tree.md +200 -0
- package/docs/components/section-block.md +130 -4
- package/docs/components/select.md +23 -18
- package/docs/components/simple-select.md +14 -10
- package/docs/components/skeleton.md +43 -33
- package/docs/components/slide-date-picker.md +37 -29
- package/docs/components/spin-loading.md +32 -23
- package/docs/components/stepper.md +6 -0
- package/docs/components/switch.md +14 -6
- package/docs/components/tab-form-item.md +7 -1
- package/docs/components/tab-switch.md +13 -6
- package/docs/components/table-dnd-wrapper.md +105 -0
- package/docs/components/table-view.md +15 -5
- package/docs/components/table.md +28 -28
- package/docs/components/text-editor.md +19 -13
- package/docs/components/textarea.md +15 -7
- package/docs/components/timeline.md +6 -0
- package/docs/components/toast.md +35 -26
- package/docs/components/toggle-button.md +24 -21
- package/docs/components/toggle.md +23 -15
- package/docs/components/tooltip.md +42 -30
- package/docs/components/transparent-label.md +29 -20
- package/docs/components/tree-drop-down.md +31 -23
- package/docs/components/tree-sub-layout.md +6 -2
- package/docs/components/tree-view.md +16 -5
- package/docs/explanation/architecture.md +54 -0
- package/docs/explanation/design-system.md +52 -0
- package/docs/how-to/data-views-from-backend-response.md +7 -4
- package/docs/how-to/guides.md +24 -18
- package/docs/migration/changelog.md +38 -0
- package/docs/reference/cli.md +87 -0
- package/docs/reference/components.md +11 -12
- package/docs/reference/hooks.md +8 -8
- package/docs/reference/providers.md +22 -22
- package/docs/reference/tailwind-plugins.md +2 -2
- package/docs/reference/theme.md +85 -0
- package/docs/reference/types.md +4 -4
- package/docs/reference/utilities.md +19 -26
- package/docs/tutorials/building-first-form.md +20 -13
- package/docs/tutorials/component-composition.md +34 -50
- package/docs/tutorials/getting-started.md +2 -2
- package/docs/tutorials/theming-basics.md +13 -10
- package/package.json +31 -3
- package/dist/src/commands/block.d.ts +0 -5
- package/dist/src/commands/block.d.ts.map +0 -1
- package/dist/src/commands/block.js +0 -255
- package/dist/src/commands/block.js.map +0 -1
- package/dist/src/shared/configureFonts.d.ts +0 -6
- package/dist/src/shared/configureFonts.d.ts.map +0 -1
- package/dist/src/shared/configureFonts.js +0 -106
- package/dist/src/shared/configureFonts.js.map +0 -1
- package/dist/src/shared/configureGlobalCss.d.ts +0 -6
- package/dist/src/shared/configureGlobalCss.d.ts.map +0 -1
- package/dist/src/shared/configureGlobalCss.js +0 -154
- package/dist/src/shared/configureGlobalCss.js.map +0 -1
- package/dist/src/shared/configureTailwind.d.ts +0 -7
- package/dist/src/shared/configureTailwind.d.ts.map +0 -1
- package/dist/src/shared/configureTailwind.js +0 -163
- package/dist/src/shared/configureTailwind.js.map +0 -1
- package/dist/src/shared/detectFramework.d.ts +0 -23
- package/dist/src/shared/detectFramework.d.ts.map +0 -1
- package/dist/src/shared/detectFramework.js +0 -119
- package/dist/src/shared/detectFramework.js.map +0 -1
- package/dist/src/shared/installBaseUtils.d.ts +0 -5
- package/dist/src/shared/installBaseUtils.d.ts.map +0 -1
- package/dist/src/shared/installBaseUtils.js +0 -79
- package/dist/src/shared/installBaseUtils.js.map +0 -1
- package/dist/src/shared/resolveAliases.d.ts +0 -24
- package/dist/src/shared/resolveAliases.d.ts.map +0 -1
- package/dist/src/shared/resolveAliases.js +0 -98
- 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
|
-
|
|
18
|
+
npx torch-glare@latest init
|
|
19
|
+
npx torch-glare@latest add SearchableSelect
|
|
16
20
|
```
|
|
17
21
|
|
|
18
|
-
|
|
22
|
+
`add` also copies any components, hooks, and utilities that `SearchableSelect` depends on.
|
|
19
23
|
|
|
20
24
|
## Import
|
|
21
25
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
35
|
-
import type { SearchableSelectOption } from
|
|
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
|
|
66
|
-
import type { SearchableSelectOption } from
|
|
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
|
|
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
|
|
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
|
|
240
|
-
import type { SearchableSelectOption } from
|
|
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
|
|
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
|
|
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,
|
|
5
|
+
keywords: [searchable-table, dialog, table, search, async, infinite-scroll, pagination, select, picker]
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# SearchableTable
|
|
9
9
|
|
|
10
|
-
> A
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
35
|
-
import type { SearchableTableColumn } from
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
99
|
-
import type { SearchableTableColumn } from
|
|
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
|
-
|
|
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
|
-
###
|
|
178
|
+
### Dialog labels (trigger, title, search)
|
|
169
179
|
|
|
170
|
-
|
|
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
|
-
|
|
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<T>
|
|
@@ -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
|
|
192
|
-
| `getLabel` | `(row: T) => string` | First column's value | Text shown
|
|
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` | `'
|
|
196
|
-
| `
|
|
197
|
-
| `
|
|
198
|
-
| `
|
|
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
|
|
201
|
-
| `className` | `string` | - | Additional classes merged onto the
|
|
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<T>
|
|
211
228
|
|
|
@@ -232,7 +249,7 @@ export interface SearchableTableColumn<T> {
|
|
|
232
249
|
### Component signature
|
|
233
250
|
|
|
234
251
|
```typescript
|
|
235
|
-
import { Themes } from
|
|
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
|
|
278
|
-
import type { SearchableTableColumn } from
|
|
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
|
|
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
|
|
399
|
-
-
|
|
400
|
-
-
|
|
401
|
-
-
|
|
402
|
-
- Provide a descriptive `placeholder` and a
|
|
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
|
|
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
|
|
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
|
|
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.
|