@mostrom/app-shell 0.1.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.
Files changed (142) hide show
  1. package/.claude/ralph-loop.local.md +9 -0
  2. package/README.md +172 -0
  3. package/bin/init.js +269 -0
  4. package/bun.lock +401 -0
  5. package/components.json +28 -0
  6. package/package.json +74 -0
  7. package/scripts/publish-npm.sh +202 -0
  8. package/src/AppShell.tsx +847 -0
  9. package/src/components/PageHeader.tsx +160 -0
  10. package/src/components/data-table/README.md +447 -0
  11. package/src/components/data-table/data-table-preferences.tsx +184 -0
  12. package/src/components/data-table/data-table-toolbar.tsx +118 -0
  13. package/src/components/data-table/data-table.tsx +37 -0
  14. package/src/components/data-table/index.ts +32 -0
  15. package/src/components/global-header/AllServicesButton.tsx +127 -0
  16. package/src/components/global-header/CategoriesButton.tsx +120 -0
  17. package/src/components/global-header/GlobalHeader.tsx +59 -0
  18. package/src/components/global-header/GlobalHeaderSearch.tsx +57 -0
  19. package/src/components/global-header/HeaderUtilities.tsx +243 -0
  20. package/src/components/global-header/ServicesMenu.tsx +246 -0
  21. package/src/components/layout/AppBreadcrumb.tsx +70 -0
  22. package/src/components/layout/AppFlashbar.tsx +95 -0
  23. package/src/components/layout/AppLayout.tsx +271 -0
  24. package/src/components/layout/AppNavigation.tsx +313 -0
  25. package/src/components/layout/AppSidebar.tsx +229 -0
  26. package/src/components/patterns/index.ts +14 -0
  27. package/src/components/patterns/p-alert-5.tsx +19 -0
  28. package/src/components/patterns/p-autocomplete-5.tsx +89 -0
  29. package/src/components/patterns/p-breadcrumb-1.tsx +28 -0
  30. package/src/components/patterns/p-button-42.tsx +37 -0
  31. package/src/components/patterns/p-button-51.tsx +14 -0
  32. package/src/components/patterns/p-button-6.tsx +5 -0
  33. package/src/components/patterns/p-calendar-1.tsx +18 -0
  34. package/src/components/patterns/p-card-1.tsx +33 -0
  35. package/src/components/patterns/p-card-2.tsx +26 -0
  36. package/src/components/patterns/p-card-5.tsx +31 -0
  37. package/src/components/patterns/p-collapsible-7.tsx +121 -0
  38. package/src/components/patterns/p-command-6.tsx +113 -0
  39. package/src/components/patterns/p-dialog-1.tsx +56 -0
  40. package/src/components/patterns/p-dropdown-menu-1.tsx +38 -0
  41. package/src/components/patterns/p-dropdown-menu-11.tsx +122 -0
  42. package/src/components/patterns/p-dropdown-menu-14.tsx +165 -0
  43. package/src/components/patterns/p-dropdown-menu-9.tsx +108 -0
  44. package/src/components/patterns/p-empty-2.tsx +34 -0
  45. package/src/components/patterns/p-file-upload-1.tsx +72 -0
  46. package/src/components/patterns/p-filters-1.tsx +666 -0
  47. package/src/components/patterns/p-frame-2.tsx +26 -0
  48. package/src/components/patterns/p-tabs-2.tsx +129 -0
  49. package/src/components/reui/alert.tsx +92 -0
  50. package/src/components/reui/autocomplete.tsx +343 -0
  51. package/src/components/reui/badge.tsx +87 -0
  52. package/src/components/reui/data-grid/data-grid-column-filter.tsx +165 -0
  53. package/src/components/reui/data-grid/data-grid-column-header.tsx +339 -0
  54. package/src/components/reui/data-grid/data-grid-column-visibility.tsx +55 -0
  55. package/src/components/reui/data-grid/data-grid-pagination.tsx +224 -0
  56. package/src/components/reui/data-grid/data-grid-table-dnd-rows.tsx +260 -0
  57. package/src/components/reui/data-grid/data-grid-table-dnd.tsx +253 -0
  58. package/src/components/reui/data-grid/data-grid-table.tsx +639 -0
  59. package/src/components/reui/data-grid/data-grid.tsx +209 -0
  60. package/src/components/reui/date-selector.tsx +1330 -0
  61. package/src/components/reui/filters.tsx +1869 -0
  62. package/src/components/reui/frame.tsx +134 -0
  63. package/src/components/reui/index.ts +17 -0
  64. package/src/components/reui/timeline.tsx +219 -0
  65. package/src/components/search/Autocomplete.tsx +183 -0
  66. package/src/components/search/AutocompleteClient.tsx +293 -0
  67. package/src/components/search/GlobalSearch.tsx +187 -0
  68. package/src/components/section-drawer/deal-drawer-content.tsx +891 -0
  69. package/src/components/section-drawer/index.ts +19 -0
  70. package/src/components/section-drawer/section-drawer.css +665 -0
  71. package/src/components/section-drawer/section-drawer.tsx +467 -0
  72. package/src/components/sectioned-list-board/README.md +78 -0
  73. package/src/components/sectioned-list-board/board-card-content.tsx +340 -0
  74. package/src/components/sectioned-list-board/date-range-filter.tsx +249 -0
  75. package/src/components/sectioned-list-board/index.ts +19 -0
  76. package/src/components/sectioned-list-board/sectioned-list-board.css +564 -0
  77. package/src/components/sectioned-list-board/sectioned-list-board.tsx +731 -0
  78. package/src/components/sectioned-list-board/sortable-card.tsx +314 -0
  79. package/src/components/sectioned-list-board/sortable-section.tsx +319 -0
  80. package/src/components/sectioned-list-board/types.ts +216 -0
  81. package/src/components/sectioned-list-table/README.md +80 -0
  82. package/src/components/sectioned-list-table/index.ts +14 -0
  83. package/src/components/sectioned-list-table/sectioned-list-table.css +534 -0
  84. package/src/components/sectioned-list-table/sectioned-list-table.tsx +740 -0
  85. package/src/components/sectioned-list-table/sortable-column-header.tsx +120 -0
  86. package/src/components/sectioned-list-table/sortable-row.tsx +420 -0
  87. package/src/components/sectioned-list-table/sortable-section.tsx +251 -0
  88. package/src/components/sectioned-list-table/table-cell-content.tsx +129 -0
  89. package/src/components/sectioned-list-table/types.ts +120 -0
  90. package/src/components/sectioned-list-table/use-column-preferences.ts +103 -0
  91. package/src/components/ui/actions-dropdown.tsx +109 -0
  92. package/src/components/ui/assignee-selector.tsx +209 -0
  93. package/src/components/ui/avatar.tsx +107 -0
  94. package/src/components/ui/breadcrumb.tsx +109 -0
  95. package/src/components/ui/button-group.tsx +83 -0
  96. package/src/components/ui/button.tsx +64 -0
  97. package/src/components/ui/calendar.tsx +220 -0
  98. package/src/components/ui/card.tsx +92 -0
  99. package/src/components/ui/chart.tsx +376 -0
  100. package/src/components/ui/checkbox.tsx +30 -0
  101. package/src/components/ui/collapsible.tsx +33 -0
  102. package/src/components/ui/command.tsx +182 -0
  103. package/src/components/ui/context-menu.tsx +250 -0
  104. package/src/components/ui/create-button-group.tsx +128 -0
  105. package/src/components/ui/dialog.tsx +156 -0
  106. package/src/components/ui/drawer.tsx +133 -0
  107. package/src/components/ui/dropdown-menu.tsx +255 -0
  108. package/src/components/ui/empty.tsx +104 -0
  109. package/src/components/ui/field.tsx +248 -0
  110. package/src/components/ui/form.tsx +165 -0
  111. package/src/components/ui/index.ts +37 -0
  112. package/src/components/ui/input-group.tsx +168 -0
  113. package/src/components/ui/input.tsx +21 -0
  114. package/src/components/ui/kbd.tsx +28 -0
  115. package/src/components/ui/label.tsx +22 -0
  116. package/src/components/ui/navigation-menu.tsx +168 -0
  117. package/src/components/ui/page-header.tsx +80 -0
  118. package/src/components/ui/popover.tsx +87 -0
  119. package/src/components/ui/scroll-area.tsx +56 -0
  120. package/src/components/ui/select.tsx +190 -0
  121. package/src/components/ui/separator.tsx +26 -0
  122. package/src/components/ui/sheet.tsx +141 -0
  123. package/src/components/ui/sidebar.tsx +726 -0
  124. package/src/components/ui/skeleton.tsx +13 -0
  125. package/src/components/ui/sonner.tsx +38 -0
  126. package/src/components/ui/switch.tsx +33 -0
  127. package/src/components/ui/tabs.tsx +91 -0
  128. package/src/components/ui/textarea.tsx +18 -0
  129. package/src/components/ui/toggle-group.tsx +83 -0
  130. package/src/components/ui/toggle.tsx +45 -0
  131. package/src/components/ui/tooltip.tsx +57 -0
  132. package/src/hooks/use-copy-to-clipboard.ts +37 -0
  133. package/src/hooks/use-file-upload.ts +415 -0
  134. package/src/hooks/use-mobile.ts +19 -0
  135. package/src/index.ts +95 -0
  136. package/src/lib/utils.ts +6 -0
  137. package/src/styles.css +1859 -0
  138. package/src/urls.ts +83 -0
  139. package/src/vite.d.ts +22 -0
  140. package/src/vite.js +241 -0
  141. package/tsconfig.base.json +18 -0
  142. package/tsconfig.json +24 -0
