torch-glare 2.3.0 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/apps/lib/components/ActionButton.tsx +7 -12
- package/apps/lib/components/ActionsGroup.tsx +4 -15
- package/apps/lib/components/AlertDialog.tsx +173 -191
- package/apps/lib/components/Avatar.tsx +13 -16
- package/apps/lib/components/Badge.tsx +7 -3
- package/apps/lib/components/BadgeField.tsx +76 -71
- package/apps/lib/components/Breadcrumb.tsx +49 -85
- package/apps/lib/components/Button.tsx +20 -19
- package/apps/lib/components/ButtonGroup.tsx +31 -41
- package/apps/lib/components/Calendar.tsx +51 -29
- package/apps/lib/components/Card.tsx +2 -13
- package/apps/lib/components/ChartBlockTool.ts +17 -52
- package/apps/lib/components/Checkbox.tsx +9 -16
- package/apps/lib/components/ContextMenu.tsx +110 -106
- package/apps/lib/components/CountBadge.tsx +4 -6
- package/apps/lib/components/DataTable.tsx +196 -216
- package/apps/lib/components/DataViews/DataViewRadio.tsx +10 -8
- package/apps/lib/components/DataViews/DataViewsConfigPanel.tsx +21 -55
- package/apps/lib/components/DataViews/DataViewsHeader.tsx +1 -3
- package/apps/lib/components/DataViews/DataViewsLayout.tsx +5 -12
- package/apps/lib/components/DataViews/FilterPanel.tsx +173 -150
- package/apps/lib/components/DataViews/HeaderSearch.tsx +1 -5
- package/apps/lib/components/DataViews/InboxView.tsx +22 -68
- package/apps/lib/components/DataViews/InboxViewCard.tsx +68 -77
- package/apps/lib/components/DataViews/KanbanView.tsx +8 -25
- package/apps/lib/components/DataViews/PanelControls.tsx +3 -13
- package/apps/lib/components/DataViews/SettingsPanel.tsx +83 -89
- package/apps/lib/components/DataViews/TableView.tsx +8 -28
- package/apps/lib/components/DataViews/TreeView.tsx +12 -40
- package/apps/lib/components/DataViews/badgeAdapter.ts +32 -28
- package/apps/lib/components/DataViews/fieldRenderers.tsx +106 -141
- package/apps/lib/components/DataViews/filters/DatePickerRangeFilter.tsx +87 -0
- package/apps/lib/components/DataViews/filters/DateRangePopover.tsx +39 -32
- package/apps/lib/components/DataViews/filters/PresetChips.tsx +15 -15
- package/apps/lib/components/DataViews/filters/RangeSliderWithInputs.tsx +69 -58
- package/apps/lib/components/DataViews/index.ts +26 -26
- package/apps/lib/components/DataViews/tree/TreeDrawer.tsx +12 -16
- package/apps/lib/components/DataViews/tree/TreeSidebar.tsx +28 -31
- package/apps/lib/components/DataViews/types.ts +111 -114
- package/apps/lib/components/DatePicker.tsx +198 -191
- package/apps/lib/components/Dialog.tsx +92 -105
- package/apps/lib/components/Divider.tsx +21 -24
- package/apps/lib/components/Drawer.tsx +79 -139
- package/apps/lib/components/DropdownMenu.tsx +144 -181
- package/apps/lib/components/FieldHint.tsx +4 -11
- package/apps/lib/components/Form.tsx +146 -155
- package/apps/lib/components/FormStepper.tsx +9 -39
- package/apps/lib/components/HeaderBar.tsx +119 -0
- package/apps/lib/components/ImageAttachment.tsx +111 -73
- package/apps/lib/components/InnerLabelField.tsx +20 -16
- package/apps/lib/components/Input.tsx +171 -172
- package/apps/lib/components/InputField.tsx +44 -45
- package/apps/lib/components/InputOTP.tsx +68 -69
- package/apps/lib/components/Label.tsx +31 -35
- package/apps/lib/components/LabelField.tsx +4 -5
- package/apps/lib/components/LabeledCheckBox.tsx +8 -12
- package/apps/lib/components/LabeledRadio.tsx +7 -5
- package/apps/lib/components/LinkButton.tsx +2 -9
- package/apps/lib/components/LoginButton.tsx +4 -9
- package/apps/lib/components/PasswordLevel.tsx +5 -5
- package/apps/lib/components/Popover.tsx +15 -33
- package/apps/lib/components/ProfileMenu.tsx +67 -72
- package/apps/lib/components/Radio.tsx +1 -5
- package/apps/lib/components/RadioCard.tsx +9 -28
- package/apps/lib/components/ScrollArea.tsx +45 -43
- package/apps/lib/components/SearchField.tsx +49 -39
- package/apps/lib/components/SearchableSelect.tsx +46 -52
- package/apps/lib/components/SearchableTable.tsx +199 -219
- package/apps/lib/components/SearchableTree.tsx +469 -0
- package/apps/lib/components/SearchableTreeDialog.tsx +493 -0
- package/apps/lib/components/SectionBlock.tsx +4 -16
- package/apps/lib/components/Select.tsx +53 -80
- package/apps/lib/components/SimpleSelect.tsx +196 -187
- package/apps/lib/components/Skeleton.tsx +9 -12
- package/apps/lib/components/SlideDatePicker.tsx +215 -166
- package/apps/lib/components/SpinLoading.tsx +33 -49
- package/apps/lib/components/Stepper.tsx +13 -56
- package/apps/lib/components/Switch.tsx +8 -10
- package/apps/lib/components/TabFormItem.tsx +3 -12
- package/apps/lib/components/TabSwitch.tsx +7 -34
- package/apps/lib/components/Table.tsx +60 -75
- package/apps/lib/components/TableDnDWrapper.ts +15 -48
- package/apps/lib/components/TextEditor.tsx +93 -104
- package/apps/lib/components/Textarea.tsx +8 -12
- package/apps/lib/components/Timeline.tsx +21 -41
- package/apps/lib/components/Toast.tsx +83 -83
- package/apps/lib/components/Toggle.tsx +112 -114
- package/apps/lib/components/ToggleButton.tsx +1 -1
- package/apps/lib/components/Tooltip.tsx +24 -19
- package/apps/lib/components/TransparentLabel.tsx +61 -62
- package/apps/lib/components/TreeDropDown.tsx +86 -52
- package/apps/lib/components/TreeFolder/TreeFolder.tsx +15 -48
- package/apps/lib/components/TreeFolder/TreeFolderBreadcrumb.tsx +16 -21
- package/apps/lib/components/TreeFolder/TreeFolderRow.tsx +9 -20
- package/apps/lib/components/TreeFolder/TreeFolderStyles.tsx +3 -3
- package/apps/lib/components/TreeFolder/icons.tsx +14 -17
- package/apps/lib/components/TreeFolder/index.ts +15 -8
- package/apps/lib/components/TreeFolder/treeFolderUtils.ts +47 -52
- package/apps/lib/components/TreeFolder/types.ts +32 -32
- package/apps/lib/components/TreeFolder/useTreeFolderDnD.ts +117 -123
- package/apps/lib/hooks/useActiveTreeItem.ts +52 -55
- package/apps/lib/hooks/useClickOutside.ts +21 -20
- package/apps/lib/hooks/useDataViewsState.ts +86 -80
- package/apps/lib/hooks/useIsMobile.ts +12 -12
- package/apps/lib/hooks/useResize.ts +54 -65
- package/apps/lib/hooks/useTagSelection.ts +201 -201
- package/apps/lib/layouts/CNLayout.tsx +181 -140
- package/apps/lib/layouts/DataViewCard.tsx +4 -18
- package/apps/lib/layouts/FieldSection.tsx +2 -6
- package/apps/lib/layouts/TreeSubLayout.tsx +24 -38
- package/apps/lib/providers/ThemeProvider.tsx +8 -23
- package/apps/lib/registry.json +1004 -0
- package/apps/lib/tsconfig.json +9 -0
- package/apps/lib/utils/cn.ts +1 -1
- package/apps/lib/utils/dataViews/columnUtils.ts +46 -44
- package/apps/lib/utils/dataViews/fieldUtils.ts +70 -71
- package/apps/lib/utils/dataViews/nestedDataUtils.tsx +129 -122
- package/apps/lib/utils/dataViews/pathUtils.ts +74 -67
- package/apps/lib/utils/dataViews/rangeUtils.ts +121 -112
- package/apps/lib/utils/dataViews/treeUtils.ts +204 -211
- package/apps/lib/utils/dateFormat.ts +64 -54
- package/apps/lib/utils/markdownParser.ts +2 -7
- package/apps/lib/utils/resize.ts +21 -25
- package/apps/lib/utils/types.ts +13 -13
- package/dist/bin/index.js +8 -2
- package/dist/bin/index.js.map +1 -1
- package/dist/src/shared/getAvailableFiles.d.ts.map +1 -1
- package/dist/src/shared/getAvailableFiles.js +6 -1
- package/dist/src/shared/getAvailableFiles.js.map +1 -1
- package/dist/src/shared/loadRegistry.d.ts +7 -0
- package/dist/src/shared/loadRegistry.d.ts.map +1 -0
- package/dist/src/shared/loadRegistry.js +31 -0
- package/dist/src/shared/loadRegistry.js.map +1 -0
- package/dist/src/shared/resolveInstallPlan.d.ts +12 -0
- package/dist/src/shared/resolveInstallPlan.d.ts.map +1 -0
- package/dist/src/shared/resolveInstallPlan.js +39 -0
- package/dist/src/shared/resolveInstallPlan.js.map +1 -0
- package/dist/src/types/main.d.ts +24 -0
- package/dist/src/types/main.d.ts.map +1 -1
- package/docs/README.md +10 -2
- package/docs/components/action-button.md +30 -24
- package/docs/components/actions-group.md +26 -20
- package/docs/components/alert-dialog.md +45 -45
- package/docs/components/avatar.md +6 -2
- package/docs/components/badge-field.md +6 -2
- package/docs/components/badge.md +7 -1
- package/docs/components/breadcrumb.md +15 -40
- package/docs/components/button-group.md +13 -10
- package/docs/components/button.md +58 -33
- package/docs/components/calendar.md +26 -19
- package/docs/components/card.md +19 -16
- package/docs/components/chart-block-tool.md +128 -0
- package/docs/components/checkbox.md +16 -8
- package/docs/components/cn-layout.md +7 -1
- package/docs/components/context-menu.md +47 -24
- package/docs/components/count-badge.md +7 -1
- package/docs/components/data-table.md +45 -33
- package/docs/components/data-views-config-panel.md +18 -14
- package/docs/components/data-views-layout.md +20 -17
- package/docs/components/date-picker.md +30 -23
- package/docs/components/dialog.md +43 -42
- package/docs/components/divider.md +10 -4
- package/docs/components/drawer.md +8 -33
- package/docs/components/dropdown-menu.md +51 -32
- package/docs/components/field-hint.md +37 -25
- package/docs/components/field-section.md +9 -3
- package/docs/components/form-stepper.md +6 -0
- package/docs/components/form.md +17 -20
- package/docs/components/header-bar.md +187 -0
- package/docs/components/image-attachment.md +33 -41
- package/docs/components/inbox-view.md +16 -5
- package/docs/components/inner-label-field.md +34 -24
- package/docs/components/input-field.md +18 -10
- package/docs/components/input-otp.md +14 -11
- package/docs/components/input.md +22 -16
- package/docs/components/kanban-view.md +15 -5
- package/docs/components/label-field.md +32 -22
- package/docs/components/label.md +43 -32
- package/docs/components/labeled-check-box.md +16 -8
- package/docs/components/labeled-radio.md +18 -11
- package/docs/components/link-button.md +7 -1
- package/docs/components/login-button.md +8 -2
- package/docs/components/password-level.md +37 -24
- package/docs/components/popover.md +26 -23
- package/docs/components/profile-menu.md +38 -27
- package/docs/components/radio-card.md +16 -9
- package/docs/components/radio.md +16 -8
- package/docs/components/scroll-area.md +6 -2
- package/docs/components/search-field.md +14 -6
- package/docs/components/searchable-select.md +19 -14
- package/docs/components/searchable-table.md +60 -41
- package/docs/components/searchable-tree-dialog.md +190 -0
- package/docs/components/searchable-tree.md +200 -0
- package/docs/components/section-block.md +130 -4
- package/docs/components/select.md +23 -18
- package/docs/components/simple-select.md +14 -10
- package/docs/components/skeleton.md +43 -33
- package/docs/components/slide-date-picker.md +37 -29
- package/docs/components/spin-loading.md +32 -23
- package/docs/components/stepper.md +6 -0
- package/docs/components/switch.md +14 -6
- package/docs/components/tab-form-item.md +7 -1
- package/docs/components/tab-switch.md +13 -6
- package/docs/components/table-dnd-wrapper.md +105 -0
- package/docs/components/table-view.md +15 -5
- package/docs/components/table.md +28 -28
- package/docs/components/text-editor.md +19 -13
- package/docs/components/textarea.md +15 -7
- package/docs/components/timeline.md +6 -0
- package/docs/components/toast.md +35 -26
- package/docs/components/toggle-button.md +24 -21
- package/docs/components/toggle.md +23 -15
- package/docs/components/tooltip.md +42 -30
- package/docs/components/transparent-label.md +29 -20
- package/docs/components/tree-drop-down.md +31 -23
- package/docs/components/tree-sub-layout.md +6 -2
- package/docs/components/tree-view.md +16 -5
- package/docs/explanation/architecture.md +54 -0
- package/docs/explanation/design-system.md +52 -0
- package/docs/how-to/data-views-from-backend-response.md +7 -4
- package/docs/how-to/guides.md +24 -18
- package/docs/migration/changelog.md +38 -0
- package/docs/reference/cli.md +87 -0
- package/docs/reference/components.md +11 -12
- package/docs/reference/hooks.md +8 -8
- package/docs/reference/providers.md +22 -22
- package/docs/reference/tailwind-plugins.md +2 -2
- package/docs/reference/theme.md +85 -0
- package/docs/reference/types.md +4 -4
- package/docs/reference/utilities.md +19 -26
- package/docs/tutorials/building-first-form.md +20 -13
- package/docs/tutorials/component-composition.md +34 -50
- package/docs/tutorials/getting-started.md +2 -2
- package/docs/tutorials/theming-basics.md +13 -10
- package/package.json +31 -3
- package/dist/src/commands/block.d.ts +0 -5
- package/dist/src/commands/block.d.ts.map +0 -1
- package/dist/src/commands/block.js +0 -255
- package/dist/src/commands/block.js.map +0 -1
- package/dist/src/shared/configureFonts.d.ts +0 -6
- package/dist/src/shared/configureFonts.d.ts.map +0 -1
- package/dist/src/shared/configureFonts.js +0 -106
- package/dist/src/shared/configureFonts.js.map +0 -1
- package/dist/src/shared/configureGlobalCss.d.ts +0 -6
- package/dist/src/shared/configureGlobalCss.d.ts.map +0 -1
- package/dist/src/shared/configureGlobalCss.js +0 -154
- package/dist/src/shared/configureGlobalCss.js.map +0 -1
- package/dist/src/shared/configureTailwind.d.ts +0 -7
- package/dist/src/shared/configureTailwind.d.ts.map +0 -1
- package/dist/src/shared/configureTailwind.js +0 -163
- package/dist/src/shared/configureTailwind.js.map +0 -1
- package/dist/src/shared/detectFramework.d.ts +0 -23
- package/dist/src/shared/detectFramework.d.ts.map +0 -1
- package/dist/src/shared/detectFramework.js +0 -119
- package/dist/src/shared/detectFramework.js.map +0 -1
- package/dist/src/shared/installBaseUtils.d.ts +0 -5
- package/dist/src/shared/installBaseUtils.d.ts.map +0 -1
- package/dist/src/shared/installBaseUtils.js +0 -79
- package/dist/src/shared/installBaseUtils.js.map +0 -1
- package/dist/src/shared/resolveAliases.d.ts +0 -24
- package/dist/src/shared/resolveAliases.d.ts.map +0 -1
- package/dist/src/shared/resolveAliases.js +0 -98
- package/dist/src/shared/resolveAliases.js.map +0 -1
|
@@ -61,14 +61,14 @@ export type DataViewsLayoutProps = {
|
|
|
61
61
|
onAddNew?: () => void;
|
|
62
62
|
addNewLabel?: string;
|
|
63
63
|
|
|
64
|
-
inboxItemHref?: (item: DynamicRecord, id:
|
|
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?:
|
|
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 {
|
|
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
|
|
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
|
|
350
|
-
|
|
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
|
-
<
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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);
|