torch-glare 2.3.0 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/README.md +8 -0
  2. package/apps/lib/components/ActionButton.tsx +7 -12
  3. package/apps/lib/components/ActionsGroup.tsx +4 -15
  4. package/apps/lib/components/AlertDialog.tsx +173 -191
  5. package/apps/lib/components/Avatar.tsx +13 -16
  6. package/apps/lib/components/Badge.tsx +7 -3
  7. package/apps/lib/components/BadgeField.tsx +76 -71
  8. package/apps/lib/components/Breadcrumb.tsx +49 -85
  9. package/apps/lib/components/Button.tsx +20 -19
  10. package/apps/lib/components/ButtonGroup.tsx +31 -41
  11. package/apps/lib/components/Calendar.tsx +51 -29
  12. package/apps/lib/components/Card.tsx +2 -13
  13. package/apps/lib/components/ChartBlockTool.ts +17 -52
  14. package/apps/lib/components/Checkbox.tsx +9 -16
  15. package/apps/lib/components/ContextMenu.tsx +110 -106
  16. package/apps/lib/components/CountBadge.tsx +4 -6
  17. package/apps/lib/components/DataTable.tsx +196 -216
  18. package/apps/lib/components/DataViews/DataViewRadio.tsx +10 -8
  19. package/apps/lib/components/DataViews/DataViewsConfigPanel.tsx +21 -55
  20. package/apps/lib/components/DataViews/DataViewsHeader.tsx +1 -3
  21. package/apps/lib/components/DataViews/DataViewsLayout.tsx +5 -12
  22. package/apps/lib/components/DataViews/FilterPanel.tsx +173 -150
  23. package/apps/lib/components/DataViews/HeaderSearch.tsx +1 -5
  24. package/apps/lib/components/DataViews/InboxView.tsx +22 -68
  25. package/apps/lib/components/DataViews/InboxViewCard.tsx +68 -77
  26. package/apps/lib/components/DataViews/KanbanView.tsx +8 -25
  27. package/apps/lib/components/DataViews/PanelControls.tsx +3 -13
  28. package/apps/lib/components/DataViews/SettingsPanel.tsx +83 -89
  29. package/apps/lib/components/DataViews/TableView.tsx +8 -28
  30. package/apps/lib/components/DataViews/TreeView.tsx +12 -40
  31. package/apps/lib/components/DataViews/badgeAdapter.ts +32 -28
  32. package/apps/lib/components/DataViews/fieldRenderers.tsx +106 -141
  33. package/apps/lib/components/DataViews/filters/DatePickerRangeFilter.tsx +87 -0
  34. package/apps/lib/components/DataViews/filters/DateRangePopover.tsx +39 -32
  35. package/apps/lib/components/DataViews/filters/PresetChips.tsx +15 -15
  36. package/apps/lib/components/DataViews/filters/RangeSliderWithInputs.tsx +69 -58
  37. package/apps/lib/components/DataViews/index.ts +26 -26
  38. package/apps/lib/components/DataViews/tree/TreeDrawer.tsx +12 -16
  39. package/apps/lib/components/DataViews/tree/TreeSidebar.tsx +28 -31
  40. package/apps/lib/components/DataViews/types.ts +111 -114
  41. package/apps/lib/components/DatePicker.tsx +198 -191
  42. package/apps/lib/components/Dialog.tsx +92 -105
  43. package/apps/lib/components/Divider.tsx +21 -24
  44. package/apps/lib/components/Drawer.tsx +79 -139
  45. package/apps/lib/components/DropdownMenu.tsx +144 -181
  46. package/apps/lib/components/FieldHint.tsx +4 -11
  47. package/apps/lib/components/Form.tsx +146 -155
  48. package/apps/lib/components/FormStepper.tsx +9 -39
  49. package/apps/lib/components/HeaderBar.tsx +119 -0
  50. package/apps/lib/components/ImageAttachment.tsx +111 -73
  51. package/apps/lib/components/InnerLabelField.tsx +20 -16
  52. package/apps/lib/components/Input.tsx +171 -172
  53. package/apps/lib/components/InputField.tsx +44 -45
  54. package/apps/lib/components/InputOTP.tsx +68 -69
  55. package/apps/lib/components/Label.tsx +31 -35
  56. package/apps/lib/components/LabelField.tsx +4 -5
  57. package/apps/lib/components/LabeledCheckBox.tsx +8 -12
  58. package/apps/lib/components/LabeledRadio.tsx +7 -5
  59. package/apps/lib/components/LinkButton.tsx +2 -9
  60. package/apps/lib/components/LoginButton.tsx +4 -9
  61. package/apps/lib/components/PasswordLevel.tsx +5 -5
  62. package/apps/lib/components/Popover.tsx +15 -33
  63. package/apps/lib/components/ProfileMenu.tsx +67 -72
  64. package/apps/lib/components/Radio.tsx +1 -5
  65. package/apps/lib/components/RadioCard.tsx +9 -28
  66. package/apps/lib/components/ScrollArea.tsx +45 -43
  67. package/apps/lib/components/SearchField.tsx +49 -39
  68. package/apps/lib/components/SearchableSelect.tsx +46 -52
  69. package/apps/lib/components/SearchableTable.tsx +199 -219
  70. package/apps/lib/components/SearchableTree.tsx +469 -0
  71. package/apps/lib/components/SearchableTreeDialog.tsx +493 -0
  72. package/apps/lib/components/SectionBlock.tsx +4 -16
  73. package/apps/lib/components/Select.tsx +53 -80
  74. package/apps/lib/components/SimpleSelect.tsx +196 -187
  75. package/apps/lib/components/Skeleton.tsx +9 -12
  76. package/apps/lib/components/SlideDatePicker.tsx +215 -166
  77. package/apps/lib/components/SpinLoading.tsx +33 -49
  78. package/apps/lib/components/Stepper.tsx +13 -56
  79. package/apps/lib/components/Switch.tsx +8 -10
  80. package/apps/lib/components/TabFormItem.tsx +3 -12
  81. package/apps/lib/components/TabSwitch.tsx +7 -34
  82. package/apps/lib/components/Table.tsx +60 -75
  83. package/apps/lib/components/TableDnDWrapper.ts +15 -48
  84. package/apps/lib/components/TextEditor.tsx +93 -104
  85. package/apps/lib/components/Textarea.tsx +8 -12
  86. package/apps/lib/components/Timeline.tsx +21 -41
  87. package/apps/lib/components/Toast.tsx +83 -83
  88. package/apps/lib/components/Toggle.tsx +112 -114
  89. package/apps/lib/components/ToggleButton.tsx +1 -1
  90. package/apps/lib/components/Tooltip.tsx +24 -19
  91. package/apps/lib/components/TransparentLabel.tsx +61 -62
  92. package/apps/lib/components/TreeDropDown.tsx +86 -52
  93. package/apps/lib/components/TreeFolder/TreeFolder.tsx +15 -48
  94. package/apps/lib/components/TreeFolder/TreeFolderBreadcrumb.tsx +16 -21
  95. package/apps/lib/components/TreeFolder/TreeFolderRow.tsx +9 -20
  96. package/apps/lib/components/TreeFolder/TreeFolderStyles.tsx +3 -3
  97. package/apps/lib/components/TreeFolder/icons.tsx +14 -17
  98. package/apps/lib/components/TreeFolder/index.ts +15 -8
  99. package/apps/lib/components/TreeFolder/treeFolderUtils.ts +47 -52
  100. package/apps/lib/components/TreeFolder/types.ts +32 -32
  101. package/apps/lib/components/TreeFolder/useTreeFolderDnD.ts +117 -123
  102. package/apps/lib/hooks/useActiveTreeItem.ts +52 -55
  103. package/apps/lib/hooks/useClickOutside.ts +21 -20
  104. package/apps/lib/hooks/useDataViewsState.ts +86 -80
  105. package/apps/lib/hooks/useIsMobile.ts +12 -12
  106. package/apps/lib/hooks/useResize.ts +54 -65
  107. package/apps/lib/hooks/useTagSelection.ts +201 -201
  108. package/apps/lib/layouts/CNLayout.tsx +181 -140
  109. package/apps/lib/layouts/DataViewCard.tsx +4 -18
  110. package/apps/lib/layouts/FieldSection.tsx +2 -6
  111. package/apps/lib/layouts/TreeSubLayout.tsx +24 -38
  112. package/apps/lib/providers/ThemeProvider.tsx +8 -23
  113. package/apps/lib/registry.json +1004 -0
  114. package/apps/lib/tsconfig.json +9 -0
  115. package/apps/lib/utils/cn.ts +1 -1
  116. package/apps/lib/utils/dataViews/columnUtils.ts +46 -44
  117. package/apps/lib/utils/dataViews/fieldUtils.ts +70 -71
  118. package/apps/lib/utils/dataViews/nestedDataUtils.tsx +129 -122
  119. package/apps/lib/utils/dataViews/pathUtils.ts +74 -67
  120. package/apps/lib/utils/dataViews/rangeUtils.ts +121 -112
  121. package/apps/lib/utils/dataViews/treeUtils.ts +204 -211
  122. package/apps/lib/utils/dateFormat.ts +64 -54
  123. package/apps/lib/utils/markdownParser.ts +2 -7
  124. package/apps/lib/utils/resize.ts +21 -25
  125. package/apps/lib/utils/types.ts +13 -13
  126. package/dist/bin/index.js +8 -2
  127. package/dist/bin/index.js.map +1 -1
  128. package/dist/src/shared/getAvailableFiles.d.ts.map +1 -1
  129. package/dist/src/shared/getAvailableFiles.js +6 -1
  130. package/dist/src/shared/getAvailableFiles.js.map +1 -1
  131. package/dist/src/shared/loadRegistry.d.ts +7 -0
  132. package/dist/src/shared/loadRegistry.d.ts.map +1 -0
  133. package/dist/src/shared/loadRegistry.js +31 -0
  134. package/dist/src/shared/loadRegistry.js.map +1 -0
  135. package/dist/src/shared/resolveInstallPlan.d.ts +12 -0
  136. package/dist/src/shared/resolveInstallPlan.d.ts.map +1 -0
  137. package/dist/src/shared/resolveInstallPlan.js +39 -0
  138. package/dist/src/shared/resolveInstallPlan.js.map +1 -0
  139. package/dist/src/types/main.d.ts +24 -0
  140. package/dist/src/types/main.d.ts.map +1 -1
  141. package/docs/README.md +10 -2
  142. package/docs/components/action-button.md +30 -24
  143. package/docs/components/actions-group.md +26 -20
  144. package/docs/components/alert-dialog.md +45 -45
  145. package/docs/components/avatar.md +6 -2
  146. package/docs/components/badge-field.md +6 -2
  147. package/docs/components/badge.md +7 -1
  148. package/docs/components/breadcrumb.md +15 -40
  149. package/docs/components/button-group.md +13 -10
  150. package/docs/components/button.md +58 -33
  151. package/docs/components/calendar.md +26 -19
  152. package/docs/components/card.md +19 -16
  153. package/docs/components/chart-block-tool.md +128 -0
  154. package/docs/components/checkbox.md +16 -8
  155. package/docs/components/cn-layout.md +7 -1
  156. package/docs/components/context-menu.md +47 -24
  157. package/docs/components/count-badge.md +7 -1
  158. package/docs/components/data-table.md +45 -33
  159. package/docs/components/data-views-config-panel.md +18 -14
  160. package/docs/components/data-views-layout.md +20 -17
  161. package/docs/components/date-picker.md +30 -23
  162. package/docs/components/dialog.md +43 -42
  163. package/docs/components/divider.md +10 -4
  164. package/docs/components/drawer.md +8 -33
  165. package/docs/components/dropdown-menu.md +51 -32
  166. package/docs/components/field-hint.md +37 -25
  167. package/docs/components/field-section.md +9 -3
  168. package/docs/components/form-stepper.md +6 -0
  169. package/docs/components/form.md +17 -20
  170. package/docs/components/header-bar.md +187 -0
  171. package/docs/components/image-attachment.md +33 -41
  172. package/docs/components/inbox-view.md +16 -5
  173. package/docs/components/inner-label-field.md +34 -24
  174. package/docs/components/input-field.md +18 -10
  175. package/docs/components/input-otp.md +14 -11
  176. package/docs/components/input.md +22 -16
  177. package/docs/components/kanban-view.md +15 -5
  178. package/docs/components/label-field.md +32 -22
  179. package/docs/components/label.md +43 -32
  180. package/docs/components/labeled-check-box.md +16 -8
  181. package/docs/components/labeled-radio.md +18 -11
  182. package/docs/components/link-button.md +7 -1
  183. package/docs/components/login-button.md +8 -2
  184. package/docs/components/password-level.md +37 -24
  185. package/docs/components/popover.md +26 -23
  186. package/docs/components/profile-menu.md +38 -27
  187. package/docs/components/radio-card.md +16 -9
  188. package/docs/components/radio.md +16 -8
  189. package/docs/components/scroll-area.md +6 -2
  190. package/docs/components/search-field.md +14 -6
  191. package/docs/components/searchable-select.md +19 -14
  192. package/docs/components/searchable-table.md +60 -41
  193. package/docs/components/searchable-tree-dialog.md +190 -0
  194. package/docs/components/searchable-tree.md +200 -0
  195. package/docs/components/section-block.md +130 -4
  196. package/docs/components/select.md +23 -18
  197. package/docs/components/simple-select.md +14 -10
  198. package/docs/components/skeleton.md +43 -33
  199. package/docs/components/slide-date-picker.md +37 -29
  200. package/docs/components/spin-loading.md +32 -23
  201. package/docs/components/stepper.md +6 -0
  202. package/docs/components/switch.md +14 -6
  203. package/docs/components/tab-form-item.md +7 -1
  204. package/docs/components/tab-switch.md +13 -6
  205. package/docs/components/table-dnd-wrapper.md +105 -0
  206. package/docs/components/table-view.md +15 -5
  207. package/docs/components/table.md +28 -28
  208. package/docs/components/text-editor.md +19 -13
  209. package/docs/components/textarea.md +15 -7
  210. package/docs/components/timeline.md +6 -0
  211. package/docs/components/toast.md +35 -26
  212. package/docs/components/toggle-button.md +24 -21
  213. package/docs/components/toggle.md +23 -15
  214. package/docs/components/tooltip.md +42 -30
  215. package/docs/components/transparent-label.md +29 -20
  216. package/docs/components/tree-drop-down.md +31 -23
  217. package/docs/components/tree-sub-layout.md +6 -2
  218. package/docs/components/tree-view.md +16 -5
  219. package/docs/explanation/architecture.md +54 -0
  220. package/docs/explanation/design-system.md +52 -0
  221. package/docs/how-to/data-views-from-backend-response.md +7 -4
  222. package/docs/how-to/guides.md +24 -18
  223. package/docs/migration/changelog.md +38 -0
  224. package/docs/reference/cli.md +87 -0
  225. package/docs/reference/components.md +11 -12
  226. package/docs/reference/hooks.md +8 -8
  227. package/docs/reference/providers.md +22 -22
  228. package/docs/reference/tailwind-plugins.md +2 -2
  229. package/docs/reference/theme.md +85 -0
  230. package/docs/reference/types.md +4 -4
  231. package/docs/reference/utilities.md +19 -26
  232. package/docs/tutorials/building-first-form.md +20 -13
  233. package/docs/tutorials/component-composition.md +34 -50
  234. package/docs/tutorials/getting-started.md +2 -2
  235. package/docs/tutorials/theming-basics.md +13 -10
  236. package/package.json +31 -3
  237. package/dist/src/commands/block.d.ts +0 -5
  238. package/dist/src/commands/block.d.ts.map +0 -1
  239. package/dist/src/commands/block.js +0 -255
  240. package/dist/src/commands/block.js.map +0 -1
  241. package/dist/src/shared/configureFonts.d.ts +0 -6
  242. package/dist/src/shared/configureFonts.d.ts.map +0 -1
  243. package/dist/src/shared/configureFonts.js +0 -106
  244. package/dist/src/shared/configureFonts.js.map +0 -1
  245. package/dist/src/shared/configureGlobalCss.d.ts +0 -6
  246. package/dist/src/shared/configureGlobalCss.d.ts.map +0 -1
  247. package/dist/src/shared/configureGlobalCss.js +0 -154
  248. package/dist/src/shared/configureGlobalCss.js.map +0 -1
  249. package/dist/src/shared/configureTailwind.d.ts +0 -7
  250. package/dist/src/shared/configureTailwind.d.ts.map +0 -1
  251. package/dist/src/shared/configureTailwind.js +0 -163
  252. package/dist/src/shared/configureTailwind.js.map +0 -1
  253. package/dist/src/shared/detectFramework.d.ts +0 -23
  254. package/dist/src/shared/detectFramework.d.ts.map +0 -1
  255. package/dist/src/shared/detectFramework.js +0 -119
  256. package/dist/src/shared/detectFramework.js.map +0 -1
  257. package/dist/src/shared/installBaseUtils.d.ts +0 -5
  258. package/dist/src/shared/installBaseUtils.d.ts.map +0 -1
  259. package/dist/src/shared/installBaseUtils.js +0 -79
  260. package/dist/src/shared/installBaseUtils.js.map +0 -1
  261. package/dist/src/shared/resolveAliases.d.ts +0 -24
  262. package/dist/src/shared/resolveAliases.d.ts.map +0 -1
  263. package/dist/src/shared/resolveAliases.js +0 -98
  264. package/dist/src/shared/resolveAliases.js.map +0 -1