@@ -0,0 +1,639 @@
1
+ "use client"
2
+
3
+ import { CSSProperties, Fragment, ReactNode } from "react"
4
+ import { useDataGrid } from "@/components/reui/data-grid/data-grid"
5
+ import {
6
+ Cell,
7
+ Column,
8
+ flexRender,
9
+ Header,
10
+ HeaderGroup,
11
+ Row,
12
+ } from "@tanstack/react-table"
13
+ import { cva } from "class-variance-authority"
14
+
15
+ import { cn } from "@/lib/utils"
16
+ import { Checkbox } from "@/components/ui/checkbox"
17
+
18
+ const headerCellSpacingVariants = cva("", {
19
+ variants: {
20
+ size: {
21
+ dense:
22
+ "px-2 h-8",
23
+ default:
24
+ "px-3",
25
+ },
26
+ },
27
+ defaultVariants: {
28
+ size: "default",
29
+ },
30
+ })
31
+
32
+ const bodyCellSpacingVariants = cva("", {
33
+ variants: {
34
+ size: {
35
+ dense:
36
+ "px-2 py-1.5",
37
+ default:
38
+ "px-3 py-2",
39
+ },
40
+ },
41
+ defaultVariants: {
42
+ size: "default",
43
+ },
44
+ })
45
+
46
+ function getPinningStyles<TData>(column: Column<TData>): CSSProperties {
47
+ const isPinned = column.getIsPinned()
48
+
49
+ return {
50
+ left: isPinned === "left" ? `${column.getStart("left")}px` : undefined,
51
+ right: isPinned === "right" ? `${column.getAfter("right")}px` : undefined,
52
+ position: isPinned ? "sticky" : "relative",
53
+ width: column.getSize(),
54
+ zIndex: isPinned ? 1 : 0,
55
+ }
56
+ }
57
+
58
+ function DataGridTableBase({ children }: { children: ReactNode }) {
59
+ const { props, table } = useDataGrid()
60
+
61
+ return (
62
+ <table
63
+ data-slot="data-grid-table"
64
+ className={cn(
65
+ "text-foreground text-sm w-full min-w-full caption-bottom text-left align-middle font-normal rtl:text-right",
66
+ props.tableLayout?.width === "auto" ? "table-auto" : "table-fixed",
67
+ !props.tableLayout?.columnsResizable && "",
68
+ !props.tableLayout?.columnsDraggable &&
69
+ "border-separate border-spacing-0",
70
+ props.tableClassNames?.base
71
+ )}
72
+ style={
73
+ props.tableLayout?.columnsResizable
74
+ ? { width: table.getTotalSize() }
75
+ : undefined
76
+ }
77
+ >
78
+ {children}
79
+ </table>
80
+ )
81
+ }
82
+
83
+ function DataGridTableHead({ children }: { children: ReactNode }) {
84
+ const { props } = useDataGrid()
85
+
86
+ return (
87
+ <thead
88
+ className={cn(
89
+ props.tableClassNames?.header,
90
+ props.tableLayout?.headerSticky && props.tableClassNames?.headerSticky
91
+ )}
92
+ >
93
+ {children}
94
+ </thead>
95
+ )
96
+ }
97
+
98
+ function DataGridTableHeadRow<TData>({
99
+ children,
100
+ headerGroup,
101
+ }: {
102
+ children: ReactNode
103
+ headerGroup: HeaderGroup<TData>
104
+ }) {
105
+ const { props } = useDataGrid()
106
+
107
+ return (
108
+ <tr
109
+ key={headerGroup.id}
110
+ className={cn(
111
+ "bg-muted/40",
112
+ props.tableLayout?.headerBorder && "[&>th]:border-b",
113
+ props.tableLayout?.cellBorder && "*:last:border-e-0",
114
+ props.tableLayout?.stripped && "bg-transparent",
115
+ props.tableLayout?.headerBackground === false && "bg-transparent",
116
+ props.tableClassNames?.headerRow
117
+ )}
118
+ >
119
+ {children}
120
+ </tr>
121
+ )
122
+ }
123
+
124
+ function DataGridTableHeadRowCell<TData>({
125
+ children,
126
+ header,
127
+ dndRef,
128
+ dndStyle,
129
+ }: {
130
+ children: ReactNode
131
+ header: Header<TData, unknown>
132
+ dndRef?: React.Ref<HTMLTableCellElement>
133
+ dndStyle?: CSSProperties
134
+ }) {
135
+ const { props } = useDataGrid()
136
+
137
+ const { column } = header
138
+ const isPinned = column.getIsPinned()
139
+ const isLastLeftPinned = isPinned === "left" && column.getIsLastColumn("left")
140
+ const isFirstRightPinned =
141
+ isPinned === "right" && column.getIsFirstColumn("right")
142
+ const headerCellSpacing = headerCellSpacingVariants({
143
+ size: props.tableLayout?.dense ? "dense" : "default",
144
+ })
145
+
146
+ return (
147
+ <th
148
+ key={header.id}
149
+ ref={dndRef}
150
+ style={{
151
+ ...((props.tableLayout?.width === "fixed" ||
152
+ props.tableLayout?.columnsResizable) && {
153
+ width: header.getSize(),
154
+ }),
155
+ ...(props.tableLayout?.columnsPinnable &&
156
+ column.getCanPin() &&
157
+ getPinningStyles(column)),
158
+ ...(dndStyle ? dndStyle : null),
159
+ }}
160
+ data-pinned={isPinned || undefined}
161
+ data-last-col={
162
+ isLastLeftPinned ? "left" : isFirstRightPinned ? "right" : undefined
163
+ }
164
+ className={cn(
165
+ "text-secondary-foreground/80 h-9 relative text-left align-middle font-normal rtl:text-right [&:has([role=checkbox])]:pe-0",
166
+ headerCellSpacing,
167
+ props.tableLayout?.cellBorder && "border-e",
168
+ props.tableLayout?.columnsResizable &&
169
+ column.getCanResize() &&
170
+ "truncate",
171
+ props.tableLayout?.columnsPinnable &&
172
+ column.getCanPin() &&
173
+ "[&[data-pinned][data-last-col]]:border-border data-pinned:bg-muted/90 data-pinned:backdrop-blur-xs [&:not([data-pinned]):has(+[data-pinned])_div.cursor-col-resize:last-child]:opacity-0 [&[data-last-col=left]_div.cursor-col-resize:last-child]:opacity-0 [&[data-pinned=left][data-last-col=left]]:border-e! [&[data-pinned=right]:last-child_div.cursor-col-resize:last-child]:opacity-0 [&[data-pinned=right][data-last-col=right]]:border-s!",
174
+ header.column.columnDef.meta?.headerClassName,
175
+ column.getIndex() === 0 ||
176
+ column.getIndex() === header.headerGroup.headers.length - 1
177
+ ? props.tableClassNames?.edgeCell
178
+ : ""
179
+ )}
180
+ >
181
+ {children}
182
+ </th>
183
+ )
184
+ }
185
+
186
+ function DataGridTableHeadRowCellResize<TData>({
187
+ header,
188
+ }: {
189
+ header: Header<TData, unknown>
190
+ }) {
191
+ const { column } = header
192
+
193
+ return (
194
+ <div
195
+ {...{
196
+ onDoubleClick: () => column.resetSize(),
197
+ onMouseDown: header.getResizeHandler(),
198
+ onTouchStart: header.getResizeHandler(),
199
+ className:
200
+ "absolute top-0 h-full w-4 cursor-col-resize user-select-none touch-none -end-2 z-10 flex justify-center before:absolute before:w-px before:inset-y-0 before:bg-border before:-translate-x-px",
201
+ }}
202
+ />
203
+ )
204
+ }
205
+
206
+ function DataGridTableRowSpacer() {
207
+ return <tbody aria-hidden="true" className="h-2"></tbody>
208
+ }
209
+
210
+ function DataGridTableBody({ children }: { children: ReactNode }) {
211
+ const { props } = useDataGrid()
212
+
213
+ return (
214
+ <tbody
215
+ className={cn(
216
+ "[&_tr:last-child]:border-0",
217
+ props.tableLayout?.rowRounded &&
218
+ "[&_td:first-child]:rounded-l-lg",
219
+ props.tableLayout?.rowRounded &&
220
+ "[&_td:last-child]:rounded-r-lg",
221
+ props.tableClassNames?.body
222
+ )}
223
+ >
224
+ {children}
225
+ </tbody>
226
+ )
227
+ }
228
+
229
+ function DataGridTableBodyRowSkeleton({ children }: { children: ReactNode }) {
230
+ const { table, props } = useDataGrid()
231
+
232
+ return (
233
+ <tr
234
+ className={cn(
235
+ "hover:bg-muted/40 data-[state=selected]:bg-muted/50",
236
+ props.onRowClick && "cursor-pointer",
237
+ !props.tableLayout?.stripped &&
238
+ props.tableLayout?.rowBorder &&
239
+ "border-border border-b [&:not(:last-child)>td]:border-b",
240
+ props.tableLayout?.cellBorder && "*:last:border-e-0",
241
+ props.tableLayout?.stripped &&
242
+ "odd:bg-muted/90 odd:hover:bg-muted hover:bg-transparent",
243
+ table.options.enableRowSelection && "*:first:relative",
244
+ props.tableClassNames?.bodyRow
245
+ )}
246
+ >
247
+ {children}
248
+ </tr>
249
+ )
250
+ }
251
+
252
+ function DataGridTableBodyRowSkeletonCell<TData>({
253
+ children,
254
+ column,
255
+ }: {
256
+ children: ReactNode
257
+ column: Column<TData>
258
+ }) {
259
+ const { props, table } = useDataGrid()
260
+ const bodyCellSpacing = bodyCellSpacingVariants({
261
+ size: props.tableLayout?.dense ? "dense" : "default",
262
+ })
263
+
264
+ return (
265
+ <td
266
+ style={
267
+ props.tableLayout?.columnsResizable
268
+ ? { width: column.getSize() }
269
+ : undefined
270
+ }
271
+ className={cn(
272
+ "align-middle",
273
+ bodyCellSpacing,
274
+ props.tableLayout?.cellBorder && "border-e",
275
+ props.tableLayout?.columnsResizable &&
276
+ column.getCanResize() &&
277
+ "truncate",
278
+ column.columnDef.meta?.cellClassName,
279
+ props.tableLayout?.columnsPinnable &&
280
+ column.getCanPin() &&
281
+ '[&[data-pinned][data-last-col]]:border-border data-pinned:bg-background/90 data-pinned:backdrop-blur-xs" [&[data-pinned=left][data-last-col=left]]:border-e! [&[data-pinned=right][data-last-col=right]]:border-s!',
282
+ column.getIndex() === 0 ||
283
+ column.getIndex() === table.getVisibleFlatColumns().length - 1
284
+ ? props.tableClassNames?.edgeCell
285
+ : ""
286
+ )}
287
+ >
288
+ {children}
289
+ </td>
290
+ )
291
+ }
292
+
293
+ function DataGridTableBodyRow<TData>({
294
+ children,
295
+ row,
296
+ dndRef,
297
+ dndStyle,
298
+ }: {
299
+ children: ReactNode
300
+ row: Row<TData>
301
+ dndRef?: React.Ref<HTMLTableRowElement>
302
+ dndStyle?: CSSProperties
303
+ }) {
304
+ const { props, table } = useDataGrid()
305
+
306
+ return (
307
+ <tr
308
+ ref={dndRef}
309
+ style={{ ...(dndStyle ? dndStyle : null) }}
310
+ data-state={
311
+ table.options.enableRowSelection && row.getIsSelected()
312
+ ? "selected"
313
+ : undefined
314
+ }
315
+ onClick={() => props.onRowClick && props.onRowClick(row.original)}
316
+ className={cn(
317
+ "hover:bg-muted/40 data-[state=selected]:bg-muted/50",
318
+ props.onRowClick && "cursor-pointer",
319
+ !props.tableLayout?.stripped &&
320
+ props.tableLayout?.rowBorder &&
321
+ "border-border border-b [&:not(:last-child)>td]:border-b",
322
+ props.tableLayout?.cellBorder && "*:last:border-e-0",
323
+ props.tableLayout?.stripped &&
324
+ "odd:bg-muted/90 odd:hover:bg-muted hover:bg-transparent",
325
+ table.options.enableRowSelection && "*:first:relative",
326
+ props.tableClassNames?.bodyRow
327
+ )}
328
+ >
329
+ {children}
330
+ </tr>
331
+ )
332
+ }
333
+
334
+ function DataGridTableBodyRowExpandded<TData>({ row }: { row: Row<TData> }) {
335
+ const { props, table } = useDataGrid()
336
+
337
+ return (
338
+ <tr
339
+ className={cn(
340
+ props.tableLayout?.rowBorder && "[&:not(:last-child)>td]:border-b"
341
+ )}
342
+ >
343
+ <td colSpan={row.getVisibleCells().length}>
344
+ {table
345
+ .getAllColumns()
346
+ .find((column) => column.columnDef.meta?.expandedContent)
347
+ ?.columnDef.meta?.expandedContent?.(row.original)}
348
+ </td>
349
+ </tr>
350
+ )
351
+ }
352
+
353
+ function DataGridTableBodyRowCell<TData>({
354
+ children,
355
+ cell,
356
+ dndRef,
357
+ dndStyle,
358
+ }: {
359
+ children: ReactNode
360
+ cell: Cell<TData, unknown>
361
+ dndRef?: React.Ref<HTMLTableCellElement>
362
+ dndStyle?: CSSProperties
363
+ }) {
364
+ const { props } = useDataGrid()
365
+
366
+ const { column, row } = cell
367
+ const isPinned = column.getIsPinned()
368
+ const isLastLeftPinned = isPinned === "left" && column.getIsLastColumn("left")
369
+ const isFirstRightPinned =
370
+ isPinned === "right" && column.getIsFirstColumn("right")
371
+ const bodyCellSpacing = bodyCellSpacingVariants({
372
+ size: props.tableLayout?.dense ? "dense" : "default",
373
+ })
374
+
375
+ return (
376
+ <td
377
+ key={cell.id}
378
+ ref={dndRef}
379
+ {...(props.tableLayout?.columnsDraggable && !isPinned ? { cell } : {})}
380
+ style={{
381
+ ...(props.tableLayout?.columnsResizable && {
382
+ width: column.getSize(),
383
+ }),
384
+ ...(props.tableLayout?.columnsPinnable &&
385
+ column.getCanPin() &&
386
+ getPinningStyles(column)),
387
+ ...(dndStyle ? dndStyle : null),
388
+ }}
389
+ data-pinned={isPinned || undefined}
390
+ data-last-col={
391
+ isLastLeftPinned ? "left" : isFirstRightPinned ? "right" : undefined
392
+ }
393
+ className={cn(
394
+ "align-middle",
395
+ bodyCellSpacing,
396
+ props.tableLayout?.cellBorder && "border-e",
397
+ props.tableLayout?.columnsResizable &&
398
+ column.getCanResize() &&
399
+ "truncate",
400
+ cell.column.columnDef.meta?.cellClassName,
401
+ props.tableLayout?.columnsPinnable &&
402
+ column.getCanPin() &&
403
+ '[&[data-pinned][data-last-col]]:border-border data-pinned:bg-background/90 data-pinned:backdrop-blur-xs" [&[data-pinned=left][data-last-col=left]]:border-e! [&[data-pinned=right][data-last-col=right]]:border-s!',
404
+ column.getIndex() === 0 ||
405
+ column.getIndex() === row.getVisibleCells().length - 1
406
+ ? props.tableClassNames?.edgeCell
407
+ : ""
408
+ )}
409
+ >
410
+ {children}
411
+ </td>
412
+ )
413
+ }
414
+
415
+ function DataGridTableEmpty() {
416
+ const { table, props } = useDataGrid()
417
+ const totalColumns = table.getAllColumns().length
418
+
419
+ return (
420
+ <tr>
421
+ <td
422
+ colSpan={totalColumns}
423
+ className="text-muted-foreground text-sm py-6 text-center"
424
+ >
425
+ {props.emptyMessage || "No data available"}
426
+ </td>
427
+ </tr>
428
+ )
429
+ }
430
+
431
+ function DataGridTableLoader() {
432
+ const { props } = useDataGrid()
433
+
434
+ return (
435
+ <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
436
+ <div className="text-muted-foreground bg-card rounded-lg text-sm flex items-center gap-2 border px-4 py-2 leading-none font-medium">
437
+ <svg
438
+ className="text-muted-foreground -ml-1 h-5 w-5 animate-spin"
439
+ xmlns="http://www.w3.org/2000/svg"
440
+ fill="none"
441
+ viewBox="0 0 24 24"
442
+ >
443
+ <circle
444
+ className="opacity-25"
445
+ cx="12"
446
+ cy="12"
447
+ r="10"
448
+ stroke="currentColor"
449
+ strokeWidth="3"
450
+ ></circle>
451
+ <path
452
+ className="opacity-75"
453
+ fill="currentColor"
454
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
455
+ ></path>
456
+ </svg>
457
+ {props.loadingMessage || "Loading..."}
458
+ </div>
459
+ </div>
460
+ )
461
+ }
462
+
463
+ function DataGridTableRowSelect<TData>({ row }: { row: Row<TData> }) {
464
+ return (
465
+ <>
466
+ <div
467
+ className={cn(
468
+ "bg-primary absolute start-0 top-0 bottom-0 hidden w-[2px]",
469
+ row.getIsSelected() && "block"
470
+ )}
471
+ ></div>
472
+ <Checkbox
473
+ checked={row.getIsSelected()}
474
+ onCheckedChange={(value) => row.toggleSelected(!!value)}
475
+ aria-label="Select row"
476
+ className="align-[inherit]"
477
+ />
478
+ </>
479
+ )
480
+ }
481
+
482
+ function DataGridTableRowSelectAll() {
483
+ const { table, recordCount, isLoading } = useDataGrid()
484
+
485
+ const isAllSelected = table.getIsAllPageRowsSelected()
486
+ const isSomeSelected = table.getIsSomePageRowsSelected()
487
+
488
+ return (
489
+ <Checkbox
490
+ checked={
491
+ isSomeSelected && !isAllSelected ? "indeterminate" : isAllSelected
492
+ }
493
+ disabled={isLoading || recordCount === 0}
494
+ onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
495
+ aria-label="Select all"
496
+ className="align-[inherit]"
497
+ />
498
+ )
499
+ }
500
+
501
+ function DataGridTable<TData>() {
502
+ const { table, isLoading, props } = useDataGrid()
503
+ const pagination = table.getState().pagination
504
+
505
+ return (
506
+ <DataGridTableBase>
507
+ <DataGridTableHead>
508
+ {table
509
+ .getHeaderGroups()
510
+ .map((headerGroup: HeaderGroup<TData>, index) => {
511
+ return (
512
+ <DataGridTableHeadRow headerGroup={headerGroup} key={index}>
513
+ {headerGroup.headers.map((header, index) => {
514
+ const { column } = header
515
+
516
+ return (
517
+ <DataGridTableHeadRowCell header={header} key={index}>
518
+ {header.isPlaceholder
519
+ ? null
520
+ : flexRender(
521
+ header.column.columnDef.header,
522
+ header.getContext()
523
+ )}
524
+ {props.tableLayout?.columnsResizable &&
525
+ column.getCanResize() && (
526
+ <DataGridTableHeadRowCellResize header={header} />
527
+ )}
528
+ </DataGridTableHeadRowCell>
529
+ )
530
+ })}
531
+ </DataGridTableHeadRow>
532
+ )
533
+ })}
534
+ </DataGridTableHead>
535
+
536
+ {(props.tableLayout?.stripped || !props.tableLayout?.rowBorder) && (
537
+ <DataGridTableRowSpacer />
538
+ )}
539
+
540
+ <DataGridTableBody>
541
+ {isLoading &&
542
+ props.loadingMode === "skeleton" &&
543
+ pagination?.pageSize ? (
544
+ // Show skeleton loading immediately
545
+ Array.from({ length: pagination.pageSize }).map((_, rowIndex) => (
546
+ <DataGridTableBodyRowSkeleton key={rowIndex}>
547
+ {table.getVisibleFlatColumns().map((column, colIndex) => {
548
+ return (
549
+ <DataGridTableBodyRowSkeletonCell
550
+ column={column}
551
+ key={colIndex}
552
+ >
553
+ {column.columnDef.meta?.skeleton}
554
+ </DataGridTableBodyRowSkeletonCell>
555
+ )
556
+ })}
557
+ </DataGridTableBodyRowSkeleton>
558
+ ))
559
+ ) : isLoading && props.loadingMode === "spinner" ? (
560
+ // Show spinner loading immediately
561
+ <tr>
562
+ <td colSpan={table.getVisibleFlatColumns().length} className="p-8">
563
+ <div className="flex items-center justify-center">
564
+ <svg
565
+ className="text-muted-foreground mr-3 -ml-1 h-5 w-5 animate-spin"
566
+ xmlns="http://www.w3.org/2000/svg"
567
+ fill="none"
568
+ viewBox="0 0 24 24"
569
+ >
570
+ <circle
571
+ className="opacity-25"
572
+ cx="12"
573
+ cy="12"
574
+ r="10"
575
+ stroke="currentColor"
576
+ strokeWidth="4"
577
+ ></circle>
578
+ <path
579
+ className="opacity-75"
580
+ fill="currentColor"
581
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
582
+ ></path>
583
+ </svg>
584
+ {props.loadingMessage || "Loading..."}
585
+ </div>
586
+ </td>
587
+ </tr>
588
+ ) : table.getRowModel().rows.length ? (
589
+ // Show actual data when not loading
590
+ table.getRowModel().rows.map((row: Row<TData>, index) => {
591
+ return (
592
+ <Fragment key={row.id}>
593
+ <DataGridTableBodyRow row={row} key={index}>
594
+ {row
595
+ .getVisibleCells()
596
+ .map((cell: Cell<TData, unknown>, colIndex) => {
597
+ return (
598
+ <DataGridTableBodyRowCell cell={cell} key={colIndex}>
599
+ {flexRender(
600
+ cell.column.columnDef.cell,
601
+ cell.getContext()
602
+ )}
603
+ </DataGridTableBodyRowCell>
604
+ )
605
+ })}
606
+ </DataGridTableBodyRow>
607
+ {row.getIsExpanded() && (
608
+ <DataGridTableBodyRowExpandded row={row} />
609
+ )}
610
+ </Fragment>
611
+ )
612
+ })
613
+ ) : (
614
+ <DataGridTableEmpty />
615
+ )}
616
+ </DataGridTableBody>
617
+ </DataGridTableBase>
618
+ )
619
+ }
620
+
621
+ export {
622
+ DataGridTable,
623
+ DataGridTableBase,
624
+ DataGridTableBody,
625
+ DataGridTableBodyRow,
626
+ DataGridTableBodyRowCell,
627
+ DataGridTableBodyRowExpandded,
628
+ DataGridTableBodyRowSkeleton,
629
+ DataGridTableBodyRowSkeletonCell,
630
+ DataGridTableEmpty,
631
+ DataGridTableHead,
632
+ DataGridTableHeadRow,
633
+ DataGridTableHeadRowCell,
634
+ DataGridTableHeadRowCellResize,
635
+ DataGridTableLoader,
636
+ DataGridTableRowSelect,
637
+ DataGridTableRowSelectAll,
638
+ DataGridTableRowSpacer,
639
+ }