torch-glare 2.5.6 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/dist/bin/index.js +9 -24
- package/dist/bin/index.js.map +1 -1
- package/dist/src/commands/add.d.ts +2 -2
- package/dist/src/commands/add.d.ts.map +1 -1
- package/dist/src/commands/add.js +4 -70
- package/dist/src/commands/add.js.map +1 -1
- package/dist/src/commands/hook.d.ts +5 -4
- package/dist/src/commands/hook.d.ts.map +1 -1
- package/dist/src/commands/hook.js +7 -75
- package/dist/src/commands/hook.js.map +1 -1
- package/dist/src/commands/init.js +1 -1
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/layout.d.ts +5 -4
- package/dist/src/commands/layout.d.ts.map +1 -1
- package/dist/src/commands/layout.js +7 -75
- package/dist/src/commands/layout.js.map +1 -1
- package/dist/src/commands/provider.d.ts +5 -4
- package/dist/src/commands/provider.d.ts.map +1 -1
- package/dist/src/commands/provider.js +7 -75
- package/dist/src/commands/provider.js.map +1 -1
- package/dist/src/commands/update.d.ts.map +1 -1
- package/dist/src/commands/update.js +78 -43
- package/dist/src/commands/update.js.map +1 -1
- package/dist/src/commands/utils.d.ts +5 -4
- package/dist/src/commands/utils.d.ts.map +1 -1
- package/dist/src/commands/utils.js +7 -80
- package/dist/src/commands/utils.js.map +1 -1
- package/dist/src/shared/addFromRegistry.d.ts +16 -0
- package/dist/src/shared/addFromRegistry.d.ts.map +1 -0
- package/dist/src/shared/addFromRegistry.js +88 -0
- package/dist/src/shared/addFromRegistry.js.map +1 -0
- package/dist/src/shared/getInstallCommand.d.ts.map +1 -1
- package/dist/src/shared/getInstallCommand.js +5 -0
- package/dist/src/shared/getInstallCommand.js.map +1 -1
- package/dist/src/shared/installDependencies.d.ts +0 -10
- package/dist/src/shared/installDependencies.d.ts.map +1 -1
- package/dist/src/shared/installDependencies.js +0 -17
- package/dist/src/shared/installDependencies.js.map +1 -1
- package/dist/src/shared/installFromPlan.d.ts +7 -5
- package/dist/src/shared/installFromPlan.d.ts.map +1 -1
- package/dist/src/shared/installFromPlan.js +103 -36
- package/dist/src/shared/installFromPlan.js.map +1 -1
- package/dist/src/shared/loadRegistry.d.ts +12 -3
- package/dist/src/shared/loadRegistry.d.ts.map +1 -1
- package/dist/src/shared/loadRegistry.js +19 -20
- package/dist/src/shared/loadRegistry.js.map +1 -1
- package/dist/src/shared/registryClient.d.ts +19 -0
- package/dist/src/shared/registryClient.d.ts.map +1 -0
- package/dist/src/shared/registryClient.js +128 -0
- package/dist/src/shared/registryClient.js.map +1 -0
- package/dist/src/shared/resolveEntry.d.ts +10 -7
- package/dist/src/shared/resolveEntry.d.ts.map +1 -1
- package/dist/src/shared/resolveEntry.js +18 -13
- package/dist/src/shared/resolveEntry.js.map +1 -1
- package/dist/src/shared/suggestOtherCommand.d.ts +7 -1
- package/dist/src/shared/suggestOtherCommand.d.ts.map +1 -1
- package/dist/src/shared/suggestOtherCommand.js +29 -23
- package/dist/src/shared/suggestOtherCommand.js.map +1 -1
- package/dist/src/types/main.d.ts +31 -2
- package/dist/src/types/main.d.ts.map +1 -1
- package/package.json +6 -11
- package/apps/lib/components/ActionButton.tsx +0 -93
- package/apps/lib/components/ActionsGroup.tsx +0 -27
- package/apps/lib/components/AlertDialog.tsx +0 -204
- package/apps/lib/components/Avatar.tsx +0 -46
- package/apps/lib/components/Badge.tsx +0 -250
- package/apps/lib/components/BadgeField.tsx +0 -403
- package/apps/lib/components/Breadcrumb.tsx +0 -280
- package/apps/lib/components/Button.tsx +0 -325
- package/apps/lib/components/ButtonGroup.tsx +0 -190
- package/apps/lib/components/Calendar.tsx +0 -115
- package/apps/lib/components/Card.tsx +0 -95
- package/apps/lib/components/Checkbox.tsx +0 -42
- package/apps/lib/components/ColorPicker.tsx +0 -441
- package/apps/lib/components/ConclusionHeader.tsx +0 -148
- package/apps/lib/components/ContextMenu.tsx +0 -571
- package/apps/lib/components/CountBadge.tsx +0 -52
- package/apps/lib/components/DataTable.tsx +0 -213
- package/apps/lib/components/DataViews/badge.ts +0 -49
- package/apps/lib/components/DataViews/cell.tsx +0 -324
- package/apps/lib/components/DataViews/context.ts +0 -144
- package/apps/lib/components/DataViews/data-views.tsx +0 -395
- package/apps/lib/components/DataViews/filters/children.tsx +0 -98
- package/apps/lib/components/DataViews/filters/custom.tsx +0 -34
- package/apps/lib/components/DataViews/filters/filters.tsx +0 -157
- package/apps/lib/components/DataViews/filters/index.ts +0 -4
- package/apps/lib/components/DataViews/filters/labelled.tsx +0 -20
- package/apps/lib/components/DataViews/filters/presets.tsx +0 -65
- package/apps/lib/components/DataViews/filters/sync.tsx +0 -35
- package/apps/lib/components/DataViews/filters/values.ts +0 -173
- package/apps/lib/components/DataViews/header.tsx +0 -220
- package/apps/lib/components/DataViews/hooks/index.ts +0 -5
- package/apps/lib/components/DataViews/hooks/useActiveRow.ts +0 -22
- package/apps/lib/components/DataViews/hooks/useControllable.ts +0 -52
- package/apps/lib/components/DataViews/index.ts +0 -88
- package/apps/lib/components/DataViews/panel/columns.tsx +0 -153
- package/apps/lib/components/DataViews/panel/controls.tsx +0 -106
- package/apps/lib/components/DataViews/panel/index.ts +0 -3
- package/apps/lib/components/DataViews/panel/panel.tsx +0 -164
- package/apps/lib/components/DataViews/panel/saved-views.tsx +0 -67
- package/apps/lib/components/DataViews/panel/section.tsx +0 -81
- package/apps/lib/components/DataViews/panel/sort.tsx +0 -42
- package/apps/lib/components/DataViews/panel/tab.tsx +0 -31
- package/apps/lib/components/DataViews/slots.ts +0 -72
- package/apps/lib/components/DataViews/states.tsx +0 -73
- package/apps/lib/components/DataViews/types.ts +0 -510
- package/apps/lib/components/DataViews/views/board-view.tsx +0 -381
- package/apps/lib/components/DataViews/views/card-rows.tsx +0 -36
- package/apps/lib/components/DataViews/views/inbox-view.tsx +0 -260
- package/apps/lib/components/DataViews/views/pane-views.tsx +0 -196
- package/apps/lib/components/DataViews/views/table-view.tsx +0 -493
- package/apps/lib/components/DataViews/views/tree-view.tsx +0 -367
- package/apps/lib/components/DatePicker.tsx +0 -232
- package/apps/lib/components/Dialog.tsx +0 -116
- package/apps/lib/components/Divider.tsx +0 -28
- package/apps/lib/components/Drawer.tsx +0 -451
- package/apps/lib/components/DropdownMenu.tsx +0 -554
- package/apps/lib/components/FieldHint.tsx +0 -69
- package/apps/lib/components/Form.tsx +0 -182
- package/apps/lib/components/FormBuilder/context.ts +0 -85
- package/apps/lib/components/FormBuilder/field-kind.ts +0 -28
- package/apps/lib/components/FormBuilder/fields/ChoiceFields.tsx +0 -60
- package/apps/lib/components/FormBuilder/fields/ColorField.tsx +0 -59
- package/apps/lib/components/FormBuilder/fields/CustomField.tsx +0 -11
- package/apps/lib/components/FormBuilder/fields/DateField.tsx +0 -32
- package/apps/lib/components/FormBuilder/fields/FieldArray.tsx +0 -74
- package/apps/lib/components/FormBuilder/fields/FieldShell.tsx +0 -168
- package/apps/lib/components/FormBuilder/fields/FileField.tsx +0 -39
- package/apps/lib/components/FormBuilder/fields/OptionListFields.tsx +0 -132
- package/apps/lib/components/FormBuilder/fields/OtpField.tsx +0 -31
- package/apps/lib/components/FormBuilder/fields/PhoneField.tsx +0 -109
- package/apps/lib/components/FormBuilder/fields/RichTextEditorField.tsx +0 -31
- package/apps/lib/components/FormBuilder/fields/SelectField.tsx +0 -114
- package/apps/lib/components/FormBuilder/fields/SignatureField.tsx +0 -157
- package/apps/lib/components/FormBuilder/fields/SliderField.tsx +0 -67
- package/apps/lib/components/FormBuilder/fields/SwitchBoxField.tsx +0 -41
- package/apps/lib/components/FormBuilder/fields/TableField.tsx +0 -336
- package/apps/lib/components/FormBuilder/fields/TextField.tsx +0 -213
- package/apps/lib/components/FormBuilder/fields/TreeSelectField.tsx +0 -43
- package/apps/lib/components/FormBuilder/fields/countries.ts +0 -303
- package/apps/lib/components/FormBuilder/fields/index.ts +0 -25
- package/apps/lib/components/FormBuilder/form-builder.tsx +0 -198
- package/apps/lib/components/FormBuilder/index.ts +0 -31
- package/apps/lib/components/FormBuilder/numberFormat.ts +0 -16
- package/apps/lib/components/FormBuilder/submit.tsx +0 -67
- package/apps/lib/components/FormBuilder/types.ts +0 -316
- package/apps/lib/components/FormRenderer/FormDrawer.tsx +0 -250
- package/apps/lib/components/FormRenderer/detail.tsx +0 -256
- package/apps/lib/components/FormRenderer/form-renderer.tsx +0 -358
- package/apps/lib/components/FormRenderer/header.tsx +0 -88
- package/apps/lib/components/FormRenderer/index.ts +0 -17
- package/apps/lib/components/FormRenderer/notch-action.tsx +0 -64
- package/apps/lib/components/FormRenderer/section.tsx +0 -39
- package/apps/lib/components/FormRenderer/stepper.tsx +0 -393
- package/apps/lib/components/FormRenderer/types.ts +0 -115
- package/apps/lib/components/FormSummary.tsx +0 -282
- package/apps/lib/components/HeaderBar.tsx +0 -117
- package/apps/lib/components/ImageAttachment.tsx +0 -209
- package/apps/lib/components/InnerLabelField.tsx +0 -154
- package/apps/lib/components/Input.tsx +0 -224
- package/apps/lib/components/InputField.tsx +0 -147
- package/apps/lib/components/InputOTP.tsx +0 -84
- package/apps/lib/components/Label.tsx +0 -129
- package/apps/lib/components/LabelField.tsx +0 -76
- package/apps/lib/components/LabeledCheckBox.tsx +0 -51
- package/apps/lib/components/LabeledRadio.tsx +0 -51
- package/apps/lib/components/LinkButton.tsx +0 -94
- package/apps/lib/components/LoginButton.tsx +0 -51
- package/apps/lib/components/PasswordLevel.tsx +0 -58
- package/apps/lib/components/Popover.tsx +0 -312
- package/apps/lib/components/ProfileMenu.tsx +0 -85
- package/apps/lib/components/Radio.tsx +0 -59
- package/apps/lib/components/RadioCard.tsx +0 -50
- package/apps/lib/components/ScrollArea.tsx +0 -54
- package/apps/lib/components/SearchField.tsx +0 -62
- package/apps/lib/components/SearchableSelect.tsx +0 -318
- package/apps/lib/components/SearchableTable.tsx +0 -344
- package/apps/lib/components/SearchableTree.tsx +0 -485
- package/apps/lib/components/SearchableTreeDialog.tsx +0 -502
- package/apps/lib/components/SectionBlock.tsx +0 -168
- package/apps/lib/components/Select.tsx +0 -347
- package/apps/lib/components/SimpleSelect.tsx +0 -225
- package/apps/lib/components/Skeleton.tsx +0 -12
- package/apps/lib/components/SlideDatePicker.tsx +0 -255
- package/apps/lib/components/SpinLoading.tsx +0 -174
- package/apps/lib/components/Stepper.tsx +0 -480
- package/apps/lib/components/Switch.tsx +0 -82
- package/apps/lib/components/TabFormItem.tsx +0 -153
- package/apps/lib/components/TabSwitch.tsx +0 -172
- package/apps/lib/components/Table.tsx +0 -597
- package/apps/lib/components/TextEditor/ChartBlockTool.ts +0 -676
- package/apps/lib/components/TextEditor/RichTextField.tsx +0 -46
- package/apps/lib/components/TextEditor/TableDnDWrapper.ts +0 -462
- package/apps/lib/components/TextEditor/TextEditor.tsx +0 -1164
- package/apps/lib/components/TextEditor/TextEditorToolbar.tsx +0 -429
- package/apps/lib/components/TextEditor/editor-tools/AlignmentTune.ts +0 -70
- package/apps/lib/components/TextEditor/editor-tools/ColorInlineTool.ts +0 -50
- package/apps/lib/components/TextEditor/editor-tools/StrikethroughInlineTool.ts +0 -48
- package/apps/lib/components/TextEditor/editor-tools/inlineFormat.ts +0 -98
- package/apps/lib/components/TextEditor/editorjs.d.ts +0 -60
- package/apps/lib/components/TextEditor/index.ts +0 -7
- package/apps/lib/components/TextEditor/markdownParser.ts +0 -346
- package/apps/lib/components/Textarea.tsx +0 -117
- package/apps/lib/components/Timeline.tsx +0 -263
- package/apps/lib/components/Toast.tsx +0 -87
- package/apps/lib/components/Toggle.tsx +0 -125
- package/apps/lib/components/ToggleButton.tsx +0 -140
- package/apps/lib/components/Tooltip.tsx +0 -116
- package/apps/lib/components/TransparentLabel.tsx +0 -71
- package/apps/lib/components/TreeDropDown.tsx +0 -115
- package/apps/lib/components/TreeFolder/TreeFolder.tsx +0 -380
- package/apps/lib/components/TreeFolder/TreeFolderBreadcrumb.tsx +0 -75
- package/apps/lib/components/TreeFolder/TreeFolderRow.tsx +0 -354
- package/apps/lib/components/TreeFolder/TreeFolderStyles.tsx +0 -60
- package/apps/lib/components/TreeFolder/icons.tsx +0 -60
- package/apps/lib/components/TreeFolder/index.ts +0 -24
- package/apps/lib/components/TreeFolder/treeFolderUtils.ts +0 -109
- package/apps/lib/components/TreeFolder/types.ts +0 -77
- package/apps/lib/components/TreeFolder/useTreeFolderDnD.ts +0 -118
- package/apps/lib/hooks/useActiveTreeItem.ts +0 -61
- package/apps/lib/hooks/useClickOutside.ts +0 -27
- package/apps/lib/hooks/useDragDrop.tsx +0 -365
- package/apps/lib/hooks/useHtmlDir.ts +0 -31
- package/apps/lib/hooks/useInfiniteScroll.ts +0 -108
- package/apps/lib/hooks/useIsMobile.ts +0 -21
- package/apps/lib/hooks/useResize.ts +0 -67
- package/apps/lib/hooks/useTagSelection.ts +0 -300
- package/apps/lib/layouts/CNLayout.tsx +0 -372
- package/apps/lib/layouts/DataViewCard.tsx +0 -62
- package/apps/lib/layouts/FieldSection.tsx +0 -91
- package/apps/lib/layouts/TreeSubLayout.tsx +0 -173
- package/apps/lib/providers/ThemeProvider.tsx +0 -84
- package/apps/lib/registry.json +0 -1217
- package/apps/lib/tsconfig.json +0 -9
- package/apps/lib/tsconfig.tsbuildinfo +0 -1
- package/apps/lib/utils/cn.ts +0 -6
- package/apps/lib/utils/color.ts +0 -175
- package/apps/lib/utils/dataViews/path.ts +0 -67
- package/apps/lib/utils/dataViews/query.ts +0 -73
- package/apps/lib/utils/dataViews/types.ts +0 -187
- package/apps/lib/utils/dateFormat.ts +0 -108
- package/apps/lib/utils/resize.ts +0 -31
- package/apps/lib/utils/scroller.ts +0 -26
- package/apps/lib/utils/types.ts +0 -16
- package/docs/BLOCKS.md +0 -129
- package/docs/CHANGELOG-1.1.16.md +0 -108
- package/docs/Cover.png +0 -0
- package/docs/README.md +0 -215
- package/docs/components/action-button.md +0 -638
- package/docs/components/actions-group.md +0 -708
- package/docs/components/alert-dialog.md +0 -920
- package/docs/components/avatar.md +0 -709
- package/docs/components/badge-field.md +0 -738
- package/docs/components/badge.md +0 -301
- package/docs/components/breadcrumb.md +0 -930
- package/docs/components/button-group.md +0 -650
- package/docs/components/button.md +0 -639
- package/docs/components/calendar.md +0 -555
- package/docs/components/card.md +0 -622
- package/docs/components/chart-block-tool.md +0 -129
- package/docs/components/checkbox.md +0 -699
- package/docs/components/cn-layout.md +0 -854
- package/docs/components/color-picker.md +0 -101
- package/docs/components/conclusion-header.md +0 -80
- package/docs/components/context-menu.md +0 -483
- package/docs/components/count-badge.md +0 -560
- package/docs/components/data-table.md +0 -854
- package/docs/components/data-views/backend-response.md +0 -324
- package/docs/components/data-views/examples/a11y-rtl.md +0 -250
- package/docs/components/data-views/examples/api-orders-route.md +0 -130
- package/docs/components/data-views/examples/fields.md +0 -362
- package/docs/components/data-views/examples/filters.md +0 -307
- package/docs/components/data-views/examples/inbox-routing.md +0 -218
- package/docs/components/data-views/examples/index.md +0 -29
- package/docs/components/data-views/examples/overview.md +0 -244
- package/docs/components/data-views/examples/panel.md +0 -212
- package/docs/components/data-views/examples/scale.md +0 -231
- package/docs/components/data-views/examples/server-side.md +0 -210
- package/docs/components/data-views/examples/state.md +0 -250
- package/docs/components/data-views/examples/tree-custom.md +0 -388
- package/docs/components/data-views/examples/view-registry.md +0 -313
- package/docs/components/data-views/examples/views.md +0 -534
- package/docs/components/data-views/guide.md +0 -405
- package/docs/components/data-views/index.md +0 -1521
- package/docs/components/data-views/migration.md +0 -81
- package/docs/components/date-picker.md +0 -857
- package/docs/components/dialog.md +0 -929
- package/docs/components/divider.md +0 -766
- package/docs/components/drawer.md +0 -654
- package/docs/components/dropdown-menu.md +0 -668
- package/docs/components/field-hint.md +0 -898
- package/docs/components/field-section.md +0 -781
- package/docs/components/form-builder.md +0 -284
- package/docs/components/form-renderer.md +0 -396
- package/docs/components/form-summary.md +0 -138
- package/docs/components/form.md +0 -785
- package/docs/components/header-bar.md +0 -188
- package/docs/components/image-attachment.md +0 -906
- package/docs/components/inner-label-field.md +0 -819
- package/docs/components/input-field.md +0 -623
- package/docs/components/input-otp.md +0 -756
- package/docs/components/input.md +0 -634
- package/docs/components/label-field.md +0 -770
- package/docs/components/label.md +0 -761
- package/docs/components/labeled-check-box.md +0 -642
- package/docs/components/labeled-radio.md +0 -796
- package/docs/components/link-button.md +0 -685
- package/docs/components/login-button.md +0 -872
- package/docs/components/password-level.md +0 -803
- package/docs/components/popover.md +0 -649
- package/docs/components/profile-menu.md +0 -656
- package/docs/components/radio-card.md +0 -801
- package/docs/components/radio.md +0 -735
- package/docs/components/scroll-area.md +0 -691
- package/docs/components/search-field.md +0 -702
- package/docs/components/searchable-select.md +0 -368
- package/docs/components/searchable-table.md +0 -438
- package/docs/components/searchable-tree-dialog.md +0 -190
- package/docs/components/searchable-tree.md +0 -200
- package/docs/components/section-block.md +0 -483
- package/docs/components/select.md +0 -709
- package/docs/components/simple-select.md +0 -741
- package/docs/components/skeleton.md +0 -706
- package/docs/components/slide-date-picker.md +0 -837
- package/docs/components/spin-loading.md +0 -696
- package/docs/components/stepper.md +0 -316
- package/docs/components/switch.md +0 -770
- package/docs/components/tab-form-item.md +0 -773
- package/docs/components/tab-switch.md +0 -170
- package/docs/components/table-dnd-wrapper.md +0 -107
- package/docs/components/table.md +0 -1088
- package/docs/components/text-editor.md +0 -735
- package/docs/components/textarea.md +0 -681
- package/docs/components/timeline.md +0 -254
- package/docs/components/toast.md +0 -773
- package/docs/components/toggle-button.md +0 -643
- package/docs/components/toggle.md +0 -882
- package/docs/components/tooltip.md +0 -513
- package/docs/components/transparent-label.md +0 -721
- package/docs/components/tree-drop-down.md +0 -740
- package/docs/components/tree-folder.md +0 -110
- package/docs/components/tree-sub-layout.md +0 -707
- package/docs/explanation/architecture.md +0 -54
- package/docs/explanation/design-system.md +0 -52
- package/docs/how-to/form-and-list-recipes.md +0 -386
- package/docs/how-to/forms-with-form-builder.md +0 -472
- package/docs/how-to/guides.md +0 -1027
- package/docs/migration/changelog.md +0 -60
- package/docs/migration/form-builder-2.5.2.md +0 -113
- package/docs/reference/cli.md +0 -112
- package/docs/reference/components.md +0 -572
- package/docs/reference/hooks.md +0 -1523
- package/docs/reference/providers.md +0 -866
- package/docs/reference/tailwind-plugins.md +0 -942
- package/docs/reference/theme.md +0 -85
- package/docs/reference/types.md +0 -1016
- package/docs/reference/utilities.md +0 -726
- package/docs/tutorials/building-first-form.md +0 -846
- package/docs/tutorials/component-composition.md +0 -882
- package/docs/tutorials/getting-started.md +0 -302
- package/docs/tutorials/theming-basics.md +0 -785
|
@@ -1,502 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
forwardRef,
|
|
5
|
-
InputHTMLAttributes,
|
|
6
|
-
ReactNode,
|
|
7
|
-
useEffect,
|
|
8
|
-
useMemo,
|
|
9
|
-
useRef,
|
|
10
|
-
useState,
|
|
11
|
-
} from "react";
|
|
12
|
-
import { cn } from "../utils/cn";
|
|
13
|
-
import { Themes } from "../utils/types";
|
|
14
|
-
import { Dialog, DialogTrigger, DialogContent, DialogTitle } from "./Dialog";
|
|
15
|
-
import { Icon, Input, Group } from "./Input";
|
|
16
|
-
import { LoadingIcon } from "./Button";
|
|
17
|
-
import { ActionButton } from "./ActionButton";
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* SearchableTreeDialog — a field that opens a modal Dialog to pick a node from a tree.
|
|
21
|
-
*
|
|
22
|
-
* Same layout as SearchableTable (trigger → dialog with a search input + a
|
|
23
|
-
* scrollable area), but the area renders a hierarchical tree instead of a table.
|
|
24
|
-
*
|
|
25
|
-
* Interaction (matches the Figma `Search.Overlay` design):
|
|
26
|
-
* • A FOLDER row (a node with children) toggles expand/collapse on click —
|
|
27
|
-
* folders are not selectable.
|
|
28
|
-
* • A LEAF row (no children) selects the node and closes the dialog.
|
|
29
|
-
* Nesting is shown with indentation + a vertical connector rail per level
|
|
30
|
-
* (no chevron). Search filters matching nodes and keeps/expands ancestors.
|
|
31
|
-
*
|
|
32
|
-
* Accepts either NESTED data (each node exposes its children via getNodeChildren)
|
|
33
|
-
* or FLAT data (pass `parentIdKey` and it builds the tree internally).
|
|
34
|
-
*/
|
|
35
|
-
|
|
36
|
-
interface Props<T> {
|
|
37
|
-
/** Tree nodes. Nested by default; flat when `parentIdKey` is provided. */
|
|
38
|
-
nodes: T[];
|
|
39
|
-
getNodeId: (node: T) => string;
|
|
40
|
-
getNodeLabel: (node: T) => ReactNode;
|
|
41
|
-
/** Returns a node's children (nested mode). Ignored when `parentIdKey` is set. */
|
|
42
|
-
getNodeChildren?: (node: T) => T[] | undefined;
|
|
43
|
-
/**
|
|
44
|
-
* When set, `nodes` is treated as a FLAT list and the tree is built from this
|
|
45
|
-
* key (each item's parent id). Root items have a nullish parent id.
|
|
46
|
-
*/
|
|
47
|
-
parentIdKey?: keyof T & string;
|
|
48
|
-
|
|
49
|
-
/** Controlled selected node (optional). */
|
|
50
|
-
value?: T | null;
|
|
51
|
-
onSelect?: (node: T) => void;
|
|
52
|
-
|
|
53
|
-
/** Plain-text label used for search matching; defaults to String(getNodeLabel). */
|
|
54
|
-
getSearchText?: (node: T) => string;
|
|
55
|
-
|
|
56
|
-
/** Trigger placeholder shown until a node is selected. */
|
|
57
|
-
placeholder?: string;
|
|
58
|
-
/** Placeholder for the search input inside the dialog. */
|
|
59
|
-
searchPlaceholder?: string;
|
|
60
|
-
/** Label shown on the dialog's search field. */
|
|
61
|
-
title?: string;
|
|
62
|
-
/** Height cap for the scrolling body, in px. Also bounded by 55vh so it shrinks on short
|
|
63
|
-
* screens. Past it the tree scrolls inside the dialog. Default 200. */
|
|
64
|
-
maxBodyHeight?: number;
|
|
65
|
-
|
|
66
|
-
size?: "XS" | "S" | "M";
|
|
67
|
-
variant?: "SystemStyle" | "PresentationStyle";
|
|
68
|
-
icon?: ReactNode;
|
|
69
|
-
theme?: Themes;
|
|
70
|
-
dir?: string;
|
|
71
|
-
className?: string;
|
|
72
|
-
/**
|
|
73
|
-
* Expand every node by default when the dialog opens. Defaults to `true` so
|
|
74
|
-
* children are visible as soon as the tree opens (matches the Figma design).
|
|
75
|
-
*/
|
|
76
|
-
defaultExpanded?: boolean;
|
|
77
|
-
/**
|
|
78
|
-
* When true, folder rows (nodes with children) are ALSO selectable: clicking
|
|
79
|
-
* any node — folder or leaf — selects it and closes the dialog. When false
|
|
80
|
-
* (default), folders only toggle expand/collapse and only leaves are selectable.
|
|
81
|
-
*/
|
|
82
|
-
selectableFolders?: boolean;
|
|
83
|
-
|
|
84
|
-
// --- Async (optional; static `nodes` still works) ---
|
|
85
|
-
/** When true (default), filters locally. Set false for server-side search. */
|
|
86
|
-
filterClientSide?: boolean;
|
|
87
|
-
/** Debounced as the user types — refetch your data here (server search). */
|
|
88
|
-
onSearchChange?: (query: string) => void;
|
|
89
|
-
searchDebounceMs?: number;
|
|
90
|
-
/** Whether a fetch is in flight; shows a loading indicator. */
|
|
91
|
-
loading?: boolean;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** Normalized internal node — always nested with resolved id/label/children. */
|
|
95
|
-
interface TreeNode<T> {
|
|
96
|
-
id: string;
|
|
97
|
-
label: ReactNode;
|
|
98
|
-
searchText: string;
|
|
99
|
-
raw: T;
|
|
100
|
-
children: TreeNode<T>[];
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export function SearchableTreeDialog<T>({
|
|
104
|
-
nodes,
|
|
105
|
-
getNodeId,
|
|
106
|
-
getNodeLabel,
|
|
107
|
-
getNodeChildren,
|
|
108
|
-
parentIdKey,
|
|
109
|
-
value,
|
|
110
|
-
onSelect,
|
|
111
|
-
getSearchText,
|
|
112
|
-
placeholder = "Select…",
|
|
113
|
-
searchPlaceholder = "Search…",
|
|
114
|
-
title = "Select an item",
|
|
115
|
-
maxBodyHeight = 200,
|
|
116
|
-
size = "M",
|
|
117
|
-
variant = "PresentationStyle",
|
|
118
|
-
icon,
|
|
119
|
-
theme,
|
|
120
|
-
dir,
|
|
121
|
-
className,
|
|
122
|
-
defaultExpanded = true,
|
|
123
|
-
selectableFolders = false,
|
|
124
|
-
filterClientSide = true,
|
|
125
|
-
onSearchChange,
|
|
126
|
-
searchDebounceMs = 300,
|
|
127
|
-
loading = false,
|
|
128
|
-
}: Props<T>) {
|
|
129
|
-
const [open, setOpen] = useState(false);
|
|
130
|
-
const [search, setSearch] = useState("");
|
|
131
|
-
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
132
|
-
|
|
133
|
-
const searchTextOf = (node: T) => getSearchText?.(node) ?? String(getNodeLabel(node) ?? "");
|
|
134
|
-
|
|
135
|
-
// Normalize raw data (nested OR flat) into a uniform nested TreeNode shape.
|
|
136
|
-
const tree = useMemo<TreeNode<T>[]>(() => {
|
|
137
|
-
const toNode = (raw: T, children: TreeNode<T>[]): TreeNode<T> => ({
|
|
138
|
-
id: getNodeId(raw),
|
|
139
|
-
label: getNodeLabel(raw),
|
|
140
|
-
searchText: searchTextOf(raw),
|
|
141
|
-
raw,
|
|
142
|
-
children,
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
// Flat mode: build nesting from parentIdKey.
|
|
146
|
-
if (parentIdKey) {
|
|
147
|
-
const byId = new Map<string, TreeNode<T>>();
|
|
148
|
-
const roots: TreeNode<T>[] = [];
|
|
149
|
-
for (const raw of nodes) byId.set(getNodeId(raw), toNode(raw, []));
|
|
150
|
-
for (const raw of nodes) {
|
|
151
|
-
const node = byId.get(getNodeId(raw))!;
|
|
152
|
-
const parentId = raw[parentIdKey] as unknown as string | null | undefined;
|
|
153
|
-
const parent = parentId != null ? byId.get(String(parentId)) : undefined;
|
|
154
|
-
if (parent) parent.children.push(node);
|
|
155
|
-
else roots.push(node);
|
|
156
|
-
}
|
|
157
|
-
return roots;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Nested mode: recurse via getNodeChildren.
|
|
161
|
-
const build = (raw: T): TreeNode<T> => toNode(raw, (getNodeChildren?.(raw) ?? []).map(build));
|
|
162
|
-
return nodes.map(build);
|
|
163
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
164
|
-
}, [nodes, parentIdKey, getNodeChildren]);
|
|
165
|
-
|
|
166
|
-
// Filter the tree by the query, keeping ancestors of any match. In server-side
|
|
167
|
-
// mode the data is already filtered, so we render it as-is.
|
|
168
|
-
const visibleTree = useMemo<TreeNode<T>[]>(() => {
|
|
169
|
-
const q = search.trim().toLowerCase();
|
|
170
|
-
if (!filterClientSide || !q) return tree;
|
|
171
|
-
const filter = (list: TreeNode<T>[]): TreeNode<T>[] =>
|
|
172
|
-
list.reduce<TreeNode<T>[]>((acc, node) => {
|
|
173
|
-
const kids = filter(node.children);
|
|
174
|
-
const self = node.searchText.toLowerCase().includes(q);
|
|
175
|
-
if (self || kids.length) acc.push({ ...node, children: kids });
|
|
176
|
-
return acc;
|
|
177
|
-
}, []);
|
|
178
|
-
return filter(tree);
|
|
179
|
-
}, [tree, search, filterClientSide]);
|
|
180
|
-
|
|
181
|
-
// Expand-state: when searching (or defaultExpanded), expand everything so the
|
|
182
|
-
// matched paths are visible; otherwise honor user toggles.
|
|
183
|
-
const [expanded, setExpanded] = useState<Set<string>>(new Set());
|
|
184
|
-
const forceExpand = (filterClientSide && search.trim().length > 0) || defaultExpanded;
|
|
185
|
-
const isExpanded = (id: string) => forceExpand || expanded.has(id);
|
|
186
|
-
const toggle = (id: string) =>
|
|
187
|
-
setExpanded((prev) => {
|
|
188
|
-
const next = new Set(prev);
|
|
189
|
-
next.has(id) ? next.delete(id) : next.add(id);
|
|
190
|
-
return next;
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
// Debounced server search.
|
|
194
|
-
useEffect(() => {
|
|
195
|
-
if (!onSearchChange) return;
|
|
196
|
-
const t = setTimeout(() => onSearchChange(search.trim()), searchDebounceMs);
|
|
197
|
-
return () => clearTimeout(t);
|
|
198
|
-
}, [search, onSearchChange, searchDebounceMs]);
|
|
199
|
-
|
|
200
|
-
// Reset the query when the dialog closes.
|
|
201
|
-
useEffect(() => {
|
|
202
|
-
if (!open) setSearch("");
|
|
203
|
-
}, [open]);
|
|
204
|
-
|
|
205
|
-
const selectedId = value != null ? getNodeId(value) : undefined;
|
|
206
|
-
|
|
207
|
-
// Leaf rows select + close; folder rows toggle expand/collapse (handled inline).
|
|
208
|
-
const handleSelect = (node: TreeNode<T>) => {
|
|
209
|
-
onSelect?.(node.raw);
|
|
210
|
-
setOpen(false);
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
// Folder rows when `selectableFolders` is on: behave just like a leaf —
|
|
214
|
-
// select the node and close the dialog so it shows in the field.
|
|
215
|
-
const handleSelectFolder = (node: TreeNode<T>) => {
|
|
216
|
-
handleSelect(node);
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
// Trigger label = selected node's label, else placeholder.
|
|
220
|
-
const triggerLabel = value != null ? getNodeLabel(value) : placeholder;
|
|
221
|
-
|
|
222
|
-
return (
|
|
223
|
-
<Dialog open={open} onOpenChange={setOpen}>
|
|
224
|
-
<DialogTrigger asChild>
|
|
225
|
-
<Group
|
|
226
|
-
dir={dir}
|
|
227
|
-
data-theme={theme}
|
|
228
|
-
variant={variant}
|
|
229
|
-
size={size === "XS" ? "S" : size}
|
|
230
|
-
role="button"
|
|
231
|
-
tabIndex={0}
|
|
232
|
-
// 3px + the 1px border = Figma's 4px from the field's outer edge.
|
|
233
|
-
className={cn("flex w-full items-center gap-1 p-[3px] cursor-pointer", className)}
|
|
234
|
-
>
|
|
235
|
-
{icon && <Icon>{icon}</Icon>}
|
|
236
|
-
<span
|
|
237
|
-
className={cn(
|
|
238
|
-
"flex-1 px-1 truncate typography-body-medium-regular",
|
|
239
|
-
value != null
|
|
240
|
-
? "text-content-presentation-action-light-primary"
|
|
241
|
-
: "text-content-presentation-action-light-secondary",
|
|
242
|
-
)}
|
|
243
|
-
>
|
|
244
|
-
{triggerLabel}
|
|
245
|
-
</span>
|
|
246
|
-
{/* Chevron toggle — the shared in-field action button (Figma `ActionButton` M). */}
|
|
247
|
-
<ActionButton
|
|
248
|
-
as="span"
|
|
249
|
-
size="M"
|
|
250
|
-
tabIndex={-1}
|
|
251
|
-
aria-label={open ? "Close" : "Open"}
|
|
252
|
-
className={cn("shrink-0", open && "bg-background-presentation-action-hover text-white")}
|
|
253
|
-
>
|
|
254
|
-
<i
|
|
255
|
-
className={cn(
|
|
256
|
-
"ri-arrow-down-s-line !text-[26px] transition-all duration-100 ease-in-out",
|
|
257
|
-
open && "rotate-180",
|
|
258
|
-
)}
|
|
259
|
-
/>
|
|
260
|
-
</ActionButton>
|
|
261
|
-
</Group>
|
|
262
|
-
</DialogTrigger>
|
|
263
|
-
|
|
264
|
-
<DialogContent
|
|
265
|
-
dir={dir}
|
|
266
|
-
data-theme={theme}
|
|
267
|
-
onOpenAutoFocus={(e) => {
|
|
268
|
-
e.preventDefault();
|
|
269
|
-
inputRef.current?.focus();
|
|
270
|
-
}}
|
|
271
|
-
className="w-[min(640px,90vw)] bg-transparent !items-stretch rounded-[14px] shadow-none gap-0"
|
|
272
|
-
>
|
|
273
|
-
<DialogTitle className="sr-only">{title ?? "Select an item"}</DialogTitle>
|
|
274
|
-
|
|
275
|
-
{/* SearchBar — frosted top-bar shell (z-2 in Figma). */}
|
|
276
|
-
<TreeSearchInput
|
|
277
|
-
ref={inputRef}
|
|
278
|
-
variant={variant}
|
|
279
|
-
theme={theme}
|
|
280
|
-
dir={dir}
|
|
281
|
-
label={title}
|
|
282
|
-
value={search}
|
|
283
|
-
placeholder={searchPlaceholder}
|
|
284
|
-
onChange={(e) => setSearch(e.target.value)}
|
|
285
|
-
/>
|
|
286
|
-
|
|
287
|
-
{/* SearchResult.Container — inset 12px each side, tucked under the bar (z-1). */}
|
|
288
|
-
<div className="flex w-full items-start px-[12px]">
|
|
289
|
-
{/* Body — frosted surface that connects to the bar with rounded-bottom. */}
|
|
290
|
-
<div
|
|
291
|
-
// `max-h-[55vh]` alone never engaged: at a 900px viewport that is 495px, far more than
|
|
292
|
-
// the tree needs, so the body simply grew to fit and there was nothing to scroll.
|
|
293
|
-
// `maxBodyHeight` is the real cap; 55vh still bounds it on short screens.
|
|
294
|
-
style={{ maxHeight: `min(${maxBodyHeight}px, 55vh)` }}
|
|
295
|
-
className="flex-1 min-w-px min-h-0 overflow-auto scrollbar-hide rounded-b-[14px] pt-[8px] pb-[4px] px-[4px] bg-[rgba(61,64,69,0.72)] backdrop-blur-[21px] shadow-[0_0_32px_2px_rgba(0,0,0,0.20),0_0_48px_2px_rgba(0,0,0,0.05)]"
|
|
296
|
-
>
|
|
297
|
-
{visibleTree.length > 0 ? (
|
|
298
|
-
<div className="flex flex-col gap-[4px]">
|
|
299
|
-
{/* "Tree" section label. */}
|
|
300
|
-
{title && (
|
|
301
|
-
<div className="flex items-end pt-[4px] px-[12px]">
|
|
302
|
-
<span className="typography-body-small-medium text-content-presentation-global-primary-light">
|
|
303
|
-
{title}
|
|
304
|
-
</span>
|
|
305
|
-
</div>
|
|
306
|
-
)}
|
|
307
|
-
|
|
308
|
-
{/* Rows — translucent fill, 1px gaps, rounded + clipped group. */}
|
|
309
|
-
<div className="flex flex-col gap-px overflow-clip rounded-[10px]">
|
|
310
|
-
<TreeLevel
|
|
311
|
-
nodes={visibleTree}
|
|
312
|
-
depth={0}
|
|
313
|
-
selectedId={selectedId}
|
|
314
|
-
selectableFolders={selectableFolders}
|
|
315
|
-
isExpanded={isExpanded}
|
|
316
|
-
onToggle={toggle}
|
|
317
|
-
onSelect={handleSelect}
|
|
318
|
-
onSelectFolder={handleSelectFolder}
|
|
319
|
-
/>
|
|
320
|
-
</div>
|
|
321
|
-
</div>
|
|
322
|
-
) : (
|
|
323
|
-
!loading && (
|
|
324
|
-
<div className="px-3 py-6 text-center typography-body-small-regular text-white-alpha-75">
|
|
325
|
-
No results found
|
|
326
|
-
</div>
|
|
327
|
-
)
|
|
328
|
-
)}
|
|
329
|
-
|
|
330
|
-
{loading && (
|
|
331
|
-
<div className="flex items-center justify-center py-3">
|
|
332
|
-
<LoadingIcon size="M" />
|
|
333
|
-
</div>
|
|
334
|
-
)}
|
|
335
|
-
</div>
|
|
336
|
-
</div>
|
|
337
|
-
</DialogContent>
|
|
338
|
-
</Dialog>
|
|
339
|
-
);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
SearchableTreeDialog.displayName = "SearchableTreeDialog";
|
|
343
|
-
|
|
344
|
-
/* -------------------------------------------------------------------------- */
|
|
345
|
-
/* Recursive tree renderer */
|
|
346
|
-
/* -------------------------------------------------------------------------- */
|
|
347
|
-
|
|
348
|
-
function TreeLevel<T>({
|
|
349
|
-
nodes,
|
|
350
|
-
depth,
|
|
351
|
-
selectedId,
|
|
352
|
-
selectableFolders = false,
|
|
353
|
-
isExpanded,
|
|
354
|
-
onToggle,
|
|
355
|
-
onSelect,
|
|
356
|
-
onSelectFolder,
|
|
357
|
-
}: {
|
|
358
|
-
nodes: TreeNode<T>[];
|
|
359
|
-
depth: number;
|
|
360
|
-
selectedId?: string;
|
|
361
|
-
selectableFolders?: boolean;
|
|
362
|
-
isExpanded: (id: string) => boolean;
|
|
363
|
-
onToggle: (id: string) => void;
|
|
364
|
-
onSelect: (node: TreeNode<T>) => void;
|
|
365
|
-
onSelectFolder: (node: TreeNode<T>) => void;
|
|
366
|
-
}) {
|
|
367
|
-
return (
|
|
368
|
-
<>
|
|
369
|
-
{nodes.map((node) => {
|
|
370
|
-
const hasChildren = node.children.length > 0;
|
|
371
|
-
const open = isExpanded(node.id);
|
|
372
|
-
const selected = selectedId === node.id;
|
|
373
|
-
// Leaf rows always select + close. Folder rows either select + toggle
|
|
374
|
-
// (selectableFolders) or just toggle expand/collapse.
|
|
375
|
-
const onRowClick = () =>
|
|
376
|
-
!hasChildren
|
|
377
|
-
? onSelect(node)
|
|
378
|
-
: selectableFolders
|
|
379
|
-
? onSelectFolder(node)
|
|
380
|
-
: onToggle(node.id);
|
|
381
|
-
// Whether THIS row participates in the selected/hover highlight.
|
|
382
|
-
const highlightable = !hasChildren || selectableFolders;
|
|
383
|
-
return (
|
|
384
|
-
<div key={node.id} className="flex flex-col gap-px">
|
|
385
|
-
{/* item-container — full-width row with the design's grey fill. */}
|
|
386
|
-
<div
|
|
387
|
-
role="button"
|
|
388
|
-
tabIndex={0}
|
|
389
|
-
aria-expanded={hasChildren ? open : undefined}
|
|
390
|
-
onClick={onRowClick}
|
|
391
|
-
onKeyDown={(e) => {
|
|
392
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
393
|
-
e.preventDefault();
|
|
394
|
-
onRowClick();
|
|
395
|
-
}
|
|
396
|
-
}}
|
|
397
|
-
className={cn(
|
|
398
|
-
"group flex items-center px-[2px] cursor-pointer bg-[rgba(184,192,204,0.36)]",
|
|
399
|
-
)}
|
|
400
|
-
>
|
|
401
|
-
{/* Connector rails — one full-height vertical line per ancestor level. */}
|
|
402
|
-
{Array.from({ length: depth }).map((_, i) => (
|
|
403
|
-
<div
|
|
404
|
-
key={i}
|
|
405
|
-
aria-hidden
|
|
406
|
-
className="flex h-[33px] shrink-0 items-center overflow-clip px-[14px]"
|
|
407
|
-
>
|
|
408
|
-
<div className="h-full w-px shrink-0 bg-white-alpha-40" />
|
|
409
|
-
</div>
|
|
410
|
-
))}
|
|
411
|
-
|
|
412
|
-
{/* Padding + label — inner row that highlights on hover/select. */}
|
|
413
|
-
<div className="flex flex-1 min-w-px flex-col items-start py-[2px]">
|
|
414
|
-
<div
|
|
415
|
-
className={cn(
|
|
416
|
-
"flex w-full items-center gap-2 px-[12px] py-[4px] rounded-[8px]",
|
|
417
|
-
"transition-all duration-300 ease-in-out",
|
|
418
|
-
"text-content-presentation-global-primary-light",
|
|
419
|
-
// Selectable rows (leaves, or folders when selectableFolders)
|
|
420
|
-
// highlight on hover/select; non-selectable folders only toggle,
|
|
421
|
-
// so they read as de-emphasized (opacity-50) and get no highlight.
|
|
422
|
-
!highlightable && "opacity-50",
|
|
423
|
-
highlightable &&
|
|
424
|
-
"group-hover:bg-white-50 group-hover:text-black-1000 group-hover:shadow-[0_0_16px_0_rgba(0,0,0,0.36)]",
|
|
425
|
-
highlightable && selected && "bg-white-alpha-75 text-black-1000",
|
|
426
|
-
)}
|
|
427
|
-
>
|
|
428
|
-
<span className="flex-1 min-w-px truncate typography-body-medium-regular">
|
|
429
|
-
{node.label}
|
|
430
|
-
</span>
|
|
431
|
-
</div>
|
|
432
|
-
</div>
|
|
433
|
-
</div>
|
|
434
|
-
|
|
435
|
-
{hasChildren && open && (
|
|
436
|
-
<TreeLevel
|
|
437
|
-
nodes={node.children}
|
|
438
|
-
depth={depth + 1}
|
|
439
|
-
selectedId={selectedId}
|
|
440
|
-
selectableFolders={selectableFolders}
|
|
441
|
-
isExpanded={isExpanded}
|
|
442
|
-
onToggle={onToggle}
|
|
443
|
-
onSelect={onSelect}
|
|
444
|
-
onSelectFolder={onSelectFolder}
|
|
445
|
-
/>
|
|
446
|
-
)}
|
|
447
|
-
</div>
|
|
448
|
-
);
|
|
449
|
-
})}
|
|
450
|
-
</>
|
|
451
|
-
);
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
/* -------------------------------------------------------------------------- */
|
|
455
|
-
/* Inline-label search field (self-contained, mirrors SearchableTable's). */
|
|
456
|
-
/* -------------------------------------------------------------------------- */
|
|
457
|
-
|
|
458
|
-
interface TreeSearchInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "size"> {
|
|
459
|
-
variant?: "SystemStyle" | "PresentationStyle";
|
|
460
|
-
theme?: Themes;
|
|
461
|
-
label?: string;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
const TreeSearchInput = forwardRef<HTMLInputElement, TreeSearchInputProps>(
|
|
465
|
-
({ variant = "PresentationStyle", theme, label, className, ...props }, ref) => (
|
|
466
|
-
<div className="relative z-[2] flex w-full flex-col items-start gap-[10px] p-[3px] rounded-[14px] bg-[rgba(61,64,69,0.65)] shadow-[0_0_18px_0_rgba(0,0,0,0.40)] backdrop-blur-[21px]">
|
|
467
|
-
<Group
|
|
468
|
-
data-theme={theme}
|
|
469
|
-
variant={variant}
|
|
470
|
-
size="M"
|
|
471
|
-
className={cn(
|
|
472
|
-
"group flex w-full items-center hover:border-blue-sparkle-400 gap-[10px] px-[15px] py-[10px] rounded-[11px] transition-colors",
|
|
473
|
-
"border border-white-alpha-40 bg-[rgba(37,44,57,0.30)]",
|
|
474
|
-
"hover:bg-background-presentation-table-row-hover",
|
|
475
|
-
"focus-within:bg-background-presentation-table-row-hover",
|
|
476
|
-
className,
|
|
477
|
-
)}
|
|
478
|
-
>
|
|
479
|
-
<Icon className="shrink-0">
|
|
480
|
-
<i className="ri-search-line text-[18px] text-content-presentation-global-secondary transition-colors group-hover:text-white group-focus-within:text-white" />
|
|
481
|
-
</Icon>
|
|
482
|
-
|
|
483
|
-
{label && (
|
|
484
|
-
<span className="shrink-0 text-content-presentation-global-primary-light typography-headers-medium-regular opacity-80 transition-colors group-hover:text-white group-focus-within:text-white">
|
|
485
|
-
{label}
|
|
486
|
-
</span>
|
|
487
|
-
)}
|
|
488
|
-
|
|
489
|
-
<Input
|
|
490
|
-
ref={ref}
|
|
491
|
-
{...props}
|
|
492
|
-
className={cn(
|
|
493
|
-
"min-w-[100px] flex-1 !h-[24px] bg-transparent",
|
|
494
|
-
"text-content-presentation-action-light-primary placeholder:hover:text-white-alpha-75",
|
|
495
|
-
"transition-colors group-hover:!text-white group-focus-within:!text-white",
|
|
496
|
-
)}
|
|
497
|
-
/>
|
|
498
|
-
</Group>
|
|
499
|
-
</div>
|
|
500
|
-
),
|
|
501
|
-
);
|
|
502
|
-
TreeSearchInput.displayName = "TreeSearchInput";
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
import { forwardRef, HTMLAttributes, ReactNode } from "react";
|
|
2
|
-
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
-
import { cn } from "../utils/cn";
|
|
4
|
-
import { horizontalScrollerStyles } from "../utils/scroller";
|
|
5
|
-
|
|
6
|
-
// LOCAL PATCH (Contact Center): the pill's side padding is asymmetric (16 leading / 22 trailing),
|
|
7
|
-
// so it must be logical — `ps`/`pe` rather than `pl`/`pr`. This pill heads every section card, so
|
|
8
|
-
// mirrored the wrong way it reads as a systematic misalignment across the whole page.
|
|
9
|
-
const titleBadge = cva(
|
|
10
|
-
"flex pt-2 pb-2 ps-[16px] pe-[22px] justify-center items-center gap-[6px] rounded-[10px] self-start typography-headers-medium-medium text-[#F4F4F4]",
|
|
11
|
-
{
|
|
12
|
-
variants: {
|
|
13
|
-
color: {
|
|
14
|
-
Blue: "bg-blue-sparkle-900",
|
|
15
|
-
Yellow: "bg-yellow-950",
|
|
16
|
-
Green: "bg-green-cyan-900",
|
|
17
|
-
Red: "bg-medium-red-900",
|
|
18
|
-
Orange: "bg-red-orange-900",
|
|
19
|
-
Purple: "bg-violet-900",
|
|
20
|
-
Pink: "bg-medium-violet-red-900",
|
|
21
|
-
// Per Figma (`FormHeader10`, Color=Gray), which pairs two *variables*: the pill is
|
|
22
|
-
// `background/presentation/button/primary` and its label `content/presentation/global/
|
|
23
|
-
// primary-inverse`. They invert together — a light pill with dark text in the dark theme,
|
|
24
|
-
// dark pill with white text in the light one — so the text token has to ride along on the
|
|
25
|
-
// variant. It overrides the base `text-[#F4F4F4]` because `titleBadge` goes through `cn`
|
|
26
|
-
// (tailwind-merge) below; the other seven are dark palette chips that still want that base.
|
|
27
|
-
//
|
|
28
|
-
// This previously read `bg-background-presentation-badge-gray`, which is not a token at all:
|
|
29
|
-
// every badge colour has `-solid`/`-subtle` and none has a bare name, so the utility was
|
|
30
|
-
// never generated and a Gray badge painted no background whatsoever.
|
|
31
|
-
Gray: "bg-background-presentation-button-primary text-content-presentation-global-primary-inverse",
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
defaultVariants: { color: "Blue" },
|
|
35
|
-
},
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
const container = cva(
|
|
39
|
-
"flex w-full px-0 flex-col rounded-[16px] bg-background-presentation-form-base shadow-[0_0_32px_2px_rgba(0,0,0,0.05)]",
|
|
40
|
-
{
|
|
41
|
-
variants: {
|
|
42
|
-
variant: {
|
|
43
|
-
Default: "pt-[6px] pb-[24px]",
|
|
44
|
-
// No bottom padding — the table's end-action row is the last element and meets
|
|
45
|
-
// the card edge; `overflow-hidden` clips it to the 16px radius.
|
|
46
|
-
Table: "pt-[6px] pb-0 overflow-hidden",
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
defaultVariants: { variant: "Default" },
|
|
50
|
-
},
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
const header = cva("flex px-[6px] justify-between gap-3", {
|
|
54
|
-
variants: {
|
|
55
|
-
variant: {
|
|
56
|
-
Default: "items-center",
|
|
57
|
-
Table: "items-start",
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
defaultVariants: { variant: "Default" },
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
// LOCAL PATCH (Contact Center): the body is the section's horizontal scrollport.
|
|
64
|
-
//
|
|
65
|
-
// `Table` renders `overflow-visible w-auto` (so its sticky header can reach a real scrollport), so
|
|
66
|
-
// a table wider than its card does not clip or scroll itself — it widens the card, and then the
|
|
67
|
-
// page. Only one call site in the app wraps its table in `TableScroller`; the rest drop a bare
|
|
68
|
-
// `<Table>` straight into a section. Owning the scroll here contains all of them at once, and
|
|
69
|
-
// replaces the `Table` variant's old `overflow-hidden`, which truncated instead of scrolling.
|
|
70
|
-
//
|
|
71
|
-
// `overflow-y-hidden` is required, not decorative: CSS computes `overflow-y: visible` to `auto`
|
|
72
|
-
// whenever `overflow-x` is not `visible`, so without it every section grows a spurious vertical
|
|
73
|
-
// scrollbar. The body is content-height, so nothing is clipped vertically. Same reasoning, and the
|
|
74
|
-
// same scrollbar styling, as `TableScroller`.
|
|
75
|
-
//
|
|
76
|
-
// The cost: a scrollport is the containing block for `position: sticky`, so a `<Table>`'s sticky
|
|
77
|
-
// header inside a section now pins to a box that never scrolls vertically — i.e. it stops
|
|
78
|
-
// sticking. Sections hold short tables, and in `variant="Table"` those headers were already inert
|
|
79
|
-
// under the old `overflow-hidden`.
|
|
80
|
-
const body = cva(`flex w-full flex-col ${horizontalScrollerStyles}`, {
|
|
81
|
-
variants: {
|
|
82
|
-
variant: {
|
|
83
|
-
Default: "px-[42px] gap-[2px]",
|
|
84
|
-
// Full bleed, with the rule that separates the header from the table.
|
|
85
|
-
Table: "mt-[6px] border-t border-border-presentation-global-primary",
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
defaultVariants: { variant: "Default" },
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
const rail = cva("flex w-full min-w-[300px] flex-col items-start", {
|
|
92
|
-
variants: {
|
|
93
|
-
variant: {
|
|
94
|
-
// Hairline between each direct child (form rows).
|
|
95
|
-
// `divide-gray-300` was a hardcoded light gray, so in the dark theme every row separator
|
|
96
|
-
// rendered as a bright rgb(209,213,219) line. The token follows the theme.
|
|
97
|
-
Default: "divide-y divide-border-presentation-global-primary",
|
|
98
|
-
// The table draws its own row borders — a divide rule would double up on the
|
|
99
|
-
// table / scroller / end-action siblings.
|
|
100
|
-
Table: "",
|
|
101
|
-
},
|
|
102
|
-
},
|
|
103
|
-
defaultVariants: { variant: "Default" },
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
export type SectionColor = NonNullable<VariantProps<typeof titleBadge>["color"]>;
|
|
107
|
-
export type SectionVariant = NonNullable<VariantProps<typeof container>["variant"]>;
|
|
108
|
-
|
|
109
|
-
export interface SectionBlockProps
|
|
110
|
-
extends Omit<HTMLAttributes<HTMLDivElement>, "title">,
|
|
111
|
-
VariantProps<typeof container> {
|
|
112
|
-
color?: SectionColor;
|
|
113
|
-
title?: ReactNode;
|
|
114
|
-
icon?: ReactNode;
|
|
115
|
-
/** Right-aligned content on the title row — e.g. action buttons. */
|
|
116
|
-
action?: ReactNode;
|
|
117
|
-
containerClassName?: string;
|
|
118
|
-
headerClassName?: string;
|
|
119
|
-
bodyClassName?: string;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export const SectionBlock = forwardRef<HTMLDivElement, SectionBlockProps>(
|
|
123
|
-
(
|
|
124
|
-
{
|
|
125
|
-
children,
|
|
126
|
-
color,
|
|
127
|
-
variant,
|
|
128
|
-
title,
|
|
129
|
-
action,
|
|
130
|
-
className,
|
|
131
|
-
containerClassName,
|
|
132
|
-
headerClassName,
|
|
133
|
-
bodyClassName,
|
|
134
|
-
icon,
|
|
135
|
-
...props
|
|
136
|
-
},
|
|
137
|
-
ref,
|
|
138
|
-
) => {
|
|
139
|
-
return (
|
|
140
|
-
<div
|
|
141
|
-
ref={ref}
|
|
142
|
-
className={cn(container({ variant }), className, containerClassName)}
|
|
143
|
-
{...props}
|
|
144
|
-
>
|
|
145
|
-
{(title || action) && (
|
|
146
|
-
<div className={cn(header({ variant }), headerClassName)}>
|
|
147
|
-
{title ? (
|
|
148
|
-
<div className={cn(titleBadge({ color }))}>
|
|
149
|
-
<span className="flex items-center gap-1.5">
|
|
150
|
-
{icon}
|
|
151
|
-
{title}
|
|
152
|
-
</span>
|
|
153
|
-
</div>
|
|
154
|
-
) : (
|
|
155
|
-
<span />
|
|
156
|
-
)}
|
|
157
|
-
{action && <div className="flex shrink-0 items-center gap-2">{action}</div>}
|
|
158
|
-
</div>
|
|
159
|
-
)}
|
|
160
|
-
<div className={cn(body({ variant }), bodyClassName)}>
|
|
161
|
-
<div className={cn(rail({ variant }))}>{children}</div>
|
|
162
|
-
</div>
|
|
163
|
-
</div>
|
|
164
|
-
);
|
|
165
|
-
},
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
SectionBlock.displayName = "SectionBlock";
|