@lovett/ui 0.0.1
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/README.md +28 -0
- package/package.json +53 -0
- package/src/accordion.tsx +153 -0
- package/src/atoms.tsx +197 -0
- package/src/badge.tsx +91 -0
- package/src/brand-logo-tile.tsx +200 -0
- package/src/bulk-action-bar.tsx +104 -0
- package/src/button-group.tsx +46 -0
- package/src/button.tsx +93 -0
- package/src/calculator-shell-v2.tsx +380 -0
- package/src/calculator-shell.tsx +310 -0
- package/src/card.tsx +369 -0
- package/src/checkbox.tsx +114 -0
- package/src/chip-nav.tsx +347 -0
- package/src/choropleth.tsx +62 -0
- package/src/code-block.tsx +235 -0
- package/src/completion-ring.tsx +119 -0
- package/src/copy-field.tsx +169 -0
- package/src/data-grid/column-options-menu.tsx +127 -0
- package/src/data-grid/data-grid.tsx +391 -0
- package/src/data-grid/drawer-panel.tsx +146 -0
- package/src/data-grid/index.ts +29 -0
- package/src/data-grid/sortable-parts.tsx +215 -0
- package/src/data-grid/table-body.tsx +238 -0
- package/src/data-grid/table-elements.tsx +135 -0
- package/src/data-grid/table-header.tsx +95 -0
- package/src/data-grid/table-overlays.tsx +78 -0
- package/src/data-grid/table-parts.tsx +15 -0
- package/src/data-grid/table-summary-footer.tsx +101 -0
- package/src/data-grid/table-view.tsx +176 -0
- package/src/data-grid/types.ts +141 -0
- package/src/data-grid/use-grid-columns.ts +311 -0
- package/src/data-grid/use-grid-editing.ts +154 -0
- package/src/device-frame.tsx +274 -0
- package/src/dropdown-button.tsx +221 -0
- package/src/dropdown-menu.tsx +450 -0
- package/src/empty-state.tsx +85 -0
- package/src/file-upload/file-drop-zone.tsx +156 -0
- package/src/file-upload/file-preview-grid.tsx +39 -0
- package/src/file-upload/file-preview-item.tsx +175 -0
- package/src/file-upload/file-thumbnail.tsx +98 -0
- package/src/file-upload/file-upload-button.tsx +95 -0
- package/src/file-upload/index.ts +13 -0
- package/src/file-upload/use-file-upload.ts +371 -0
- package/src/filter-bar.tsx +125 -0
- package/src/filter-dropdown.tsx +289 -0
- package/src/floating-drawer.tsx +420 -0
- package/src/folder-card.tsx +188 -0
- package/src/folder-tree-picker.tsx +511 -0
- package/src/format.ts +117 -0
- package/src/frame-stack.tsx +73 -0
- package/src/hero-form-card.tsx +471 -0
- package/src/index.ts +372 -0
- package/src/input.tsx +130 -0
- package/src/kbd.tsx +41 -0
- package/src/lib/layout.ts +22 -0
- package/src/lib/utils.ts +6 -0
- package/src/markdown.tsx +214 -0
- package/src/menu-button.tsx +142 -0
- package/src/meta-previews/CreativeMedia.tsx +119 -0
- package/src/meta-previews/InstagramExplorePreview.tsx +340 -0
- package/src/meta-previews/InstagramFeedCarousel.tsx +245 -0
- package/src/meta-previews/InstagramFeedPreview.tsx +259 -0
- package/src/meta-previews/MetaFeedCarousel.tsx +228 -0
- package/src/meta-previews/MetaFeedPreview.tsx +294 -0
- package/src/meta-previews/MetaInstreamPreview.tsx +135 -0
- package/src/meta-previews/MetaMessengerPreview.tsx +795 -0
- package/src/meta-previews/MetaReelPreview.tsx +750 -0
- package/src/meta-previews/MetaRightColumnPreview.tsx +245 -0
- package/src/meta-previews/MetaSearchPreview.tsx +113 -0
- package/src/meta-previews/MetaStoryPreview.tsx +1132 -0
- package/src/meta-previews/ProfileAvatar.tsx +79 -0
- package/src/meta-previews/defaults.ts +27 -0
- package/src/meta-previews/index.ts +37 -0
- package/src/meta-previews/types.ts +219 -0
- package/src/metric-card.tsx +117 -0
- package/src/modal.tsx +131 -0
- package/src/option-tile.tsx +252 -0
- package/src/page-hero.tsx +78 -0
- package/src/page-shell.tsx +440 -0
- package/src/pagination.tsx +86 -0
- package/src/pill-button.tsx +103 -0
- package/src/profile-section.tsx +389 -0
- package/src/progress-bar.tsx +84 -0
- package/src/radio.tsx +172 -0
- package/src/range-slider.tsx +161 -0
- package/src/resizable.tsx +282 -0
- package/src/search-bar.tsx +81 -0
- package/src/segmented-pill.tsx +143 -0
- package/src/shell.tsx +464 -0
- package/src/slider.tsx +107 -0
- package/src/sortable-table.tsx +198 -0
- package/src/sortable.tsx +137 -0
- package/src/stat-row.tsx +132 -0
- package/src/stats-grid.tsx +38 -0
- package/src/step-loader.tsx +141 -0
- package/src/styles.css +562 -0
- package/src/tabs.tsx +143 -0
- package/src/tag-chip-input.tsx +394 -0
- package/src/textarea.tsx +39 -0
- package/src/toast.tsx +59 -0
- package/src/tokens.css +579 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DataGrid — top-level orchestrator that composes the table view +
|
|
3
|
+
* drawer panel and threads state through the editing + columns hooks.
|
|
4
|
+
*
|
|
5
|
+
* Ported 2026-05-18 from gray-ui-csm
|
|
6
|
+
* https://github.com/Jason-uxui/gray-ui-csm/blob/baf8346ac0c1160282e0b173de1bffc594d9c9c3/components/data-grid/data-grid.tsx
|
|
7
|
+
* License: MIT (see packages/ui/licenses/gray-ui-csm-LICENSE.txt)
|
|
8
|
+
* Workspace ADR: ADR-025
|
|
9
|
+
*
|
|
10
|
+
* BRING_WITH_REFACTOR per ADR-025 D5:
|
|
11
|
+
* • Source wraps everything in `<DialogPrimitive.Root open={...}>`
|
|
12
|
+
* and lets DrawerPanel discover open + close via Dialog context.
|
|
13
|
+
* CLAUDE.md §1 forbids `@base-ui/react`. The workspace port hoists
|
|
14
|
+
* open state into this component (already present here as
|
|
15
|
+
* `drawerCell`) and threads `isOpen` + `closeDrawer` explicitly
|
|
16
|
+
* into the default <DrawerPanel> (which renders a workspace
|
|
17
|
+
* <FloatingDrawer side="right">).
|
|
18
|
+
* • `drawerModal` + new `drawerSize` props pass through to
|
|
19
|
+
* FloatingDrawer.
|
|
20
|
+
* • `disablePointerDismissal` is dropped — FloatingDrawer's modal
|
|
21
|
+
* mode handles pointer-dismissal via its backdrop click handler
|
|
22
|
+
* only when `modal=true`. Non-modal mode never dismisses on
|
|
23
|
+
* pointer events. This matches the source's `disablePointerDismissal`
|
|
24
|
+
* default-true behavior when used in non-modal mode (the source's
|
|
25
|
+
* primary mode). DEVIATION-5 in the completion report.
|
|
26
|
+
*
|
|
27
|
+
* Token reskin per ADR-025 D7. No tabler icons used here.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import {
|
|
31
|
+
useCallback,
|
|
32
|
+
useEffect,
|
|
33
|
+
useId,
|
|
34
|
+
useMemo,
|
|
35
|
+
useRef,
|
|
36
|
+
useState,
|
|
37
|
+
type ReactNode,
|
|
38
|
+
} from 'react'
|
|
39
|
+
|
|
40
|
+
import { PointerSensor, useSensor, useSensors } from '@dnd-kit/core'
|
|
41
|
+
|
|
42
|
+
import { cn } from '../lib/utils'
|
|
43
|
+
import { DrawerPanel } from './drawer-panel'
|
|
44
|
+
import { DataGridTableView } from './table-view'
|
|
45
|
+
import { useGridColumns } from './use-grid-columns'
|
|
46
|
+
import { useGridEditing } from './use-grid-editing'
|
|
47
|
+
import type {
|
|
48
|
+
DataGridDrawerPanelProps,
|
|
49
|
+
DataGridProps,
|
|
50
|
+
DataGridRowBase,
|
|
51
|
+
EditingCell,
|
|
52
|
+
} from './types'
|
|
53
|
+
|
|
54
|
+
export type {
|
|
55
|
+
DataGridColumn,
|
|
56
|
+
DataGridDrawerPanelProps,
|
|
57
|
+
DataGridProps,
|
|
58
|
+
DataGridRowBase,
|
|
59
|
+
DataGridToolbarRenderProps,
|
|
60
|
+
} from './types'
|
|
61
|
+
|
|
62
|
+
const MIN_COLUMN_WIDTH = 96
|
|
63
|
+
const CONTROL_COLUMN_WIDTH = 40
|
|
64
|
+
|
|
65
|
+
function isEmptyValue(value: ReactNode) {
|
|
66
|
+
if (value === null || value === undefined) return true
|
|
67
|
+
if (typeof value === 'string') return value.trim().length === 0
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function DataGrid<Row extends DataGridRowBase, ColumnId extends string>({
|
|
72
|
+
rows,
|
|
73
|
+
columns,
|
|
74
|
+
getRowLabel,
|
|
75
|
+
renderCell,
|
|
76
|
+
isEditableColumn,
|
|
77
|
+
getCellEditValue,
|
|
78
|
+
applyCellEdit,
|
|
79
|
+
getDrawerCellValue,
|
|
80
|
+
canOpenDrawer = () => true,
|
|
81
|
+
renderSummary,
|
|
82
|
+
renderDrawerPanel,
|
|
83
|
+
renderToolbar,
|
|
84
|
+
onToolbarPropsChange,
|
|
85
|
+
onOpenDrawerCell,
|
|
86
|
+
drawerModal = false,
|
|
87
|
+
drawerSize,
|
|
88
|
+
stickySummaryFooter = false,
|
|
89
|
+
fillAvailableHeight = false,
|
|
90
|
+
tableContainerClassName,
|
|
91
|
+
onRowsChange,
|
|
92
|
+
sort,
|
|
93
|
+
onSortChange,
|
|
94
|
+
}: DataGridProps<Row, ColumnId>) {
|
|
95
|
+
const [showSummaries, setShowSummaries] = useState(true)
|
|
96
|
+
const [drawerCell, setDrawerCell] =
|
|
97
|
+
useState<EditingCell<ColumnId> | null>(null)
|
|
98
|
+
const [selectedRowIds, setSelectedRowIds] = useState<string[]>([])
|
|
99
|
+
const reactInstanceId = useId()
|
|
100
|
+
const instanceId = useMemo(
|
|
101
|
+
() => reactInstanceId.replace(/:/g, ''),
|
|
102
|
+
[reactInstanceId],
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
const gridRef = useRef<HTMLDivElement>(null)
|
|
106
|
+
const inputRef = useRef<HTMLInputElement>(null)
|
|
107
|
+
const tableRef = useRef<HTMLTableElement>(null)
|
|
108
|
+
|
|
109
|
+
const {
|
|
110
|
+
columnWidths,
|
|
111
|
+
visibleColumnIds,
|
|
112
|
+
draggingColumnId,
|
|
113
|
+
visibleColumns,
|
|
114
|
+
gridMinWidth,
|
|
115
|
+
hiddenColumns,
|
|
116
|
+
toggleColumnVisibility,
|
|
117
|
+
handleOptionColumnDragEnd,
|
|
118
|
+
beginResize,
|
|
119
|
+
} = useGridColumns({
|
|
120
|
+
columns,
|
|
121
|
+
tableRef,
|
|
122
|
+
minColumnWidth: MIN_COLUMN_WIDTH,
|
|
123
|
+
controlColumnWidth: CONTROL_COLUMN_WIDTH,
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
const optionsPointerSensorOptions = useMemo(
|
|
127
|
+
() => ({
|
|
128
|
+
activationConstraint: {
|
|
129
|
+
distance: 6,
|
|
130
|
+
},
|
|
131
|
+
}),
|
|
132
|
+
[],
|
|
133
|
+
)
|
|
134
|
+
const optionsSensors = useSensors(
|
|
135
|
+
useSensor(PointerSensor, optionsPointerSensorOptions),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
const drawerRow = useMemo(() => {
|
|
139
|
+
if (!drawerCell) return null
|
|
140
|
+
return rows.find((row) => row.id === drawerCell.rowId) ?? null
|
|
141
|
+
}, [drawerCell, rows])
|
|
142
|
+
|
|
143
|
+
const drawerRowIndex = useMemo(() => {
|
|
144
|
+
if (!drawerRow) return -1
|
|
145
|
+
return rows.findIndex((row) => row.id === drawerRow.id)
|
|
146
|
+
}, [drawerRow, rows])
|
|
147
|
+
|
|
148
|
+
const drawerColumn = useMemo(() => {
|
|
149
|
+
if (!drawerCell) return null
|
|
150
|
+
return columns.find((column) => column.id === drawerCell.columnId) ?? null
|
|
151
|
+
}, [columns, drawerCell])
|
|
152
|
+
|
|
153
|
+
const drawerCellValue = useMemo(() => {
|
|
154
|
+
if (!drawerCell || !drawerRow) return null
|
|
155
|
+
return getDrawerCellValue(drawerRow, drawerCell.columnId)
|
|
156
|
+
}, [drawerCell, drawerRow, getDrawerCellValue])
|
|
157
|
+
|
|
158
|
+
const commitRows = useCallback(
|
|
159
|
+
(updater: (currentRows: Row[]) => Row[]) => {
|
|
160
|
+
const nextRows = updater(rows)
|
|
161
|
+
onRowsChange?.(nextRows)
|
|
162
|
+
},
|
|
163
|
+
[onRowsChange, rows],
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
const {
|
|
167
|
+
editingCell,
|
|
168
|
+
draftValue,
|
|
169
|
+
setDraftValue,
|
|
170
|
+
startEditing,
|
|
171
|
+
commitEdit,
|
|
172
|
+
cancelEdit,
|
|
173
|
+
handleCellKeyDown,
|
|
174
|
+
} = useGridEditing<Row, ColumnId>({
|
|
175
|
+
gridRef,
|
|
176
|
+
inputRef,
|
|
177
|
+
visibleRowCount: rows.length,
|
|
178
|
+
visibleColumnCount: visibleColumns.length,
|
|
179
|
+
isEditableColumn,
|
|
180
|
+
getCellEditValue,
|
|
181
|
+
applyCellEdit,
|
|
182
|
+
commitRows,
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
const closeDrawer = useCallback(() => {
|
|
186
|
+
setDrawerCell(null)
|
|
187
|
+
}, [])
|
|
188
|
+
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
setSelectedRowIds((current) => {
|
|
191
|
+
if (current.length === 0) return current
|
|
192
|
+
const visibleRowIds = new Set(rows.map((row) => row.id))
|
|
193
|
+
const filtered = current.filter((rowId) => visibleRowIds.has(rowId))
|
|
194
|
+
return filtered.length === current.length ? current : filtered
|
|
195
|
+
})
|
|
196
|
+
}, [rows])
|
|
197
|
+
|
|
198
|
+
const toggleRowSelection = useCallback(
|
|
199
|
+
(rowId: string, checked: boolean) => {
|
|
200
|
+
setSelectedRowIds((current) => {
|
|
201
|
+
if (checked) {
|
|
202
|
+
return current.includes(rowId) ? current : [...current, rowId]
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return current.filter((currentRowId) => currentRowId !== rowId)
|
|
206
|
+
})
|
|
207
|
+
},
|
|
208
|
+
[],
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
const toggleAllRows = useCallback(
|
|
212
|
+
(checked: boolean) => {
|
|
213
|
+
setSelectedRowIds(checked ? rows.map((row) => row.id) : [])
|
|
214
|
+
},
|
|
215
|
+
[rows],
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
const clearSelection = useCallback(() => {
|
|
219
|
+
setSelectedRowIds([])
|
|
220
|
+
}, [])
|
|
221
|
+
|
|
222
|
+
const selectedRowCount = selectedRowIds.length
|
|
223
|
+
const allVisibleRowsSelected =
|
|
224
|
+
rows.length > 0 && selectedRowCount === rows.length
|
|
225
|
+
const someVisibleRowsSelected =
|
|
226
|
+
selectedRowCount > 0 && !allVisibleRowsSelected
|
|
227
|
+
|
|
228
|
+
const updateRow = useCallback(
|
|
229
|
+
(rowId: string, updater: (row: Row) => Row) => {
|
|
230
|
+
commitRows((currentRows) =>
|
|
231
|
+
currentRows.map((row) => (row.id === rowId ? updater(row) : row)),
|
|
232
|
+
)
|
|
233
|
+
},
|
|
234
|
+
[commitRows],
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
const openDrawerRowAt = useCallback(
|
|
238
|
+
(rowIndex: number) => {
|
|
239
|
+
if (!drawerCell) return
|
|
240
|
+
|
|
241
|
+
const nextRow = rows[rowIndex]
|
|
242
|
+
if (!nextRow) return
|
|
243
|
+
|
|
244
|
+
setDrawerCell({
|
|
245
|
+
rowId: nextRow.id,
|
|
246
|
+
columnId: drawerCell.columnId,
|
|
247
|
+
})
|
|
248
|
+
},
|
|
249
|
+
[drawerCell, rows],
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
const openPreviousRow = useCallback(() => {
|
|
253
|
+
openDrawerRowAt(drawerRowIndex - 1)
|
|
254
|
+
}, [drawerRowIndex, openDrawerRowAt])
|
|
255
|
+
|
|
256
|
+
const openNextRow = useCallback(() => {
|
|
257
|
+
openDrawerRowAt(drawerRowIndex + 1)
|
|
258
|
+
}, [drawerRowIndex, openDrawerRowAt])
|
|
259
|
+
|
|
260
|
+
const drawerIsOpen = drawerCell !== null
|
|
261
|
+
|
|
262
|
+
const drawerPanelProps: DataGridDrawerPanelProps<Row, ColumnId> = {
|
|
263
|
+
drawerRow,
|
|
264
|
+
drawerRowIndex,
|
|
265
|
+
drawerRowCount: rows.length,
|
|
266
|
+
drawerColumn,
|
|
267
|
+
drawerCellValue,
|
|
268
|
+
getRowLabel,
|
|
269
|
+
isEditableColumn,
|
|
270
|
+
isEmptyValue,
|
|
271
|
+
updateRow,
|
|
272
|
+
openPreviousRow,
|
|
273
|
+
openNextRow,
|
|
274
|
+
closeDrawer,
|
|
275
|
+
isOpen: drawerIsOpen,
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const toolbarProps = useMemo(
|
|
279
|
+
() => ({
|
|
280
|
+
visibleRowCount: rows.length,
|
|
281
|
+
selectedRowIds,
|
|
282
|
+
selectedRowCount,
|
|
283
|
+
allVisibleRowsSelected,
|
|
284
|
+
someVisibleRowsSelected,
|
|
285
|
+
onToggleAllRows: toggleAllRows,
|
|
286
|
+
clearSelection,
|
|
287
|
+
showSummaries,
|
|
288
|
+
onShowSummariesChange: setShowSummaries,
|
|
289
|
+
visibleColumns,
|
|
290
|
+
visibleColumnIds,
|
|
291
|
+
hiddenColumns,
|
|
292
|
+
toggleColumnVisibility,
|
|
293
|
+
optionsSensors,
|
|
294
|
+
onOptionColumnDragEnd: handleOptionColumnDragEnd,
|
|
295
|
+
optionsDndContextId: `data-grid-options-${instanceId}`,
|
|
296
|
+
}),
|
|
297
|
+
[
|
|
298
|
+
rows.length,
|
|
299
|
+
selectedRowIds,
|
|
300
|
+
selectedRowCount,
|
|
301
|
+
allVisibleRowsSelected,
|
|
302
|
+
someVisibleRowsSelected,
|
|
303
|
+
toggleAllRows,
|
|
304
|
+
clearSelection,
|
|
305
|
+
showSummaries,
|
|
306
|
+
visibleColumns,
|
|
307
|
+
visibleColumnIds,
|
|
308
|
+
hiddenColumns,
|
|
309
|
+
toggleColumnVisibility,
|
|
310
|
+
optionsSensors,
|
|
311
|
+
handleOptionColumnDragEnd,
|
|
312
|
+
instanceId,
|
|
313
|
+
],
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
useEffect(() => {
|
|
317
|
+
onToolbarPropsChange?.(toolbarProps)
|
|
318
|
+
}, [onToolbarPropsChange, toolbarProps])
|
|
319
|
+
|
|
320
|
+
return (
|
|
321
|
+
<>
|
|
322
|
+
<div
|
|
323
|
+
className={cn('min-h-0', fillAvailableHeight && 'flex h-full flex-col')}
|
|
324
|
+
ref={gridRef}
|
|
325
|
+
>
|
|
326
|
+
{renderToolbar?.(toolbarProps)}
|
|
327
|
+
|
|
328
|
+
<div className={cn(fillAvailableHeight && 'min-h-0 flex-1')}>
|
|
329
|
+
<DataGridTableView
|
|
330
|
+
tableRef={tableRef}
|
|
331
|
+
gridMinWidth={gridMinWidth}
|
|
332
|
+
visibleColumns={visibleColumns}
|
|
333
|
+
columnWidths={columnWidths}
|
|
334
|
+
draggingColumnId={draggingColumnId}
|
|
335
|
+
visibleRows={rows}
|
|
336
|
+
getRowLabel={getRowLabel}
|
|
337
|
+
editingCell={editingCell}
|
|
338
|
+
isEditableColumn={isEditableColumn}
|
|
339
|
+
startEditing={startEditing}
|
|
340
|
+
onCellKeyDown={handleCellKeyDown}
|
|
341
|
+
inputRef={inputRef}
|
|
342
|
+
draftValue={draftValue}
|
|
343
|
+
setDraftValue={setDraftValue}
|
|
344
|
+
commitEdit={commitEdit}
|
|
345
|
+
cancelEdit={cancelEdit}
|
|
346
|
+
canOpenDrawer={canOpenDrawer}
|
|
347
|
+
renderCell={renderCell}
|
|
348
|
+
onOpenDrawer={(cell) => {
|
|
349
|
+
if (onOpenDrawerCell) {
|
|
350
|
+
onOpenDrawerCell(cell)
|
|
351
|
+
return
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
setDrawerCell(cell)
|
|
355
|
+
}}
|
|
356
|
+
selectedRowIds={selectedRowIds}
|
|
357
|
+
allVisibleRowsSelected={allVisibleRowsSelected}
|
|
358
|
+
someVisibleRowsSelected={someVisibleRowsSelected}
|
|
359
|
+
onToggleRowSelection={toggleRowSelection}
|
|
360
|
+
onToggleAllRows={toggleAllRows}
|
|
361
|
+
showSummaries={showSummaries}
|
|
362
|
+
renderSummary={renderSummary}
|
|
363
|
+
stickySummaryFooter={stickySummaryFooter}
|
|
364
|
+
tableContainerClassName={tableContainerClassName}
|
|
365
|
+
isEmptyValue={isEmptyValue}
|
|
366
|
+
onResizeStart={beginResize}
|
|
367
|
+
sort={sort ?? null}
|
|
368
|
+
onSortChange={onSortChange}
|
|
369
|
+
/>
|
|
370
|
+
</div>
|
|
371
|
+
</div>
|
|
372
|
+
|
|
373
|
+
{renderDrawerPanel ? (
|
|
374
|
+
renderDrawerPanel(drawerPanelProps)
|
|
375
|
+
) : (
|
|
376
|
+
<DrawerPanel
|
|
377
|
+
isOpen={drawerIsOpen}
|
|
378
|
+
onClose={closeDrawer}
|
|
379
|
+
drawerRow={drawerRow}
|
|
380
|
+
drawerColumn={drawerColumn}
|
|
381
|
+
drawerCellValue={drawerCellValue}
|
|
382
|
+
getRowLabel={getRowLabel}
|
|
383
|
+
isEditableColumn={isEditableColumn}
|
|
384
|
+
isEmptyValue={isEmptyValue}
|
|
385
|
+
modal={drawerModal}
|
|
386
|
+
size={drawerSize ?? undefined}
|
|
387
|
+
/>
|
|
388
|
+
)}
|
|
389
|
+
</>
|
|
390
|
+
)
|
|
391
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DrawerPanel — cell-detail side drawer for the DataGrid.
|
|
3
|
+
*
|
|
4
|
+
* Ported 2026-05-18 from gray-ui-csm
|
|
5
|
+
* https://github.com/Jason-uxui/gray-ui-csm/blob/baf8346ac0c1160282e0b173de1bffc594d9c9c3/components/data-grid/drawer-panel.tsx
|
|
6
|
+
* License: MIT (see packages/ui/licenses/gray-ui-csm-LICENSE.txt)
|
|
7
|
+
* Workspace ADR: ADR-025
|
|
8
|
+
*
|
|
9
|
+
* BRING_WITH_REFACTOR (load-bearing) per ADR-025 D5:
|
|
10
|
+
* • Source mounts inside a `@base-ui/react/dialog` Root, reading
|
|
11
|
+
* open + close through Dialog context. CLAUDE.md §1 forbids
|
|
12
|
+
* `@base-ui/react`. This port rewires to the workspace
|
|
13
|
+
* <FloatingDrawer side="right">, which the orchestrator drives
|
|
14
|
+
* via explicit `isOpen` + `onClose` props (see data-grid.tsx).
|
|
15
|
+
* • The header title/description, body content (cell value tile +
|
|
16
|
+
* metadata <dl>), and footer Close button all carry over —
|
|
17
|
+
* chrome is supplied by FloatingDrawer's title + children +
|
|
18
|
+
* footer slots.
|
|
19
|
+
* • `modal` / `size` pass through to FloatingDrawer.
|
|
20
|
+
*
|
|
21
|
+
* Token reskin per ADR-025 D7. Icons replaced by Lucide imports
|
|
22
|
+
* (workspace doesn't ship the source's tabler set; CLAUDE.md §6).
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import type { ReactNode } from 'react'
|
|
26
|
+
|
|
27
|
+
import { Button } from '../button'
|
|
28
|
+
import { FloatingDrawer } from '../floating-drawer'
|
|
29
|
+
|
|
30
|
+
import type { DataGridColumn } from './types'
|
|
31
|
+
|
|
32
|
+
type DrawerPanelProps<Row, ColumnId extends string> = {
|
|
33
|
+
isOpen: boolean
|
|
34
|
+
onClose: () => void
|
|
35
|
+
drawerRow: Row | null
|
|
36
|
+
drawerColumn: DataGridColumn<ColumnId> | null
|
|
37
|
+
drawerCellValue: ReactNode
|
|
38
|
+
getRowLabel: (row: Row) => string
|
|
39
|
+
isEditableColumn: (columnId: ColumnId) => boolean
|
|
40
|
+
isEmptyValue: (value: ReactNode) => boolean
|
|
41
|
+
/** Optional — controls FloatingDrawer modal mode. Defaults to false
|
|
42
|
+
* (matches the source's `modal={drawerModal}` default). */
|
|
43
|
+
modal?: boolean
|
|
44
|
+
/** Pixel width of the drawer panel. Defaults to FloatingDrawer's
|
|
45
|
+
* 520 for `side="right"`. */
|
|
46
|
+
size: number | undefined
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function DrawerPanel<Row, ColumnId extends string>({
|
|
50
|
+
isOpen,
|
|
51
|
+
onClose,
|
|
52
|
+
drawerRow,
|
|
53
|
+
drawerColumn,
|
|
54
|
+
drawerCellValue,
|
|
55
|
+
getRowLabel,
|
|
56
|
+
isEditableColumn,
|
|
57
|
+
isEmptyValue,
|
|
58
|
+
modal = false,
|
|
59
|
+
size,
|
|
60
|
+
}: DrawerPanelProps<Row, ColumnId>) {
|
|
61
|
+
const title = (
|
|
62
|
+
<div className="min-w-0">
|
|
63
|
+
<div className="truncate text-sm font-medium">
|
|
64
|
+
{drawerRow ? getRowLabel(drawerRow) : 'Record'} ·{' '}
|
|
65
|
+
{drawerColumn?.label ?? 'Details'}
|
|
66
|
+
</div>
|
|
67
|
+
<div
|
|
68
|
+
className="text-xs"
|
|
69
|
+
style={{ color: 'rgb(var(--text-muted))' }}
|
|
70
|
+
>
|
|
71
|
+
Context panel for the selected cell.
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
const footer = (
|
|
77
|
+
<Button variant="outline" onClick={onClose}>
|
|
78
|
+
Close
|
|
79
|
+
</Button>
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<FloatingDrawer
|
|
84
|
+
isOpen={isOpen}
|
|
85
|
+
onClose={onClose}
|
|
86
|
+
side="right"
|
|
87
|
+
modal={modal}
|
|
88
|
+
{...(size !== undefined ? { size } : {})}
|
|
89
|
+
title={title}
|
|
90
|
+
footer={footer}
|
|
91
|
+
>
|
|
92
|
+
<div className="space-y-4">
|
|
93
|
+
<div
|
|
94
|
+
className="rounded-[var(--radius-md)] p-3"
|
|
95
|
+
style={{
|
|
96
|
+
background: 'rgb(var(--surface-overlay-soft))',
|
|
97
|
+
border: '1px solid rgb(var(--border))',
|
|
98
|
+
}}
|
|
99
|
+
>
|
|
100
|
+
<div
|
|
101
|
+
className="text-xs"
|
|
102
|
+
style={{ color: 'rgb(var(--text-muted))' }}
|
|
103
|
+
>
|
|
104
|
+
Cell value
|
|
105
|
+
</div>
|
|
106
|
+
<div
|
|
107
|
+
className="mt-1 break-words text-sm"
|
|
108
|
+
style={{ color: 'rgb(var(--foreground))' }}
|
|
109
|
+
>
|
|
110
|
+
{!isEmptyValue(drawerCellValue) ? drawerCellValue : 'Empty'}
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<dl className="grid gap-3 text-sm">
|
|
115
|
+
<div className="flex items-center justify-between gap-3">
|
|
116
|
+
<dt style={{ color: 'rgb(var(--text-muted))' }}>Record</dt>
|
|
117
|
+
<dd
|
|
118
|
+
className="text-right font-medium"
|
|
119
|
+
style={{ color: 'rgb(var(--foreground))' }}
|
|
120
|
+
>
|
|
121
|
+
{drawerRow ? getRowLabel(drawerRow) : '-'}
|
|
122
|
+
</dd>
|
|
123
|
+
</div>
|
|
124
|
+
<div className="flex items-center justify-between gap-3">
|
|
125
|
+
<dt style={{ color: 'rgb(var(--text-muted))' }}>Field</dt>
|
|
126
|
+
<dd
|
|
127
|
+
className="text-right font-medium"
|
|
128
|
+
style={{ color: 'rgb(var(--foreground))' }}
|
|
129
|
+
>
|
|
130
|
+
{drawerColumn?.label ?? '-'}
|
|
131
|
+
</dd>
|
|
132
|
+
</div>
|
|
133
|
+
<div className="flex items-center justify-between gap-3">
|
|
134
|
+
<dt style={{ color: 'rgb(var(--text-muted))' }}>Editable</dt>
|
|
135
|
+
<dd
|
|
136
|
+
className="text-right font-medium"
|
|
137
|
+
style={{ color: 'rgb(var(--foreground))' }}
|
|
138
|
+
>
|
|
139
|
+
{drawerColumn && isEditableColumn(drawerColumn.id) ? 'Yes' : 'No'}
|
|
140
|
+
</dd>
|
|
141
|
+
</div>
|
|
142
|
+
</dl>
|
|
143
|
+
</div>
|
|
144
|
+
</FloatingDrawer>
|
|
145
|
+
)
|
|
146
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public surface of the @lovett/ui DataGrid primitive.
|
|
3
|
+
*
|
|
4
|
+
* Ported 2026-05-18 from gray-ui-csm
|
|
5
|
+
* https://github.com/Jason-uxui/gray-ui-csm/blob/baf8346ac0c1160282e0b173de1bffc594d9c9c3/components/data-grid/index.ts
|
|
6
|
+
* License: MIT (see packages/ui/licenses/gray-ui-csm-LICENSE.txt)
|
|
7
|
+
* Workspace ADR: ADR-025
|
|
8
|
+
*
|
|
9
|
+
* BRING_AS_IS: re-exports + relative-path adjustments only.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export { DataGrid } from './data-grid'
|
|
13
|
+
export { DataGridColumnOptionsMenu } from './column-options-menu'
|
|
14
|
+
export { DrawerPanel } from './drawer-panel'
|
|
15
|
+
export {
|
|
16
|
+
DataGridDragOverlay,
|
|
17
|
+
DataGridDropIndicator,
|
|
18
|
+
} from './table-overlays'
|
|
19
|
+
|
|
20
|
+
export type {
|
|
21
|
+
DataGridColumn,
|
|
22
|
+
DataGridDrawerPanelProps,
|
|
23
|
+
DataGridProps,
|
|
24
|
+
DataGridRowBase,
|
|
25
|
+
DataGridSortState,
|
|
26
|
+
DataGridToolbarRenderProps,
|
|
27
|
+
DataGridIcon,
|
|
28
|
+
EditingCell,
|
|
29
|
+
} from './types'
|