@@ -1,29 +1,28 @@
1
1
  "use client";
2
2
 
3
- import { ReactNode, useEffect, useMemo, useRef, useState } from "react";
4
- import { cva } from "class-variance-authority";
3
+ import {
4
+ forwardRef,
5
+ InputHTMLAttributes,
6
+ ReactNode,
7
+ useEffect,
8
+ useMemo,
9
+ useRef,
10
+ useState,
11
+ } from "react";
5
12
  import { cn } from "../utils/cn";
6
13
  import { Themes } from "../utils/types";
7
- import { Popover, PopoverContent, PopoverTrigger } from "./Popover";
14
+ import { Dialog, DialogTrigger, DialogContent, DialogTitle } from "./Dialog";
8
15
  import { Icon, Input, Group } from "./Input";
9
- import { Button, LoadingIcon } from "./Button";
10
- import { useClickOutside } from "../hooks/useClickOutside";
11
- import {
12
- Table,
13
- TableHeader,
14
- TableBody,
15
- TableHead,
16
- TableRow,
17
- TableCell,
18
- } from "./Table";
16
+ import { LoadingIcon } from "./Button";
17
+ import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from "./Table";
19
18
 
20
19
  /**
21
- * SearchableTable — a searchable combobox whose dropdown renders a real Table.
20
+ * SearchableTable — a field that opens a modal Dialog to pick a row from a Table.
22
21
  *
23
- * Type in the input to filter rows; the dropdown opens on focus and shows the
24
- * filtered data using the existing Table component (composed inside a Popover,
25
- * the same mechanism BadgeField uses). Click a row to select it (single-select):
26
- * the dropdown closes and the input shows the selected row's label.
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.
27
26
  */
