torch-glare 2.4.4 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/lib/components/DataViews/{badgeAdapter.ts → badge.ts} +2 -2
- package/apps/lib/components/DataViews/cell.tsx +324 -0
- package/apps/lib/components/DataViews/context.ts +144 -0
- package/apps/lib/components/DataViews/data-views.tsx +383 -0
- package/apps/lib/components/DataViews/filters/children.tsx +98 -0
- package/apps/lib/components/DataViews/filters/custom.tsx +34 -0
- package/apps/lib/components/DataViews/filters/filters.tsx +163 -0
- package/apps/lib/components/DataViews/filters/index.ts +4 -0
- package/apps/lib/components/DataViews/filters/labelled.tsx +20 -0
- package/apps/lib/components/DataViews/filters/presets.tsx +65 -0
- package/apps/lib/components/DataViews/filters/summary.tsx +65 -0
- package/apps/lib/components/DataViews/filters/sync.tsx +35 -0
- package/apps/lib/components/DataViews/filters/values.ts +173 -0
- package/apps/lib/components/DataViews/header.tsx +217 -0
- package/apps/lib/components/DataViews/hooks/index.ts +5 -0
- package/apps/lib/components/DataViews/hooks/useActiveRow.ts +22 -0
- package/apps/lib/components/DataViews/hooks/useControllable.ts +52 -0
- package/apps/lib/components/DataViews/index.ts +74 -26
- package/apps/lib/components/DataViews/panel/columns.tsx +153 -0
- package/apps/lib/components/DataViews/panel/controls.tsx +106 -0
- package/apps/lib/components/DataViews/panel/index.ts +3 -0
- package/apps/lib/components/DataViews/panel/panel.tsx +164 -0
- package/apps/lib/components/DataViews/panel/saved-views.tsx +67 -0
- package/apps/lib/components/DataViews/panel/section.tsx +79 -0
- package/apps/lib/components/DataViews/panel/sort.tsx +42 -0
- package/apps/lib/components/DataViews/panel/tab.tsx +31 -0
- package/apps/lib/components/DataViews/slots.ts +63 -0
- package/apps/lib/components/DataViews/states.tsx +38 -0
- package/apps/lib/components/DataViews/types.ts +485 -178
- package/apps/lib/components/DataViews/views/board-view.tsx +379 -0
- package/apps/lib/components/DataViews/views/card-rows.tsx +36 -0
- package/apps/lib/components/DataViews/views/inbox-view.tsx +257 -0
- package/apps/lib/components/DataViews/views/pane-views.tsx +192 -0
- package/apps/lib/components/DataViews/views/table-view.tsx +426 -0
- package/apps/lib/components/DataViews/views/tree-view.tsx +365 -0
- package/apps/lib/components/FormBuilder/context.ts +20 -6
- package/apps/lib/components/FormBuilder/field-kind.ts +28 -0
- package/apps/lib/components/FormBuilder/fields/DateField.tsx +3 -3
- package/apps/lib/components/FormBuilder/fields/FieldShell.tsx +7 -6
- package/apps/lib/components/FormBuilder/fields/PhoneField.tsx +30 -4
- package/apps/lib/components/FormBuilder/fields/SelectField.tsx +7 -7
- package/apps/lib/components/FormBuilder/fields/TableField.tsx +80 -52
- package/apps/lib/components/FormBuilder/fields/TextField.tsx +9 -9
- package/apps/lib/components/FormBuilder/form-builder.tsx +66 -6
- package/apps/lib/components/FormBuilder/index.ts +3 -1
- package/apps/lib/components/FormBuilder/types.ts +40 -0
- package/apps/lib/components/Input.tsx +3 -0
- package/apps/lib/components/SearchableTable.tsx +5 -4
- package/apps/lib/components/SectionBlock.tsx +58 -11
- package/apps/lib/components/Select.tsx +3 -1
- package/apps/lib/components/TabSwitch.tsx +16 -4
- package/apps/lib/components/Table.tsx +265 -67
- package/apps/lib/components/TreeFolder/TreeFolder.tsx +6 -3
- package/apps/lib/components/TreeFolder/TreeFolderRow.tsx +16 -14
- package/apps/lib/components/TreeFolder/index.ts +1 -1
- package/apps/lib/components/TreeFolder/useTreeFolderDnD.ts +70 -207
- package/apps/lib/hooks/useDragDrop.tsx +365 -0
- package/apps/lib/hooks/useInfiniteScroll.ts +108 -0
- package/apps/lib/registry.json +159 -4
- package/apps/lib/tsconfig.tsbuildinfo +1 -1
- package/apps/lib/utils/dataViews/path.ts +67 -0
- package/apps/lib/utils/dataViews/query.ts +73 -0
- package/apps/lib/utils/dataViews/types.ts +187 -0
- package/docs/components/breadcrumb.md +1 -1
- package/docs/components/button-group.md +1 -1
- package/docs/components/button.md +1 -1
- package/docs/components/card.md +1 -1
- package/docs/components/checkbox.md +1 -1
- package/docs/components/data-views/backend-response.md +324 -0
- package/docs/components/data-views/examples/a11y-rtl.md +250 -0
- package/docs/components/data-views/examples/api-orders-route.md +130 -0
- package/docs/components/data-views/examples/fields.md +362 -0
- package/docs/components/data-views/examples/filters.md +308 -0
- package/docs/components/data-views/examples/inbox-routing.md +218 -0
- package/docs/components/data-views/examples/index.md +29 -0
- package/docs/components/data-views/examples/overview.md +244 -0
- package/docs/components/data-views/examples/panel.md +212 -0
- package/docs/components/data-views/examples/scale.md +231 -0
- package/docs/components/data-views/examples/server-side.md +210 -0
- package/docs/components/data-views/examples/state.md +250 -0
- package/docs/components/data-views/examples/tree-custom.md +388 -0
- package/docs/components/data-views/examples/view-registry.md +313 -0
- package/docs/components/data-views/examples/views.md +534 -0
- package/docs/components/data-views/guide.md +405 -0
- package/docs/components/data-views/index.md +1504 -0
- package/docs/components/data-views/migration.md +79 -0
- package/docs/components/date-picker.md +0 -1
- package/docs/components/form-builder.md +19 -8
- package/docs/components/form-renderer.md +2 -1
- package/docs/components/form.md +1 -1
- package/docs/components/input-field.md +1 -1
- package/docs/components/input-otp.md +1 -1
- package/docs/components/input.md +1 -1
- package/docs/components/labeled-check-box.md +1 -1
- package/docs/components/labeled-radio.md +1 -1
- package/docs/components/radio-card.md +1 -1
- package/docs/components/radio.md +1 -1
- package/docs/components/search-field.md +1 -1
- package/docs/components/section-block.md +79 -3
- package/docs/components/select.md +1 -1
- package/docs/components/simple-select.md +1 -1
- package/docs/components/switch.md +1 -1
- package/docs/components/tab-switch.md +1 -1
- package/docs/components/table.md +45 -8
- package/docs/components/text-editor.md +1 -1
- package/docs/components/textarea.md +1 -1
- package/docs/components/toggle-button.md +1 -1
- package/docs/components/toggle.md +1 -1
- package/docs/components/tree-folder.md +110 -0
- package/docs/how-to/forms-with-form-builder.md +6 -4
- package/docs/reference/components.md +16 -6
- package/docs/tutorials/component-composition.md +11 -13
- package/package.json +3 -2
- package/apps/lib/components/DataViews/DataViewRadio.tsx +0 -49
- package/apps/lib/components/DataViews/DataViewsConfigPanel.tsx +0 -393
- package/apps/lib/components/DataViews/DataViewsHeader.tsx +0 -207
- package/apps/lib/components/DataViews/DataViewsLayout.tsx +0 -332
- package/apps/lib/components/DataViews/FilterPanel.tsx +0 -493
- package/apps/lib/components/DataViews/HeaderSearch.tsx +0 -93
- package/apps/lib/components/DataViews/InboxView.tsx +0 -463
- package/apps/lib/components/DataViews/InboxViewCard.tsx +0 -127
- package/apps/lib/components/DataViews/KanbanView.tsx +0 -336
- package/apps/lib/components/DataViews/PanelControls.tsx +0 -39
- package/apps/lib/components/DataViews/SettingsPanel.tsx +0 -279
- package/apps/lib/components/DataViews/TableView.tsx +0 -212
- package/apps/lib/components/DataViews/TreeView.tsx +0 -364
- package/apps/lib/components/DataViews/fieldRenderers.tsx +0 -299
- package/apps/lib/components/DataViews/filters/DatePickerRangeFilter.tsx +0 -87
- package/apps/lib/components/DataViews/filters/DateRangePopover.tsx +0 -120
- package/apps/lib/components/DataViews/filters/PresetChips.tsx +0 -45
- package/apps/lib/components/DataViews/filters/RangeSliderWithInputs.tsx +0 -165
- package/apps/lib/components/DataViews/tree/TreeDrawer.tsx +0 -50
- package/apps/lib/components/DataViews/tree/TreeSidebar.tsx +0 -74
- package/apps/lib/hooks/useDataViewsState.ts +0 -175
- package/apps/lib/utils/dataViews/columnUtils.ts +0 -132
- package/apps/lib/utils/dataViews/fieldUtils.ts +0 -197
- package/apps/lib/utils/dataViews/nestedDataUtils.tsx +0 -371
- package/apps/lib/utils/dataViews/pathUtils.ts +0 -139
- package/apps/lib/utils/dataViews/rangeUtils.ts +0 -234
- package/apps/lib/utils/dataViews/treeUtils.ts +0 -396
- package/docs/components/data-views-config-panel.md +0 -208
- package/docs/components/data-views-layout.md +0 -291
- package/docs/components/inbox-view.md +0 -170
- package/docs/components/kanban-view.md +0 -135
- package/docs/components/table-view.md +0 -141
- package/docs/components/tree-view.md +0 -147
- package/docs/how-to/data-views-from-backend-response.md +0 -194
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: DataViews example — View registry
|
|
3
|
+
description: A view of your own via `markView`, beside the built-in four.
|
|
4
|
+
group: examples
|
|
5
|
+
component: DataViews
|
|
6
|
+
keywords: [data-views, example, examples, view, registry]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# DataViews example — View registry
|
|
10
|
+
|
|
11
|
+
A view of your own via `markView`, beside the built-in four.
|
|
12
|
+
|
|
13
|
+
Complete and runnable — this is the page itself, not an excerpt. In the monorepo it lives at `apps/app/data-views/view-registry/page.tsx`.
|
|
14
|
+
|
|
15
|
+
See the [component reference](../index.md) for what each prop does, or the [guide](../guide.md) for the same ground as scenarios.
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
"use client";
|
|
19
|
+
|
|
20
|
+
import { useMemo, useState } from "react";
|
|
21
|
+
import { useInfiniteQuery } from "@tanstack/react-query";
|
|
22
|
+
import { Filter, Settings } from "lucide-react";
|
|
23
|
+
import { Button } from "@/components/Button";
|
|
24
|
+
import {
|
|
25
|
+
Cell,
|
|
26
|
+
DataViews,
|
|
27
|
+
emptyQuery,
|
|
28
|
+
markView,
|
|
29
|
+
queryToParams,
|
|
30
|
+
SkeletonBar,
|
|
31
|
+
skeletonKeys,
|
|
32
|
+
useDataViewsData,
|
|
33
|
+
type SavedView,
|
|
34
|
+
} from "@/components/DataViews";
|
|
35
|
+
import { FormBuilder } from "@/components/FormBuilder";
|
|
36
|
+
import { cn } from "@/utils/cn";
|
|
37
|
+
import type {
|
|
38
|
+
ViewBaseProps,
|
|
39
|
+
} from "@/components/DataViews";
|
|
40
|
+
import type {
|
|
41
|
+
DataViewsQuery,
|
|
42
|
+
FieldConfig,
|
|
43
|
+
Row,
|
|
44
|
+
RowGroup,
|
|
45
|
+
TreeNode,
|
|
46
|
+
} from "@/utils/dataViews/types";
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* A view the library does not ship — a gallery of cards — registered with `markView`.
|
|
50
|
+
*
|
|
51
|
+
* It is here to prove two things a custom view needs and could otherwise only be told about:
|
|
52
|
+
* `useDataViewsData()` hands it the same `rows`/`visibleFields` the built-ins get, **and the same
|
|
53
|
+
* `loading` flag**, so it can paint a skeleton in its own shape using the exported pieces rather
|
|
54
|
+
* than inventing a second loading look.
|
|
55
|
+
*/
|
|
56
|
+
const GalleryView = markView(
|
|
57
|
+
function GalleryView({ className }: ViewBaseProps) {
|
|
58
|
+
const { rows, visibleFields, getRowId, loading } = useDataViewsData();
|
|
59
|
+
const [title, ...rest] = visibleFields;
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div
|
|
63
|
+
className={cn(
|
|
64
|
+
"bg-background-presentation-body-primary grid h-full content-start gap-3 overflow-y-auto p-3",
|
|
65
|
+
"grid-cols-[repeat(auto-fill,minmax(220px,1fr))]",
|
|
66
|
+
className,
|
|
67
|
+
)}
|
|
68
|
+
>
|
|
69
|
+
{loading
|
|
70
|
+
? skeletonKeys(8).map((i) => (
|
|
71
|
+
<div
|
|
72
|
+
key={`skeleton-${i}`}
|
|
73
|
+
className="border-border-presentation-global-primary bg-background-presentation-form-base flex flex-col gap-2 rounded-[12px] border p-3"
|
|
74
|
+
>
|
|
75
|
+
<SkeletonBar className="h-[18px] w-[70%]" />
|
|
76
|
+
<SkeletonBar className="w-[45%]" />
|
|
77
|
+
</div>
|
|
78
|
+
))
|
|
79
|
+
: rows.map((row, index) => (
|
|
80
|
+
<div
|
|
81
|
+
key={getRowId(row, index)}
|
|
82
|
+
className="border-border-presentation-global-primary bg-background-presentation-form-base flex flex-col gap-2 rounded-[12px] border p-3"
|
|
83
|
+
>
|
|
84
|
+
<span className="typography-body-large-semibold text-content-presentation-global-primary">
|
|
85
|
+
{title && <Cell field={title} row={row} />}
|
|
86
|
+
</span>
|
|
87
|
+
{rest.slice(0, 2).map((field, i) => (
|
|
88
|
+
<span key={`${field.path}-${i}`} className="typography-body-small-regular">
|
|
89
|
+
<Cell field={field} row={row} />
|
|
90
|
+
</span>
|
|
91
|
+
))}
|
|
92
|
+
</div>
|
|
93
|
+
))}
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
},
|
|
97
|
+
{ defaultId: "gallery", defaultLabel: "Gallery" },
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
// ─── Data ─────────────────────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
interface Order extends Row {
|
|
103
|
+
id: number;
|
|
104
|
+
customer: { name: string };
|
|
105
|
+
status: "Pending" | "Shipped" | "Delivered";
|
|
106
|
+
priority: "High" | "Medium" | "Low";
|
|
107
|
+
total: number;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const FIELDS: FieldConfig[] = [
|
|
111
|
+
{ path: "id", label: "Order #", type: "number" },
|
|
112
|
+
{ path: "customer.name", label: "Customer", type: "text" },
|
|
113
|
+
{ path: "brand.name", label: "Brand", type: "text" },
|
|
114
|
+
{ path: "status", label: "Status", type: "enum-badge", variants: { Pending: "yellow", Shipped: "blue", Delivered: "green" } },
|
|
115
|
+
{ path: "priority", label: "Priority", type: "enum-badge", variants: { High: "redOrange", Medium: "purple", Low: "gray" } },
|
|
116
|
+
{ path: "total", label: "Total", type: "currency", currency: "USD" },
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
/** Dynamic sets — in a real app these come from the endpoint that also does the filtering. */
|
|
120
|
+
const CUSTOMER_OPTIONS = [
|
|
121
|
+
"Acme Inc.", "Globex Corp.", "Initech", "Umbrella",
|
|
122
|
+
"Hooli", "Stark Industries", "Wayne Enterprises", "Cyberdyne",
|
|
123
|
+
].map((v) => ({ label: v, value: v }));
|
|
124
|
+
|
|
125
|
+
const BRAND_OPTIONS = ["Bosch", "Makita", "DeWalt", "Hilti"].map((v) => ({ label: v, value: v }));
|
|
126
|
+
|
|
127
|
+
const PRIORITY_OPTIONS = ["High", "Medium", "Low"].map((v) => ({ label: v, value: v }));
|
|
128
|
+
|
|
129
|
+
const STATUS_OPTIONS = [
|
|
130
|
+
{ label: "Pending", value: "Pending" },
|
|
131
|
+
{ label: "Shipped", value: "Shipped" },
|
|
132
|
+
{ label: "Delivered", value: "Delivered" },
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The request this page makes. The querying itself happens in `app/api/orders/route.ts` —
|
|
137
|
+
* nothing on this page filters, sorts or pages anything, which is the split DataViews is built
|
|
138
|
+
* around.
|
|
139
|
+
*/
|
|
140
|
+
async function fetchOrders(q: DataViewsQuery): Promise<{ rows: Order[]; total: number }> {
|
|
141
|
+
const params = queryToParams(q);
|
|
142
|
+
const res = await fetch(`/api/orders?${params}`);
|
|
143
|
+
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
|
|
144
|
+
return res.json();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const byStatus = (rows: readonly Order[]): RowGroup[] =>
|
|
148
|
+
(["Pending", "Shipped", "Delivered"] as const).map((status) => ({
|
|
149
|
+
id: status,
|
|
150
|
+
label: status,
|
|
151
|
+
color: ({ Pending: "gray", Shipped: "blue", Delivered: "green" } as const)[status],
|
|
152
|
+
rows: rows.filter((row) => row.status === status),
|
|
153
|
+
}));
|
|
154
|
+
|
|
155
|
+
const byPriority = (rows: readonly Order[]): RowGroup[] =>
|
|
156
|
+
(["High", "Medium", "Low"] as const).map((priority) => ({
|
|
157
|
+
id: priority,
|
|
158
|
+
label: priority,
|
|
159
|
+
color: ({ High: "red", Medium: "purple", Low: "gray" } as const)[priority],
|
|
160
|
+
rows: rows.filter((row) => row.priority === priority),
|
|
161
|
+
}));
|
|
162
|
+
|
|
163
|
+
const nodesFromRows = (rows: readonly Order[]): TreeNode[] =>
|
|
164
|
+
rows.map((row) => ({ id: String(row.id), row, depth: 0, children: [] }));
|
|
165
|
+
|
|
166
|
+
// ─── Page ─────────────────────────────────────────────────────────────────────
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* A view exists because you rendered it — the switcher is built from the views in the tree, so
|
|
170
|
+
* there is no visibility map to keep in step and no way for the two to disagree.
|
|
171
|
+
*
|
|
172
|
+
* Each view defaults its own id and label, which is why the two boards below **must** carry
|
|
173
|
+
* explicit `id`s: without them both would answer to `"board"` and the second would be
|
|
174
|
+
* unreachable. Revoking the one you are looking at converges on the first registered view and
|
|
175
|
+
* reports it through `onViewChange`, in an effect rather than during render.
|
|
176
|
+
*/
|
|
177
|
+
export default function ViewRegistryExample() {
|
|
178
|
+
const [query, setQuery] = useState(emptyQuery());
|
|
179
|
+
const [saved, setSaved] = useState<SavedView[]>([]);
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
const [onlyOne, setOnlyOne] = useState(false);
|
|
183
|
+
const [swapIcon, setSwapIcon] = useState(false);
|
|
184
|
+
const [canSeeBoard, setCanSeeBoard] = useState(true);
|
|
185
|
+
|
|
186
|
+
const { data, isPending, fetchNextPage, isFetchingNextPage } = useInfiniteQuery({
|
|
187
|
+
// The key *is* the query: touch any part of it and TanStack refetches, and a response that
|
|
188
|
+
// has been superseded is discarded rather than landing on top of a newer one.
|
|
189
|
+
queryKey: ["registry-orders", { ...query, page: undefined }],
|
|
190
|
+
queryFn: ({ pageParam }) => fetchOrders({ ...query, page: pageParam }),
|
|
191
|
+
initialPageParam: 1,
|
|
192
|
+
// Undefined means "no more" — which is what the component's `hasMore` resolves to.
|
|
193
|
+
getNextPageParam: (last, pages) => {
|
|
194
|
+
const loaded = pages.reduce((n, page) => n + page.rows.length, 0);
|
|
195
|
+
return loaded < last.total ? pages.length + 1 : undefined;
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const rows = useMemo(() => data?.pages.flatMap((page) => page.rows) ?? [], [data]);
|
|
200
|
+
const total = data?.pages[0]?.total ?? 0;
|
|
201
|
+
|
|
202
|
+
return (
|
|
203
|
+
<div className="flex h-full min-h-0 flex-col p-4">
|
|
204
|
+
<DataViews
|
|
205
|
+
rows={rows}
|
|
206
|
+
fields={FIELDS}
|
|
207
|
+
total={total}
|
|
208
|
+
loading={isPending}
|
|
209
|
+
onLoadMore={fetchNextPage}
|
|
210
|
+
loadingMore={isFetchingNextPage}
|
|
211
|
+
onQueryChange={setQuery}
|
|
212
|
+
className="h-full"
|
|
213
|
+
>
|
|
214
|
+
<DataViews.Header title="Orders">
|
|
215
|
+
<DataViews.ViewSwitch />
|
|
216
|
+
<DataViews.Search />
|
|
217
|
+
<DataViews.Actions>
|
|
218
|
+
<Button variant="BluColStyle" size="M" onClick={() => setOnlyOne((v) => !v)}>
|
|
219
|
+
{onlyOne ? "All views" : "Only the table"}
|
|
220
|
+
</Button>
|
|
221
|
+
<Button variant="BluColStyle" size="M" onClick={() => setSwapIcon((v) => !v)}>
|
|
222
|
+
Swap icons
|
|
223
|
+
</Button>
|
|
224
|
+
<Button variant="BluColStyle"
|
|
225
|
+
size="M"
|
|
226
|
+
onClick={() => setCanSeeBoard((v) => !v)}
|
|
227
|
+
>
|
|
228
|
+
{canSeeBoard ? "Revoke the board" : "Grant the board"}
|
|
229
|
+
</Button>
|
|
230
|
+
</DataViews.Actions>
|
|
231
|
+
<DataViews.PanelToggle />
|
|
232
|
+
</DataViews.Header>
|
|
233
|
+
|
|
234
|
+
<DataViews.Table
|
|
235
|
+
icon={<i className={swapIcon ? "ri-list-check" : "ri-table-line"} />}
|
|
236
|
+
/>
|
|
237
|
+
|
|
238
|
+
{/* `{flag && <View/>}` is how a view disappears behind a permission check:
|
|
239
|
+
`Children.toArray` drops the `false`, and the view unregisters. */}
|
|
240
|
+
{!onlyOne && canSeeBoard && (
|
|
241
|
+
<DataViews.Board
|
|
242
|
+
id="board-status"
|
|
243
|
+
label="By status"
|
|
244
|
+
icon={<i className={swapIcon ? "ri-flag-line" : "ri-kanban-view"} />}
|
|
245
|
+
groups={byStatus(rows)}
|
|
246
|
+
titlePath="customer.name"
|
|
247
|
+
/>
|
|
248
|
+
)}
|
|
249
|
+
{!onlyOne && (
|
|
250
|
+
<DataViews.Board
|
|
251
|
+
id="board-priority"
|
|
252
|
+
label="By priority"
|
|
253
|
+
icon={<i className={swapIcon ? "ri-fire-line" : "ri-alarm-warning-line"} />}
|
|
254
|
+
groups={byPriority(rows)}
|
|
255
|
+
titlePath="customer.name"
|
|
256
|
+
/>
|
|
257
|
+
)}
|
|
258
|
+
{!onlyOne && (
|
|
259
|
+
<DataViews.Tree nodes={nodesFromRows(rows)} labelPath="customer.name">
|
|
260
|
+
<DataViews.Tree.Table />
|
|
261
|
+
<DataViews.Tree.Cards />
|
|
262
|
+
</DataViews.Tree>
|
|
263
|
+
)}
|
|
264
|
+
{/* Not one of the four — a view of this page's own, registered by `markView` and reading
|
|
265
|
+
the same context, loading state included. */}
|
|
266
|
+
{!onlyOne && <GalleryView icon={<i className="ri-layout-grid-line" />} />}
|
|
267
|
+
|
|
268
|
+
<DataViews.Panel>
|
|
269
|
+
<DataViews.Panel.Tab value="config" label="Config." icon={<Settings />}>
|
|
270
|
+
{/* Saving is the app's job — a view outlives the component. Restoring is not:
|
|
271
|
+
hand the snapshot back and selecting it puts everything back internally. */}
|
|
272
|
+
<DataViews.Panel.SavedViews
|
|
273
|
+
views={saved}
|
|
274
|
+
onSave={(snapshot) =>
|
|
275
|
+
setSaved((prev) => [
|
|
276
|
+
...prev,
|
|
277
|
+
{ id: `view-${prev.length + 1}`, label: `View ${prev.length + 1}`, snapshot },
|
|
278
|
+
])
|
|
279
|
+
}
|
|
280
|
+
/>
|
|
281
|
+
<DataViews.Panel.Columns />
|
|
282
|
+
<DataViews.Panel.Sort />
|
|
283
|
+
</DataViews.Panel.Tab>
|
|
284
|
+
|
|
285
|
+
<DataViews.Panel.Tab value="filters" label="Filters" icon={<Filter />}>
|
|
286
|
+
{/* The controls are FormBuilder fields — the same Select and Slider any form in
|
|
287
|
+
this library uses. The <FormBuilder> itself lives inside Filters; you write only
|
|
288
|
+
its fields, and Filters reads each one's name, label and bounds to learn what
|
|
289
|
+
it is. */}
|
|
290
|
+
<DataViews.Filters
|
|
291
|
+
title={null}
|
|
292
|
+
className="border-b-0 p-0"
|
|
293
|
+
>
|
|
294
|
+
{/* One control per section type. Which one a field gets is decided by the data:
|
|
295
|
+
can the option set grow, and can the user pick more than one. */}
|
|
296
|
+
<FormBuilder.CheckboxGroup name="status" label="Status" options={STATUS_OPTIONS} />
|
|
297
|
+
<FormBuilder.RadioList name="priority" label="Priority" options={PRIORITY_OPTIONS} />
|
|
298
|
+
<FormBuilder.SearchableSelect
|
|
299
|
+
name="customer.name"
|
|
300
|
+
label="Customer"
|
|
301
|
+
options={CUSTOMER_OPTIONS}
|
|
302
|
+
/>
|
|
303
|
+
<FormBuilder.MultiSelect name="brand.name" label="Brand" options={BRAND_OPTIONS} />
|
|
304
|
+
<FormBuilder.Slider name="total" label="Total" range min={0} max={15000} step={100} />
|
|
305
|
+
<FormBuilder.DateRange name="createdAt" label="Created" />
|
|
306
|
+
</DataViews.Filters>
|
|
307
|
+
</DataViews.Panel.Tab>
|
|
308
|
+
</DataViews.Panel>
|
|
309
|
+
</DataViews>
|
|
310
|
+
</div>
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
```
|