Docker images / prepare-release (push) Successful in 5s
Docker images / backend-image (push) Successful in 2m37s
Docker images / frontend-image (push) Successful in 2m22s
Docker images / updater-image (push) Successful in 38s
Docker images / notify-webhook (push) Has been skipped
Docker images / publish-release (push) Successful in 8s
1433 lines
40 KiB
TypeScript
1433 lines
40 KiB
TypeScript
"use client"
|
|
|
|
import {
|
|
CSSProperties,
|
|
Fragment,
|
|
memo,
|
|
MouseEvent as ReactMouseEvent,
|
|
ReactNode,
|
|
TouchEvent as ReactTouchEvent,
|
|
Ref,
|
|
useCallback,
|
|
useEffect,
|
|
useMemo,
|
|
useState,
|
|
} from "react"
|
|
import { useDataGrid } from "@/components/reui/data-grid/data-grid"
|
|
import {
|
|
Cell,
|
|
Column,
|
|
flexRender,
|
|
Header,
|
|
HeaderGroup,
|
|
Row,
|
|
Table,
|
|
} from "@tanstack/react-table"
|
|
import { cva } from "class-variance-authority"
|
|
|
|
import { cn } from "@/lib/utils"
|
|
import { Checkbox } from "@/components/ui/checkbox"
|
|
import { Spinner } from "@/components/ui/spinner"
|
|
|
|
const headerCellSpacingVariants = cva("", {
|
|
variants: {
|
|
size: {
|
|
dense:
|
|
"px-2 h-8",
|
|
default:
|
|
"px-3",
|
|
},
|
|
},
|
|
defaultVariants: {
|
|
size: "default",
|
|
},
|
|
})
|
|
|
|
const bodyCellSpacingVariants = cva("", {
|
|
variants: {
|
|
size: {
|
|
dense:
|
|
"px-2 py-1.5",
|
|
default:
|
|
"px-3 py-2",
|
|
},
|
|
},
|
|
defaultVariants: {
|
|
size: "default",
|
|
},
|
|
})
|
|
|
|
const footerCellSpacingVariants = cva("", {
|
|
variants: {
|
|
size: {
|
|
dense:
|
|
"px-2 py-1.5",
|
|
default:
|
|
"px-3 py-2",
|
|
},
|
|
},
|
|
defaultVariants: {
|
|
size: "default",
|
|
},
|
|
})
|
|
|
|
function getPinningStyles<TData>(column: Column<TData>): CSSProperties {
|
|
const isPinned = column.getIsPinned()
|
|
|
|
return {
|
|
left: isPinned === "left" ? `${column.getStart("left")}px` : undefined,
|
|
right: isPinned === "right" ? `${column.getAfter("right")}px` : undefined,
|
|
position: isPinned ? "sticky" : "relative",
|
|
width: column.getSize(),
|
|
zIndex: isPinned ? 1 : 0,
|
|
}
|
|
}
|
|
|
|
function assignRef<T>(ref: Ref<T> | undefined, value: T | null) {
|
|
if (!ref) return
|
|
|
|
if (typeof ref === "function") {
|
|
ref(value)
|
|
return
|
|
}
|
|
|
|
;(ref as { current: T | null }).current = value
|
|
}
|
|
|
|
type DataGridResizeStartEvent =
|
|
| ReactMouseEvent<HTMLDivElement>
|
|
| ReactTouchEvent<HTMLDivElement>
|
|
|
|
type DataGridResizeDocumentEvent = globalThis.MouseEvent | globalThis.TouchEvent
|
|
|
|
function isDataGridTouchEvent(
|
|
event: DataGridResizeStartEvent | DataGridResizeDocumentEvent
|
|
): event is ReactTouchEvent<HTMLDivElement> | globalThis.TouchEvent {
|
|
return "touches" in event
|
|
}
|
|
|
|
function getDataGridResizeEventClientX(
|
|
event: DataGridResizeStartEvent | DataGridResizeDocumentEvent
|
|
) {
|
|
if (isDataGridTouchEvent(event)) {
|
|
return event.touches[0]?.clientX ?? event.changedTouches[0]?.clientX
|
|
}
|
|
|
|
return event.clientX
|
|
}
|
|
|
|
function startDataGridColumnResizeOnEnd<TData>(
|
|
event: DataGridResizeStartEvent,
|
|
header: Header<TData, unknown>,
|
|
table: Table<TData>
|
|
) {
|
|
const column = table.getColumn(header.column.id)
|
|
|
|
if (!column || !column.getCanResize()) return
|
|
if (isDataGridTouchEvent(event) && event.touches.length > 1) return
|
|
|
|
event.persist?.()
|
|
|
|
const ownerDocument = event.currentTarget.ownerDocument
|
|
const previousBodyCursor = ownerDocument.body.style.cursor
|
|
const previousDocumentCursor = ownerDocument.documentElement.style.cursor
|
|
const startSize = header.getSize()
|
|
const dragStartClientX = getDataGridResizeEventClientX(event)
|
|
const headerCell = event.currentTarget.closest("th")
|
|
const headerRect = headerCell?.getBoundingClientRect()
|
|
const startOffset =
|
|
headerRect &&
|
|
Number.isFinite(
|
|
table.options.columnResizeDirection === "rtl"
|
|
? headerRect.left
|
|
: headerRect.right
|
|
)
|
|
? table.options.columnResizeDirection === "rtl"
|
|
? headerRect.left
|
|
: headerRect.right
|
|
: dragStartClientX
|
|
|
|
if (typeof dragStartClientX !== "number" || typeof startOffset !== "number") {
|
|
return
|
|
}
|
|
|
|
ownerDocument.body.style.cursor = "col-resize"
|
|
ownerDocument.documentElement.style.cursor = "col-resize"
|
|
|
|
const columnSizingStart = header
|
|
.getLeafHeaders()
|
|
.map(
|
|
(leafHeader) =>
|
|
[leafHeader.column.id, leafHeader.column.getSize()] as [string, number]
|
|
)
|
|
const directionMultiplier =
|
|
table.options.columnResizeDirection === "rtl" ? -1 : 1
|
|
|
|
const updateOffset = (clientXPos?: number, commit = false) => {
|
|
if (typeof clientXPos !== "number") return
|
|
|
|
let nextColumnSizing: Record<string, number> = {}
|
|
const deltaOffset = (clientXPos - dragStartClientX) * directionMultiplier
|
|
const deltaPercentage = Math.max(deltaOffset / startSize, -0.999999)
|
|
|
|
columnSizingStart.forEach(([columnId, headerSize]) => {
|
|
nextColumnSizing[columnId] =
|
|
Math.round(
|
|
Math.max(headerSize + headerSize * deltaPercentage, 0) * 100
|
|
) / 100
|
|
})
|
|
|
|
table.setColumnSizingInfo((old) => ({
|
|
...old,
|
|
startOffset,
|
|
startSize,
|
|
deltaOffset,
|
|
deltaPercentage,
|
|
columnSizingStart,
|
|
isResizingColumn: column.id,
|
|
}))
|
|
|
|
if (commit) {
|
|
table.setColumnSizing((old) => ({
|
|
...old,
|
|
...nextColumnSizing,
|
|
}))
|
|
}
|
|
}
|
|
|
|
const endResize = (clientXPos?: number) => {
|
|
updateOffset(clientXPos, true)
|
|
table.setColumnSizingInfo((old) => ({
|
|
...old,
|
|
isResizingColumn: false,
|
|
startOffset: null,
|
|
startSize: null,
|
|
deltaOffset: null,
|
|
deltaPercentage: null,
|
|
columnSizingStart: [],
|
|
}))
|
|
ownerDocument.body.style.cursor = previousBodyCursor
|
|
ownerDocument.documentElement.style.cursor = previousDocumentCursor
|
|
}
|
|
|
|
const mouseMoveHandler = (moveEvent: globalThis.MouseEvent) => {
|
|
updateOffset(moveEvent.clientX)
|
|
}
|
|
const mouseUpHandler = (upEvent: globalThis.MouseEvent) => {
|
|
ownerDocument.removeEventListener("mousemove", mouseMoveHandler)
|
|
ownerDocument.removeEventListener("mouseup", mouseUpHandler)
|
|
endResize(upEvent.clientX)
|
|
}
|
|
const touchMoveHandler = (moveEvent: globalThis.TouchEvent) => {
|
|
if (moveEvent.cancelable) {
|
|
moveEvent.preventDefault()
|
|
moveEvent.stopPropagation()
|
|
}
|
|
|
|
updateOffset(getDataGridResizeEventClientX(moveEvent))
|
|
}
|
|
const touchEndHandler = (endEvent: globalThis.TouchEvent) => {
|
|
ownerDocument.removeEventListener("touchmove", touchMoveHandler)
|
|
ownerDocument.removeEventListener("touchend", touchEndHandler)
|
|
|
|
if (endEvent.cancelable) {
|
|
endEvent.preventDefault()
|
|
endEvent.stopPropagation()
|
|
}
|
|
|
|
endResize(getDataGridResizeEventClientX(endEvent))
|
|
}
|
|
|
|
const passiveIfSupported = { passive: false } as const
|
|
|
|
if (isDataGridTouchEvent(event)) {
|
|
ownerDocument.addEventListener(
|
|
"touchmove",
|
|
touchMoveHandler,
|
|
passiveIfSupported
|
|
)
|
|
ownerDocument.addEventListener(
|
|
"touchend",
|
|
touchEndHandler,
|
|
passiveIfSupported
|
|
)
|
|
} else {
|
|
ownerDocument.addEventListener(
|
|
"mousemove",
|
|
mouseMoveHandler,
|
|
passiveIfSupported
|
|
)
|
|
ownerDocument.addEventListener(
|
|
"mouseup",
|
|
mouseUpHandler,
|
|
passiveIfSupported
|
|
)
|
|
}
|
|
|
|
table.setColumnSizingInfo((old) => ({
|
|
...old,
|
|
startOffset,
|
|
startSize,
|
|
deltaOffset: 0,
|
|
deltaPercentage: 0,
|
|
columnSizingStart,
|
|
isResizingColumn: column.id,
|
|
}))
|
|
}
|
|
|
|
type DataGridTablePinnedBoundary = "top" | "bottom"
|
|
|
|
function getDataGridTableRowSections<TData>(
|
|
table: Table<TData>,
|
|
rowsPinnable?: boolean
|
|
) {
|
|
if (!rowsPinnable) {
|
|
return {
|
|
topRows: [] as Row<TData>[],
|
|
centerRows: table.getRowModel().rows as Row<TData>[],
|
|
bottomRows: [] as Row<TData>[],
|
|
}
|
|
}
|
|
|
|
return {
|
|
topRows: table.getTopRows() as Row<TData>[],
|
|
centerRows: table.getCenterRows() as Row<TData>[],
|
|
bottomRows: table.getBottomRows() as Row<TData>[],
|
|
}
|
|
}
|
|
|
|
function getDataGridTableResolvedRows<TData>(
|
|
table: Table<TData>,
|
|
rowsPinnable?: boolean
|
|
) {
|
|
const { topRows, centerRows, bottomRows } = getDataGridTableRowSections(
|
|
table,
|
|
rowsPinnable
|
|
)
|
|
const resolvedRows: Array<{
|
|
row: Row<TData>
|
|
pinnedBoundary?: DataGridTablePinnedBoundary
|
|
}> = []
|
|
|
|
topRows.forEach((row, index) => {
|
|
resolvedRows.push({
|
|
row,
|
|
pinnedBoundary:
|
|
index === topRows.length - 1 &&
|
|
(centerRows.length > 0 || bottomRows.length > 0)
|
|
? "top"
|
|
: undefined,
|
|
})
|
|
})
|
|
|
|
centerRows.forEach((row) => {
|
|
resolvedRows.push({ row })
|
|
})
|
|
|
|
bottomRows.forEach((row, index) => {
|
|
resolvedRows.push({
|
|
row,
|
|
pinnedBoundary:
|
|
index === 0 && (centerRows.length > 0 || topRows.length > 0)
|
|
? "bottom"
|
|
: undefined,
|
|
})
|
|
})
|
|
|
|
return resolvedRows
|
|
}
|
|
|
|
function DataGridTableFillCol() {
|
|
const { props } = useDataGrid()
|
|
|
|
if (!props.tableLayout?.columnsResizable) return null
|
|
|
|
return (
|
|
<col
|
|
data-slot="data-grid-table-fill-col"
|
|
style={{ width: "var(--data-grid-fill-size, 0px)" }}
|
|
/>
|
|
)
|
|
}
|
|
|
|
function DataGridTableFillHeadCell() {
|
|
const { props } = useDataGrid()
|
|
|
|
if (!props.tableLayout?.columnsResizable) return null
|
|
|
|
return (
|
|
<th
|
|
aria-hidden="true"
|
|
data-slot="data-grid-table-fill-head-cell"
|
|
style={{ width: "var(--data-grid-fill-size, 0px)" }}
|
|
className="p-0"
|
|
/>
|
|
)
|
|
}
|
|
|
|
function DataGridTableFillBodyCell() {
|
|
const { props } = useDataGrid()
|
|
|
|
if (!props.tableLayout?.columnsResizable) return null
|
|
|
|
return (
|
|
<td
|
|
aria-hidden="true"
|
|
data-slot="data-grid-table-fill-body-cell"
|
|
style={{ width: "var(--data-grid-fill-size, 0px)" }}
|
|
className="p-0"
|
|
/>
|
|
)
|
|
}
|
|
|
|
function DataGridTableFillFootCell() {
|
|
const { props } = useDataGrid()
|
|
|
|
if (!props.tableLayout?.columnsResizable) return null
|
|
|
|
return (
|
|
<td
|
|
aria-hidden="true"
|
|
data-slot="data-grid-table-fill-foot-cell"
|
|
style={{ width: "var(--data-grid-fill-size, 0px)" }}
|
|
className="border-t p-0"
|
|
/>
|
|
)
|
|
}
|
|
|
|
function DataGridTableBase({ children }: { children: ReactNode }) {
|
|
const { props, table } = useDataGrid()
|
|
const visibleColumns = table.getVisibleLeafColumns()
|
|
|
|
/**
|
|
* Compute column widths as CSS custom properties once upfront (memoized).
|
|
* Cells reference these via calc(var(--col-X-size) * 1px) so the browser
|
|
* handles width propagation without per-cell getSize() calls or React
|
|
* re-renders of the body.
|
|
*/
|
|
const columnSizeVars = useMemo(() => {
|
|
if (!props.tableLayout?.columnsResizable) return undefined
|
|
const headers = table.getFlatHeaders()
|
|
const colSizes: Record<string, number> = {}
|
|
for (let i = 0; i < headers.length; i++) {
|
|
const header = headers[i]!
|
|
colSizes[`--header-${header.id}-size`] = header.getSize()
|
|
colSizes[`--col-${header.column.id}-size`] = header.column.getSize()
|
|
}
|
|
return colSizes
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [
|
|
props.tableLayout?.columnsResizable,
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
table.getState().columnSizing,
|
|
])
|
|
|
|
return (
|
|
<table
|
|
data-slot="data-grid-table"
|
|
className={cn(
|
|
"text-foreground text-sm caption-bottom text-left align-middle font-normal rtl:text-right",
|
|
props.tableLayout?.columnsResizable ? "min-w-0" : "w-full min-w-full",
|
|
props.tableLayout?.width === "auto" ? "table-auto" : "table-fixed",
|
|
!props.tableLayout?.columnsResizable && "",
|
|
!props.tableLayout?.columnsDraggable &&
|
|
"border-separate border-spacing-0",
|
|
props.tableClassNames?.base
|
|
)}
|
|
style={
|
|
props.tableLayout?.columnsResizable
|
|
? {
|
|
...columnSizeVars,
|
|
width: `calc(${table.getTotalSize()}px + var(--data-grid-fill-size, 0px))`,
|
|
}
|
|
: undefined
|
|
}
|
|
>
|
|
<colgroup>
|
|
{visibleColumns.map((column) => (
|
|
<col
|
|
key={column.id}
|
|
style={
|
|
props.tableLayout?.columnsResizable
|
|
? { width: `calc(var(--col-${column.id}-size) * 1px)` }
|
|
: props.tableLayout?.width === "fixed"
|
|
? { width: column.getSize() }
|
|
: undefined
|
|
}
|
|
/>
|
|
))}
|
|
<DataGridTableFillCol />
|
|
</colgroup>
|
|
{children}
|
|
</table>
|
|
)
|
|
}
|
|
|
|
function DataGridTableViewport({
|
|
children,
|
|
className,
|
|
viewportRef,
|
|
style,
|
|
}: {
|
|
children: ReactNode
|
|
className?: string
|
|
viewportRef?: Ref<HTMLDivElement>
|
|
style?: CSSProperties
|
|
}) {
|
|
const { props, table } = useDataGrid()
|
|
const [viewportElement, setViewportElement] = useState<HTMLDivElement | null>(
|
|
null
|
|
)
|
|
const [containerWidth, setContainerWidth] = useState(0)
|
|
const handleViewportRef = useCallback(
|
|
(node: HTMLDivElement | null) => {
|
|
setViewportElement(node)
|
|
assignRef(viewportRef, node)
|
|
},
|
|
[viewportRef]
|
|
)
|
|
const fillWidth =
|
|
props.tableLayout?.columnsResizable && containerWidth > 0
|
|
? Math.max(0, containerWidth - table.getTotalSize())
|
|
: 0
|
|
|
|
useEffect(() => {
|
|
if (!viewportElement || !props.tableLayout?.columnsResizable) {
|
|
setContainerWidth(0)
|
|
return
|
|
}
|
|
|
|
const scrollViewport =
|
|
(viewportElement.closest(
|
|
'[data-slot="scroll-area-viewport"]'
|
|
) as HTMLElement | null) ?? viewportElement.parentElement
|
|
const measurementTarget = scrollViewport ?? viewportElement
|
|
|
|
const syncContainerWidth = () => {
|
|
setContainerWidth(measurementTarget.clientWidth)
|
|
}
|
|
|
|
syncContainerWidth()
|
|
|
|
if (typeof ResizeObserver === "undefined") return
|
|
|
|
const observer = new ResizeObserver(syncContainerWidth)
|
|
observer.observe(measurementTarget)
|
|
|
|
return () => {
|
|
observer.disconnect()
|
|
}
|
|
}, [props.tableLayout?.columnsResizable, viewportElement])
|
|
|
|
return (
|
|
<div
|
|
data-slot="data-grid-table-viewport"
|
|
ref={handleViewportRef}
|
|
className={cn("relative min-w-full align-top", className)}
|
|
style={{
|
|
...(props.tableLayout?.columnsResizable
|
|
? {
|
|
width: `calc(${table.getTotalSize()}px + var(--data-grid-fill-size, 0px))`,
|
|
["--data-grid-fill-size" as string]: `${fillWidth}px`,
|
|
}
|
|
: undefined),
|
|
...style,
|
|
}}
|
|
>
|
|
{children}
|
|
<DataGridTableResizeIndicator viewportElement={viewportElement} />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function DataGridTableHead({ children }: { children: ReactNode }) {
|
|
const { props } = useDataGrid()
|
|
|
|
return (
|
|
<thead
|
|
className={cn(
|
|
props.tableClassNames?.header,
|
|
props.tableLayout?.headerSticky && props.tableClassNames?.headerSticky
|
|
)}
|
|
>
|
|
{children}
|
|
</thead>
|
|
)
|
|
}
|
|
|
|
function DataGridTableHeadRow<TData>({
|
|
children,
|
|
headerGroup,
|
|
}: {
|
|
children: ReactNode
|
|
headerGroup: HeaderGroup<TData>
|
|
}) {
|
|
const { props } = useDataGrid()
|
|
|
|
return (
|
|
<tr
|
|
key={headerGroup.id}
|
|
className={cn(
|
|
"bg-muted/40",
|
|
props.tableLayout?.headerBorder && "[&>th]:border-b",
|
|
props.tableLayout?.cellBorder && "*:last:border-e-0",
|
|
props.tableLayout?.stripped && "bg-transparent",
|
|
props.tableLayout?.headerBackground === false && "bg-transparent",
|
|
props.tableClassNames?.headerRow
|
|
)}
|
|
>
|
|
{children}
|
|
<DataGridTableFillHeadCell />
|
|
</tr>
|
|
)
|
|
}
|
|
|
|
function DataGridTableHeadRowCell<TData>({
|
|
children,
|
|
header,
|
|
dndRef,
|
|
dndStyle,
|
|
}: {
|
|
children: ReactNode
|
|
header: Header<TData, unknown>
|
|
dndRef?: React.Ref<HTMLTableCellElement>
|
|
dndStyle?: CSSProperties
|
|
}) {
|
|
const { props } = useDataGrid()
|
|
|
|
const { column } = header
|
|
const isPinned = column.getIsPinned()
|
|
const isLastLeftPinned = isPinned === "left" && column.getIsLastColumn("left")
|
|
const isFirstRightPinned =
|
|
isPinned === "right" && column.getIsFirstColumn("right")
|
|
const isLastVisibleColumn =
|
|
column.getIndex() ===
|
|
header.getContext().table.getVisibleLeafColumns().length - 1
|
|
const headerCellSpacing = headerCellSpacingVariants({
|
|
size: props.tableLayout?.dense ? "dense" : "default",
|
|
})
|
|
|
|
return (
|
|
<th
|
|
key={header.id}
|
|
ref={dndRef}
|
|
style={{
|
|
...(props.tableLayout?.width === "fixed" &&
|
|
!props.tableLayout?.columnsResizable && {
|
|
width: header.getSize(),
|
|
}),
|
|
...(props.tableLayout?.columnsPinnable &&
|
|
column.getCanPin() &&
|
|
getPinningStyles(column)),
|
|
...(props.tableLayout?.columnsResizable && {
|
|
width: `calc(var(--header-${header.id}-size) * 1px)`,
|
|
}),
|
|
...(dndStyle ? dndStyle : null),
|
|
}}
|
|
data-pinned={isPinned || undefined}
|
|
data-last-col={
|
|
isLastLeftPinned ? "left" : isFirstRightPinned ? "right" : undefined
|
|
}
|
|
className={cn(
|
|
"text-secondary-foreground/80 h-9 relative text-left align-middle font-normal rtl:text-right [&:has([role=checkbox])]:pe-0",
|
|
headerCellSpacing,
|
|
props.tableLayout?.cellBorder && "border-e",
|
|
props.tableLayout?.columnsResizable &&
|
|
column.getCanResize() &&
|
|
"overflow-visible",
|
|
props.tableLayout?.columnsResizable &&
|
|
column.getCanResize() &&
|
|
isLastVisibleColumn &&
|
|
"pe-8",
|
|
props.tableLayout?.columnsPinnable &&
|
|
column.getCanPin() &&
|
|
"[&[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!",
|
|
header.column.columnDef.meta?.headerClassName,
|
|
column.getIndex() === 0 ||
|
|
column.getIndex() === header.headerGroup.headers.length - 1
|
|
? props.tableClassNames?.edgeCell
|
|
: ""
|
|
)}
|
|
>
|
|
{children}
|
|
</th>
|
|
)
|
|
}
|
|
|
|
function DataGridTableHeadRowCellResize<TData>({
|
|
header,
|
|
}: {
|
|
header: Header<TData, unknown>
|
|
}) {
|
|
const { props, table } = useDataGrid()
|
|
const { column } = header
|
|
const isLastVisibleColumn =
|
|
column.getIndex() ===
|
|
header.getContext().table.getVisibleLeafColumns().length - 1
|
|
const isResizeModeOnEnd =
|
|
(props.tableLayout?.columnsResizeMode ?? table.options.columnResizeMode) ===
|
|
"onEnd"
|
|
|
|
const handleMouseDown = (event: ReactMouseEvent<HTMLDivElement>) => {
|
|
event.preventDefault()
|
|
event.stopPropagation()
|
|
|
|
if (isResizeModeOnEnd) {
|
|
startDataGridColumnResizeOnEnd(event, header, table)
|
|
return
|
|
}
|
|
|
|
header.getResizeHandler()(event)
|
|
}
|
|
|
|
const handleTouchStart = (event: ReactTouchEvent<HTMLDivElement>) => {
|
|
event.preventDefault()
|
|
event.stopPropagation()
|
|
|
|
if (isResizeModeOnEnd) {
|
|
startDataGridColumnResizeOnEnd(event, header, table)
|
|
return
|
|
}
|
|
|
|
header.getResizeHandler()(event)
|
|
}
|
|
|
|
return (
|
|
<div
|
|
{...{
|
|
onDoubleClick: () => column.resetSize(),
|
|
onMouseDown: handleMouseDown,
|
|
onTouchStart: handleTouchStart,
|
|
className: cn(
|
|
"absolute top-0 h-full cursor-col-resize user-select-none touch-none z-10 flex",
|
|
isLastVisibleColumn
|
|
? "end-0 w-5 justify-end before:hidden"
|
|
: "-end-2 w-5 justify-center before:absolute before:inset-y-0 before:w-px before:-translate-x-px before:bg-border",
|
|
column.getIsResizing() &&
|
|
(isResizeModeOnEnd
|
|
? "opacity-100"
|
|
: isLastVisibleColumn
|
|
? "before:absolute before:end-0 before:block before:inset-y-0 before:w-0.5 before:bg-primary opacity-100"
|
|
: "before:block before:bg-primary before:w-0.5 opacity-100")
|
|
),
|
|
}}
|
|
/>
|
|
)
|
|
}
|
|
|
|
function DataGridTableResizeIndicator({
|
|
viewportElement,
|
|
}: {
|
|
viewportElement: HTMLDivElement | null
|
|
}) {
|
|
const { props, table } = useDataGrid()
|
|
const columnSizingInfo = table.getState().columnSizingInfo
|
|
const resizingColumnId = columnSizingInfo.isResizingColumn
|
|
const resizeMode =
|
|
props.tableLayout?.columnsResizeMode ?? table.options.columnResizeMode
|
|
|
|
if (
|
|
!props.tableLayout?.columnsResizable ||
|
|
resizeMode !== "onEnd" ||
|
|
!resizingColumnId
|
|
) {
|
|
return null
|
|
}
|
|
|
|
const resizingHeader = table
|
|
.getFlatHeaders()
|
|
.find(
|
|
(header) =>
|
|
header.column.id === resizingColumnId || header.id === resizingColumnId
|
|
)
|
|
|
|
if (!resizingHeader) return null
|
|
|
|
const deltaOffset = columnSizingInfo.deltaOffset ?? 0
|
|
const headerHeight =
|
|
viewportElement
|
|
?.querySelector('[data-slot="data-grid-table"] thead')
|
|
?.getBoundingClientRect().height ?? 0
|
|
const indicatorLeft =
|
|
typeof columnSizingInfo.startOffset === "number" && viewportElement
|
|
? columnSizingInfo.startOffset -
|
|
viewportElement.getBoundingClientRect().left
|
|
: resizingHeader.getStart() + resizingHeader.getSize()
|
|
|
|
return (
|
|
<div
|
|
aria-hidden="true"
|
|
className="pointer-events-none absolute inset-y-0 z-20"
|
|
style={{
|
|
left: indicatorLeft,
|
|
transform: `translateX(${deltaOffset}px)`,
|
|
}}
|
|
>
|
|
<div className="bg-primary/85 absolute inset-y-0 left-0 w-px -translate-x-1/2" />
|
|
<div
|
|
className="bg-primary absolute top-0 left-0 -translate-x-1/2 rounded-b-sm shadow-xs"
|
|
style={{
|
|
width: 5,
|
|
height: Math.max(headerHeight, 6),
|
|
}}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function DataGridTableRowSpacer() {
|
|
return <tbody aria-hidden="true" className="h-2"></tbody>
|
|
}
|
|
|
|
function DataGridTableBody({ children }: { children: ReactNode }) {
|
|
const { props } = useDataGrid()
|
|
|
|
return (
|
|
<tbody
|
|
className={cn(
|
|
"[&_tr:last-child]:border-0",
|
|
props.tableLayout?.rowRounded &&
|
|
"[&_td:first-child]:rounded-l-lg",
|
|
props.tableLayout?.rowRounded &&
|
|
"[&_td:last-child]:rounded-r-lg",
|
|
props.tableClassNames?.body
|
|
)}
|
|
>
|
|
{children}
|
|
</tbody>
|
|
)
|
|
}
|
|
|
|
function DataGridTableFoot({ children }: { children: ReactNode }) {
|
|
const { props } = useDataGrid()
|
|
return (
|
|
<tfoot className={cn("border-t", props.tableClassNames?.footer)}>
|
|
{children}
|
|
</tfoot>
|
|
)
|
|
}
|
|
|
|
function DataGridTableFootRow({ children }: { children: ReactNode }) {
|
|
const { props } = useDataGrid()
|
|
return (
|
|
<tr
|
|
className={cn(
|
|
"bg-muted/40 dark:bg-background",
|
|
props.tableLayout?.cellBorder && "*:last:border-e-0"
|
|
)}
|
|
>
|
|
{children}
|
|
<DataGridTableFillFootCell />
|
|
</tr>
|
|
)
|
|
}
|
|
|
|
function DataGridTableFootRowCell({
|
|
children,
|
|
colSpan,
|
|
className,
|
|
}: {
|
|
children?: ReactNode
|
|
colSpan?: number
|
|
className?: string
|
|
}) {
|
|
const { props } = useDataGrid()
|
|
const spacing = footerCellSpacingVariants({
|
|
size: props.tableLayout?.dense ? "dense" : "default",
|
|
})
|
|
return (
|
|
<td
|
|
colSpan={colSpan}
|
|
className={cn(
|
|
"text-secondary-foreground/80 border-t align-middle font-medium",
|
|
spacing,
|
|
props.tableLayout?.cellBorder && "border-e",
|
|
className
|
|
)}
|
|
>
|
|
{children}
|
|
</td>
|
|
)
|
|
}
|
|
|
|
function DataGridTableBodyRowSkeleton({ children }: { children: ReactNode }) {
|
|
const { table, props } = useDataGrid()
|
|
|
|
return (
|
|
<tr
|
|
className={cn(
|
|
"hover:bg-muted/40 data-[state=selected]:bg-muted/50",
|
|
props.onRowClick && "cursor-pointer",
|
|
!props.tableLayout?.stripped &&
|
|
props.tableLayout?.rowBorder &&
|
|
"border-border border-b [&:not(:last-child)>td]:border-b",
|
|
props.tableLayout?.cellBorder && "*:last:border-e-0",
|
|
props.tableLayout?.stripped &&
|
|
"odd:bg-muted/90 odd:hover:bg-muted hover:bg-transparent",
|
|
table.options.enableRowSelection && "*:first:relative",
|
|
props.tableClassNames?.bodyRow
|
|
)}
|
|
>
|
|
{children}
|
|
<DataGridTableFillBodyCell />
|
|
</tr>
|
|
)
|
|
}
|
|
|
|
function DataGridTableBodyRowSkeletonCell<TData>({
|
|
children,
|
|
column,
|
|
}: {
|
|
children: ReactNode
|
|
column: Column<TData>
|
|
}) {
|
|
const { props, table } = useDataGrid()
|
|
const bodyCellSpacing = bodyCellSpacingVariants({
|
|
size: props.tableLayout?.dense ? "dense" : "default",
|
|
})
|
|
|
|
return (
|
|
<td
|
|
style={
|
|
props.tableLayout?.columnsResizable
|
|
? { width: `calc(var(--col-${column.id}-size) * 1px)` }
|
|
: undefined
|
|
}
|
|
className={cn(
|
|
"align-middle",
|
|
bodyCellSpacing,
|
|
props.tableLayout?.cellBorder && "border-e",
|
|
props.tableLayout?.columnsResizable &&
|
|
column.getCanResize() &&
|
|
"truncate",
|
|
column.columnDef.meta?.cellClassName,
|
|
props.tableLayout?.columnsPinnable &&
|
|
column.getCanPin() &&
|
|
'[&[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!',
|
|
column.getIndex() === 0 ||
|
|
column.getIndex() === table.getVisibleFlatColumns().length - 1
|
|
? props.tableClassNames?.edgeCell
|
|
: ""
|
|
)}
|
|
>
|
|
{children}
|
|
</td>
|
|
)
|
|
}
|
|
|
|
function DataGridTableBodyRow<TData>({
|
|
children,
|
|
row,
|
|
pinnedBoundary,
|
|
rowRef,
|
|
dndRef,
|
|
dndStyle,
|
|
}: {
|
|
children: ReactNode
|
|
row: Row<TData>
|
|
pinnedBoundary?: DataGridTablePinnedBoundary
|
|
rowRef?: React.Ref<HTMLTableRowElement>
|
|
dndRef?: React.Ref<HTMLTableRowElement>
|
|
dndStyle?: CSSProperties
|
|
}) {
|
|
const { props, table } = useDataGrid()
|
|
const isRowPinned = row.getIsPinned()
|
|
|
|
return (
|
|
<tr
|
|
ref={(node) => {
|
|
assignRef(rowRef, node)
|
|
assignRef(dndRef, node)
|
|
}}
|
|
style={{ ...(dndStyle ? dndStyle : null) }}
|
|
data-state={
|
|
table.options.enableRowSelection && row.getIsSelected()
|
|
? "selected"
|
|
: undefined
|
|
}
|
|
data-row-pinned={isRowPinned || undefined}
|
|
data-row-pinned-boundary={pinnedBoundary}
|
|
onClick={() => props.onRowClick && props.onRowClick(row.original)}
|
|
className={cn(
|
|
"hover:bg-muted/40 data-[state=selected]:bg-muted/50",
|
|
props.onRowClick && "cursor-pointer",
|
|
!props.tableLayout?.stripped &&
|
|
props.tableLayout?.rowBorder &&
|
|
"border-border border-b [&:not(:last-child)>td]:border-b",
|
|
props.tableLayout?.cellBorder && "*:last:border-e-0",
|
|
props.tableLayout?.stripped &&
|
|
"odd:bg-muted/90 odd:hover:bg-muted hover:bg-transparent",
|
|
table.options.enableRowSelection && "*:first:relative",
|
|
props.tableLayout?.rowsPinnable &&
|
|
isRowPinned &&
|
|
"bg-muted/30 hover:bg-muted/50",
|
|
pinnedBoundary === "top" && "[&>td]:shadow-[0_2px_0_rgba(0,0,0,0.03)]",
|
|
pinnedBoundary === "bottom" &&
|
|
"[&>td]:shadow-[0_2px_0_rgba(0,0,0,0.03)]",
|
|
props.tableClassNames?.bodyRow
|
|
)}
|
|
>
|
|
{children}
|
|
<DataGridTableFillBodyCell />
|
|
</tr>
|
|
)
|
|
}
|
|
|
|
function DataGridTableBodyRowExpandded<TData>({ row }: { row: Row<TData> }) {
|
|
const { props, table } = useDataGrid()
|
|
|
|
return (
|
|
<tr
|
|
className={cn(
|
|
props.tableLayout?.rowBorder && "[&:not(:last-child)>td]:border-b"
|
|
)}
|
|
>
|
|
<td
|
|
colSpan={
|
|
row.getVisibleCells().length +
|
|
(props.tableLayout?.columnsResizable ? 1 : 0)
|
|
}
|
|
>
|
|
{table
|
|
.getAllColumns()
|
|
.find((column) => column.columnDef.meta?.expandedContent)
|
|
?.columnDef.meta?.expandedContent?.(row.original)}
|
|
</td>
|
|
</tr>
|
|
)
|
|
}
|
|
|
|
function DataGridTableBodyRowCell<TData>({
|
|
children,
|
|
cell,
|
|
dndRef,
|
|
dndStyle,
|
|
}: {
|
|
children: ReactNode
|
|
cell: Cell<TData, unknown>
|
|
dndRef?: React.Ref<HTMLTableCellElement>
|
|
dndStyle?: CSSProperties
|
|
}) {
|
|
const { props } = useDataGrid()
|
|
|
|
const { column, row } = cell
|
|
const isPinned = column.getIsPinned()
|
|
const isLastLeftPinned = isPinned === "left" && column.getIsLastColumn("left")
|
|
const isFirstRightPinned =
|
|
isPinned === "right" && column.getIsFirstColumn("right")
|
|
const bodyCellSpacing = bodyCellSpacingVariants({
|
|
size: props.tableLayout?.dense ? "dense" : "default",
|
|
})
|
|
|
|
return (
|
|
<td
|
|
key={cell.id}
|
|
ref={dndRef}
|
|
{...(props.tableLayout?.columnsDraggable && !isPinned ? { cell } : {})}
|
|
style={{
|
|
...(props.tableLayout?.columnsPinnable &&
|
|
column.getCanPin() &&
|
|
getPinningStyles(column)),
|
|
...(props.tableLayout?.columnsResizable && {
|
|
width: `calc(var(--col-${column.id}-size) * 1px)`,
|
|
}),
|
|
...(dndStyle ? dndStyle : null),
|
|
}}
|
|
data-pinned={isPinned || undefined}
|
|
data-last-col={
|
|
isLastLeftPinned ? "left" : isFirstRightPinned ? "right" : undefined
|
|
}
|
|
className={cn(
|
|
"align-middle",
|
|
bodyCellSpacing,
|
|
props.tableLayout?.cellBorder && "border-e",
|
|
props.tableLayout?.columnsResizable &&
|
|
column.getCanResize() &&
|
|
"truncate",
|
|
cell.column.columnDef.meta?.cellClassName,
|
|
props.tableLayout?.columnsPinnable &&
|
|
column.getCanPin() &&
|
|
'[&[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!',
|
|
column.getIndex() === 0 ||
|
|
column.getIndex() === row.getVisibleCells().length - 1
|
|
? props.tableClassNames?.edgeCell
|
|
: ""
|
|
)}
|
|
>
|
|
{children}
|
|
</td>
|
|
)
|
|
}
|
|
|
|
function DataGridTableRenderedRow<TData>({
|
|
row,
|
|
pinnedBoundary,
|
|
rowRef,
|
|
}: {
|
|
row: Row<TData>
|
|
pinnedBoundary?: DataGridTablePinnedBoundary
|
|
rowRef?: React.Ref<HTMLTableRowElement>
|
|
}) {
|
|
return (
|
|
<Fragment>
|
|
<DataGridTableBodyRow
|
|
row={row}
|
|
pinnedBoundary={pinnedBoundary}
|
|
rowRef={rowRef}
|
|
>
|
|
{row.getVisibleCells().map((cell: Cell<TData, unknown>) => (
|
|
<DataGridTableBodyRowCell cell={cell} key={cell.id}>
|
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
</DataGridTableBodyRowCell>
|
|
))}
|
|
</DataGridTableBodyRow>
|
|
{row.getIsExpanded() && <DataGridTableBodyRowExpandded row={row} />}
|
|
</Fragment>
|
|
)
|
|
}
|
|
|
|
function DataGridTableEmpty() {
|
|
const { table, props } = useDataGrid()
|
|
const visibleColumnCount =
|
|
table.getVisibleLeafColumns().length +
|
|
(props.tableLayout?.columnsResizable ? 1 : 0)
|
|
|
|
return (
|
|
<tr>
|
|
<td
|
|
colSpan={Math.max(visibleColumnCount, 1)}
|
|
className="text-muted-foreground text-sm py-6 text-center"
|
|
>
|
|
{props.emptyMessage || "No data available"}
|
|
</td>
|
|
</tr>
|
|
)
|
|
}
|
|
|
|
function DataGridTableLoader() {
|
|
const { props } = useDataGrid()
|
|
|
|
return (
|
|
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
|
|
<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">
|
|
<Spinner className="size-5 opacity-60" />
|
|
{props.loadingMessage || "Loading..."}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function DataGridTableRowPin<TData>({ row }: { row: Row<TData> }) {
|
|
const isPinned = row.getIsPinned()
|
|
|
|
return (
|
|
<button
|
|
type="button"
|
|
aria-label={isPinned ? "Unpin row" : "Pin row"}
|
|
onClick={() => {
|
|
if (isPinned) {
|
|
row.pin(false)
|
|
} else {
|
|
row.pin("top")
|
|
}
|
|
}}
|
|
className={cn(
|
|
"text-muted-foreground hover:text-foreground inline-flex size-7 items-center justify-center rounded-md transition-colors",
|
|
isPinned && "text-primary hover:text-primary/80"
|
|
)}
|
|
>
|
|
{isPinned ? (
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 24 24"
|
|
fill="currentColor"
|
|
stroke="none"
|
|
>
|
|
<path d="M16 2l4.585 4.586-2.122 2.121L17.05 7.293l-3.535 3.536 1.413 5.658-2.12 2.121-4.244-4.243L4.322 18.6l-1.414-1.41 4.242-4.244-4.243-4.243 2.122-2.121 5.656 1.414 3.536-3.536-1.414-1.414z" />
|
|
</svg>
|
|
) : (
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth="2"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
>
|
|
<line x1="12" y1="17" x2="12" y2="22" />
|
|
<path d="M5 17h14v-1.76a2 2 0 00-1.11-1.79l-1.78-.9A2 2 0 0115 10.76V6h1a2 2 0 000-4H8a2 2 0 000 4h1v4.76a2 2 0 01-1.11 1.79l-1.78.9A2 2 0 005 15.24z" />
|
|
</svg>
|
|
)}
|
|
</button>
|
|
)
|
|
}
|
|
|
|
function DataGridTableRowSelect<TData>({ row }: { row: Row<TData> }) {
|
|
return (
|
|
<>
|
|
<div
|
|
className={cn(
|
|
"bg-primary absolute inset-s-0 top-0 bottom-0 hidden w-[2px]",
|
|
row.getIsSelected() && "block"
|
|
)}
|
|
></div>
|
|
<Checkbox
|
|
checked={row.getIsSelected()}
|
|
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
|
aria-label="Select row"
|
|
className="align-[inherit]"
|
|
/>
|
|
</>
|
|
)
|
|
}
|
|
|
|
function DataGridTableRowSelectAll() {
|
|
const { table, recordCount, isLoading } = useDataGrid()
|
|
|
|
const isAllSelected = table.getIsAllPageRowsSelected()
|
|
const isSomeSelected = table.getIsSomePageRowsSelected()
|
|
|
|
return (
|
|
<Checkbox
|
|
checked={isAllSelected}
|
|
indeterminate={isSomeSelected && !isAllSelected}
|
|
disabled={isLoading || recordCount === 0}
|
|
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
|
aria-label="Select all"
|
|
className="align-[inherit]"
|
|
/>
|
|
)
|
|
}
|
|
|
|
function DataGridTableBodyRows<TData>({ table }: { table: Table<TData> }) {
|
|
const { isLoading, props } = useDataGrid()
|
|
const pagination = table.getState().pagination
|
|
|
|
if (isLoading && props.loadingMode === "skeleton" && pagination?.pageSize) {
|
|
return (
|
|
<>
|
|
{Array.from({ length: pagination.pageSize }).map((_, rowIndex) => (
|
|
<DataGridTableBodyRowSkeleton key={rowIndex}>
|
|
{table.getVisibleFlatColumns().map((column, colIndex) => (
|
|
<DataGridTableBodyRowSkeletonCell column={column} key={colIndex}>
|
|
{column.columnDef.meta?.skeleton}
|
|
</DataGridTableBodyRowSkeletonCell>
|
|
))}
|
|
</DataGridTableBodyRowSkeleton>
|
|
))}
|
|
</>
|
|
)
|
|
}
|
|
|
|
if (isLoading && props.loadingMode === "spinner") {
|
|
return (
|
|
<tr>
|
|
<td colSpan={table.getVisibleFlatColumns().length} className="p-8">
|
|
<div className="flex items-center justify-center">
|
|
<svg
|
|
className="text-muted-foreground mr-3 -ml-1 h-5 w-5 animate-spin"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<circle
|
|
className="opacity-25"
|
|
cx="12"
|
|
cy="12"
|
|
r="10"
|
|
stroke="currentColor"
|
|
strokeWidth="4"
|
|
></circle>
|
|
<path
|
|
className="opacity-75"
|
|
fill="currentColor"
|
|
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"
|
|
></path>
|
|
</svg>
|
|
{props.loadingMessage || "Loading..."}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
)
|
|
}
|
|
|
|
const resolvedRows = getDataGridTableResolvedRows(
|
|
table,
|
|
props.tableLayout?.rowsPinnable
|
|
)
|
|
|
|
if (!resolvedRows.length) return <DataGridTableEmpty />
|
|
|
|
return (
|
|
<>
|
|
{resolvedRows.map(({ row, pinnedBoundary }) => (
|
|
<DataGridTableRenderedRow
|
|
key={row.id}
|
|
row={row}
|
|
pinnedBoundary={pinnedBoundary}
|
|
/>
|
|
))}
|
|
</>
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Memoized body rows: skip re-renders during active column resize.
|
|
* Column widths update via CSS variables on the <table> element,
|
|
* so the browser handles width changes without React re-renders.
|
|
*/
|
|
const MemoizedDataGridTableBodyRows = memo(
|
|
DataGridTableBodyRows,
|
|
(_prev, next) => !!next.table.getState().columnSizingInfo.isResizingColumn
|
|
) as typeof DataGridTableBodyRows
|
|
|
|
function DataGridTableHeader<TData>() {
|
|
const { table, props } = useDataGrid()
|
|
|
|
return (
|
|
<DataGridTableViewport>
|
|
<DataGridTableBase>
|
|
<DataGridTableHead>
|
|
{table
|
|
.getHeaderGroups()
|
|
.map((headerGroup: HeaderGroup<TData>, index) => {
|
|
return (
|
|
<DataGridTableHeadRow headerGroup={headerGroup} key={index}>
|
|
{headerGroup.headers.map((header, index) => {
|
|
const { column } = header
|
|
|
|
return (
|
|
<DataGridTableHeadRowCell header={header} key={index}>
|
|
{header.isPlaceholder ? null : props.tableLayout
|
|
?.columnsResizable && column.getCanResize() ? (
|
|
<div className="truncate">
|
|
{flexRender(
|
|
header.column.columnDef.header,
|
|
header.getContext()
|
|
)}
|
|
</div>
|
|
) : (
|
|
flexRender(
|
|
header.column.columnDef.header,
|
|
header.getContext()
|
|
)
|
|
)}
|
|
{props.tableLayout?.columnsResizable &&
|
|
column.getCanResize() && (
|
|
<DataGridTableHeadRowCellResize header={header} />
|
|
)}
|
|
</DataGridTableHeadRowCell>
|
|
)
|
|
})}
|
|
</DataGridTableHeadRow>
|
|
)
|
|
})}
|
|
</DataGridTableHead>
|
|
</DataGridTableBase>
|
|
</DataGridTableViewport>
|
|
)
|
|
}
|
|
|
|
function DataGridTable<TData>({
|
|
footerContent,
|
|
renderHeader = true,
|
|
}: {
|
|
footerContent?: ReactNode
|
|
renderHeader?: boolean
|
|
}) {
|
|
const { table, props } = useDataGrid()
|
|
|
|
return (
|
|
<DataGridTableViewport>
|
|
<DataGridTableBase>
|
|
{renderHeader && (
|
|
<DataGridTableHead>
|
|
{table
|
|
.getHeaderGroups()
|
|
.map((headerGroup: HeaderGroup<TData>, index) => {
|
|
return (
|
|
<DataGridTableHeadRow headerGroup={headerGroup} key={index}>
|
|
{headerGroup.headers.map((header, index) => {
|
|
const { column } = header
|
|
|
|
return (
|
|
<DataGridTableHeadRowCell header={header} key={index}>
|
|
{header.isPlaceholder ? null : props.tableLayout
|
|
?.columnsResizable && column.getCanResize() ? (
|
|
<div className="truncate">
|
|
{flexRender(
|
|
header.column.columnDef.header,
|
|
header.getContext()
|
|
)}
|
|
</div>
|
|
) : (
|
|
flexRender(
|
|
header.column.columnDef.header,
|
|
header.getContext()
|
|
)
|
|
)}
|
|
{props.tableLayout?.columnsResizable &&
|
|
column.getCanResize() && (
|
|
<DataGridTableHeadRowCellResize header={header} />
|
|
)}
|
|
</DataGridTableHeadRowCell>
|
|
)
|
|
})}
|
|
</DataGridTableHeadRow>
|
|
)
|
|
})}
|
|
</DataGridTableHead>
|
|
)}
|
|
|
|
{renderHeader &&
|
|
(props.tableLayout?.stripped || !props.tableLayout?.rowBorder) && (
|
|
<DataGridTableRowSpacer />
|
|
)}
|
|
|
|
<DataGridTableBody>
|
|
<MemoizedDataGridTableBodyRows table={table} />
|
|
</DataGridTableBody>
|
|
|
|
{footerContent && (
|
|
<DataGridTableFoot>{footerContent}</DataGridTableFoot>
|
|
)}
|
|
</DataGridTableBase>
|
|
</DataGridTableViewport>
|
|
)
|
|
}
|
|
|
|
export {
|
|
DataGridTable,
|
|
DataGridTableBase,
|
|
DataGridTableBody,
|
|
DataGridTableBodyRow,
|
|
DataGridTableBodyRowCell,
|
|
DataGridTableBodyRowExpandded,
|
|
DataGridTableRenderedRow,
|
|
DataGridTableBodyRowSkeleton,
|
|
DataGridTableBodyRowSkeletonCell,
|
|
DataGridTableEmpty,
|
|
DataGridTableFoot,
|
|
DataGridTableFootRow,
|
|
DataGridTableFootRowCell,
|
|
DataGridTableHeader,
|
|
DataGridTableHead,
|
|
DataGridTableHeadRow,
|
|
DataGridTableHeadRowCell,
|
|
DataGridTableHeadRowCellResize,
|
|
DataGridTableLoader,
|
|
DataGridTableRowPin,
|
|
DataGridTableRowSelect,
|
|
DataGridTableRowSelectAll,
|
|
DataGridTableRowSpacer,
|
|
DataGridTableViewport,
|
|
getDataGridTableResolvedRows,
|
|
getDataGridTableRowSections,
|
|
}
|
|
|
|
export type { DataGridTablePinnedBoundary } |