torch-glare 2.5.5 → 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 -338
- package/apps/lib/components/Breadcrumb.tsx +0 -280
- package/apps/lib/components/Button.tsx +0 -317
- 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 -383
- 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 -84
- 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 -63
- package/apps/lib/components/DataViews/states.tsx +0 -38
- 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 -420
- package/apps/lib/components/DropdownMenu.tsx +0 -540
- package/apps/lib/components/FieldHint.tsx +0 -69
- package/apps/lib/components/Form.tsx +0 -182
- package/apps/lib/components/FormBuilder/context.ts +0 -73
- 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 -163
- 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 -91
- 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 -47
- package/apps/lib/components/FormBuilder/types.ts +0 -311
- package/apps/lib/components/FormRenderer/FormDrawer.tsx +0 -128
- package/apps/lib/components/FormRenderer/detail.tsx +0 -207
- package/apps/lib/components/FormRenderer/form-renderer.tsx +0 -297
- package/apps/lib/components/FormRenderer/header.tsx +0 -88
- package/apps/lib/components/FormRenderer/index.ts +0 -15
- package/apps/lib/components/FormRenderer/section.tsx +0 -39
- package/apps/lib/components/FormRenderer/stepper.tsx +0 -339
- package/apps/lib/components/FormRenderer/types.ts +0 -78
- 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 -147
- 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 -253
- 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 -610
- 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 -58
- package/apps/lib/hooks/useClickOutside.ts +0 -27
- package/apps/lib/hooks/useDragDrop.tsx +0 -365
- 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 -214
- 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 -1201
- 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/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 -712
- 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 -1495
- package/docs/components/data-views/migration.md +0 -79
- 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 -276
- package/docs/components/form-renderer.md +0 -326
- 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 -477
- 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 -54
- 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 -1500
- 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 -704
- 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,318 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { ReactNode, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
-
import { cva } from "class-variance-authority";
|
|
5
|
-
import { cn } from "../utils/cn";
|
|
6
|
-
import { Themes } from "../utils/types";
|
|
7
|
-
import { Popover, PopoverContent, PopoverTrigger } from "./Popover";
|
|
8
|
-
import { Icon, Input, Group } from "./Input";
|
|
9
|
-
import { LoadingIcon } from "./Button";
|
|
10
|
-
import { ActionButton } from "./ActionButton";
|
|
11
|
-
import { useClickOutside } from "../hooks/useClickOutside";
|
|
12
|
-
// Reuse the exact menu-item styling so rows look identical to DropdownMenuItem.
|
|
13
|
-
import { MenuItemStyles } from "./DropdownMenu";
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* SearchableSelect — a searchable single-select combobox.
|
|
17
|
-
*
|
|
18
|
-
* Same search-on-focus behavior as SearchableTable, but the dropdown renders
|
|
19
|
-
* DropdownMenu-style rows (MenuItemStyles) instead of a table. Built on Popover
|
|
20
|
-
* so the text input keeps focus while filtering. Click an option to select it:
|
|
21
|
-
* the dropdown closes and the input shows the option's label.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
export interface SearchableSelectOption {
|
|
25
|
-
value: string;
|
|
26
|
-
label: string;
|
|
27
|
-
icon?: ReactNode;
|
|
28
|
-
/** Text used for client-side filtering. Defaults to `label` — set it to match on a subset
|
|
29
|
-
* (e.g. a country name only, when the label also shows a dial code). */
|
|
30
|
-
searchText?: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
interface Props {
|
|
34
|
-
options: SearchableSelectOption[];
|
|
35
|
-
/** Controlled selected value (optional). */
|
|
36
|
-
value?: string | null;
|
|
37
|
-
onValueChange?: (value: string, option: SearchableSelectOption) => void;
|
|
38
|
-
placeholder?: string;
|
|
39
|
-
size?: "XS" | "S" | "M";
|
|
40
|
-
variant?: "SystemStyle" | "PresentationStyle";
|
|
41
|
-
icon?: ReactNode;
|
|
42
|
-
theme?: Themes;
|
|
43
|
-
dir?: string;
|
|
44
|
-
className?: string;
|
|
45
|
-
inputClassName?: string;
|
|
46
|
-
/** Transparent border/background so the trigger blends into a table cell. */
|
|
47
|
-
onTable?: boolean;
|
|
48
|
-
|
|
49
|
-
// --- Async / backend pagination (all optional; static `options` still works) ---
|
|
50
|
-
/**
|
|
51
|
-
* When true (default), the component filters `options` by label locally.
|
|
52
|
-
* Set false for server-side search — `options` are rendered as-is and you
|
|
53
|
-
* refetch them in response to `onSearchChange`.
|
|
54
|
-
*/
|
|
55
|
-
filterClientSide?: boolean;
|
|
56
|
-
/** Debounced as the user types — refetch your data here (server search). */
|
|
57
|
-
onSearchChange?: (query: string) => void;
|
|
58
|
-
/** Debounce for `onSearchChange`, in ms (default 300). */
|
|
59
|
-
searchDebounceMs?: number;
|
|
60
|
-
/** Whether more pages are available; gates the infinite-scroll loader. */
|
|
61
|
-
hasMore?: boolean;
|
|
62
|
-
/** Whether a fetch is in flight; shows a loading row and blocks onLoadMore. */
|
|
63
|
-
loading?: boolean;
|
|
64
|
-
/** Called when the bottom sentinel scrolls into view and `hasMore && !loading`. */
|
|
65
|
-
onLoadMore?: () => void;
|
|
66
|
-
/** Max rows visible before the list scrolls (default 5). */
|
|
67
|
-
maxVisibleItems?: number;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// One M-size row ≈ 32px height + 2px*2 padding + 1px gap.
|
|
71
|
-
const ROW_HEIGHT = 37;
|
|
72
|
-
|
|
73
|
-
// Popover surface styled like the DropdownMenu (self-contained, mirrors the
|
|
74
|
-
// menu surface used by DropdownMenu/ContextMenu).
|
|
75
|
-
const menuContentStyles = cva(
|
|
76
|
-
[
|
|
77
|
-
"p-1",
|
|
78
|
-
"rounded-[14px]",
|
|
79
|
-
"border-0",
|
|
80
|
-
"outline-none",
|
|
81
|
-
"overflow-hidden", // the inner scroll viewport owns scrolling
|
|
82
|
-
"backdrop-blur-[21px]",
|
|
83
|
-
"flex flex-col gap-1",
|
|
84
|
-
"data-[state=open]:animate-in",
|
|
85
|
-
"data-[state=open]:fade-in-0",
|
|
86
|
-
],
|
|
87
|
-
{
|
|
88
|
-
variants: {
|
|
89
|
-
variant: {
|
|
90
|
-
PresentationStyle: [
|
|
91
|
-
"bg-[rgba(61,64,69,0.72)]",
|
|
92
|
-
"shadow-[0_0_32px_2px_rgba(0,0,0,0.20),0_0_48px_2px_rgba(0,0,0,0.05)]",
|
|
93
|
-
],
|
|
94
|
-
SystemStyle: [
|
|
95
|
-
"bg-background-system-body-primary",
|
|
96
|
-
"shadow-[0px_0px_18px_0px_rgba(0,0,0,0.75)]",
|
|
97
|
-
],
|
|
98
|
-
},
|
|
99
|
-
},
|
|
100
|
-
defaultVariants: { variant: "PresentationStyle" },
|
|
101
|
-
},
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
export function SearchableSelect({
|
|
105
|
-
options,
|
|
106
|
-
value,
|
|
107
|
-
onValueChange,
|
|
108
|
-
placeholder = "Search…",
|
|
109
|
-
size = "M",
|
|
110
|
-
variant = "PresentationStyle",
|
|
111
|
-
icon,
|
|
112
|
-
theme,
|
|
113
|
-
dir,
|
|
114
|
-
className,
|
|
115
|
-
inputClassName,
|
|
116
|
-
onTable,
|
|
117
|
-
filterClientSide = true,
|
|
118
|
-
onSearchChange,
|
|
119
|
-
searchDebounceMs = 300,
|
|
120
|
-
hasMore = false,
|
|
121
|
-
loading = false,
|
|
122
|
-
onLoadMore,
|
|
123
|
-
maxVisibleItems = 5,
|
|
124
|
-
}: Props) {
|
|
125
|
-
const [open, setOpen] = useState(false);
|
|
126
|
-
const [search, setSearch] = useState("");
|
|
127
|
-
const [searching, setSearching] = useState(false);
|
|
128
|
-
const [dropdownWidth, setDropdownWidth] = useState(0);
|
|
129
|
-
const popoverContentRef = useRef<HTMLDivElement>(null);
|
|
130
|
-
const scrollRef = useRef<HTMLDivElement>(null);
|
|
131
|
-
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
132
|
-
|
|
133
|
-
const groupRef = useClickOutside<HTMLDivElement>((e) => {
|
|
134
|
-
const target = e?.target as Node;
|
|
135
|
-
if (!groupRef.current?.contains(target) && !popoverContentRef.current?.contains(target)) {
|
|
136
|
-
setOpen(false);
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
// Debounced server search: notify the consumer to refetch when the query
|
|
141
|
-
// settles. Skipped entirely if no onSearchChange is provided (static mode).
|
|
142
|
-
useEffect(() => {
|
|
143
|
-
if (!onSearchChange) return;
|
|
144
|
-
const id = setTimeout(() => onSearchChange(search.trim()), searchDebounceMs);
|
|
145
|
-
return () => clearTimeout(id);
|
|
146
|
-
}, [search, onSearchChange, searchDebounceMs]);
|
|
147
|
-
|
|
148
|
-
// Infinite scroll: when the scroll viewport nears the bottom, ask the consumer
|
|
149
|
-
// to load the next page. An onScroll handler is more reliable here than an
|
|
150
|
-
// IntersectionObserver, which races with Radix's async portal mount.
|
|
151
|
-
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
|
|
152
|
-
if (!onLoadMore || !hasMore || loading) return;
|
|
153
|
-
const el = e.currentTarget;
|
|
154
|
-
if (el.scrollHeight - el.scrollTop - el.clientHeight < 80) {
|
|
155
|
-
onLoadMore();
|
|
156
|
-
}
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
const filteredOptions = useMemo(() => {
|
|
160
|
-
// Server-side search mode: render options as-is (already filtered upstream).
|
|
161
|
-
if (!filterClientSide) return options;
|
|
162
|
-
const q = search.trim().toLowerCase();
|
|
163
|
-
if (!q) return options;
|
|
164
|
-
return options.filter((o) => (o.searchText ?? o.label).toLowerCase().includes(q));
|
|
165
|
-
}, [options, search, filterClientSide]);
|
|
166
|
-
|
|
167
|
-
const selectedOption = options.find((o) => o.value === value) ?? null;
|
|
168
|
-
|
|
169
|
-
const handleSelect = (option: SearchableSelectOption) => {
|
|
170
|
-
onValueChange?.(option.value, option);
|
|
171
|
-
setSearch("");
|
|
172
|
-
setSearching(false);
|
|
173
|
-
setOpen(false);
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
// Solid value: search text while typing, otherwise the selected label.
|
|
177
|
-
const displayValue = searching ? search : (selectedOption?.label ?? "");
|
|
178
|
-
|
|
179
|
-
return (
|
|
180
|
-
<Popover open={open}>
|
|
181
|
-
<PopoverTrigger asChild>
|
|
182
|
-
<Group
|
|
183
|
-
dir={dir}
|
|
184
|
-
data-theme={theme}
|
|
185
|
-
variant={variant}
|
|
186
|
-
size={size === "XS" ? "S" : size}
|
|
187
|
-
onTable={onTable}
|
|
188
|
-
ref={groupRef as never}
|
|
189
|
-
onFocus={(e: React.FocusEvent<HTMLDivElement>) =>
|
|
190
|
-
setDropdownWidth(e.currentTarget.offsetWidth)
|
|
191
|
-
}
|
|
192
|
-
// Trigger styling mirrors the `Select` component's trigger so the two read identically.
|
|
193
|
-
// `p-[3px]` so the end button lands 4px from the outer edge once the 1px border is added.
|
|
194
|
-
// The old `rounded-[6px]` here was dead but dangerous: `twMerge` cannot dedupe it against
|
|
195
|
-
// the Group's `rounded-radius-xl` (custom scale key vs arbitrary value), so both shipped
|
|
196
|
-
// and the 12px only won on CSS source order.
|
|
197
|
-
className={cn("flex w-full items-center gap-1 p-[3px]", className)}
|
|
198
|
-
>
|
|
199
|
-
{icon && <Icon>{icon}</Icon>}
|
|
200
|
-
<Input
|
|
201
|
-
ref={inputRef}
|
|
202
|
-
placeholder={placeholder}
|
|
203
|
-
value={displayValue}
|
|
204
|
-
onChange={(e) => {
|
|
205
|
-
setSearch(e.target.value);
|
|
206
|
-
setSearching(true);
|
|
207
|
-
}}
|
|
208
|
-
onFocus={() => {
|
|
209
|
-
setSearching(true);
|
|
210
|
-
setSearch("");
|
|
211
|
-
setOpen(true);
|
|
212
|
-
}}
|
|
213
|
-
onBlur={() => setSearching(false)}
|
|
214
|
-
className={cn("min-w-[100px] flex-1", inputClassName, {
|
|
215
|
-
"!h-[18px]": size === "XS",
|
|
216
|
-
"!h-[22px]": size === "S",
|
|
217
|
-
"!h-[24px]": size === "M",
|
|
218
|
-
})}
|
|
219
|
-
/>
|
|
220
|
-
{/* Chevron toggle — the shared in-field action button (Figma `ActionButton` M). */}
|
|
221
|
-
<ActionButton
|
|
222
|
-
as="span"
|
|
223
|
-
size="M"
|
|
224
|
-
tabIndex={-1}
|
|
225
|
-
aria-label={open ? "Close" : "Open"}
|
|
226
|
-
onMouseDown={(e: React.MouseEvent) => {
|
|
227
|
-
e.preventDefault();
|
|
228
|
-
if (open) {
|
|
229
|
-
setOpen(false);
|
|
230
|
-
} else {
|
|
231
|
-
inputRef.current?.focus();
|
|
232
|
-
setOpen(true);
|
|
233
|
-
}
|
|
234
|
-
}}
|
|
235
|
-
className={cn("shrink-0", open && "bg-background-presentation-action-hover text-white")}
|
|
236
|
-
>
|
|
237
|
-
<i
|
|
238
|
-
className={cn(
|
|
239
|
-
"ri-arrow-down-s-line !text-[26px] transition-all duration-100 ease-in-out",
|
|
240
|
-
open && "rotate-180",
|
|
241
|
-
)}
|
|
242
|
-
/>
|
|
243
|
-
</ActionButton>
|
|
244
|
-
</Group>
|
|
245
|
-
</PopoverTrigger>
|
|
246
|
-
|
|
247
|
-
<PopoverContent
|
|
248
|
-
dir={dir}
|
|
249
|
-
data-theme={theme}
|
|
250
|
-
ref={popoverContentRef}
|
|
251
|
-
variant={variant}
|
|
252
|
-
onOpenAutoFocus={(e) => e.preventDefault()}
|
|
253
|
-
onWheel={(e) => e.stopPropagation()}
|
|
254
|
-
className={cn(menuContentStyles({ variant }))}
|
|
255
|
-
// The panel carries the cap — `maxVisibleItems` rows, but never more than the room Radix has
|
|
256
|
-
// on screen. Inline beats the `max-h-[…]` class popoverStyles contributes.
|
|
257
|
-
style={{
|
|
258
|
-
width: dropdownWidth || undefined,
|
|
259
|
-
maxHeight: `min(${maxVisibleItems * ROW_HEIGHT}px, var(--radix-popover-content-available-height, 100vh))`,
|
|
260
|
-
}}
|
|
261
|
-
>
|
|
262
|
-
{/* Dedicated scroll viewport. The height cap lives on the panel above; this just fills what is
|
|
263
|
-
left and scrolls. `min-h-0` is required — a flex item will not shrink below its content, so
|
|
264
|
-
without it the list grew past the panel and was clipped instead of scrolling. */}
|
|
265
|
-
<div
|
|
266
|
-
ref={scrollRef}
|
|
267
|
-
onScroll={handleScroll}
|
|
268
|
-
className="flex-1 min-h-0 overflow-y-auto overflow-x-hidden rounded-[10px] scrollbar-hide"
|
|
269
|
-
>
|
|
270
|
-
{filteredOptions.length > 0 && (
|
|
271
|
-
// Boxed group container — matches the DropdownMenu's auto-grouped look.
|
|
272
|
-
<div className="flex flex-col gap-[1px] rounded-[10px] overflow-hidden">
|
|
273
|
-
{filteredOptions.map((option) => {
|
|
274
|
-
const isSelected = option.value === value;
|
|
275
|
-
return (
|
|
276
|
-
// Same structure as DropdownMenuItem: MenuItemStyles on the
|
|
277
|
-
// element + a single inner <div> the styles target via [&>div].
|
|
278
|
-
<button
|
|
279
|
-
type="button"
|
|
280
|
-
key={option.value}
|
|
281
|
-
onClick={() => handleSelect(option)}
|
|
282
|
-
data-highlighted={isSelected ? "" : undefined}
|
|
283
|
-
className={cn(
|
|
284
|
-
MenuItemStyles({ variant: "Default", size: "M" }),
|
|
285
|
-
"shrink-0", // keep full row height; the list scrolls instead of squishing
|
|
286
|
-
)}
|
|
287
|
-
>
|
|
288
|
-
<div>
|
|
289
|
-
{option.icon}
|
|
290
|
-
<span className="flex-1 text-start">{option.label}</span>
|
|
291
|
-
{isSelected && <i className="ri-check-line text-[16px] shrink-0" />}
|
|
292
|
-
</div>
|
|
293
|
-
</button>
|
|
294
|
-
);
|
|
295
|
-
})}
|
|
296
|
-
</div>
|
|
297
|
-
)}
|
|
298
|
-
|
|
299
|
-
{/* Empty state — only when nothing is loading. */}
|
|
300
|
-
{filteredOptions.length === 0 && !loading && (
|
|
301
|
-
<div className="px-3 py-2 typography-body-small-regular text-white-alpha-75">
|
|
302
|
-
No results found
|
|
303
|
-
</div>
|
|
304
|
-
)}
|
|
305
|
-
|
|
306
|
-
{/* Loading row (initial load or fetching the next page). */}
|
|
307
|
-
{loading && (
|
|
308
|
-
<div className="flex items-center justify-center py-2">
|
|
309
|
-
<LoadingIcon size="M" />
|
|
310
|
-
</div>
|
|
311
|
-
)}
|
|
312
|
-
</div>
|
|
313
|
-
</PopoverContent>
|
|
314
|
-
</Popover>
|
|
315
|
-
);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
SearchableSelect.displayName = "SearchableSelect";
|
|
@@ -1,344 +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 { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from "./Table";
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* SearchableTable — a field that opens a modal Dialog to pick a row from a Table.
|
|
21
|
-
*
|
|
22
|
-
* The trigger shows a placeholder until something is selected, then shows the
|
|
23
|
-
* selected row's label. Clicking it opens a dialog containing a search input and
|
|
24
|
-
* the data Table; clicking a row selects it, sets the value, and closes the
|
|
25
|
-
* dialog. Supports client- or server-side search and infinite-scroll pagination.
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
export interface SearchableTableColumn<T> {
|
|
29
|
-
/** Property on the row used for default rendering and search. */
|
|
30
|
-
key: keyof T & string;
|
|
31
|
-
header: ReactNode;
|
|
32
|
-
/** Custom cell renderer; defaults to String(row[key]). */
|
|
33
|
-
render?: (row: T) => ReactNode;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
interface Props<T> {
|
|
37
|
-
columns: SearchableTableColumn<T>[];
|
|
38
|
-
rows: T[];
|
|
39
|
-
/** Controlled selected row (optional). */
|
|
40
|
-
value?: T | null;
|
|
41
|
-
onSelect?: (row: T) => void;
|
|
42
|
-
/** Text shown on the trigger after selection; defaults to the first column's value. */
|
|
43
|
-
getLabel?: (row: T) => string;
|
|
44
|
-
/** Stable id/key per row; defaults to JSON of the row. */
|
|
45
|
-
getRowId?: (row: T) => string;
|
|
46
|
-
/** Which fields the search matches; defaults to every column key. */
|
|
47
|
-
searchKeys?: (keyof T & string)[];
|
|
48
|
-
/** Trigger placeholder shown until a row is selected. */
|
|
49
|
-
placeholder?: string;
|
|
50
|
-
/** Placeholder for the search input inside the dialog. */
|
|
51
|
-
searchPlaceholder?: string;
|
|
52
|
-
/** Label shown on the dialog's search field. */
|
|
53
|
-
title?: string;
|
|
54
|
-
size?: "XS" | "S" | "M";
|
|
55
|
-
variant?: "SystemStyle" | "PresentationStyle";
|
|
56
|
-
icon?: ReactNode;
|
|
57
|
-
theme?: Themes;
|
|
58
|
-
dir?: string;
|
|
59
|
-
className?: string;
|
|
60
|
-
|
|
61
|
-
// --- Async / backend pagination (all optional; static `rows` still works) ---
|
|
62
|
-
/**
|
|
63
|
-
* When true (default), the component filters `rows` by searchKeys locally.
|
|
64
|
-
* Set false for server-side search — `rows` are rendered as-is and you
|
|
65
|
-
* refetch them in response to `onSearchChange`.
|
|
66
|
-
*/
|
|
67
|
-
filterClientSide?: boolean;
|
|
68
|
-
/** Debounced as the user types — refetch your data here (server search). */
|
|
69
|
-
onSearchChange?: (query: string) => void;
|
|
70
|
-
/** Debounce for `onSearchChange`, in ms (default 300). */
|
|
71
|
-
searchDebounceMs?: number;
|
|
72
|
-
/** Whether more pages are available; gates the infinite-scroll loader. */
|
|
73
|
-
hasMore?: boolean;
|
|
74
|
-
/** Whether a fetch is in flight; shows a loading row and blocks onLoadMore. */
|
|
75
|
-
loading?: boolean;
|
|
76
|
-
/** Called when the list nears the bottom and `hasMore && !loading`. */
|
|
77
|
-
onLoadMore?: () => void;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function SearchableTable<T extends Record<string, unknown>>({
|
|
81
|
-
columns,
|
|
82
|
-
rows,
|
|
83
|
-
value,
|
|
84
|
-
onSelect,
|
|
85
|
-
getLabel,
|
|
86
|
-
getRowId,
|
|
87
|
-
searchKeys,
|
|
88
|
-
placeholder = "Select…",
|
|
89
|
-
searchPlaceholder = "Search…",
|
|
90
|
-
title = "Select an item",
|
|
91
|
-
size = "M",
|
|
92
|
-
variant = "PresentationStyle",
|
|
93
|
-
icon,
|
|
94
|
-
theme,
|
|
95
|
-
dir,
|
|
96
|
-
className,
|
|
97
|
-
filterClientSide = true,
|
|
98
|
-
onSearchChange,
|
|
99
|
-
searchDebounceMs = 300,
|
|
100
|
-
hasMore = false,
|
|
101
|
-
loading = false,
|
|
102
|
-
onLoadMore,
|
|
103
|
-
}: Props<T>) {
|
|
104
|
-
const [open, setOpen] = useState(false);
|
|
105
|
-
const [search, setSearch] = useState("");
|
|
106
|
-
const scrollRef = useRef<HTMLDivElement>(null);
|
|
107
|
-
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
108
|
-
|
|
109
|
-
const rowId = (row: T) => getRowId?.(row) ?? JSON.stringify(row);
|
|
110
|
-
const label = (row: T) => getLabel?.(row) ?? String(row[columns[0]?.key] ?? "");
|
|
111
|
-
|
|
112
|
-
const keysToSearch = searchKeys ?? columns.map((c) => c.key);
|
|
113
|
-
|
|
114
|
-
// Debounced server search: notify the consumer to refetch when the query
|
|
115
|
-
// settles. Skipped entirely if no onSearchChange is provided (static mode).
|
|
116
|
-
useEffect(() => {
|
|
117
|
-
if (!onSearchChange) return;
|
|
118
|
-
const id = setTimeout(() => onSearchChange(search.trim()), searchDebounceMs);
|
|
119
|
-
return () => clearTimeout(id);
|
|
120
|
-
}, [search, onSearchChange, searchDebounceMs]);
|
|
121
|
-
|
|
122
|
-
// Reset the query whenever the dialog closes so it reopens clean.
|
|
123
|
-
useEffect(() => {
|
|
124
|
-
if (!open) setSearch("");
|
|
125
|
-
}, [open]);
|
|
126
|
-
|
|
127
|
-
// Infinite scroll: when the scroll viewport nears the bottom, ask the consumer
|
|
128
|
-
// to load the next page.
|
|
129
|
-
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
|
|
130
|
-
if (!onLoadMore || !hasMore || loading) return;
|
|
131
|
-
const el = e.currentTarget;
|
|
132
|
-
if (el.scrollHeight - el.scrollTop - el.clientHeight < 80) {
|
|
133
|
-
onLoadMore();
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
const filteredRows = useMemo(() => {
|
|
138
|
-
// Server-side search mode: render rows as-is (already filtered upstream).
|
|
139
|
-
if (!filterClientSide) return rows;
|
|
140
|
-
const q = search.trim().toLowerCase();
|
|
141
|
-
if (!q) return rows;
|
|
142
|
-
return rows.filter((row) =>
|
|
143
|
-
keysToSearch.some((k) =>
|
|
144
|
-
String(row[k] ?? "")
|
|
145
|
-
.toLowerCase()
|
|
146
|
-
.includes(q),
|
|
147
|
-
),
|
|
148
|
-
);
|
|
149
|
-
}, [rows, search, keysToSearch, filterClientSide]);
|
|
150
|
-
|
|
151
|
-
const selectedId = value ? rowId(value) : undefined;
|
|
152
|
-
|
|
153
|
-
const handleSelect = (row: T) => {
|
|
154
|
-
onSelect?.(row);
|
|
155
|
-
setOpen(false);
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
return (
|
|
159
|
-
<Dialog open={open} onOpenChange={setOpen}>
|
|
160
|
-
<DialogTrigger asChild>
|
|
161
|
-
{/* Trigger: shows placeholder until a row is selected, then its label. */}
|
|
162
|
-
<Group
|
|
163
|
-
dir={dir}
|
|
164
|
-
data-theme={theme}
|
|
165
|
-
variant={variant}
|
|
166
|
-
size={size === "XS" ? "S" : size}
|
|
167
|
-
role="button"
|
|
168
|
-
tabIndex={0}
|
|
169
|
-
className={cn("flex w-full items-center gap-1 p-1 cursor-pointer", className)}
|
|
170
|
-
>
|
|
171
|
-
{icon && <Icon>{icon}</Icon>}
|
|
172
|
-
<span
|
|
173
|
-
className={cn(
|
|
174
|
-
"flex-1 px-1 truncate typography-body-medium-regular",
|
|
175
|
-
value
|
|
176
|
-
? "text-content-presentation-action-light-primary"
|
|
177
|
-
: "text-content-presentation-action-light-secondary",
|
|
178
|
-
)}
|
|
179
|
-
>
|
|
180
|
-
{value ? label(value) : placeholder}
|
|
181
|
-
</span>
|
|
182
|
-
<i className="ri-arrow-down-s-line text-[20px] shrink-0 text-content-presentation-action-light-primary" />
|
|
183
|
-
</Group>
|
|
184
|
-
</DialogTrigger>
|
|
185
|
-
|
|
186
|
-
<DialogContent
|
|
187
|
-
dir={dir}
|
|
188
|
-
data-theme={theme}
|
|
189
|
-
onOpenAutoFocus={(e) => {
|
|
190
|
-
// Focus the search input instead of the first row.
|
|
191
|
-
e.preventDefault();
|
|
192
|
-
inputRef.current?.focus();
|
|
193
|
-
}}
|
|
194
|
-
className={cn(
|
|
195
|
-
"w-[min(640px,90vw)] bg-transparent !items-stretch rounded-[14px] shadow-none",
|
|
196
|
-
)}
|
|
197
|
-
>
|
|
198
|
-
{/* Visually-hidden title for accessibility (Radix requires a DialogTitle). */}
|
|
199
|
-
<DialogTitle className="sr-only">{title ?? "Select an item"}</DialogTitle>
|
|
200
|
-
|
|
201
|
-
{/* Search input — local inline-label field (full control over colors). */}
|
|
202
|
-
<SearchInput
|
|
203
|
-
ref={inputRef}
|
|
204
|
-
variant={variant}
|
|
205
|
-
theme={theme}
|
|
206
|
-
dir={dir}
|
|
207
|
-
label={title}
|
|
208
|
-
value={search}
|
|
209
|
-
placeholder={searchPlaceholder}
|
|
210
|
-
onChange={(e) => setSearch(e.target.value)}
|
|
211
|
-
/>
|
|
212
|
-
|
|
213
|
-
{/* Scrollable table area */}
|
|
214
|
-
<div className=" w-full flex justify-center">
|
|
215
|
-
<div className="bg-[#696a6f] pt-[8px] rounded-b-[14px] pr-[4px] pb-[4px] pl-[4px] w-[95%] shadow-[0_0_32px_2px_rgba(0,0,0,0.20),0_0_48px_2px_rgba(0,0,0,0.05)]">
|
|
216
|
-
<div
|
|
217
|
-
ref={scrollRef}
|
|
218
|
-
onScroll={handleScroll}
|
|
219
|
-
className="max-h-[55vh] overflow-auto scrollbar-hide rounded-[10px] border border-white-alpha-40 bg-[rgba(184,192,204,0.5)]"
|
|
220
|
-
>
|
|
221
|
-
{filteredRows.length > 0 && (
|
|
222
|
-
<Table
|
|
223
|
-
theme={theme as never}
|
|
224
|
-
data-theme="dark"
|
|
225
|
-
className="w-full rounded-[10px] overflow-hidden"
|
|
226
|
-
>
|
|
227
|
-
<TableHeader className="backdrop-blur-none bg-black-alpha-20 shadow-[0px_4px_8px_0px_rgba(0,0,0,0.15)]">
|
|
228
|
-
<TableRow className="h-[44px] [&_button:not([data-slot=resize-handle])]:bg-white-alpha-40 [&>th]:border-white-alpha-20">
|
|
229
|
-
{columns.map((col) => (
|
|
230
|
-
<TableHead
|
|
231
|
-
key={col.key}
|
|
232
|
-
className="cursor-default"
|
|
233
|
-
contentClassName="typography-body-medium-medium text-white"
|
|
234
|
-
>
|
|
235
|
-
{col.header}
|
|
236
|
-
</TableHead>
|
|
237
|
-
))}
|
|
238
|
-
</TableRow>
|
|
239
|
-
</TableHeader>
|
|
240
|
-
<TableBody>
|
|
241
|
-
{filteredRows.map((row) => {
|
|
242
|
-
const id = rowId(row);
|
|
243
|
-
return (
|
|
244
|
-
<TableRow
|
|
245
|
-
key={id}
|
|
246
|
-
state={selectedId === id ? "selected" : undefined}
|
|
247
|
-
onClick={() => handleSelect(row)}
|
|
248
|
-
className={cn(
|
|
249
|
-
"cursor-pointer h-[40px]",
|
|
250
|
-
selectedId === id && "bg-white-alpha-50",
|
|
251
|
-
)}
|
|
252
|
-
>
|
|
253
|
-
{columns.map((col) => (
|
|
254
|
-
<TableCell
|
|
255
|
-
key={col.key}
|
|
256
|
-
className="h-[40px] border-b border-white-alpha-20 text-white px-[8px] !whitespace-nowrap !break-normal"
|
|
257
|
-
>
|
|
258
|
-
{col.render ? col.render(row) : String(row[col.key] ?? "")}
|
|
259
|
-
</TableCell>
|
|
260
|
-
))}
|
|
261
|
-
</TableRow>
|
|
262
|
-
);
|
|
263
|
-
})}
|
|
264
|
-
</TableBody>
|
|
265
|
-
</Table>
|
|
266
|
-
)}
|
|
267
|
-
|
|
268
|
-
{/* Empty state — only when nothing is loading. */}
|
|
269
|
-
{filteredRows.length === 0 && !loading && (
|
|
270
|
-
<div className="px-3 py-6 text-center typography-body-small-regular text-white-alpha-75">
|
|
271
|
-
No results found
|
|
272
|
-
</div>
|
|
273
|
-
)}
|
|
274
|
-
|
|
275
|
-
{/* Loading row (initial load or fetching the next page). */}
|
|
276
|
-
{loading && (
|
|
277
|
-
<div className="flex items-center justify-center py-3">
|
|
278
|
-
<LoadingIcon size="M" />
|
|
279
|
-
</div>
|
|
280
|
-
)}
|
|
281
|
-
</div>
|
|
282
|
-
</div>
|
|
283
|
-
</div>
|
|
284
|
-
</DialogContent>
|
|
285
|
-
</Dialog>
|
|
286
|
-
);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
SearchableTable.displayName = "SearchableTable";
|
|
290
|
-
|
|
291
|
-
/* -------------------------------------------------------------------------- */
|
|
292
|
-
/* SearchInput — local inline-label search field (replaces InnerLabelField). */
|
|
293
|
-
/* The whole field + its text turn white on hover/focus, fully controlled here. */
|
|
294
|
-
/* -------------------------------------------------------------------------- */
|
|
295
|
-
|
|
296
|
-
interface SearchInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "size"> {
|
|
297
|
-
variant?: "SystemStyle" | "PresentationStyle";
|
|
298
|
-
theme?: Themes;
|
|
299
|
-
label?: string;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const SearchInput = forwardRef<HTMLInputElement, SearchInputProps>(
|
|
303
|
-
({ variant = "PresentationStyle", theme, label, className, ...props }, ref) => (
|
|
304
|
-
<div className="flex p-[3px] flex-col gap-[10px] rounded-[14px] bg-[rgba(61,64,69,0.65)] shadow-[0_0_18px_0_rgba(0,0,0,0.40)] backdrop-blur-[21px]">
|
|
305
|
-
<Group
|
|
306
|
-
data-theme={theme}
|
|
307
|
-
variant={variant}
|
|
308
|
-
size="M"
|
|
309
|
-
className={cn(
|
|
310
|
-
"group flex w-full items-center gap-2 p-1 rounded-[10px] transition-colors",
|
|
311
|
-
"border border-white-alpha-40 bg-[rgba(184,192,204,0.5)]",
|
|
312
|
-
"hover:bg-background-presentation-table-row-hover",
|
|
313
|
-
"focus-within:bg-background-presentation-table-row-hover rounded-[11px] border border-white-alpha-40 bg-[rgba(37,44,57,0.30)]",
|
|
314
|
-
className,
|
|
315
|
-
)}
|
|
316
|
-
>
|
|
317
|
-
<Icon className="shrink-0">
|
|
318
|
-
<i className="ri-search-line text-content-presentation-global-secondary transition-colors group-hover:text-white group-focus-within:text-white" />
|
|
319
|
-
</Icon>
|
|
320
|
-
|
|
321
|
-
{label && (
|
|
322
|
-
<>
|
|
323
|
-
<span className="shrink-0 px-1 typography-labels-small-regular text-content-presentation-global-secondary text-[14px] text-white transition-colors group-hover:text-white group-focus-within:text-white">
|
|
324
|
-
{label}
|
|
325
|
-
</span>
|
|
326
|
-
<span className="h-[14px] w-px shrink-0 rounded-full bg-border-presentation-action-labelless-divider transition-colors group-hover:bg-white-alpha-40" />
|
|
327
|
-
</>
|
|
328
|
-
)}
|
|
329
|
-
|
|
330
|
-
<Input
|
|
331
|
-
ref={ref}
|
|
332
|
-
{...props}
|
|
333
|
-
className={cn(
|
|
334
|
-
"min-w-[100px] flex-1 !h-[24px] bg-transparent",
|
|
335
|
-
"text-content-presentation-action-light-primary",
|
|
336
|
-
"placeholder:hover:text-white-alpha-75",
|
|
337
|
-
"transition-colors group-hover:!text-white group-focus-within:!text-white",
|
|
338
|
-
)}
|
|
339
|
-
/>
|
|
340
|
-
</Group>
|
|
341
|
-
</div>
|
|
342
|
-
),
|
|
343
|
-
);
|
|
344
|
-
SearchInput.displayName = "SearchInput";
|