torch-glare 2.4.5 → 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.
Files changed (142) hide show
  1. package/apps/lib/components/DataViews/{badgeAdapter.ts → badge.ts} +2 -2
  2. package/apps/lib/components/DataViews/cell.tsx +324 -0
  3. package/apps/lib/components/DataViews/context.ts +144 -0
  4. package/apps/lib/components/DataViews/data-views.tsx +383 -0
  5. package/apps/lib/components/DataViews/filters/children.tsx +98 -0
  6. package/apps/lib/components/DataViews/filters/custom.tsx +34 -0
  7. package/apps/lib/components/DataViews/filters/filters.tsx +163 -0
  8. package/apps/lib/components/DataViews/filters/index.ts +4 -0
  9. package/apps/lib/components/DataViews/filters/labelled.tsx +20 -0
  10. package/apps/lib/components/DataViews/filters/presets.tsx +65 -0
  11. package/apps/lib/components/DataViews/filters/summary.tsx +65 -0
  12. package/apps/lib/components/DataViews/filters/sync.tsx +35 -0
  13. package/apps/lib/components/DataViews/filters/values.ts +173 -0
  14. package/apps/lib/components/DataViews/header.tsx +217 -0
  15. package/apps/lib/components/DataViews/hooks/index.ts +5 -0
  16. package/apps/lib/components/DataViews/hooks/useActiveRow.ts +22 -0
  17. package/apps/lib/components/DataViews/hooks/useControllable.ts +52 -0
  18. package/apps/lib/components/DataViews/index.ts +74 -26
  19. package/apps/lib/components/DataViews/panel/columns.tsx +153 -0
  20. package/apps/lib/components/DataViews/panel/controls.tsx +106 -0
  21. package/apps/lib/components/DataViews/panel/index.ts +3 -0
  22. package/apps/lib/components/DataViews/panel/panel.tsx +164 -0
  23. package/apps/lib/components/DataViews/panel/saved-views.tsx +67 -0
  24. package/apps/lib/components/DataViews/panel/section.tsx +79 -0
  25. package/apps/lib/components/DataViews/panel/sort.tsx +42 -0
  26. package/apps/lib/components/DataViews/panel/tab.tsx +31 -0
  27. package/apps/lib/components/DataViews/slots.ts +63 -0
  28. package/apps/lib/components/DataViews/states.tsx +38 -0
  29. package/apps/lib/components/DataViews/types.ts +485 -178
  30. package/apps/lib/components/DataViews/views/board-view.tsx +379 -0
  31. package/apps/lib/components/DataViews/views/card-rows.tsx +36 -0
  32. package/apps/lib/components/DataViews/views/inbox-view.tsx +257 -0
  33. package/apps/lib/components/DataViews/views/pane-views.tsx +192 -0
  34. package/apps/lib/components/DataViews/views/table-view.tsx +426 -0
  35. package/apps/lib/components/DataViews/views/tree-view.tsx +365 -0
  36. package/apps/lib/components/FormBuilder/context.ts +20 -6
  37. package/apps/lib/components/FormBuilder/field-kind.ts +28 -0
  38. package/apps/lib/components/FormBuilder/fields/DateField.tsx +3 -3
  39. package/apps/lib/components/FormBuilder/fields/FieldShell.tsx +7 -6
  40. package/apps/lib/components/FormBuilder/fields/PhoneField.tsx +7 -6
  41. package/apps/lib/components/FormBuilder/fields/SelectField.tsx +7 -7
  42. package/apps/lib/components/FormBuilder/fields/TableField.tsx +1 -1
  43. package/apps/lib/components/FormBuilder/fields/TextField.tsx +9 -9
  44. package/apps/lib/components/FormBuilder/form-builder.tsx +55 -3
  45. package/apps/lib/components/FormBuilder/index.ts +3 -1
  46. package/apps/lib/components/FormBuilder/types.ts +40 -0
  47. package/apps/lib/components/TabSwitch.tsx +16 -4
  48. package/apps/lib/components/TreeFolder/TreeFolder.tsx +6 -3
  49. package/apps/lib/components/TreeFolder/TreeFolderRow.tsx +16 -14
  50. package/apps/lib/components/TreeFolder/index.ts +1 -1
  51. package/apps/lib/components/TreeFolder/useTreeFolderDnD.ts +70 -207
  52. package/apps/lib/hooks/useDragDrop.tsx +365 -0
  53. package/apps/lib/hooks/useInfiniteScroll.ts +108 -0
  54. package/apps/lib/registry.json +159 -4
  55. package/apps/lib/tsconfig.tsbuildinfo +1 -1
  56. package/apps/lib/utils/dataViews/path.ts +67 -0
  57. package/apps/lib/utils/dataViews/query.ts +73 -0
  58. package/apps/lib/utils/dataViews/types.ts +187 -0
  59. package/docs/components/breadcrumb.md +1 -1
  60. package/docs/components/button-group.md +1 -1
  61. package/docs/components/button.md +1 -1
  62. package/docs/components/card.md +1 -1
  63. package/docs/components/checkbox.md +1 -1
  64. package/docs/components/data-views/backend-response.md +324 -0
  65. package/docs/components/data-views/examples/a11y-rtl.md +250 -0
  66. package/docs/components/data-views/examples/api-orders-route.md +130 -0
  67. package/docs/components/data-views/examples/fields.md +362 -0
  68. package/docs/components/data-views/examples/filters.md +308 -0
  69. package/docs/components/data-views/examples/inbox-routing.md +218 -0
  70. package/docs/components/data-views/examples/index.md +29 -0
  71. package/docs/components/data-views/examples/overview.md +244 -0
  72. package/docs/components/data-views/examples/panel.md +212 -0
  73. package/docs/components/data-views/examples/scale.md +231 -0
  74. package/docs/components/data-views/examples/server-side.md +210 -0
  75. package/docs/components/data-views/examples/state.md +250 -0
  76. package/docs/components/data-views/examples/tree-custom.md +388 -0
  77. package/docs/components/data-views/examples/view-registry.md +313 -0
  78. package/docs/components/data-views/examples/views.md +534 -0
  79. package/docs/components/data-views/guide.md +405 -0
  80. package/docs/components/data-views/index.md +1504 -0
  81. package/docs/components/data-views/migration.md +79 -0
  82. package/docs/components/date-picker.md +0 -1
  83. package/docs/components/form-builder.md +3 -2
  84. package/docs/components/form-renderer.md +2 -1
  85. package/docs/components/form.md +1 -1
  86. package/docs/components/input-field.md +1 -1
  87. package/docs/components/input-otp.md +1 -1
  88. package/docs/components/input.md +1 -1
  89. package/docs/components/labeled-check-box.md +1 -1
  90. package/docs/components/labeled-radio.md +1 -1
  91. package/docs/components/radio-card.md +1 -1
  92. package/docs/components/radio.md +1 -1
  93. package/docs/components/search-field.md +1 -1
  94. package/docs/components/section-block.md +1 -1
  95. package/docs/components/select.md +1 -1
  96. package/docs/components/simple-select.md +1 -1
  97. package/docs/components/switch.md +1 -1
  98. package/docs/components/tab-switch.md +1 -1
  99. package/docs/components/table.md +1 -1
  100. package/docs/components/text-editor.md +1 -1
  101. package/docs/components/textarea.md +1 -1
  102. package/docs/components/toggle-button.md +1 -1
  103. package/docs/components/toggle.md +1 -1
  104. package/docs/components/tree-folder.md +110 -0
  105. package/docs/how-to/forms-with-form-builder.md +2 -2
  106. package/docs/reference/components.md +16 -6
  107. package/docs/tutorials/component-composition.md +11 -13
  108. package/package.json +3 -2
  109. package/apps/lib/components/DataViews/DataViewRadio.tsx +0 -49
  110. package/apps/lib/components/DataViews/DataViewsConfigPanel.tsx +0 -393
  111. package/apps/lib/components/DataViews/DataViewsHeader.tsx +0 -207
  112. package/apps/lib/components/DataViews/DataViewsLayout.tsx +0 -332
  113. package/apps/lib/components/DataViews/FilterPanel.tsx +0 -493
  114. package/apps/lib/components/DataViews/HeaderSearch.tsx +0 -93
  115. package/apps/lib/components/DataViews/InboxView.tsx +0 -463
  116. package/apps/lib/components/DataViews/InboxViewCard.tsx +0 -127
  117. package/apps/lib/components/DataViews/KanbanView.tsx +0 -336
  118. package/apps/lib/components/DataViews/PanelControls.tsx +0 -39
  119. package/apps/lib/components/DataViews/SettingsPanel.tsx +0 -279
  120. package/apps/lib/components/DataViews/TableView.tsx +0 -212
  121. package/apps/lib/components/DataViews/TreeView.tsx +0 -364
  122. package/apps/lib/components/DataViews/fieldRenderers.tsx +0 -299
  123. package/apps/lib/components/DataViews/filters/DatePickerRangeFilter.tsx +0 -87
  124. package/apps/lib/components/DataViews/filters/DateRangePopover.tsx +0 -120
  125. package/apps/lib/components/DataViews/filters/PresetChips.tsx +0 -45
  126. package/apps/lib/components/DataViews/filters/RangeSliderWithInputs.tsx +0 -165
  127. package/apps/lib/components/DataViews/tree/TreeDrawer.tsx +0 -50
  128. package/apps/lib/components/DataViews/tree/TreeSidebar.tsx +0 -74
  129. package/apps/lib/hooks/useDataViewsState.ts +0 -175
  130. package/apps/lib/utils/dataViews/columnUtils.ts +0 -132
  131. package/apps/lib/utils/dataViews/fieldUtils.ts +0 -197
  132. package/apps/lib/utils/dataViews/nestedDataUtils.tsx +0 -371
  133. package/apps/lib/utils/dataViews/pathUtils.ts +0 -139
  134. package/apps/lib/utils/dataViews/rangeUtils.ts +0 -234
  135. package/apps/lib/utils/dataViews/treeUtils.ts +0 -396
  136. package/docs/components/data-views-config-panel.md +0 -208
  137. package/docs/components/data-views-layout.md +0 -291
  138. package/docs/components/inbox-view.md +0 -170
  139. package/docs/components/kanban-view.md +0 -135
  140. package/docs/components/table-view.md +0 -141
  141. package/docs/components/tree-view.md +0 -147
  142. 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
+ ```