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
@@ -61,14 +61,14 @@ export type DataViewsLayoutProps = {
61
61
  onAddNew?: () => void;
62
62
  addNewLabel?: string;
63
63
 
64
- inboxItemHref?: (item: DynamicRecord, id: any) => string;
64
+ inboxItemHref?: (item: DynamicRecord, id: unknown) => string;
65
65
  /**
66
66
  * Component used to render inbox item links when `inboxItemHref` is set.
67
67
  * Defaults to a plain `<a>` (full-page navigation). Pass your router's link
68
68
  * (e.g. Next.js `Link`, React Router `Link`) for client-side navigation.
69
69
  */
70
70
  inboxLinkComponent?: ElementType;
71
- inboxSelectedId?: any;
71
+ inboxSelectedId?: unknown;
72
72
  inboxRenderDetail?: (item: DynamicRecord | null) => ReactNode;
73
73
 
74
74
  searchValue?: string;
@@ -79,10 +79,7 @@ export type DataViewsLayoutProps = {
79
79
  theme?: Themes;
80
80
  };
81
81
 
82
- const VIEW_META: Record<
83
- ViewType,
84
- { label: string; icon: DataViewsHeaderView["icon"] }
85
- > = {
82
+ const VIEW_META: Record<ViewType, { label: string; icon: DataViewsHeaderView["icon"] }> = {
86
83
  table: { label: "List", icon: <List /> },
87
84
  kanban: { label: "Board", icon: <LayoutGrid /> },
88
85
  inbox: { label: "Inbox", icon: <InboxIcon /> },
@@ -163,9 +160,7 @@ export const DataViewsLayout = forwardRef<HTMLDivElement, DataViewsLayoutProps>(
163
160
  // Mount at width 0 first, then flip to open on the next frame so the
164
161
  // width transition animates from 0 → 260px instead of snapping.
165
162
  setPanelMounted(true);
166
- requestAnimationFrame(() =>
167
- requestAnimationFrame(() => setPanelOpen(true)),
168
- );
163
+ requestAnimationFrame(() => requestAnimationFrame(() => setPanelOpen(true)));
169
164
  }, []);
170
165
 
171
166
  const closePanel = useCallback(() => {
@@ -250,9 +245,7 @@ export const DataViewsLayout = forwardRef<HTMLDivElement, DataViewsLayoutProps>(
250
245
  fields={resolvedFields}
251
246
  config={effectiveConfig}
252
247
  onDataUpdate={onDataUpdate}
253
- onSortChange={(sortBy, sortOrder) =>
254
- setConfig({ sortBy, sortOrder })
255
- }
248
+ onSortChange={(sortBy, sortOrder) => setConfig({ sortBy, sortOrder })}
256
249
  filters={filters}
257
250
  filterState={filterState}
258
251
  onFilterChange={setFilterState}
@@ -1,15 +1,15 @@
1
- "use client"
2
-
3
- import { Fragment } from "react"
4
- import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
5
- import { Button } from "../Button"
6
- import { Badge } from "../Badge"
7
- import { X } from "lucide-react"
8
- import { Checkbox } from "../Checkbox"
9
- import { Divider } from "../Divider"
10
- import { Label } from "../Label"
11
- import { DataViewRadio } from "./DataViewRadio"
12
- import { cn } from "../../utils/cn"
1
+ "use client";
2
+
3
+ import { Fragment } from "react";
4
+ import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
5
+ import { Button } from "../Button";
6
+ import { Badge } from "../Badge";
7
+ import { X } from "lucide-react";
8
+ import { Checkbox } from "../Checkbox";
9
+ import { Divider } from "../Divider";
10
+ import { Label } from "../Label";
11
+ import { DataViewRadio } from "./DataViewRadio";
12
+ import { cn } from "../../utils/cn";
13
13
  import type {
14
14
  DynamicRecord,
15
15
  DynamicFilterConfig,
@@ -19,8 +19,8 @@ import type {
19
19
  FilterValue,
20
20
  NumericRangeFilter,
21
21
  DateRangeFilter,
22
- } from "./types"
23
- import { getByPath, formatPathLabel } from "../../utils/dataViews/pathUtils"
22
+ } from "./types";
23
+ import { getByPath, formatPathLabel } from "../../utils/dataViews/pathUtils";
24
24
  import {
25
25
  computeNumericExtremes,
26
26
  countActiveFilters,
@@ -28,27 +28,29 @@ import {
28
28
  isDateRange,
29
29
  isNumericRange,
30
30
  resolvePresets,
31
- } from "../../utils/dataViews/rangeUtils"
32
- import { RangeSliderWithInputs } from "./filters/RangeSliderWithInputs"
33
- import { DateRangePopover } from "./filters/DateRangePopover"
34
- import { PresetChips } from "./filters/PresetChips"
35
- import { resolveBadgeVariant } from "./badgeAdapter"
31
+ } from "../../utils/dataViews/rangeUtils";
32
+ import { RangeSliderWithInputs } from "./filters/RangeSliderWithInputs";
33
+ import { DatePickerRangeFilter } from "./filters/DatePickerRangeFilter";
34
+ import { PresetChips } from "./filters/PresetChips";
35
+ import { resolveBadgeVariant } from "./badgeAdapter";
36
+ import { SearchableSelect } from "../SearchableSelect";
37
+ import type { SearchableSelectOption } from "../SearchableSelect";
36
38
 
37
39
  type FilterPanelProps = {
38
- data: DynamicRecord[]
39
- fields: FieldConfig[]
40
- filters: FilterState
41
- onFilterChange: (path: string, value: FilterValue) => void
42
- onClearAll: () => void
43
- filterConfig?: DynamicFilterConfig[]
40
+ data: DynamicRecord[];
41
+ fields: FieldConfig[];
42
+ filters: FilterState;
43
+ onFilterChange: (path: string, value: FilterValue) => void;
44
+ onClearAll: () => void;
45
+ filterConfig?: DynamicFilterConfig[];
44
46
  /**
45
47
  * "default": standalone left-rail style (border, padding, light surface).
46
48
  * "panel": matches the Config tab inside DataViewsConfigPanel — no outer
47
49
  * chrome, white section headers, categorical options inside a #1C1D1F
48
50
  * rounded container, sections separated by #2C2D2E dividers.
49
51
  */
50
- variant?: "default" | "panel"
51
- }
52
+ variant?: "default" | "panel";
53
+ };
52
54
 
53
55
  const NUMERIC_TYPES: FieldType[] = [
54
56
  "number",
@@ -56,19 +58,19 @@ const NUMERIC_TYPES: FieldType[] = [
56
58
  "currency",
57
59
  "progress-bar",
58
60
  "star-rating",
59
- ]
61
+ ];
60
62
 
61
- const DATE_TYPES: FieldType[] = ["date", "date-format"]
63
+ const DATE_TYPES: FieldType[] = ["date", "date-format"];
62
64
 
63
- type FilterKind = "categorical" | "numeric-range" | "date-range"
65
+ type FilterKind = "categorical" | "numeric-range" | "date-range";
64
66
 
65
67
  type Entry = {
66
- path: string
67
- label: string
68
- kind: FilterKind
69
- field?: FieldConfig
70
- legacy?: DynamicFilterConfig
71
- }
68
+ path: string;
69
+ label: string;
70
+ kind: FilterKind;
71
+ field?: FieldConfig;
72
+ legacy?: DynamicFilterConfig;
73
+ };
72
74
 
73
75
  function buildFilterableEntries(
74
76
  data: DynamicRecord[],
@@ -77,41 +79,41 @@ function buildFilterableEntries(
77
79
  ): Entry[] {
78
80
  const legacyByPath = new Map(
79
81
  (legacy ?? []).filter((f) => f.enabled !== false).map((f) => [f.id, f]),
80
- )
82
+ );
81
83
 
82
- const entries: Entry[] = []
83
- const seen = new Set<string>()
84
+ const entries: Entry[] = [];
85
+ const seen = new Set<string>();
84
86
 
85
87
  for (const f of fields) {
86
- if (f.type === "hidden") continue
87
- if (f.filterable === false) continue
88
+ if (f.type === "hidden") continue;
89
+ if (f.filterable === false) continue;
88
90
 
89
- const isExplicit = f.filterable === true
91
+ const isExplicit = f.filterable === true;
90
92
  const isCategoricalAuto =
91
93
  f.type === "enum-badge" ||
92
94
  f.type === "boolean" ||
93
95
  f.type === "badge-array" ||
94
- f.type === "icon-text"
95
- const isNumeric = f.type != null && NUMERIC_TYPES.includes(f.type)
96
- const isDate = f.type != null && DATE_TYPES.includes(f.type)
96
+ f.type === "icon-text";
97
+ const isNumeric = f.type != null && NUMERIC_TYPES.includes(f.type);
98
+ const isDate = f.type != null && DATE_TYPES.includes(f.type);
97
99
 
98
- let include = isExplicit || isCategoricalAuto
100
+ let include = isExplicit || isCategoricalAuto;
99
101
 
100
102
  if (!include) {
101
- if (f.type !== "text" && f.type !== undefined) continue
102
- const unique = new Set<string>()
103
+ if (f.type !== "text" && f.type !== undefined) continue;
104
+ const unique = new Set<string>();
103
105
  for (const item of data) {
104
- const v = getByPath(item, f.path)
105
- if (v == null) continue
106
- unique.add(String(v))
107
- if (unique.size > 10) break
106
+ const v = getByPath(item, f.path);
107
+ if (v == null) continue;
108
+ unique.add(String(v));
109
+ if (unique.size > 10) break;
108
110
  }
109
- include = unique.size > 0 && unique.size <= 10
111
+ include = unique.size > 0 && unique.size <= 10;
110
112
  }
111
113
 
112
- if (!include) continue
114
+ if (!include) continue;
113
115
 
114
- const kind: FilterKind = isNumeric ? "numeric-range" : isDate ? "date-range" : "categorical"
116
+ const kind: FilterKind = isNumeric ? "numeric-range" : isDate ? "date-range" : "categorical";
115
117
 
116
118
  entries.push({
117
119
  path: f.path,
@@ -119,21 +121,21 @@ function buildFilterableEntries(
119
121
  kind,
120
122
  field: f,
121
123
  legacy: legacyByPath.get(f.path),
122
- })
123
- seen.add(f.path)
124
+ });
125
+ seen.add(f.path);
124
126
  }
125
127
 
126
128
  for (const lf of legacyByPath.values()) {
127
- if (seen.has(lf.id)) continue
129
+ if (seen.has(lf.id)) continue;
128
130
  entries.push({
129
131
  path: lf.id,
130
132
  label: lf.label ?? formatPathLabel(lf.id),
131
133
  kind: "categorical",
132
134
  legacy: lf,
133
- })
135
+ });
134
136
  }
135
137
 
136
- return entries
138
+ return entries;
137
139
  }
