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,79 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useId, useState } from "react";
|
|
4
|
+
import { cn } from "../../../utils/cn";
|
|
5
|
+
import { ConclusionHeader } from "../../ConclusionHeader";
|
|
6
|
+
import type { PanelSectionProps } from "../types";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A titled block in the rail — and, when it has a title, a **collapsible** one.
|
|
10
|
+
*
|
|
11
|
+
* The header is `ConclusionHeader`, the same clickable section header `FormSummary` uses for its
|
|
12
|
+
* groups and the one Figma draws in this rail (`Conclusion-Header-1.0`). `Columns`, `Sort` and
|
|
13
|
+
* `SavedViews` all render through here, so they all fold from one implementation.
|
|
14
|
+
*
|
|
15
|
+
* It colours itself from `content-presentation-global-*` tokens rather than a `text-white`
|
|
16
|
+
* literal, which is what lets the same section sit in this dark rail and in the light content
|
|
17
|
+
* area without a second variant.
|
|
18
|
+
*/
|
|
19
|
+
export function Section({
|
|
20
|
+
title,
|
|
21
|
+
description,
|
|
22
|
+
collapsible = true,
|
|
23
|
+
defaultOpen = true,
|
|
24
|
+
children,
|
|
25
|
+
className,
|
|
26
|
+
}: PanelSectionProps) {
|
|
27
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
28
|
+
const bodyId = useId();
|
|
29
|
+
const isCollapsible = collapsible && title != null;
|
|
30
|
+
const shown = !isCollapsible || open;
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<section className={cn("flex flex-col", className)}>
|
|
34
|
+
{title != null &&
|
|
35
|
+
(isCollapsible ? (
|
|
36
|
+
<ConclusionHeader
|
|
37
|
+
label={title}
|
|
38
|
+
open={open}
|
|
39
|
+
onOpenChange={setOpen}
|
|
40
|
+
aria-controls={bodyId}
|
|
41
|
+
/>
|
|
42
|
+
) : (
|
|
43
|
+
<h3 className="text-content-presentation-global-primary text-[18px] font-[510] leading-[1.32] tracking-[-0.01em]">
|
|
44
|
+
{title}
|
|
45
|
+
</h3>
|
|
46
|
+
))}
|
|
47
|
+
|
|
48
|
+
{/* Figma groups the description with the header (a 47px block: 28px header + 18px line),
|
|
49
|
+
not with the rows — so it sits outside the fold and still reads when the group is shut. */}
|
|
50
|
+
{description != null && (
|
|
51
|
+
<p className="typography-body-small-regular text-content-presentation-global-primary">
|
|
52
|
+
{description}
|
|
53
|
+
</p>
|
|
54
|
+
)}
|
|
55
|
+
|
|
56
|
+
{/* Fold/unfold: `grid-rows-[0fr → 1fr]` animates to the body's natural height without
|
|
57
|
+
measuring it, so this stays a pure-CSS transition. The inner `overflow-hidden` is what
|
|
58
|
+
the 0fr track actually clips — it has to stay a bare wrapper. There is no flex `gap`
|
|
59
|
+
here either: a gap survives a collapsed body as dead space, so the spacing below the
|
|
60
|
+
title lives on the body instead, inside what collapses. */}
|
|
61
|
+
<div
|
|
62
|
+
id={bodyId}
|
|
63
|
+
// A collapsed body keeps its controls in the DOM, so `inert` is what takes them out of
|
|
64
|
+
// the tab order and the a11y tree — `overflow-hidden` alone would still let focus in.
|
|
65
|
+
inert={!shown}
|
|
66
|
+
aria-hidden={!shown}
|
|
67
|
+
className={cn(
|
|
68
|
+
"grid transition-[grid-template-rows,opacity] duration-200 ease-in-out",
|
|
69
|
+
"motion-reduce:transition-none",
|
|
70
|
+
shown ? "grid-rows-[1fr] opacity-100" : "grid-rows-[0fr] opacity-0",
|
|
71
|
+
)}
|
|
72
|
+
>
|
|
73
|
+
<div className="overflow-hidden">
|
|
74
|
+
<div className={cn("space-y-3", title != null && "pt-3")}>{children}</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</section>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { formatPathLabel } from "../../../utils/dataViews/path";
|
|
4
|
+
import { useDataViewsData, useDataViewsView } from "../context";
|
|
5
|
+
import { RadioGroup } from "./controls";
|
|
6
|
+
import { Section } from "./section";
|
|
7
|
+
import type { PanelSortProps } from "../types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* `DataViews.Panel.Sort` — the same intent the table headers emit, reachable from views that have
|
|
11
|
+
* no headers. Picking an option writes the sort into the root's query, which leaves through
|
|
12
|
+
* `onQueryChange`; nothing is reordered here, and this part takes no callback of its own.
|
|
13
|
+
*/
|
|
14
|
+
export function Sort({ title = "Default Sort", className }: PanelSortProps) {
|
|
15
|
+
const { fields } = useDataViewsData();
|
|
16
|
+
const { sort, setSort } = useDataViewsView();
|
|
17
|
+
const sortable = fields.filter((f) => f.type !== "hidden");
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<Section title={title} className={className}>
|
|
21
|
+
{sortable.length === 0 ? (
|
|
22
|
+
<p className="text-content-presentation-global-tertiary text-xs">No sortable columns.</p>
|
|
23
|
+
) : (
|
|
24
|
+
<RadioGroup
|
|
25
|
+
value={sort?.path ?? ""}
|
|
26
|
+
onValueChange={(path) =>
|
|
27
|
+
// Re-picking the active field flips the direction, as clicking a header twice does.
|
|
28
|
+
setSort(
|
|
29
|
+
sort?.path === path && sort.direction === "asc"
|
|
30
|
+
? { path, direction: "desc" }
|
|
31
|
+
: { path, direction: "asc" },
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
items={sortable.map((field) => ({
|
|
35
|
+
value: field.path,
|
|
36
|
+
label: field.label ?? formatPathLabel(field.path),
|
|
37
|
+
}))}
|
|
38
|
+
/>
|
|
39
|
+
)}
|
|
40
|
+
</Section>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { useDataViewsPanelTabs } from "../context";
|
|
5
|
+
import type { PanelTabProps } from "../types";
|
|
6
|
+
|
|
7
|
+
const TAB_MARKER = "__dvPanelTab";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* One tab in the panel. It renders its own children when it is the open tab and nothing when it
|
|
11
|
+
* is not, so a tab is declared and filled in one place:
|
|
12
|
+
*
|
|
13
|
+
* ```tsx
|
|
14
|
+
* <DataViews.Panel.Tab value="columns" label="Columns">
|
|
15
|
+
* <DataViews.Panel.Columns />
|
|
16
|
+
* </DataViews.Panel.Tab>
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export function Tab({ value, children }: PanelTabProps) {
|
|
20
|
+
const { tab } = useDataViewsPanelTabs();
|
|
21
|
+
if (tab !== value) return null;
|
|
22
|
+
return <>{children}</>;
|
|
23
|
+
}
|
|
24
|
+
(Tab as unknown as Record<string, boolean>)[TAB_MARKER] = true;
|
|
25
|
+
|
|
26
|
+
export function isTabElement(node: React.ReactNode): node is React.ReactElement<PanelTabProps> {
|
|
27
|
+
return (
|
|
28
|
+
React.isValidElement(node) &&
|
|
29
|
+
(node.type as unknown as Record<string, unknown>)?.[TAB_MARKER] === true
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* How the root recognises its children.
|
|
5
|
+
*
|
|
6
|
+
* Children are the API: rendering `<DataViews.Table/>` is what makes a table view exist, and
|
|
7
|
+
* rendering `<DataViews.Panel/>` is what puts a sidebar beside it. To lay those out the root has
|
|
8
|
+
* to tell them apart, so each part is stamped with a marker here and found with the matching
|
|
9
|
+
* guard — never `child.type === Panel`, which breaks across duplicated module instances and
|
|
10
|
+
* silently stops matching after a hot reload.
|
|
11
|
+
*
|
|
12
|
+
* Markers and guards live in this one file rather than beside their components so that the root
|
|
13
|
+
* can import all of them without importing the components themselves — otherwise `header.tsx`
|
|
14
|
+
* would have to import from `data-views.tsx`, which already imports `header.tsx`.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
function mark<P extends object>(component: React.ComponentType<P>, key: string) {
|
|
18
|
+
(component as unknown as Record<string, boolean>)[key] = true;
|
|
19
|
+
return component;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isMarked(node: React.ReactNode, key: string) {
|
|
23
|
+
return React.isValidElement(node) && (node.type as unknown as Record<string, unknown>)?.[key] === true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ─── Views ────────────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
/** What a view is called in the switcher when the caller does not say. */
|
|
29
|
+
export interface ViewMeta {
|
|
30
|
+
defaultId: string;
|
|
31
|
+
defaultLabel: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function markView<P extends object>(component: React.ComponentType<P>, meta: ViewMeta) {
|
|
35
|
+
(component as unknown as { __dataView: ViewMeta }).__dataView = meta;
|
|
36
|
+
return component;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function viewMetaOf(node: React.ReactNode): ViewMeta | undefined {
|
|
40
|
+
if (!React.isValidElement(node)) return undefined;
|
|
41
|
+
return (node.type as { __dataView?: ViewMeta })?.__dataView;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type ViewElement = React.ReactElement<{
|
|
45
|
+
id?: string;
|
|
46
|
+
label?: string;
|
|
47
|
+
icon?: React.ReactNode;
|
|
48
|
+
}>;
|
|
49
|
+
|
|
50
|
+
export function isViewElement(node: React.ReactNode): node is ViewElement {
|
|
51
|
+
return viewMetaOf(node) !== undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ─── Everything else the root positions ───────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
export const markHeader = <P extends object>(c: React.ComponentType<P>) => mark(c, "__dvHeader");
|
|
57
|
+
export const isHeaderElement = (n: React.ReactNode) => isMarked(n, "__dvHeader");
|
|
58
|
+
|
|
59
|
+
export const markPanel = <P extends object>(c: React.ComponentType<P>) => mark(c, "__dvPanel");
|
|
60
|
+
export const isPanelElement = (n: React.ReactNode) => isMarked(n, "__dvPanel");
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { cn } from "../../utils/cn";
|
|
5
|
+
import { Skeleton } from "../Skeleton";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The shared parts of a loading state.
|
|
9
|
+
*
|
|
10
|
+
* There is deliberately no `Empty` here. When there is nothing to show, the view shows nothing —
|
|
11
|
+
* a table keeps its header band and has no rows, a board keeps its columns and has no cards. A
|
|
12
|
+
* centred sentence in place of the view threw the chrome away and made the layout jump twice on
|
|
13
|
+
* every query, and it could not tell "no results" apart from "not fetched yet", so the first load
|
|
14
|
+
* of every page announced that nothing matched before anything had been asked for.
|
|
15
|
+
*
|
|
16
|
+
* Loading is answered per view instead, because a skeleton is only useful if it is the shape of
|
|
17
|
+
* the thing that is coming. These are the pieces the four views share so they cannot drift; the
|
|
18
|
+
* shapes themselves live with the view that owns them.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* One shimmer bar.
|
|
23
|
+
*
|
|
24
|
+
* Vary the width at the call site — a column of identical bars reads as a grid rather than as
|
|
25
|
+
* text that has not arrived.
|
|
26
|
+
*/
|
|
27
|
+
export function SkeletonBar({ className }: { className?: string }) {
|
|
28
|
+
return <Skeleton className={cn("h-[14px] w-full rounded-[4px]", className)} />;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* `n` keys to map over.
|
|
33
|
+
*
|
|
34
|
+
* Counts are the view's own constant, not `pageSize`: they exist to fill the fold, and a skeleton
|
|
35
|
+
* that promises exactly as many rows as are coming is a promise it cannot keep — the server
|
|
36
|
+
* decides that, and it has not answered yet.
|
|
37
|
+
*/
|
|
38
|
+
export const skeletonKeys = (n: number) => Array.from({ length: n }, (_, i) => i);
|