28
27
 
29
28
  export interface SearchableTableColumn<T> {
@@ -40,13 +39,18 @@ interface Props<T> {
40
39
  /** Controlled selected row (optional). */
41
40
  value?: T | null;
42
41
  onSelect?: (row: T) => void;
43
- /** Text shown in the input after selection; defaults to the first column's value. */
42
+ /** Text shown on the trigger after selection; defaults to the first column's value. */
44
43
  getLabel?: (row: T) => string;
45
44
  /** Stable id/key per row; defaults to JSON of the row. */
46
45
  getRowId?: (row: T) => string;
47
46
  /** Which fields the search matches; defaults to every column key. */
48
47
  searchKeys?: (keyof T & string)[];
48
+ /** Trigger placeholder shown until a row is selected. */
49
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;
50
54
  size?: "XS" | "S" | "M";
51
55
  variant?: "SystemStyle" | "PresentationStyle";
52
56
  icon?: ReactNode;
@@ -71,44 +75,8 @@ interface Props<T> {
71
75
  loading?: boolean;
72
76
  /** Called when the list nears the bottom and `hasMore && !loading`. */
73
77
  onLoadMore?: () => void;
74
- /** Max rows visible before the list scrolls (default 6). */
75
- maxVisibleRows?: number;
76
78
  }
77
79
 
78
- // One M-size table row ≈ 40px + the 44px sticky header.
79
- const ROW_HEIGHT = 40;
80
- const HEADER_HEIGHT = 44;
81
-
82
- // Popover surface styled like the DropdownMenu (kept local — self-contained).
83
- const tableDropdownStyles = cva(
84
- [
85
- "p-1",
86
- "rounded-[14px]",
87
- "border-0", // neutralize PopoverContent's base border (the card provides its own)
88
- "outline-none",
89
- "overflow-y-auto",
90
- "scrollbar-hide",
91
- "backdrop-blur-[21px]",
92
- "data-[state=open]:animate-in",
93
- "data-[state=open]:fade-in-0",
94
- ],
95
- {
96
- variants: {
97
- variant: {
98
- PresentationStyle: [
99
- "bg-[rgba(61,64,69,0.72)]",
100
- "shadow-[0_0_32px_2px_rgba(0,0,0,0.20),0_0_48px_2px_rgba(0,0,0,0.05)]",
101
- ],
102
- SystemStyle: [
103
- "bg-background-system-body-primary",
104
- "shadow-[0px_0px_18px_0px_rgba(0,0,0,0.75)]",
105
- ],
106
- },
107
- },
108
- defaultVariants: { variant: "PresentationStyle" },
109
- }
110
- );
111
-
112
80
  export function SearchableTable<T extends Record<string, unknown>>({
113
81
  columns,
114
82
  rows,
@@ -117,7 +85,9 @@ export function SearchableTable<T extends Record<string, unknown>>({
117
85
  getLabel,
118
86
  getRowId,
119
87
  searchKeys,
120
- placeholder = "Search…",
88
+ placeholder = "Select…",
89
+ searchPlaceholder = "Search…",
90
+ title = "Select an item",
121
91
  size = "M",
122
92
  variant = "PresentationStyle",
123
93
  icon,
@@ -130,14 +100,17 @@ export function SearchableTable<T extends Record<string, unknown>>({
130
100
  hasMore = false,
131
101
  loading = false,
132
102
  onLoadMore,
133
- maxVisibleRows = 6,
134
103
  }: Props<T>) {
135
104
  const [open, setOpen] = useState(false);
136
105
  const [search, setSearch] = useState("");
137
- const [dropdownWidth, setDropdownWidth] = useState(0);
138
- const popoverContentRef = useRef<HTMLDivElement>(null);
106
+ const scrollRef = useRef<HTMLDivElement>(null);
139
107
  const inputRef = useRef<HTMLInputElement | null>(null);
140
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
+
141
114
  // Debounced server search: notify the consumer to refetch when the query
142
115
  // settles. Skipped entirely if no onSearchChange is provided (static mode).
143
116
  useEffect(() => {
@@ -146,8 +119,13 @@ export function SearchableTable<T extends Record<string, unknown>>({
146
119
  return () => clearTimeout(id);
147
120
  }, [search, onSearchChange, searchDebounceMs]);
148
121
 
149
- // Infinite scroll: when the vertical scroll viewport nears the bottom, ask the
150
- // consumer to load the next page (onScroll is reliable across Radix's portal).
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.
151
129
  const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
152
130
  if (!onLoadMore || !hasMore || loading) return;
153
131
  const el = e.currentTarget;
@@ -156,22 +134,6 @@ export function SearchableTable<T extends Record<string, unknown>>({
156
134
  }
157
135
  };
158
136
 
159
- const groupRef = useClickOutside<HTMLDivElement>((e) => {
160
- const target = e?.target as Node;
161
- if (
162
- !groupRef.current?.contains(target) &&
163
- !popoverContentRef.current?.contains(target)
164
- ) {
165
- setOpen(false);
166
- }
167
- });
168
-
169
- const rowId = (row: T) => getRowId?.(row) ?? JSON.stringify(row);
170
- const label = (row: T) =>
171
- getLabel?.(row) ?? String(row[columns[0]?.key] ?? "");
172
-
173
- const keysToSearch = searchKeys ?? columns.map((c) => c.key);
174
-
175
137
  const filteredRows = useMemo(() => {
176
138
  // Server-side search mode: render rows as-is (already filtered upstream).
177
139
  if (!filterClientSide) return rows;
@@ -179,185 +141,203 @@ export function SearchableTable<T extends Record<string, unknown>>({
179
141
  if (!q) return rows;
180
142
  return rows.filter((row) =>
181
143
  keysToSearch.some((k) =>
182
- String(row[k] ?? "").toLowerCase().includes(q)
183
- )
144
+ String(row[k] ?? "")
145
+ .toLowerCase()
146
+ .includes(q),
147
+ ),
184
148
  );
185
149
  }, [rows, search, keysToSearch, filterClientSide]);
186
150
 
187
151
  const selectedId = value ? rowId(value) : undefined;
188
- // True while the user is actively typing a query. When false, the input shows
189
- // the selected row's label as its real value (solid text, not placeholder).
190
- const [searching, setSearching] = useState(false);
191
152
 
192
153
  const handleSelect = (row: T) => {
193
154
  onSelect?.(row);
194
- setSearch("");
195
- setSearching(false);
196
155
  setOpen(false);
197
156
  };
198
157
 
199
- // Solid value: search text while typing, otherwise the selected label.
200
- const displayValue = searching ? search : value ? label(value) : "";
201
-
202
158
  return (
203
- <Popover open={open}>
204
- <PopoverTrigger asChild>
159
+ <Dialog open={open} onOpenChange={setOpen}>
160
+ <DialogTrigger asChild>
161
+ {/* Trigger: shows placeholder until a row is selected, then its label. */}
205
162
  <Group
206
163
  dir={dir}
207
164
  data-theme={theme}
208
165
  variant={variant}
209
166
  size={size === "XS" ? "S" : size}
210
- ref={groupRef as never}
211
- onFocus={(e: React.FocusEvent<HTMLDivElement>) =>
212
- setDropdownWidth(e.currentTarget.offsetWidth)
213
- }
214
- className={cn("flex w-full items-center gap-1 p-1", className)}
167
+ role="button"
168
+ tabIndex={0}
169
+ className={cn("flex w-full items-center gap-1 p-1 cursor-pointer", className)}
215
170
  >
216
171
  {icon && <Icon>{icon}</Icon>}
217
- <Input
218
- ref={inputRef}
219
- placeholder={placeholder}
220
- value={displayValue}
221
- onChange={(e) => {
222
- setSearch(e.target.value);
223
- setSearching(true);
224
- }}
225
- onFocus={() => {
226
- // Start a fresh search; the selected label clears so the user can type.
227
- setSearching(true);
228
- setSearch("");
229
- setOpen(true);
230
- }}
231
- onBlur={() => setSearching(false)}
232
- className={cn("min-w-[100px] flex-1", {
233
- "!h-[18px]": size === "XS",
234
- "!h-[22px]": size === "S",
235
- "!h-[24px]": size === "M",
236
- })}
237
- />
238
- {/* Chevron toggle — boxed icon button matching the Select component. */}
239
- <Button
240
- as="span"
241
- buttonType="icon"
242
- tabIndex={-1}
243
- aria-label={open ? "Close" : "Open"}
244
- onMouseDown={(e: React.MouseEvent) => {
245
- // Prevent the input's blur/focus race; toggle the dropdown.
246
- e.preventDefault();
247
- if (open) {
248
- setOpen(false);
249
- } else {
250
- inputRef.current?.focus();
251
- setOpen(true);
252
- }
253
- }}
172
+ <span
254
173
  className={cn(
255
- "shrink-0 h-[32px] w-[32px] rounded-[4px]",
256
- open && "bg-background-presentation-action-hover text-white"
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",
257
178
  )}
258
179
  >
259
- <i
260
- className={cn(
261
- "ri-arrow-down-s-line text-[16px] transition-all duration-100 ease-in-out",
262
- open && "rotate-180"
263
- )}
264
- />
265
- </Button>
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" />
266
183
  </Group>
267
- </PopoverTrigger>
184
+ </DialogTrigger>
268
185
 
269
- <PopoverContent
186
+ <DialogContent
270
187
  dir={dir}
271
188
  data-theme={theme}
272
- ref={popoverContentRef}
273
- variant={variant}
274
- onScroll={handleScroll}
275
- // Lock the dropdown to the input's width; the table scrolls horizontally
276
- // inside it. Cap height to ~maxVisibleRows (+ header) so the rest scrolls.
277
- style={{
278
- width: dropdownWidth || undefined,
279
- maxHeight: HEADER_HEIGHT + maxVisibleRows * ROW_HEIGHT + 8,
189
+ onOpenAutoFocus={(e) => {
190
+ // Focus the search input instead of the first row.
191
+ e.preventDefault();
192
+ inputRef.current?.focus();
280
193
  }}
281
- onOpenAutoFocus={(e) => e.preventDefault()}
282
- className={cn(tableDropdownStyles({ variant }))}
194
+ className={cn(
195
+ "w-[min(640px,90vw)] bg-transparent !items-stretch rounded-[14px] shadow-none",
196
+ )}
283
197
  >
284
- {filteredRows.length > 0 && (
285
- // Table-DDV wrapper: frosted bordered card around the table (Figma).
286
- // The radius + overflow-hidden also go on the <table> itself, because a
287
- // <table>'s corner cells paint to square edges and otherwise cover an
288
- // ancestor's rounded corners.
289
- <div className="rounded-[10px] border overflow-x-auto border-white-alpha-40 bg-[rgba(184,192,204,0.5)]">
290
- <Table
291
- theme={theme as never}
292
- data-theme="dark"
293
- // Natural width (w-max) so columns keep their sizes and the wrapper
294
- // scrolls horizontally instead of squeezing them into the input width.
295
- className="w-max rounded-[10px] overflow-hidden"
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)]"
296
220
  >
297
- {/* The blurred dark header bar is painted on the TableHEAD cells
298
- (which fill the row); bg/height on a <tr> doesn't render
299
- reliably because cells paint over it. */}
300
- <TableHeader className=" bg-black-alpha-20 shadow-[0px_4px_8px_0px_rgba(0,0,0,0.15)]">
301
- <TableRow className="h-[44px] [&_button]:bg-white-alpha-40 [&>th]:border-white-alpha-20">
302
- {columns.map((col) => (
303
- <TableHead
304
- key={col.key}
305
- // White semibold header text from the design.
306
- className="cursor-default typography-body-medium-medium text-white"
307
- >
308
- {col.header}
309
- </TableHead>
310
- ))}
311
- </TableRow>
312
- </TableHeader>
313
- <TableBody>
314
- {filteredRows.map((row) => {
315
- const id = rowId(row);
316
- return (
317
- <TableRow
318
- key={id}
319
- state={selectedId === id ? "selected" : undefined}
320
- onClick={() => handleSelect(row)}
321
- // h-[40px] rows with a translucent white bottom rule.
322
- className={cn("cursor-pointer h-[40px] ", selectedId === id && "bg-white-alpha-50")}
323
- >
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="bg-black-alpha-20 shadow-[0px_4px_8px_0px_rgba(0,0,0,0.15)]">
228
+ <TableRow className="h-[44px] [&_button]:bg-white-alpha-40 [&>th]:border-white-alpha-20">
324
229
  {columns.map((col) => (
325
- <TableCell
230
+ <TableHead
326
231
  key={col.key}
327
- // White body text; whitespace-nowrap (overriding the
328
- // component's break-all) keeps columns wide so the
329
- // table overflows horizontally instead of wrapping.
330
- className="border-b border-white-alpha-20 text-white px-[8px] !whitespace-nowrap !break-normal"
232
+ className="cursor-default typography-body-medium-medium text-white"
331
233
  >
332
- {col.render
333
- ? col.render(row)
334
- : String(row[col.key] ?? "")}
335
- </TableCell>
234
+ {col.header}
235
+ </TableHead>
336
236
  ))}
337
237
  </TableRow>
338
- );
339
- })}
340
- </TableBody>
341
- </Table>
342
- </div>
343
- )}
238
+ </TableHeader>
239
+ <TableBody>
240
+ {filteredRows.map((row) => {
241
+ const id = rowId(row);
242
+ return (
243
+ <TableRow
244
+ key={id}
245
+ state={selectedId === id ? "selected" : undefined}
246
+ onClick={() => handleSelect(row)}
247
+ className={cn(
248
+ "cursor-pointer h-[40px]",
249
+ selectedId === id && "bg-white-alpha-50",
250
+ )}
251
+ >
252
+ {columns.map((col) => (
253
+ <TableCell
254
+ key={col.key}
255
+ className="border-b border-white-alpha-20 text-white px-[8px] !whitespace-nowrap !break-normal"
256
+ >
257
+ {col.render ? col.render(row) : String(row[col.key] ?? "")}
258
+ </TableCell>
259
+ ))}
260
+ </TableRow>
261
+ );
262
+ })}
263
+ </TableBody>
264
+ </Table>
265
+ )}
344
266
 
345
- {/* Empty state — only when nothing is loading. */}
346
- {filteredRows.length === 0 && !loading && (
347
- <div className="px-3 py-4 typography-body-small-regular text-white-alpha-75">
348
- No results found
349
- </div>
350
- )}
267
+ {/* Empty state — only when nothing is loading. */}
268
+ {filteredRows.length === 0 && !loading && (
269
+ <div className="px-3 py-6 text-center typography-body-small-regular text-white-alpha-75">
270
+ No results found
271
+ </div>
272
+ )}
351
273
 
352
- {/* Loading row (initial load or fetching the next page). */}
353
- {loading && (
354
- <div className="flex items-center justify-center py-2">
355
- <LoadingIcon size="M" />
274
+ {/* Loading row (initial load or fetching the next page). */}
275
+ {loading && (
276
+ <div className="flex items-center justify-center py-3">
277
+ <LoadingIcon size="M" />
278
+ </div>
279
+ )}
280
+ </div>
356
281
  </div>
357
- )}
358
- </PopoverContent>
359
- </Popover>
282
+ </div>
283
+ </DialogContent>
284
+ </Dialog>
360
285
  );
361
286
  }
362
287
 
363
288
  SearchableTable.displayName = "SearchableTable";
289
+
290
+ /* -------------------------------------------------------------------------- */
291
+ /* SearchInput — local inline-label search field (replaces InnerLabelField). */
292
+ /* The whole field + its text turn white on hover/focus, fully controlled here. */
293
+ /* -------------------------------------------------------------------------- */
294
+
295
+ interface SearchInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "size"> {
296
+ variant?: "SystemStyle" | "PresentationStyle";
297
+ theme?: Themes;
298
+ label?: string;
299
+ }
300
+
301
+ const SearchInput = forwardRef<HTMLInputElement, SearchInputProps>(
302
+ ({ variant = "PresentationStyle", theme, label, className, ...props }, ref) => (
303
+ <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]">
304
+ <Group
305
+ data-theme={theme}
306
+ variant={variant}
307
+ size="M"
308
+ className={cn(
309
+ "group flex w-full items-center gap-2 p-1 rounded-[10px] transition-colors",
310
+ "border border-white-alpha-40 bg-[rgba(184,192,204,0.5)]",
311
+ "hover:bg-background-presentation-table-row-hover",
312
+ "focus-within:bg-background-presentation-table-row-hover rounded-[11px] border border-white-alpha-40 bg-[rgba(37,44,57,0.30)]",
313
+ className,
314
+ )}
315
+ >
316
+ <Icon className="shrink-0">
317
+ <i className="ri-search-line text-content-presentation-global-secondary transition-colors group-hover:text-white group-focus-within:text-white" />
318
+ </Icon>
319
+
320
+ {label && (
321
+ <>
322
+ <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">
323
+ {label}
324
+ </span>
325
+ <span className="h-[14px] w-px shrink-0 rounded-full bg-border-presentation-action-labelless-divider transition-colors group-hover:bg-white-alpha-40" />
326
+ </>
327
+ )}
328
+
329
+ <Input
330
+ ref={ref}
331
+ {...props}
332
+ className={cn(
333
+ "min-w-[100px] flex-1 !h-[24px] bg-transparent",
334
+ "text-content-presentation-action-light-primary",
335
+ "placeholder:hover:text-white-alpha-75",
336
+ "transition-colors group-hover:!text-white group-focus-within:!text-white",
337
+ )}
338
+ />
339
+ </Group>
340
+ </div>
341
+ ),
342
+ );
343
+ SearchInput.displayName = "SearchInput";