138
140
 
139
141
  function getCategoricalOptions(
@@ -143,37 +145,37 @@ function getCategoricalOptions(
143
145
  legacy?: DynamicFilterConfig,
144
146
  ): string[] {
145
147
  if (field?.filterOptions && field.filterOptions.length > 0) {
146
- return normalizeOptions(field.filterOptions)
148
+ return normalizeOptions(field.filterOptions);
147
149
  }
148
150
  if (legacy?.options && legacy.options.length > 0) {
149
- return normalizeOptions(legacy.options)
151
+ return normalizeOptions(legacy.options);
150
152
  }
151
153
  if (field?.variants) {
152
- const fromMap = Object.keys(field.variants)
153
- const fromData = collectUnique(data, path)
154
- return Array.from(new Set([...fromMap, ...fromData]))
154
+ const fromMap = Object.keys(field.variants);
155
+ const fromData = collectUnique(data, path);
156
+ return Array.from(new Set([...fromMap, ...fromData]));
155
157
  }
156
- return collectUnique(data, path)
158
+ return collectUnique(data, path);
157
159
  }
158
160
 
159
161
  function normalizeOptions(opts: NonNullable<FieldConfig["filterOptions"]>): string[] {
160
- if (opts.length === 0) return []
161
- if (typeof opts[0] === "string") return opts as string[]
162
- return (opts as { label: string; value: string }[]).map((o) => o.value)
162
+ if (opts.length === 0) return [];
163
+ if (typeof opts[0] === "string") return opts as string[];
164
+ return (opts as { label: string; value: string }[]).map((o) => o.value);
163
165
  }
164
166
 
165
167
  function collectUnique(data: DynamicRecord[], path: string): string[] {
166
- const set = new Set<string>()
168
+ const set = new Set<string>();
167
169
  for (const item of data) {
168
- const v = getByPath(item, path)
169
- if (v == null) continue
170
+ const v = getByPath(item, path);
171
+ if (v == null) continue;
170
172
  if (Array.isArray(v)) {
171
- for (const x of v) set.add(String(x))
173
+ for (const x of v) set.add(String(x));
172
174
  } else {
173
- set.add(String(v))
175
+ set.add(String(v));
174
176
  }
175
177
  }
176
- return Array.from(set).sort()
178
+ return Array.from(set).sort();
177
179
  }
178
180
 
179
181
  export function FilterPanel({
@@ -185,30 +187,30 @@ export function FilterPanel({
185
187
  filterConfig,
186
188
  variant = "default",
187
189
  }: FilterPanelProps) {
188
- const entries = buildFilterableEntries(data, fields, filterConfig)
190
+ const entries = buildFilterableEntries(data, fields, filterConfig);
189
191
 
190
192
  const setFilter = (path: string, value: FilterValue) => {
191
- onFilterChange(path, value)
192
- const field = fields.find((f) => f.path === path)
193
- field?.onFilterChange?.(value)
193
+ onFilterChange(path, value);
194
+ const field = fields.find((f) => f.path === path);
195
+ field?.onFilterChange?.(value);
194
196
  if (Array.isArray(value)) {
195
- const legacy = filterConfig?.find((f) => f.id === path)
196
- legacy?.onChange?.(value)
197
+ const legacy = filterConfig?.find((f) => f.id === path);
198
+ legacy?.onChange?.(value);
197
199
  }
198
- }
200
+ };
199
201
 
200
202
  const toggleCategorical = (path: string, option: string) => {
201
- const current = filters[path]
202
- const arr = Array.isArray(current) ? current : []
203
- const next = arr.includes(option) ? arr.filter((v) => v !== option) : [...arr, option]
204
- setFilter(path, next)
205
- }
203
+ const current = filters[path];
204
+ const arr = Array.isArray(current) ? current : [];
205
+ const next = arr.includes(option) ? arr.filter((v) => v !== option) : [...arr, option];
206
+ setFilter(path, next);
207
+ };
206
208
 
207
- const totalFilters = countActiveFilters(filters)
209
+ const totalFilters = countActiveFilters(filters);
208
210
 
209
- if (entries.length === 0) return null
211
+ if (entries.length === 0) return null;
210
212
 
211
- const countBadge = resolveBadgeVariant("gray")
213
+ const countBadge = resolveBadgeVariant("gray");
212
214
 
213
215
  if (variant === "panel") {
214
216
  return (
@@ -259,7 +261,7 @@ export function FilterPanel({
259
261
  </Fragment>
260
262
  ))}
261
263
  </div>
262
- )
264
+ );
263
265
  }
