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.
Files changed (73) hide show
  1. package/apps/lib/components/Avatar.tsx +1 -1
  2. package/apps/lib/components/BadgeField.tsx +2 -2
  3. package/apps/lib/components/Card.tsx +68 -54
  4. package/apps/lib/components/DataViews/ARCHITECTURE.md +439 -0
  5. package/apps/lib/components/DataViews/DataViewRadio.tsx +47 -0
  6. package/apps/lib/components/DataViews/DataViewsConfigPanel.tsx +427 -0
  7. package/apps/lib/components/DataViews/DataViewsHeader.tsx +228 -0
  8. package/apps/lib/components/DataViews/DataViewsLayout.tsx +330 -0
  9. package/apps/lib/components/DataViews/FilterPanel.tsx +469 -0
  10. package/apps/lib/components/DataViews/HeaderSearch.tsx +97 -0
  11. package/apps/lib/components/DataViews/InboxView.tsx +495 -0
  12. package/apps/lib/components/DataViews/InboxViewCard.tsx +136 -0
  13. package/apps/lib/components/DataViews/KanbanView.tsx +353 -0
  14. package/apps/lib/components/DataViews/PanelControls.tsx +49 -0
  15. package/apps/lib/components/DataViews/SettingsPanel.tsx +285 -0
  16. package/apps/lib/components/DataViews/TableView.tsx +232 -0
  17. package/apps/lib/components/DataViews/TreeView.tsx +392 -0
  18. package/apps/lib/components/DataViews/badgeAdapter.ts +45 -0
  19. package/apps/lib/components/DataViews/fieldRenderers.tsx +334 -0
  20. package/apps/lib/components/DataViews/filters/DateRangePopover.tsx +113 -0
  21. package/apps/lib/components/DataViews/filters/PresetChips.tsx +45 -0
  22. package/apps/lib/components/DataViews/filters/RangeSliderWithInputs.tsx +154 -0
  23. package/apps/lib/components/DataViews/index.ts +36 -0
  24. package/apps/lib/components/DataViews/tree/TreeDrawer.tsx +54 -0
  25. package/apps/lib/components/DataViews/tree/TreeSidebar.tsx +77 -0
  26. package/apps/lib/components/DataViews/types.ts +206 -0
  27. package/apps/lib/components/Radio.tsx +18 -21
  28. package/apps/lib/components/Switch.tsx +3 -1
  29. package/apps/lib/components/Table.tsx +1 -1
  30. package/apps/lib/components/TreeFolder/TreeFolder.tsx +410 -0
  31. package/apps/lib/components/TreeFolder/TreeFolderBreadcrumb.tsx +80 -0
  32. package/apps/lib/components/TreeFolder/TreeFolderRow.tsx +363 -0
  33. package/apps/lib/components/TreeFolder/TreeFolderStyles.tsx +60 -0
  34. package/apps/lib/components/TreeFolder/icons.tsx +63 -0
  35. package/apps/lib/components/TreeFolder/index.ts +17 -0
  36. package/apps/lib/components/TreeFolder/treeFolderUtils.ts +114 -0
  37. package/apps/lib/components/TreeFolder/types.ts +77 -0
  38. package/apps/lib/components/TreeFolder/useTreeFolderDnD.ts +261 -0
  39. package/apps/lib/hooks/useDataViewsState.ts +169 -0
  40. package/apps/lib/hooks/useIsMobile.ts +21 -0
  41. package/apps/lib/layouts/DataViewCard.tsx +76 -0
  42. package/apps/lib/utils/dataViews/columnUtils.ts +130 -0
  43. package/apps/lib/utils/dataViews/fieldUtils.ts +198 -0
  44. package/apps/lib/utils/dataViews/nestedDataUtils.tsx +364 -0
  45. package/apps/lib/utils/dataViews/pathUtils.ts +132 -0
  46. package/apps/lib/utils/dataViews/rangeUtils.ts +225 -0
  47. package/apps/lib/utils/dataViews/treeUtils.ts +403 -0
  48. package/dist/bin/index.js +3 -3
  49. package/dist/bin/index.js.map +1 -1
  50. package/dist/src/commands/add.d.ts.map +1 -1
  51. package/dist/src/commands/add.js +29 -6
  52. package/dist/src/commands/add.js.map +1 -1
  53. package/dist/src/commands/utils.d.ts.map +1 -1
  54. package/dist/src/commands/utils.js +22 -2
  55. package/dist/src/commands/utils.js.map +1 -1
  56. package/dist/src/shared/copyComponentsRecursively.d.ts.map +1 -1
  57. package/dist/src/shared/copyComponentsRecursively.js +17 -2
  58. package/dist/src/shared/copyComponentsRecursively.js.map +1 -1
  59. package/dist/src/shared/getDependenciesAndInstallNestedComponents.d.ts +18 -4
  60. package/dist/src/shared/getDependenciesAndInstallNestedComponents.d.ts.map +1 -1
  61. package/dist/src/shared/getDependenciesAndInstallNestedComponents.js +110 -40
  62. package/dist/src/shared/getDependenciesAndInstallNestedComponents.js.map +1 -1
  63. package/docs/components/data-views-config-panel.md +204 -0
  64. package/docs/components/data-views-layout.md +270 -0
  65. package/docs/components/form-stepper.md +244 -0
  66. package/docs/components/stepper.md +215 -0
  67. package/docs/components/timeline.md +248 -0
  68. package/package.json +6 -6
  69. package/apps/lib/components/Charts-dev.tsx +0 -365
  70. package/apps/lib/components/Command-dev.tsx +0 -151
  71. package/apps/lib/components/IosDatePicker-dev.tsx +0 -341
  72. /package/docs/components/{labeled-checkbox.md → labeled-check-box.md} +0 -0
  73. /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
+ );