torch-glare 2.1.1 → 2.1.3
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/Avatar.tsx +1 -1
- package/apps/lib/components/BadgeField.tsx +2 -2
- package/apps/lib/components/Card.tsx +68 -54
- package/apps/lib/components/DataViews/ARCHITECTURE.md +439 -0
- package/apps/lib/components/DataViews/DataViewRadio.tsx +47 -0
- package/apps/lib/components/DataViews/DataViewsConfigPanel.tsx +427 -0
- package/apps/lib/components/DataViews/DataViewsHeader.tsx +228 -0
- package/apps/lib/components/DataViews/DataViewsLayout.tsx +330 -0
- package/apps/lib/components/DataViews/FilterPanel.tsx +469 -0
- package/apps/lib/components/DataViews/HeaderSearch.tsx +97 -0
- package/apps/lib/components/DataViews/InboxView.tsx +495 -0
- package/apps/lib/components/DataViews/InboxViewCard.tsx +136 -0
- package/apps/lib/components/DataViews/KanbanView.tsx +353 -0
- package/apps/lib/components/DataViews/PanelControls.tsx +49 -0
- package/apps/lib/components/DataViews/SettingsPanel.tsx +285 -0
- package/apps/lib/components/DataViews/TableView.tsx +232 -0
- package/apps/lib/components/DataViews/TreeView.tsx +392 -0
- package/apps/lib/components/DataViews/badgeAdapter.ts +45 -0
- package/apps/lib/components/DataViews/fieldRenderers.tsx +334 -0
- package/apps/lib/components/DataViews/filters/DateRangePopover.tsx +113 -0
- package/apps/lib/components/DataViews/filters/PresetChips.tsx +45 -0
- package/apps/lib/components/DataViews/filters/RangeSliderWithInputs.tsx +154 -0
- package/apps/lib/components/DataViews/index.ts +36 -0
- package/apps/lib/components/DataViews/tree/TreeDrawer.tsx +54 -0
- package/apps/lib/components/DataViews/tree/TreeSidebar.tsx +77 -0
- package/apps/lib/components/DataViews/types.ts +206 -0
- package/apps/lib/components/Radio.tsx +18 -21
- package/apps/lib/components/Switch.tsx +3 -1
- package/apps/lib/components/Table.tsx +1 -1
- package/apps/lib/components/TreeFolder/TreeFolder.tsx +410 -0
- package/apps/lib/components/TreeFolder/TreeFolderBreadcrumb.tsx +80 -0
- package/apps/lib/components/TreeFolder/TreeFolderRow.tsx +363 -0
- package/apps/lib/components/TreeFolder/TreeFolderStyles.tsx +60 -0
- package/apps/lib/components/TreeFolder/icons.tsx +63 -0
- package/apps/lib/components/TreeFolder/index.ts +17 -0
- package/apps/lib/components/TreeFolder/treeFolderUtils.ts +114 -0
- package/apps/lib/components/TreeFolder/types.ts +77 -0
- package/apps/lib/components/TreeFolder/useTreeFolderDnD.ts +261 -0
- package/apps/lib/hooks/useDataViewsState.ts +169 -0
- package/apps/lib/hooks/useIsMobile.ts +21 -0
- package/apps/lib/layouts/DataViewCard.tsx +76 -0
- package/apps/lib/utils/dataViews/columnUtils.ts +130 -0
- package/apps/lib/utils/dataViews/fieldUtils.ts +198 -0
- package/apps/lib/utils/dataViews/nestedDataUtils.tsx +364 -0
- package/apps/lib/utils/dataViews/pathUtils.ts +132 -0
- package/apps/lib/utils/dataViews/rangeUtils.ts +225 -0
- package/apps/lib/utils/dataViews/treeUtils.ts +403 -0
- package/dist/bin/index.js +3 -3
- package/dist/bin/index.js.map +1 -1
- package/dist/src/commands/add.d.ts.map +1 -1
- package/dist/src/commands/add.js +29 -6
- package/dist/src/commands/add.js.map +1 -1
- package/dist/src/commands/utils.d.ts.map +1 -1
- package/dist/src/commands/utils.js +22 -2
- package/dist/src/commands/utils.js.map +1 -1
- package/dist/src/shared/copyComponentsRecursively.d.ts.map +1 -1
- package/dist/src/shared/copyComponentsRecursively.js +17 -2
- package/dist/src/shared/copyComponentsRecursively.js.map +1 -1
- package/dist/src/shared/getDependenciesAndInstallNestedComponents.d.ts +18 -4
- package/dist/src/shared/getDependenciesAndInstallNestedComponents.d.ts.map +1 -1
- package/dist/src/shared/getDependenciesAndInstallNestedComponents.js +110 -40
- package/dist/src/shared/getDependenciesAndInstallNestedComponents.js.map +1 -1
- package/docs/components/data-views-config-panel.md +204 -0
- package/docs/components/data-views-layout.md +270 -0
- package/docs/components/form-stepper.md +244 -0
- package/docs/components/stepper.md +215 -0
- package/docs/components/timeline.md +248 -0
- package/package.json +6 -6
- package/apps/lib/components/Charts-dev.tsx +0 -365
- package/apps/lib/components/Command-dev.tsx +0 -151
- package/apps/lib/components/IosDatePicker-dev.tsx +0 -341
- /package/docs/components/{labeled-checkbox.md → labeled-check-box.md} +0 -0
- /package/docs/components/{tree-dropdown.md → tree-drop-down.md} +0 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
forwardRef,
|
|
5
|
+
useCallback,
|
|
6
|
+
useEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useRef,
|
|
9
|
+
useState,
|
|
10
|
+
type ReactNode,
|
|
11
|
+
} from "react";
|
|
12
|
+
import { List, LayoutGrid, Inbox as InboxIcon, Network } from "lucide-react";
|
|
13
|
+
import type {
|
|
14
|
+
DynamicRecord,
|
|
15
|
+
DynamicColumnConfig,
|
|
16
|
+
DynamicFilterConfig,
|
|
17
|
+
FilterState,
|
|
18
|
+
FieldConfig,
|
|
19
|
+
InboxConfig,
|
|
20
|
+
TreeConfig,
|
|
21
|
+
ViewConfig,
|
|
22
|
+
ViewType,
|
|
23
|
+
ViewVisibility,
|
|
24
|
+
} from "./types";
|
|
25
|
+
import { TableView } from "./TableView";
|
|
26
|
+
import { KanbanView } from "./KanbanView";
|
|
27
|
+
import { InboxView } from "./InboxView";
|
|
28
|
+
import { TreeView } from "./TreeView";
|
|
29
|
+
import { DataViewsHeader, type DataViewsHeaderView } from "./DataViewsHeader";
|
|
30
|
+
import { DataViewsConfigPanel } from "./DataViewsConfigPanel";
|
|
31
|
+
import { useDataViewsState } from "../../hooks/useDataViewsState";
|
|
32
|
+
import { cn } from "../../utils/cn";
|
|
33
|
+
import type { Themes } from "../../utils/types";
|
|
34
|
+
|
|
35
|
+
export type DataViewsLayoutProps = {
|
|
36
|
+
data?: DynamicRecord[];
|
|
37
|
+
config?: Partial<ViewConfig>;
|
|
38
|
+
title?: string;
|
|
39
|
+
description?: string;
|
|
40
|
+
|
|
41
|
+
fields?: FieldConfig[];
|
|
42
|
+
inboxConfig?: InboxConfig;
|
|
43
|
+
treeConfig?: TreeConfig;
|
|
44
|
+
kanbanGroupBy?: string;
|
|
45
|
+
kanbanTitleField?: string;
|
|
46
|
+
onKanbanColumnAction?: (columnId: string) => void;
|
|
47
|
+
|
|
48
|
+
views?: ViewVisibility;
|
|
49
|
+
|
|
50
|
+
columns?: Partial<DynamicColumnConfig>[];
|
|
51
|
+
filters?: DynamicFilterConfig[];
|
|
52
|
+
|
|
53
|
+
filterState?: FilterState;
|
|
54
|
+
onFilterChange?: (filters: FilterState) => void;
|
|
55
|
+
|
|
56
|
+
showFilters?: boolean;
|
|
57
|
+
showSettings?: boolean;
|
|
58
|
+
showTitle?: boolean;
|
|
59
|
+
|
|
60
|
+
onAddNew?: () => void;
|
|
61
|
+
addNewLabel?: string;
|
|
62
|
+
|
|
63
|
+
inboxItemHref?: (item: DynamicRecord, id: any) => string;
|
|
64
|
+
inboxSelectedId?: any;
|
|
65
|
+
inboxRenderDetail?: (item: DynamicRecord | null) => ReactNode;
|
|
66
|
+
|
|
67
|
+
searchValue?: string;
|
|
68
|
+
onSearchChange?: (value: string) => void;
|
|
69
|
+
searchPlaceholder?: string;
|
|
70
|
+
|
|
71
|
+
className?: string;
|
|
72
|
+
theme?: Themes;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const VIEW_META: Record<
|
|
76
|
+
ViewType,
|
|
77
|
+
{ label: string; icon: DataViewsHeaderView["icon"] }
|
|
78
|
+
> = {
|
|
79
|
+
table: { label: "List", icon: <List /> },
|
|
80
|
+
kanban: { label: "Board", icon: <LayoutGrid /> },
|
|
81
|
+
inbox: { label: "Inbox", icon: <InboxIcon /> },
|
|
82
|
+
tree: { label: "Tree", icon: <Network /> },
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const VIEW_ORDER: ViewType[] = ["table", "kanban", "inbox", "tree"];
|
|
86
|
+
|
|
87
|
+
export const DataViewsLayout = forwardRef<HTMLDivElement, DataViewsLayoutProps>(
|
|
88
|
+
function DataViewsLayout(props, ref) {
|
|
89
|
+
const {
|
|
90
|
+
title = "Data Views",
|
|
91
|
+
data,
|
|
92
|
+
config: initialConfig,
|
|
93
|
+
fields,
|
|
94
|
+
inboxConfig,
|
|
95
|
+
treeConfig,
|
|
96
|
+
kanbanGroupBy,
|
|
97
|
+
kanbanTitleField,
|
|
98
|
+
onKanbanColumnAction,
|
|
99
|
+
views,
|
|
100
|
+
columns,
|
|
101
|
+
filters,
|
|
102
|
+
filterState: externalFilterState,
|
|
103
|
+
onFilterChange,
|
|
104
|
+
// `showFilters` is retained on the public API but filters now live in the
|
|
105
|
+
// right-side config rail rather than an inline per-view panel.
|
|
106
|
+
showSettings = true,
|
|
107
|
+
showTitle = true,
|
|
108
|
+
onAddNew,
|
|
109
|
+
addNewLabel,
|
|
110
|
+
inboxItemHref,
|
|
111
|
+
inboxSelectedId,
|
|
112
|
+
inboxRenderDetail,
|
|
113
|
+
searchValue,
|
|
114
|
+
onSearchChange,
|
|
115
|
+
searchPlaceholder,
|
|
116
|
+
className,
|
|
117
|
+
theme,
|
|
118
|
+
} = props;
|
|
119
|
+
|
|
120
|
+
const {
|
|
121
|
+
items,
|
|
122
|
+
flatItems,
|
|
123
|
+
resolvedFields,
|
|
124
|
+
detectedColumns,
|
|
125
|
+
config,
|
|
126
|
+
setConfig,
|
|
127
|
+
currentView,
|
|
128
|
+
setCurrentView,
|
|
129
|
+
filterState,
|
|
130
|
+
setFilterState,
|
|
131
|
+
onDataUpdate,
|
|
132
|
+
enabledViews,
|
|
133
|
+
} = useDataViewsState({
|
|
134
|
+
data,
|
|
135
|
+
fields,
|
|
136
|
+
columns,
|
|
137
|
+
config: initialConfig,
|
|
138
|
+
treeConfig,
|
|
139
|
+
views,
|
|
140
|
+
filterState: externalFilterState,
|
|
141
|
+
onFilterChange,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// `panelOpen` drives intent; `panelMounted` keeps the panel in the tree
|
|
145
|
+
// through the close animation before unmounting.
|
|
146
|
+
const [panelOpen, setPanelOpen] = useState(false);
|
|
147
|
+
const [panelMounted, setPanelMounted] = useState(false);
|
|
148
|
+
const closeTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
149
|
+
|
|
150
|
+
const openPanel = useCallback(() => {
|
|
151
|
+
if (closeTimer.current) {
|
|
152
|
+
clearTimeout(closeTimer.current);
|
|
153
|
+
closeTimer.current = null;
|
|
154
|
+
}
|
|
155
|
+
// Mount at width 0 first, then flip to open on the next frame so the
|
|
156
|
+
// width transition animates from 0 → 260px instead of snapping.
|
|
157
|
+
setPanelMounted(true);
|
|
158
|
+
requestAnimationFrame(() =>
|
|
159
|
+
requestAnimationFrame(() => setPanelOpen(true)),
|
|
160
|
+
);
|
|
161
|
+
}, []);
|
|
162
|
+
|
|
163
|
+
const closePanel = useCallback(() => {
|
|
164
|
+
setPanelOpen(false);
|
|
165
|
+
if (closeTimer.current) clearTimeout(closeTimer.current);
|
|
166
|
+
// Keep mounted through the width/fade animation (300ms) before unmounting.
|
|
167
|
+
closeTimer.current = setTimeout(() => setPanelMounted(false), 300);
|
|
168
|
+
}, []);
|
|
169
|
+
|
|
170
|
+
const togglePanel = useCallback(() => {
|
|
171
|
+
if (panelOpen) closePanel();
|
|
172
|
+
else openPanel();
|
|
173
|
+
}, [panelOpen, openPanel, closePanel]);
|
|
174
|
+
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
return () => {
|
|
177
|
+
if (closeTimer.current) clearTimeout(closeTimer.current);
|
|
178
|
+
};
|
|
179
|
+
}, []);
|
|
180
|
+
|
|
181
|
+
const effectiveKanbanGroupBy = kanbanGroupBy ?? config.kanbanGroupBy;
|
|
182
|
+
// Filters now live in the right-side rail, not as an inline per-view panel.
|
|
183
|
+
const effectiveConfig: ViewConfig = { ...config, showFilters: false };
|
|
184
|
+
|
|
185
|
+
const headerViews = useMemo<DataViewsHeaderView[]>(
|
|
186
|
+
() =>
|
|
187
|
+
VIEW_ORDER.filter((v) => enabledViews[v]).map((v) => ({
|
|
188
|
+
id: v,
|
|
189
|
+
label: VIEW_META[v].label,
|
|
190
|
+
icon: VIEW_META[v].icon,
|
|
191
|
+
})),
|
|
192
|
+
[enabledViews],
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<div
|
|
197
|
+
ref={ref}
|
|
198
|
+
data-theme={theme}
|
|
199
|
+
className={cn(
|
|
200
|
+
// Shell is always black (matches Figma): the dark header and config
|
|
201
|
+
// rail sit on it; the Master Container is the white surface inside.
|
|
202
|
+
// `overflow-hidden` traps any child overflow — without it, a tall
|
|
203
|
+
// config-panel body escapes and creates a page-level scrollbar in
|
|
204
|
+
// addition to the panel's own.
|
|
205
|
+
"flex h-screen gap-2 overflow-hidden bg-black text-content-presentation-global-primary",
|
|
206
|
+
className,
|
|
207
|
+
)}
|
|
208
|
+
>
|
|
209
|
+
{/* Left column: header + content. Shrinks as the panel expands. */}
|
|
210
|
+
<div className="flex min-w-0 flex-1 flex-col gap-2">
|
|
211
|
+
{showTitle && (
|
|
212
|
+
<DataViewsHeader
|
|
213
|
+
title={title}
|
|
214
|
+
views={headerViews}
|
|
215
|
+
currentView={currentView}
|
|
216
|
+
onViewChange={setCurrentView}
|
|
217
|
+
showSettings={showSettings}
|
|
218
|
+
settingsOpen={panelOpen}
|
|
219
|
+
onToggleSettings={togglePanel}
|
|
220
|
+
onAddNew={onAddNew}
|
|
221
|
+
addNewLabel={addNewLabel}
|
|
222
|
+
searchValue={searchValue}
|
|
223
|
+
onSearchChange={onSearchChange}
|
|
224
|
+
searchPlaceholder={searchPlaceholder}
|
|
225
|
+
/>
|
|
226
|
+
)}
|
|
227
|
+
|
|
228
|
+
<main className="flex min-h-0 flex-1 overflow-hidden ">
|
|
229
|
+
{/* Master Container — white card, 16px radius, #D4D4D4 hairline
|
|
230
|
+
border. Fixed surface (matches header chrome). */}
|
|
231
|
+
<div className="flex flex-1 overflow-hidden rounded-[16px]">
|
|
232
|
+
{/* Clip the scrollable surface to the parent radius MINUS the 1px
|
|
233
|
+
border (16 − 1 = 15px). Using the full 16px here let the
|
|
234
|
+
opaque view background sit flush with the parent's outer edge
|
|
235
|
+
and bleed past the border as a ~1px light line on the
|
|
236
|
+
left/right straight sides. */}
|
|
237
|
+
<div className="flex-1 overflow-auto rounded-[15px]">
|
|
238
|
+
{currentView === "table" && enabledViews.table && (
|
|
239
|
+
<TableView
|
|
240
|
+
data={flatItems}
|
|
241
|
+
columns={detectedColumns}
|
|
242
|
+
fields={resolvedFields}
|
|
243
|
+
config={effectiveConfig}
|
|
244
|
+
onDataUpdate={onDataUpdate}
|
|
245
|
+
onSortChange={(sortBy, sortOrder) =>
|
|
246
|
+
setConfig({ sortBy, sortOrder })
|
|
247
|
+
}
|
|
248
|
+
filters={filters}
|
|
249
|
+
filterState={filterState}
|
|
250
|
+
onFilterChange={setFilterState}
|
|
251
|
+
showFilters={false}
|
|
252
|
+
/>
|
|
253
|
+
)}
|
|
254
|
+
{currentView === "kanban" && enabledViews.kanban && (
|
|
255
|
+
<KanbanView
|
|
256
|
+
data={flatItems}
|
|
257
|
+
columns={detectedColumns}
|
|
258
|
+
fields={resolvedFields}
|
|
259
|
+
config={effectiveConfig}
|
|
260
|
+
onDataUpdate={onDataUpdate}
|
|
261
|
+
groupByField={effectiveKanbanGroupBy}
|
|
262
|
+
titleField={kanbanTitleField}
|
|
263
|
+
onColumnAction={onKanbanColumnAction}
|
|
264
|
+
/>
|
|
265
|
+
)}
|
|
266
|
+
{currentView === "inbox" && enabledViews.inbox && (
|
|
267
|
+
<InboxView
|
|
268
|
+
data={flatItems}
|
|
269
|
+
columns={detectedColumns}
|
|
270
|
+
fields={resolvedFields}
|
|
271
|
+
inboxConfig={inboxConfig}
|
|
272
|
+
config={effectiveConfig}
|
|
273
|
+
onDataUpdate={onDataUpdate}
|
|
274
|
+
filters={filters}
|
|
275
|
+
filterState={filterState}
|
|
276
|
+
onFilterChange={setFilterState}
|
|
277
|
+
showFilters={false}
|
|
278
|
+
itemHref={inboxItemHref}
|
|
279
|
+
selectedItemId={inboxSelectedId}
|
|
280
|
+
renderDetail={inboxRenderDetail}
|
|
281
|
+
/>
|
|
282
|
+
)}
|
|
283
|
+
{currentView === "tree" && enabledViews.tree && (
|
|
284
|
+
<TreeView
|
|
285
|
+
data={items}
|
|
286
|
+
columns={detectedColumns}
|
|
287
|
+
fields={resolvedFields}
|
|
288
|
+
treeConfig={treeConfig}
|
|
289
|
+
config={effectiveConfig}
|
|
290
|
+
onDataUpdate={onDataUpdate}
|
|
291
|
+
filters={filters}
|
|
292
|
+
filterState={filterState}
|
|
293
|
+
onFilterChange={setFilterState}
|
|
294
|
+
showFilters={false}
|
|
295
|
+
/>
|
|
296
|
+
)}
|
|
297
|
+
</div>
|
|
298
|
+
</div>
|
|
299
|
+
</main>
|
|
300
|
+
</div>
|
|
301
|
+
|
|
302
|
+
{/* Right rail: full-height sibling of the [header + content] column, so
|
|
303
|
+
opening it pushes the header as well as the content. The wrapper
|
|
304
|
+
animates its width so the left column reflows in sync with the
|
|
305
|
+
panel's slide-in. */}
|
|
306
|
+
{showSettings && panelMounted && (
|
|
307
|
+
<div
|
|
308
|
+
className={cn(
|
|
309
|
+
"shrink-0 overflow-hidden transition-[width] duration-300 ease-in-out",
|
|
310
|
+
panelOpen ? "w-[260px]" : "w-0",
|
|
311
|
+
)}
|
|
312
|
+
>
|
|
313
|
+
<DataViewsConfigPanel
|
|
314
|
+
state={panelOpen ? "open" : "closed"}
|
|
315
|
+
config={config}
|
|
316
|
+
onConfigChange={setConfig}
|
|
317
|
+
onClose={closePanel}
|
|
318
|
+
currentView={currentView}
|
|
319
|
+
fields={resolvedFields}
|
|
320
|
+
data={flatItems}
|
|
321
|
+
filterState={filterState}
|
|
322
|
+
onFilterChange={setFilterState}
|
|
323
|
+
filterConfig={filters}
|
|
324
|
+
/>
|
|
325
|
+
</div>
|
|
326
|
+
)}
|
|
327
|
+
</div>
|
|
328
|
+
);
|
|
329
|
+
},
|
|
330
|
+
);
|