264
266
 
265
267
  return (
@@ -303,7 +305,7 @@ export function FilterPanel({
303
305
  ))}
304
306
  </div>
305
307
  </div>
306
- )
308
+ );
307
309
  }
308
310
 
309
311
  function FilterBody({
@@ -314,21 +316,23 @@ function FilterBody({
314
316
  onSetFilter,
315
317
  variant = "default",
316
318
  }: {
317
- entry: Entry
318
- data: DynamicRecord[]
319
- value: FilterValue | undefined
320
- onCategoricalToggle: (option: string) => void
321
- onSetFilter: (next: FilterValue) => void
322
- variant?: "default" | "panel"
319
+ entry: Entry;
320
+ data: DynamicRecord[];
321
+ value: FilterValue | undefined;
322
+ onCategoricalToggle: (option: string) => void;
323
+ onSetFilter: (next: FilterValue) => void;
324
+ variant?: "default" | "panel";
323
325
  }) {
324
326
  if (entry.kind === "numeric-range" && entry.field) {
325
- const extremes = computeNumericExtremes(data, entry.path)
327
+ const extremes = computeNumericExtremes(data, entry.path);
326
328
  if (!extremes || extremes.min === extremes.max) {
327
- return <div className="text-xs text-content-presentation-global-tertiary">No range to filter.</div>
329
+ return (
330
+ <div className="text-xs text-content-presentation-global-tertiary">No range to filter.</div>
331
+ );
328
332
  }
329
- const step = inferStep(entry.field, extremes)
330
- const presets = resolvePresets(entry.field)
331
- const numericValue: NumericRangeFilter | undefined = isNumericRange(value) ? value : undefined
333
+ const step = inferStep(entry.field, extremes);
334
+ const presets = resolvePresets(entry.field);
335
+ const numericValue: NumericRangeFilter | undefined = isNumericRange(value) ? value : undefined;
332
336
  return (
333
337
  <div className="space-y-3">
334
338
  {presets.length > 0 && (
@@ -342,28 +346,43 @@ function FilterBody({
342
346
  onChange={onSetFilter}
343
347
  />
344
348
  </div>
345
- )
349
+ );
346
350
  }
347
351
 
348
352
  if (entry.kind === "date-range" && entry.field) {
349
- const presets = resolvePresets(entry.field)
350
- const dateValue: DateRangeFilter | undefined = isDateRange(value) ? value : undefined
353
+ const dateValue: DateRangeFilter | undefined = isDateRange(value) ? value : undefined;
354
+ // Glare DatePicker in range mode pick a from→to span. (No quick-preset
355
+ // chips: the calendar range selection is the single intended interaction.)
356
+ return <DatePickerRangeFilter value={dateValue} onChange={onSetFilter} />;
357
+ }
358
+
359
+ const opts = getCategoricalOptions(data, entry.path, entry.field, entry.legacy);
360
+ const selected = Array.isArray(value) ? value : [];
361
+ const isSingle = entry.field?.filterMode === "single";
362
+
363
+ // Searchable single-select dropdown — for fields with many options. Stores
364
+ // the chosen value as a 1-element array (empty when cleared) to stay
365
+ // consistent with the categorical FilterValue shape.
366
+ if (entry.field?.filterVariant === "searchable-select") {
367
+ const selectOptions: SearchableSelectOption[] = opts.map((opt) => ({
368
+ value: opt,
369
+ label: opt,
370
+ }));
371
+ const current = selected[0] ?? null;
351
372
  return (
352
- <DateRangePopover
353
- value={dateValue}
354
- onChange={onSetFilter}
355
- presets={presets.length > 0 ? presets : undefined}
373
+ <SearchableSelect
374
+ options={selectOptions}
375
+ value={current}
376
+ onValueChange={(v) => onSetFilter(v ? [v] : [])}
377
+ placeholder={`Select ${entry.label}…`}
378
+ icon={<i className="ri-search-line" />}
356
379
  />
357
- )
380
+ );
358
381
  }
359
382
 
360
- const opts = getCategoricalOptions(data, entry.path, entry.field, entry.legacy)
361
- const selected = Array.isArray(value) ? value : []
362
- const isSingle = entry.field?.filterMode === "single"
363
-
364
383
  if (isSingle) {
365
- const current = selected[0] ?? ""
366
- const onSingleChange = (next: string) => onSetFilter(next ? [next] : [])
384
+ const current = selected[0] ?? "";
385
+ const onSingleChange = (next: string) => onSetFilter(next ? [next] : []);
367
386
  return (
368
387
  <RadioGroupPrimitive.Root
369
388
  value={current}
@@ -376,26 +395,26 @@ function FilterBody({
376
395
  )}
377
396
  >
378
397
  {opts.map((opt, i) => {
379
- const isSelected = current === opt
380
- const badgeVariant = entry.field?.variants?.[opt]
381
- const badgeProps = badgeVariant ? resolveBadgeVariant(badgeVariant) : null
398
+ const isSelected = current === opt;
399
+ const badgeVariant = entry.field?.variants?.[opt];
400
+ const badgeProps = badgeVariant ? resolveBadgeVariant(badgeVariant) : null;
382
401
  return (
383
402
  <div key={opt}>
384
- {i > 0 && (
385
- <div className="dv-divider h-px bg-[#2C2D2E]" />
386
- )}
403
+ {i > 0 && <div className="dv-divider h-px bg-[#2C2D2E]" />}
387
404
  <DataViewRadio value={opt}>
388
- {entry.legacy?.render
389
- ? entry.legacy.render(opt, isSelected)
390
- : badgeProps
391
- ? <Badge {...badgeProps} label={opt} size="XS" />
392
- : opt}
405
+ {entry.legacy?.render ? (
406
+ entry.legacy.render(opt, isSelected)
407
+ ) : badgeProps ? (
408
+ <Badge {...badgeProps} label={opt} size="XS" />
409
+ ) : (
410
+ opt
411
+ )}
393
412
  </DataViewRadio>
394
413
  </div>
395
- )
414
+ );
396
415
  })}
397
416
  </RadioGroupPrimitive.Root>
398
- )
417
+ );
399
418
  }
400
419
 
401
420
  if (variant === "panel") {
@@ -409,9 +428,9 @@ function FilterBody({
409
428
  )}
410
429
  >
411
430
  {opts.map((opt, i) => {
412
- const isSelected = selected.includes(opt)
413
- const badgeVariant = entry.field?.variants?.[opt]
414
- const badgeProps = badgeVariant ? resolveBadgeVariant(badgeVariant) : null
431
+ const isSelected = selected.includes(opt);
432
+ const badgeVariant = entry.field?.variants?.[opt];
433
+ const badgeProps = badgeVariant ? resolveBadgeVariant(badgeVariant) : null;
415
434
  return (
416
435
  <div key={opt}>
417
436
  {i > 0 && <div className="dv-divider h-px bg-[#2C2D2E]" />}
@@ -425,26 +444,28 @@ function FilterBody({
425
444
  onCheckedChange={() => onCategoricalToggle(opt)}
426
445
  />
427
446
  <span className="flex-1 leading-none">
428
- {entry.legacy?.render
429
- ? entry.legacy.render(opt, isSelected)
430
- : badgeProps
431
- ? <Badge {...badgeProps} label={opt} size="XS" />
432
- : opt}
447
+ {entry.legacy?.render ? (
448
+ entry.legacy.render(opt, isSelected)
449
+ ) : badgeProps ? (
450
+ <Badge {...badgeProps} label={opt} size="XS" />
451
+ ) : (
452
+ opt
453
+ )}
433
454
  </span>
434
455
  </label>
435
456
  </div>
436
- )
457
+ );
437
458
  })}
438
459
  </div>
439
- )
460
+ );
440
461
  }
441
462
 
442
463
  return (
443
464
  <div className="space-y-2">
444
465
  {opts.map((opt) => {
445
- const isSelected = selected.includes(opt)
446
- const badgeVariant = entry.field?.variants?.[opt]
447
- const badgeProps = badgeVariant ? resolveBadgeVariant(badgeVariant) : null
466
+ const isSelected = selected.includes(opt);
467
+ const badgeVariant = entry.field?.variants?.[opt];
468
+ const badgeProps = badgeVariant ? resolveBadgeVariant(badgeVariant) : null;
448
469
  return (
449
470
  <div key={opt} className="flex items-center space-x-2">
450
471
  <Checkbox
@@ -456,15 +477,17 @@ function FilterBody({
456
477
  htmlFor={`${entry.path}-${opt}`}
457
478
  className="text-sm text-content-presentation-global-primary cursor-pointer leading-none flex-1"
458
479
  >
459
- {entry.legacy?.render
460
- ? entry.legacy.render(opt, isSelected)
461
- : badgeProps
462
- ? <Badge {...badgeProps} label={opt} size="XS" />
463
- : opt}
480
+ {entry.legacy?.render ? (
481
+ entry.legacy.render(opt, isSelected)
482
+ ) : badgeProps ? (
483
+ <Badge {...badgeProps} label={opt} size="XS" />
484
+ ) : (
485
+ opt
486
+ )}
464
487
  </label>
465
488
  </div>
466
- )
489
+ );
467
490
  })}
468
491
  </div>
469
- )
492
+ );
470
493
  }
@@ -10,11 +10,7 @@ export type HeaderSearchProps = {
10
10
  placeholder?: string;
11
11
  };
12
12
 
13
- export function HeaderSearch({
14
- value,
15
- onChange,
16
- placeholder = "Search...",
17
- }: HeaderSearchProps) {
13
+ export function HeaderSearch({ value, onChange, placeholder = "Search..." }: HeaderSearchProps) {
18
14
  const [open, setOpen] = useState(false);
19
15
  const inputRef = useRef<HTMLInputElement>(null);
20
16
  const wrapRef = useRef<HTMLDivElement>(null);