@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.
Files changed (102) hide show
  1. package/README.md +28 -0
  2. package/package.json +53 -0
  3. package/src/accordion.tsx +153 -0
  4. package/src/atoms.tsx +197 -0
  5. package/src/badge.tsx +91 -0
  6. package/src/brand-logo-tile.tsx +200 -0
  7. package/src/bulk-action-bar.tsx +104 -0
  8. package/src/button-group.tsx +46 -0
  9. package/src/button.tsx +93 -0
  10. package/src/calculator-shell-v2.tsx +380 -0
  11. package/src/calculator-shell.tsx +310 -0
  12. package/src/card.tsx +369 -0
  13. package/src/checkbox.tsx +114 -0
  14. package/src/chip-nav.tsx +347 -0
  15. package/src/choropleth.tsx +62 -0
  16. package/src/code-block.tsx +235 -0
  17. package/src/completion-ring.tsx +119 -0
  18. package/src/copy-field.tsx +169 -0
  19. package/src/data-grid/column-options-menu.tsx +127 -0
  20. package/src/data-grid/data-grid.tsx +391 -0
  21. package/src/data-grid/drawer-panel.tsx +146 -0
  22. package/src/data-grid/index.ts +29 -0
  23. package/src/data-grid/sortable-parts.tsx +215 -0
  24. package/src/data-grid/table-body.tsx +238 -0
  25. package/src/data-grid/table-elements.tsx +135 -0
  26. package/src/data-grid/table-header.tsx +95 -0
  27. package/src/data-grid/table-overlays.tsx +78 -0
  28. package/src/data-grid/table-parts.tsx +15 -0
  29. package/src/data-grid/table-summary-footer.tsx +101 -0
  30. package/src/data-grid/table-view.tsx +176 -0
  31. package/src/data-grid/types.ts +141 -0
  32. package/src/data-grid/use-grid-columns.ts +311 -0
  33. package/src/data-grid/use-grid-editing.ts +154 -0
  34. package/src/device-frame.tsx +274 -0
  35. package/src/dropdown-button.tsx +221 -0
  36. package/src/dropdown-menu.tsx +450 -0
  37. package/src/empty-state.tsx +85 -0
  38. package/src/file-upload/file-drop-zone.tsx +156 -0
  39. package/src/file-upload/file-preview-grid.tsx +39 -0
  40. package/src/file-upload/file-preview-item.tsx +175 -0
  41. package/src/file-upload/file-thumbnail.tsx +98 -0
  42. package/src/file-upload/file-upload-button.tsx +95 -0
  43. package/src/file-upload/index.ts +13 -0
  44. package/src/file-upload/use-file-upload.ts +371 -0
  45. package/src/filter-bar.tsx +125 -0
  46. package/src/filter-dropdown.tsx +289 -0
  47. package/src/floating-drawer.tsx +420 -0
  48. package/src/folder-card.tsx +188 -0
  49. package/src/folder-tree-picker.tsx +511 -0
  50. package/src/format.ts +117 -0
  51. package/src/frame-stack.tsx +73 -0
  52. package/src/hero-form-card.tsx +471 -0
  53. package/src/index.ts +372 -0
  54. package/src/input.tsx +130 -0
  55. package/src/kbd.tsx +41 -0
  56. package/src/lib/layout.ts +22 -0
  57. package/src/lib/utils.ts +6 -0
  58. package/src/markdown.tsx +214 -0
  59. package/src/menu-button.tsx +142 -0
  60. package/src/meta-previews/CreativeMedia.tsx +119 -0
  61. package/src/meta-previews/InstagramExplorePreview.tsx +340 -0
  62. package/src/meta-previews/InstagramFeedCarousel.tsx +245 -0
  63. package/src/meta-previews/InstagramFeedPreview.tsx +259 -0
  64. package/src/meta-previews/MetaFeedCarousel.tsx +228 -0
  65. package/src/meta-previews/MetaFeedPreview.tsx +294 -0
  66. package/src/meta-previews/MetaInstreamPreview.tsx +135 -0
  67. package/src/meta-previews/MetaMessengerPreview.tsx +795 -0
  68. package/src/meta-previews/MetaReelPreview.tsx +750 -0
  69. package/src/meta-previews/MetaRightColumnPreview.tsx +245 -0
  70. package/src/meta-previews/MetaSearchPreview.tsx +113 -0
  71. package/src/meta-previews/MetaStoryPreview.tsx +1132 -0
  72. package/src/meta-previews/ProfileAvatar.tsx +79 -0
  73. package/src/meta-previews/defaults.ts +27 -0
  74. package/src/meta-previews/index.ts +37 -0
  75. package/src/meta-previews/types.ts +219 -0
  76. package/src/metric-card.tsx +117 -0
  77. package/src/modal.tsx +131 -0
  78. package/src/option-tile.tsx +252 -0
  79. package/src/page-hero.tsx +78 -0
  80. package/src/page-shell.tsx +440 -0
  81. package/src/pagination.tsx +86 -0
  82. package/src/pill-button.tsx +103 -0
  83. package/src/profile-section.tsx +389 -0
  84. package/src/progress-bar.tsx +84 -0
  85. package/src/radio.tsx +172 -0
  86. package/src/range-slider.tsx +161 -0
  87. package/src/resizable.tsx +282 -0
  88. package/src/search-bar.tsx +81 -0
  89. package/src/segmented-pill.tsx +143 -0
  90. package/src/shell.tsx +464 -0
  91. package/src/slider.tsx +107 -0
  92. package/src/sortable-table.tsx +198 -0
  93. package/src/sortable.tsx +137 -0
  94. package/src/stat-row.tsx +132 -0
  95. package/src/stats-grid.tsx +38 -0
  96. package/src/step-loader.tsx +141 -0
  97. package/src/styles.css +562 -0
  98. package/src/tabs.tsx +143 -0
  99. package/src/tag-chip-input.tsx +394 -0
  100. package/src/textarea.tsx +39 -0
  101. package/src/toast.tsx +59 -0
  102. 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'