implementation of datatable

This commit is contained in:
Amirhossein Mahmoodi
2024-04-16 11:45:14 +03:30
parent 78548e89e7
commit a8e28a4f8c
29 changed files with 2983 additions and 2 deletions

View File

@@ -25,7 +25,7 @@
"next": "14.1.0",
"react": "^18",
"react-dom": "^18",
"sass": "^1.70.0",
"sass": "^1.75.0",
"stylis": "^4.3.1",
"stylis-plugin-rtl": "^2.1.1"
},

View File

@@ -0,0 +1,207 @@
import { parseFromValuesOrFunc } from "@/core/utils/utils";
import { TableBody, Typography } from "@mui/material";
import { useMRT_RowVirtualizer, useMRT_Rows } from "material-react-table";
import { memo, useMemo } from "react";
import DataTable_TableBodyRow, { Memo_DataTable_TableBodyRow } from "./TableBodyRow";
const DataTable_TableBody = ({
columnVirtualizer,
table,
...rest
}) => {
const {
getBottomRows,
getIsSomeRowsPinned,
getRowModel,
getState,
getTopRows,
options: {
enableStickyFooter,
enableStickyHeader,
layoutMode,
localization,
memoMode,
muiTableBodyProps,
renderDetailPanel,
renderEmptyRowsFallback,
rowPinningDisplayMode,
},
refs: { tableFooterRef, tableHeadRef, tablePaperRef },
} = table;
const { columnFilters, globalFilter, isFullScreen, rowPinning } = getState();
const tableBodyProps = {
...parseFromValuesOrFunc(muiTableBodyProps, { table }),
...rest,
};
const tableHeadHeight =
((enableStickyHeader || isFullScreen) &&
tableHeadRef.current?.clientHeight) ||
0;
const tableFooterHeight =
(enableStickyFooter && tableFooterRef.current?.clientHeight) || 0;
const pinnedRowIds = useMemo(() => {
if (!rowPinning.bottom?.length && !rowPinning.top?.length) return [];
return getRowModel()
.rows.filter((row) => row.getIsPinned())
.map((r) => r.id);
}, [rowPinning, getRowModel().rows]);
const rows = useMRT_Rows(table);
const rowVirtualizer = useMRT_RowVirtualizer(table, rows);
const { virtualRows } = rowVirtualizer ?? {};
const commonRowProps = {
columnVirtualizer,
numRows: rows.length,
table,
};
return (
<>
{!rowPinningDisplayMode?.includes('sticky') &&
getIsSomeRowsPinned('top') && (
<TableBody
{...tableBodyProps}
sx={(theme) => ({
display: layoutMode?.startsWith('grid') ? 'grid' : undefined,
position: 'sticky',
top: tableHeadHeight - 1,
zIndex: 1,
...(parseFromValuesOrFunc(tableBodyProps?.sx, theme)),
})}
>
{getTopRows().map((row, staticRowIndex) => {
const props = {
...commonRowProps,
row,
staticRowIndex,
};
return memoMode === 'rows' ? (
<Memo_DataTable_TableBodyRow key={row.id} {...props} />
) : (
<DataTable_TableBodyRow key={row.id} {...props} />
);
})}
</TableBody>
)}
<TableBody
{...tableBodyProps}
sx={(theme) => ({
display: layoutMode?.startsWith('grid') ? 'grid' : undefined,
height: rowVirtualizer
? `${rowVirtualizer.getTotalSize()}px`
: undefined,
minHeight: !rows.length ? '100px' : undefined,
position: 'relative',
...(parseFromValuesOrFunc(tableBodyProps?.sx, theme)),
})}
>
{tableBodyProps?.children ??
(!rows.length ? (
<tr
style={{
display: layoutMode?.startsWith('grid') ? 'grid' : undefined,
}}
>
<td
colSpan={table.getVisibleLeafColumns().length}
style={{
display: layoutMode?.startsWith('grid') ? 'grid' : undefined,
}}
>
{renderEmptyRowsFallback?.({ table }) ?? (
<Typography
sx={{
color: 'text.secondary',
fontStyle: 'italic',
maxWidth: `min(100vw, ${tablePaperRef.current?.clientWidth ?? 360
}px)`,
py: '2rem',
textAlign: 'center',
width: '100%',
}}
>
{globalFilter || columnFilters.length
? localization.noResultsFound
: localization.noRecordsToDisplay}
</Typography>
)}
</td>
</tr>
) : (
<>
{(virtualRows ?? rows).map((rowOrVirtualRow, staticRowIndex) => {
let row = rowOrVirtualRow;
if (rowVirtualizer) {
if (renderDetailPanel) {
if (rowOrVirtualRow.index % 2 === 1) {
return null;
} else {
staticRowIndex = rowOrVirtualRow.index / 2;
}
} else {
staticRowIndex = rowOrVirtualRow.index;
}
row = rows[staticRowIndex];
}
const props = {
...commonRowProps,
pinnedRowIds,
row,
rowVirtualizer,
staticRowIndex,
virtualRow: rowVirtualizer
? (rowOrVirtualRow)
: undefined,
};
const key = `${row.id}-${row.index}`;
return memoMode === 'rows' ? (
<Memo_DataTable_TableBodyRow key={key} {...props} />
) : (
<DataTable_TableBodyRow key={key} {...props} />
);
})}
</>
))}
</TableBody>
{!rowPinningDisplayMode?.includes('sticky') &&
getIsSomeRowsPinned('bottom') && (
<TableBody
{...tableBodyProps}
sx={(theme) => ({
bottom: tableFooterHeight - 1,
display: layoutMode?.startsWith('grid') ? 'grid' : undefined,
position: 'sticky',
zIndex: 1,
...(parseFromValuesOrFunc(tableBodyProps?.sx, theme)),
})}
>
{getBottomRows().map((row, staticRowIndex) => {
const props = {
...commonRowProps,
row,
staticRowIndex,
};
return memoMode === 'rows' ? (
<Memo_DataTable_TableBodyRow key={row.id} {...props} />
) : (
<DataTable_TableBodyRow key={row.id} {...props} />
);
})}
</TableBody>
)}
</>
);
}
export default DataTable_TableBody
export const Memo_DataTable_TableBody = memo(
DataTable_TableBody,
(prev, next) => prev.table.options.data === next.table.options.data,
);

View File

@@ -0,0 +1,307 @@
import { isCellEditable } from "material-react-table";
const { parseFromValuesOrFunc, getCommonMRTCellStyles } = require("@/core/utils/utils");
const { useTheme } = require("@emotion/react");
const { TableCell, Skeleton } = require("@mui/material");
const { useState, useEffect, useMemo, memo } = require("react");
const { default: DataTable_CopyButton } = require("../buttons/CopyButton");
const { default: DataTable_TableBodyCellValue } = require("./TableBodyCellValue");
const DataTable_TableBodyCell = ({
cell,
numRows,
rowRef,
staticColumnIndex,
staticRowIndex,
table,
...rest
}) => {
const theme = useTheme();
const {
getState,
options: {
columnResizeDirection,
columnResizeMode,
createDisplayMode,
editDisplayMode,
enableCellActions,
enableClickToCopy,
enableColumnOrdering,
enableColumnPinning,
enableGrouping,
layoutMode,
mrtTheme: { draggingBorderColor },
muiSkeletonProps,
muiTableBodyCellProps,
},
setHoveredColumn,
} = table;
const {
actionCell,
columnSizingInfo,
creatingRow,
density,
draggingColumn,
draggingRow,
editingCell,
editingRow,
hoveredColumn,
hoveredRow,
isLoading,
showSkeletons,
} = getState();
const { column, row } = cell;
const { columnDef } = column;
const { columnDefType } = columnDef;
const args = { cell, column, row, table };
const tableCellProps = {
...parseFromValuesOrFunc(muiTableBodyCellProps, args),
...parseFromValuesOrFunc(columnDef.muiTableBodyCellProps, args),
...rest,
};
const skeletonProps = parseFromValuesOrFunc(muiSkeletonProps, {
cell,
column,
row,
table,
});
const [skeletonWidth, setSkeletonWidth] = useState(100);
useEffect(() => {
if ((!isLoading && !showSkeletons) || skeletonWidth !== 100) return;
const size = column.getSize();
setSkeletonWidth(
columnDefType === 'display'
? size / 2
: Math.round(Math.random() * (size - size / 3) + size / 3),
);
}, [isLoading, showSkeletons]);
const draggingBorders = useMemo(() => {
const isDraggingColumn = draggingColumn?.id === column.id;
const isHoveredColumn = hoveredColumn?.id === column.id;
const isDraggingRow = draggingRow?.id === row.id;
const isHoveredRow = hoveredRow?.id === row.id;
const isFirstColumn = column.getIsFirstColumn();
const isLastColumn = column.getIsLastColumn();
const isLastRow = numRows && staticRowIndex === numRows - 1;
const isResizingColumn = columnSizingInfo.isResizingColumn === column.id;
const showResizeBorder =
isResizingColumn && columnResizeMode === 'onChange';
const borderStyle = showResizeBorder
? `2px solid ${draggingBorderColor} !important`
: isDraggingColumn || isDraggingRow
? `1px dashed ${theme.palette.grey[500]} !important`
: isHoveredColumn || isHoveredRow || isResizingColumn
? `2px dashed ${draggingBorderColor} !important`
: undefined;
if (showResizeBorder) {
return columnResizeDirection === 'ltr'
? { borderRight: borderStyle }
: { borderLeft: borderStyle };
}
return borderStyle
? {
borderBottom:
isDraggingRow || isHoveredRow || (isLastRow && !isResizingColumn)
? borderStyle
: undefined,
borderLeft:
isDraggingColumn ||
isHoveredColumn ||
((isDraggingRow || isHoveredRow) && isFirstColumn)
? borderStyle
: undefined,
borderRight:
isDraggingColumn ||
isHoveredColumn ||
((isDraggingRow || isHoveredRow) && isLastColumn)
? borderStyle
: undefined,
borderTop: isDraggingRow || isHoveredRow ? borderStyle : undefined,
}
: undefined;
}, [
columnSizingInfo.isResizingColumn,
draggingColumn,
draggingRow,
hoveredColumn,
hoveredRow,
staticRowIndex,
]);
const isColumnPinned =
enableColumnPinning &&
columnDef.columnDefType !== 'group' &&
column.getIsPinned();
const isEditable = isCellEditable({ cell, table });
const isEditing =
isEditable &&
!['custom', 'modal'].includes(editDisplayMode) &&
(editDisplayMode === 'table' ||
editingRow?.id === row.id ||
editingCell?.id === cell.id) &&
!row.getIsGrouped();
const isCreating =
isEditable && createDisplayMode === 'row' && creatingRow?.id === row.id;
const showClickToCopyButton =
(parseFromValuesOrFunc(enableClickToCopy, cell) === true ||
parseFromValuesOrFunc(columnDef.enableClickToCopy, cell) === true) &&
!['context-menu', false].includes(
// @ts-ignore
parseFromValuesOrFunc(columnDef.enableClickToCopy, cell),
);
const isRightClickable = parseFromValuesOrFunc(enableCellActions, cell);
const cellValueProps = {
cell,
table,
};
const handleDoubleClick = (event) => {
tableCellProps?.onDoubleClick?.(event);
openEditingCell({ cell, table });
};
const handleDragEnter = (e) => {
tableCellProps?.onDragEnter?.(e);
if (enableGrouping && hoveredColumn?.id === 'drop-zone') {
setHoveredColumn(null);
}
if (enableColumnOrdering && draggingColumn) {
setHoveredColumn(
columnDef.enableColumnOrdering !== false ? column : null,
);
}
};
const handleDragOver = (e) => {
if (columnDef.enableColumnOrdering !== false) {
e.preventDefault();
}
};
const handleContextMenu = (e) => {
tableCellProps?.onContextMenu?.(e);
if (isRightClickable) {
e.preventDefault();
table.setActionCell(cell);
table.refs.actionCellRef.current = e.currentTarget;
}
};
return (
<TableCell
align={theme.direction === 'rtl' ? 'right' : 'left'}
data-index={staticColumnIndex}
data-pinned={!!isColumnPinned || undefined}
{...tableCellProps}
onContextMenu={handleContextMenu}
onDoubleClick={handleDoubleClick}
onDragEnter={handleDragEnter}
onDragOver={handleDragOver}
sx={(theme) => ({
'&:hover': {
outline:
actionCell?.id === cell.id ||
(editDisplayMode === 'cell' && isEditable) ||
(editDisplayMode === 'table' && (isCreating || isEditing))
? `1px solid ${theme.palette.grey[500]}`
: undefined,
textOverflow: 'clip',
},
alignItems: layoutMode?.startsWith('grid') ? 'center' : undefined,
cursor: isRightClickable
? 'context-menu'
: isEditable && editDisplayMode === 'cell'
? 'pointer'
: 'inherit',
outline:
actionCell?.id === cell.id
? `1px solid ${theme.palette.grey[500]}`
: undefined,
outlineOffset: '-1px',
overflow: 'hidden',
p:
density === 'compact'
? columnDefType === 'display'
? '0 0.5rem'
: '0.5rem'
: density === 'comfortable'
? columnDefType === 'display'
? '0.5rem 0.75rem'
: '1rem'
: columnDefType === 'display'
? '1rem 1.25rem'
: '1.5rem',
textOverflow: columnDefType !== 'display' ? 'ellipsis' : undefined,
whiteSpace:
row.getIsPinned() || density === 'compact' ? 'nowrap' : 'normal',
...getCommonMRTCellStyles({
column,
table,
tableCellProps,
theme,
}),
...draggingBorders,
})}
>
{tableCellProps.children ?? (
<>
{cell.getIsPlaceholder() ? (
columnDef.PlaceholderCell?.({ cell, column, row, table }) ?? null
) : showSkeletons !== false && (isLoading || showSkeletons) ? (
<Skeleton
animation="wave"
height={20}
width={skeletonWidth}
{...skeletonProps}
/>
) : columnDefType === 'display' &&
(['mrt-row-expand', 'mrt-row-numbers', 'mrt-row-select'].includes(
column.id,
) ||
!row.getIsGrouped()) ? (
columnDef.Cell?.({
cell,
column,
renderedCellValue: cell.renderValue(),
row,
rowRef,
staticColumnIndex,
staticRowIndex,
table,
})
) : showClickToCopyButton && columnDef.enableClickToCopy !== false ? (
<DataTable_CopyButton cell={cell} table={table}>
<DataTable_TableBodyCellValue {...cellValueProps} />
</DataTable_CopyButton>
) : (
<DataTable_TableBodyCellValue {...cellValueProps} />
)}
{cell.getIsGrouped() && !columnDef.GroupedCell && (
<> ({row.subRows?.length})</>
)}
</>
)}
</TableCell>
);
}
export default DataTable_TableBodyCell;
export const Memo_DataTable_TableBodyCell = memo(
DataTable_TableBodyCell,
(prev, next) => next.cell === prev.cell,
);

View File

@@ -0,0 +1,112 @@
import { Box } from "@mui/material";
const allowedTypes = ["string", "number"];
const DataTable_TableBodyCellValue = ({
cell,
rowRef,
staticColumnIndex,
staticRowIndex,
table,
}) => {
const {
getState,
options: {
enableFilterMatchHighlighting,
mrtTheme: { matchHighlightColor },
},
} = table;
const { column, row } = cell;
const { columnDef } = column;
const { globalFilter, globalFilterFn } = getState();
const filterValue = column.getFilterValue();
let renderedCellValue =
cell.getIsAggregated() && columnDef.AggregatedCell
? columnDef.AggregatedCell({
cell,
column,
row,
table,
})
: row.getIsGrouped() && !cell.getIsGrouped()
? null
: cell.getIsGrouped() && columnDef.GroupedCell
? columnDef.GroupedCell({
cell,
column,
row,
table,
})
: undefined;
const isGroupedValue = renderedCellValue !== undefined;
if (!isGroupedValue) {
renderedCellValue = cell.renderValue();
}
if (
enableFilterMatchHighlighting &&
columnDef.enableFilterMatchHighlighting !== false &&
String(renderedCellValue) &&
allowedTypes.includes(typeof renderedCellValue) &&
((filterValue &&
allowedTypes.includes(typeof filterValue) &&
["autocomplete", "text"].includes(columnDef.filterVariant)) ||
(globalFilter &&
allowedTypes.includes(typeof globalFilter) &&
column.getCanGlobalFilter()))
) {
const chunks = highlightWords?.({
matchExactly:
(filterValue ? columnDef._filterFn : globalFilterFn) !== "fuzzy",
query: (filterValue ?? globalFilter ?? "").toString(),
text: renderedCellValue?.toString(),
});
if (chunks?.length > 1 || chunks?.[0]?.match) {
renderedCellValue = (
<span aria-label={renderedCellValue} role="note">
{chunks?.map(({ key, match, text }) => (
<Box
aria-hidden="true"
component="span"
key={key}
sx={
match
? {
backgroundColor: matchHighlightColor,
borderRadius: "2px",
color: (theme) =>
theme.palette.mode === "dark"
? theme.palette.common.white
: theme.palette.common.black,
padding: "2px 1px",
}
: undefined
}
>
{text}
</Box>
)) ?? renderedCellValue}
</span>
);
}
}
if (columnDef.Cell && !isGroupedValue) {
renderedCellValue = columnDef.Cell({
cell,
column,
renderedCellValue,
row,
rowRef,
staticColumnIndex,
staticRowIndex,
table,
});
}
return renderedCellValue;
};
export default DataTable_TableBodyCellValue;

View File

@@ -0,0 +1,254 @@
import { commonCellBeforeAfterStyles, getCommonPinnedCellStyles, parseFromValuesOrFunc } from "@/core/utils/utils";
import { useTheme } from "@emotion/react";
import { TableRow, alpha, darken, lighten } from "@mui/material";
import { getIsRowSelected } from "material-react-table";
import { memo, useMemo, useRef } from "react";
import DataTable_TableBodyCell, { Memo_DataTable_TableBodyCell } from "./TableBodyCell";
import DataTable_TableDetailPanel from "./TableDetailPanel";
const DataTable_TableBodyRow = ({
columnVirtualizer,
numRows,
pinnedRowIds,
row,
rowVirtualizer,
staticRowIndex,
table,
virtualRow,
...rest
}) => {
const theme = useTheme();
const {
getState,
options: {
enableRowOrdering,
enableRowPinning,
enableStickyFooter,
enableStickyHeader,
layoutMode,
memoMode,
mrtTheme: {
baseBackgroundColor,
pinnedRowBackgroundColor,
selectedRowBackgroundColor,
},
muiTableBodyRowProps,
renderDetailPanel,
rowPinningDisplayMode,
},
refs: { tableFooterRef, tableHeadRef },
setHoveredRow,
} = table;
const {
density,
draggingColumn,
draggingRow,
editingCell,
editingRow,
hoveredRow,
isFullScreen,
rowPinning,
} = getState();
const visibleCells = row.getVisibleCells();
const { virtualColumns, virtualPaddingLeft, virtualPaddingRight } =
columnVirtualizer ?? {};
const isRowSelected = getIsRowSelected({ row, table });
const isRowPinned = enableRowPinning && row.getIsPinned();
const isDraggingRow = draggingRow?.id === row.id;
const isHoveredRow = hoveredRow?.id === row.id;
const tableRowProps = {
...parseFromValuesOrFunc(muiTableBodyRowProps, {
row,
staticRowIndex,
table,
}),
...rest,
};
const [bottomPinnedIndex, topPinnedIndex] = useMemo(() => {
if (
!enableRowPinning ||
!rowPinningDisplayMode?.includes('sticky') ||
!pinnedRowIds ||
!row.getIsPinned()
)
return [];
return [
[...pinnedRowIds].reverse().indexOf(row.id),
pinnedRowIds.indexOf(row.id),
];
}, [pinnedRowIds, rowPinning]);
const tableHeadHeight =
((enableStickyHeader || isFullScreen) &&
tableHeadRef.current?.clientHeight) ||
0;
const tableFooterHeight =
(enableStickyFooter && tableFooterRef.current?.clientHeight) || 0;
const sx = parseFromValuesOrFunc(tableRowProps?.sx, theme);
const defaultRowHeight =
density === 'compact' ? 37 : density === 'comfortable' ? 53 : 69;
const customRowHeight =
// @ts-ignore
parseInt(tableRowProps?.style?.height ?? sx?.height, 10) || undefined;
const rowHeight = customRowHeight || defaultRowHeight;
const handleDragEnter = (_e) => {
if (enableRowOrdering && draggingRow) {
setHoveredRow(row);
}
};
const handleDragOver = (e) => {
e.preventDefault();
};
const rowRef = useRef(null);
const cellHighlightColor = isRowSelected
? selectedRowBackgroundColor
: isRowPinned
? pinnedRowBackgroundColor
: undefined;
const cellHighlightColorHover =
tableRowProps?.hover !== false
? isRowSelected
? cellHighlightColor
: theme.palette.mode === 'dark'
? `${lighten(baseBackgroundColor, 0.3)}`
: `${darken(baseBackgroundColor, 0.3)}`
: undefined;
return (
<>
<TableRow
data-index={renderDetailPanel ? staticRowIndex * 2 : staticRowIndex}
data-pinned={!!isRowPinned || undefined}
data-selected={isRowSelected || undefined}
onDragEnter={handleDragEnter}
onDragOver={handleDragOver}
ref={(node) => {
if (node) {
rowRef.current = node;
rowVirtualizer?.measureElement(node);
}
}}
selected={isRowSelected}
{...tableRowProps}
style={{
transform: virtualRow
? `translateY(${virtualRow.start}px)`
: undefined,
...tableRowProps?.style,
}}
sx={(theme) => ({
'&:hover td:after': cellHighlightColorHover
? {
backgroundColor: alpha(cellHighlightColorHover, 0.3),
...commonCellBeforeAfterStyles,
}
: undefined,
backgroundColor: `${baseBackgroundColor} !important`,
bottom:
!virtualRow && bottomPinnedIndex !== undefined && isRowPinned
? `${bottomPinnedIndex * rowHeight +
(enableStickyFooter ? tableFooterHeight - 1 : 0)
}px`
: undefined,
boxSizing: 'border-box',
display: layoutMode?.startsWith('grid') ? 'flex' : undefined,
opacity: isRowPinned ? 0.97 : isDraggingRow || isHoveredRow ? 0.5 : 1,
position: virtualRow
? 'absolute'
: rowPinningDisplayMode?.includes('sticky') && isRowPinned
? 'sticky'
: 'relative',
td: {
...getCommonPinnedCellStyles({ table, theme }),
},
'td:after': cellHighlightColor
? {
backgroundColor: cellHighlightColor,
...commonCellBeforeAfterStyles,
}
: undefined,
top: virtualRow
? 0
: topPinnedIndex !== undefined && isRowPinned
? `${topPinnedIndex * rowHeight +
(enableStickyHeader || isFullScreen ? tableHeadHeight - 1 : 0)
}px`
: undefined,
transition: virtualRow ? 'none' : 'all 150ms ease-in-out',
width: '100%',
zIndex:
rowPinningDisplayMode?.includes('sticky') && isRowPinned ? 2 : 0,
...(sx),
})}
>
{virtualPaddingLeft ? (
<td style={{ display: 'flex', width: virtualPaddingLeft }} />
) : null}
{(virtualColumns ?? visibleCells).map(
(cellOrVirtualCell, staticColumnIndex) => {
let cell = cellOrVirtualCell;
if (columnVirtualizer) {
staticColumnIndex = (cellOrVirtualCell).index;
cell = visibleCells[staticColumnIndex];
}
const props = {
cell,
numRows,
rowRef,
staticColumnIndex,
staticRowIndex,
table,
};
return cell ? (
memoMode === 'cells' &&
cell.column.columnDef.columnDefType === 'data' &&
!draggingColumn &&
!draggingRow &&
editingCell?.id !== cell.id &&
editingRow?.id !== row.id ? (
<Memo_DataTable_TableBodyCell key={cell.id} {...props} />
) : (
<DataTable_TableBodyCell key={cell.id} {...props} />
)
) : null;
},
)}
{virtualPaddingRight ? (
<td style={{ display: 'flex', width: virtualPaddingRight }} />
) : null}
</TableRow>
{renderDetailPanel && !row.getIsGrouped() && (
<DataTable_TableDetailPanel
parentRowRef={rowRef}
row={row}
rowVirtualizer={rowVirtualizer}
staticRowIndex={staticRowIndex}
table={table}
virtualRow={virtualRow}
/>
)}
</>
);
}
export default DataTable_TableBodyRow
export const Memo_DataTable_TableBodyRow = memo(
DataTable_TableBodyRow,
(prev, next) =>
prev.row === next.row && prev.staticRowIndex === next.staticRowIndex,
);

View File

@@ -0,0 +1,93 @@
import { parseFromValuesOrFunc } from "@/core/utils/utils";
import { Collapse, TableCell, TableRow } from "@mui/material";
const DataTable_TableDetailPanel = ({
parentRowRef,
row,
rowVirtualizer,
staticRowIndex,
table,
virtualRow,
...rest
}) => {
const {
getState,
getVisibleLeafColumns,
options: {
layoutMode,
mrtTheme: { baseBackgroundColor },
muiDetailPanelProps,
muiTableBodyRowProps,
renderDetailPanel,
},
} = table;
const { isLoading } = getState();
const tableRowProps = parseFromValuesOrFunc(muiTableBodyRowProps, {
isDetailPanel: true,
row,
staticRowIndex,
table,
});
const tableCellProps = {
...parseFromValuesOrFunc(muiDetailPanelProps, {
row,
table,
}),
...rest,
};
const DetailPanel = !isLoading && renderDetailPanel?.({ row, table });
return (
<TableRow
className="Mui-TableBodyCell-DetailPanel"
data-index={renderDetailPanel ? staticRowIndex * 2 + 1 : staticRowIndex}
ref={(node) => {
if (node) {
rowVirtualizer?.measureElement?.(node);
}
}}
{...tableRowProps}
sx={(theme) => ({
display: layoutMode?.startsWith('grid') ? 'flex' : undefined,
position: virtualRow ? 'absolute' : undefined,
top: virtualRow
? `${parentRowRef.current?.getBoundingClientRect()?.height}px`
: undefined,
transform: virtualRow
? `translateY(${virtualRow?.start}px)`
: undefined,
width: '100%',
...(parseFromValuesOrFunc(tableRowProps?.sx, theme)),
})}
>
<TableCell
className="Mui-TableBodyCell-DetailPanel"
colSpan={getVisibleLeafColumns().length}
{...tableCellProps}
sx={(theme) => ({
backgroundColor: virtualRow ? baseBackgroundColor : undefined,
borderBottom: !row.getIsExpanded() ? 'none' : undefined,
display: layoutMode?.startsWith('grid') ? 'flex' : undefined,
py: !!DetailPanel && row.getIsExpanded() ? '1rem' : 0,
transition: !virtualRow ? 'all 150ms ease-in-out' : undefined,
width: `100%`,
...(parseFromValuesOrFunc(tableCellProps?.sx, theme)),
})}
>
{virtualRow ? (
row.getIsExpanded() && DetailPanel
) : (
<Collapse in={row.getIsExpanded()} mountOnEnter unmountOnExit>
{DetailPanel}
</Collapse>
)}
</TableCell>
</TableRow>
);
}
export default DataTable_TableDetailPanel

View File

@@ -0,0 +1,74 @@
import { getCommonTooltipProps, parseFromValuesOrFunc } from "@/core/utils/utils";
import { Button, Tooltip } from "@mui/material";
const DataTable_CopyButton = ({
cell,
table,
...rest
}) => {
const {
options: { localization, muiCopyButtonProps },
} = table;
const { column, row } = cell;
const { columnDef } = column;
const [copied, setCopied] = useState(false);
const handleCopy = (event, text) => {
event.stopPropagation();
navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 4000);
};
const buttonProps = {
...parseFromValuesOrFunc(muiCopyButtonProps, {
cell,
column,
row,
table,
}),
...parseFromValuesOrFunc(columnDef.muiCopyButtonProps, {
cell,
column,
row,
table,
}),
...rest,
};
return (
<Tooltip
{...getCommonTooltipProps('top')}
title={
buttonProps?.title ??
(copied ? localization.copiedToClipboard : localization.clickToCopy)
}
>
<Button
onClick={(e) => handleCopy(e, cell.getValue())}
size="small"
type="button"
variant="text"
{...buttonProps}
sx={(theme) => ({
backgroundColor: 'transparent',
border: 'none',
color: 'inherit',
cursor: 'copy',
fontFamily: 'inherit',
fontSize: 'inherit',
letterSpacing: 'inherit',
m: '-0.25rem',
minWidth: 'unset',
py: 0,
textAlign: 'inherit',
textTransform: 'inherit',
...(parseFromValuesOrFunc(buttonProps?.sx, theme)),
})}
title={undefined}
/>
</Tooltip>
);
}
export default DataTable_CopyButton

View File

@@ -0,0 +1,54 @@
import { getCommonTooltipProps, parseFromValuesOrFunc } from "@/core/utils/utils";
import { IconButton, Tooltip } from "@mui/material";
const DataTable_GrabHandleButton = ({
location,
table,
...rest
}) => {
const {
options: {
icons: { DragHandleIcon },
localization,
},
} = table;
return (
<Tooltip
{...getCommonTooltipProps('top')}
title={rest?.title ?? localization.move}
>
<IconButton
aria-label={rest.title ?? localization.move}
disableRipple
draggable="true"
size="small"
{...rest}
onClick={(e) => {
e.stopPropagation();
rest?.onClick?.(e);
}}
sx={(theme) => ({
'&:active': {
cursor: 'grabbing',
},
'&:hover': {
backgroundColor: 'transparent',
opacity: 1,
},
cursor: 'grab',
m: '0 -0.1rem',
opacity: location === 'row' ? 1 : 0.5,
p: '2px',
transition: 'all 150ms ease-in-out',
...(parseFromValuesOrFunc(rest?.sx, theme)),
})}
title={undefined}
>
<DragHandleIcon />
</IconButton>
</Tooltip>
);
}
export default DataTable_GrabHandleButton

View File

@@ -0,0 +1,41 @@
import { IconButton, Tooltip } from "@mui/material";
import { useState } from "react";
import DataTable_ShowHideColumnsMenu from "../menus/ShowHideColumnsMenu";
const DataTable_ShowHideColumnsButton = ({ table, ...rest }) => {
const {
options: {
icons: { ViewColumnIcon },
localization,
},
} = table;
const [anchorEl, setAnchorEl] = useState(null);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
return (
<>
<Tooltip title={rest?.title ?? localization.showHideColumns}>
<IconButton
aria-label={localization.showHideColumns}
onClick={handleClick}
{...rest}
title={undefined}
>
<ViewColumnIcon />
</IconButton>
</Tooltip>
{anchorEl && (
<DataTable_ShowHideColumnsMenu
anchorEl={anchorEl}
setAnchorEl={setAnchorEl}
table={table}
/>
)}
</>
);
}
export default DataTable_ShowHideColumnsButton

View File

@@ -0,0 +1,4 @@
const DataTable_ToggleFiltersButton = ({ table, ...rest }) => {
return <></>
}
export default DataTable_ToggleFiltersButton

View File

@@ -0,0 +1,63 @@
import { parseFromValuesOrFunc } from "@/core/utils/utils";
import { TableHead } from "@mui/material";
import DataTable_TableHeadRow from "./TableHeadRow";
const DataTable_TableHead = ({
columnVirtualizer,
table,
...rest
}) => {
const {
getState,
options: {
enableStickyHeader,
layoutMode,
muiTableHeadProps,
positionToolbarAlertBanner,
},
refs: { tableHeadRef },
} = table;
const { isFullScreen, showAlertBanner } = getState();
const tableHeadProps = {
...parseFromValuesOrFunc(muiTableHeadProps, { table }),
...rest,
};
const stickyHeader = enableStickyHeader || isFullScreen;
return (
<TableHead
{...tableHeadProps}
ref={(ref) => {
tableHeadRef.current = ref;
if (tableHeadProps?.ref) {
// @ts-ignore
tableHeadProps.ref.current = ref;
}
}}
sx={(theme) => ({
display: layoutMode?.startsWith('grid') ? 'grid' : undefined,
opacity: 0.97,
position: stickyHeader ? 'sticky' : 'relative',
top: stickyHeader && layoutMode?.startsWith('grid') ? 0 : undefined,
zIndex: stickyHeader ? 2 : undefined,
...(parseFromValuesOrFunc(tableHeadProps?.sx, theme)),
})}
>
{table
.getHeaderGroups()
.map((headerGroup) => (
<DataTable_TableHeadRow
columnVirtualizer={columnVirtualizer}
headerGroup={headerGroup}
key={headerGroup.id}
table={table}
/>
))
}
</TableHead>
);
}
export default DataTable_TableHead

View File

@@ -0,0 +1,284 @@
import { getCommonMRTCellStyles, parseFromValuesOrFunc } from "@/core/utils/utils";
import { useTheme } from "@emotion/react";
import { Box, TableCell } from "@mui/material";
import { MRT_TableHeadCellSortLabel } from "material-react-table";
import { useMemo } from "react";
const DataTable_TableHeadCell = ({
columnVirtualizer,
header,
staticColumnIndex,
table,
...rest
}) => {
const theme = useTheme();
const {
getState,
options: {
columnResizeDirection,
columnResizeMode,
enableColumnActions,
enableColumnDragging,
enableColumnOrdering,
enableColumnPinning,
enableGrouping,
enableMultiSort,
layoutMode,
mrtTheme: { draggingBorderColor },
muiTableHeadCellProps,
},
refs: { tableHeadCellRefs },
setHoveredColumn,
} = table;
const {
columnSizingInfo,
density,
draggingColumn,
grouping,
hoveredColumn,
showColumnFilters,
} = getState();
const { column } = header;
const { columnDef } = column;
const { columnDefType } = columnDef;
const tableCellProps = {
...parseFromValuesOrFunc(muiTableHeadCellProps, { column, table }),
...parseFromValuesOrFunc(columnDef.muiTableHeadCellProps, {
column,
table,
}),
...rest,
};
const isColumnPinned =
enableColumnPinning &&
columnDef.columnDefType !== 'group' &&
column.getIsPinned();
const showColumnActions =
(enableColumnActions || columnDef.enableColumnActions) &&
columnDef.enableColumnActions !== false;
const showDragHandle =
enableColumnDragging !== false &&
columnDef.enableColumnDragging !== false &&
(enableColumnDragging ||
(enableColumnOrdering && columnDef.enableColumnOrdering !== false) ||
(enableGrouping &&
columnDef.enableGrouping !== false &&
!grouping.includes(column.id)));
const headerPL = useMemo(() => {
let pl = 0;
if (column.getCanSort()) pl += 1;
if (showColumnActions) pl += 1.75;
if (showDragHandle) pl += 1.5;
return pl;
}, [showColumnActions, showDragHandle]);
const draggingBorders = useMemo(() => {
const showResizeBorder =
columnSizingInfo.isResizingColumn === column.id &&
columnResizeMode === 'onChange' &&
!header.subHeaders.length;
const borderStyle = showResizeBorder
? `2px solid ${draggingBorderColor} !important`
: draggingColumn?.id === column.id
? `1px dashed ${theme.palette.grey[500]}`
: hoveredColumn?.id === column.id
? `2px dashed ${draggingBorderColor}`
: undefined;
if (showResizeBorder) {
return columnResizeDirection === 'ltr'
? { borderRight: borderStyle }
: { borderLeft: borderStyle };
}
const draggingBorders = borderStyle
? {
borderLeft: borderStyle,
borderRight: borderStyle,
borderTop: borderStyle,
}
: undefined;
return draggingBorders;
}, [draggingColumn, hoveredColumn, columnSizingInfo.isResizingColumn]);
const handleDragEnter = (_e) => {
if (enableGrouping && hoveredColumn?.id === 'drop-zone') {
setHoveredColumn(null);
}
if (enableColumnOrdering && draggingColumn && columnDefType !== 'group') {
setHoveredColumn(
columnDef.enableColumnOrdering !== false ? column : null,
);
}
};
const handleDragOver = (e) => {
if (columnDef.enableColumnOrdering !== false) {
e.preventDefault();
}
};
const HeaderElement =
parseFromValuesOrFunc(columnDef.Header, {
column,
header,
table,
}) ?? columnDef.header;
const columnRelativeDepth = header.depth - column.depth;
if (
columnRelativeDepth > 1
) {
return null;
}
let rowSpan = 1;
if (header.isPlaceholder) {
const leafs = header.getLeafHeaders();
rowSpan = leafs[leafs.length - 1].depth - header.depth;
}
return (
<TableCell
align={
columnDefType === 'group'
? 'center'
: theme.direction === 'rtl'
? 'right'
: 'left'
}
colSpan={header.colSpan}
rowSpan={rowSpan}
data-index={staticColumnIndex}
data-pinned={!!isColumnPinned || undefined}
onDragEnter={handleDragEnter}
onDragOver={handleDragOver}
ref={(node) => {
if (node) {
tableHeadCellRefs.current[column.id] = node;
if (columnDefType !== 'group') {
columnVirtualizer?.measureElement?.(node);
}
}
}}
{...tableCellProps}
sx={(theme) => ({
'& :hover': {
'.MuiButtonBase-root': {
opacity: 1,
},
},
border: 1,
flexDirection: layoutMode?.startsWith('grid') ? 'column' : undefined,
fontWeight: 'bold',
overflow: 'visible',
p:
density === 'compact'
? '0.5rem'
: density === 'comfortable'
? columnDefType === 'display'
? '0.75rem'
: '1rem'
: columnDefType === 'display'
? '1rem 1.25rem'
: '1.5rem',
pb:
columnDefType === 'display'
? 0
: showColumnFilters || density === 'compact'
? '0.4rem'
: '0.6rem',
pt:
columnDefType === 'group' || density === 'compact'
? '0.25rem'
: density === 'comfortable'
? '.75rem'
: '1.25rem',
userSelect: enableMultiSort && column.getCanSort() ? 'none' : undefined,
verticalAlign: 'top',
...getCommonMRTCellStyles({
column,
header,
table,
tableCellProps,
theme,
}),
...draggingBorders,
}
)}
>
{tableCellProps.children ?? (
<Box
className="Mui-TableHeadCell-Content"
sx={{
alignItems: 'center',
display: 'flex',
flexDirection:
tableCellProps?.align === 'right' ? 'row-reverse' : 'row',
justifyContent:
columnDefType === 'group' ||
tableCellProps?.align === 'center'
? 'center'
: column.getCanResize()
? 'space-between'
: 'flex-start',
position: 'relative',
width: '100%',
}}
>
<Box
className="Mui-TableHeadCell-Content-Labels"
onClick={column.getToggleSortingHandler()}
sx={{
alignItems: 'center',
cursor:
column.getCanSort() && columnDefType !== 'group'
? 'pointer'
: undefined,
display: 'flex',
flexDirection:
tableCellProps?.align === 'right' ? 'row-reverse' : 'row',
overflow: columnDefType === 'data' ? 'hidden' : undefined,
pl:
tableCellProps?.align === 'center'
? `${headerPL}rem`
: undefined,
}}
>
<Box
className="Mui-TableHeadCell-Content-Wrapper"
sx={{
'&:hover': {
textOverflow: 'clip',
},
minWidth: `${Math.min(columnDef.header?.length ?? 0, 4)}ch`,
overflow: columnDefType === 'data' ? 'hidden' : undefined,
textOverflow: 'ellipsis',
whiteSpace:
(columnDef.header?.length ?? 0) < 20
? 'nowrap'
: 'normal',
}}
>
{HeaderElement}
</Box>
{(column.getCanSort() && columnDefType !== 'group') && (
<MRT_TableHeadCellSortLabel sx={{ width: 20 }} header={header} table={table} />
)}
</Box>
</Box>
)}
</TableCell>
);
}
export default DataTable_TableHeadCell;

View File

@@ -0,0 +1,76 @@
import { parseFromValuesOrFunc } from "@/core/utils/utils";
import { TableRow } from "@mui/material";
import DataTable_TableHeadCell from "./TableHeadCell";
const DataTable_TableHeadRow = ({
columnVirtualizer,
headerGroup,
table,
...rest
}) => {
const {
options: {
enableStickyHeader,
layoutMode,
mrtTheme: { baseBackgroundColor },
muiTableHeadRowProps,
},
} = table;
const { virtualColumns, virtualPaddingLeft, virtualPaddingRight } =
columnVirtualizer ?? {};
const tableRowProps = {
...parseFromValuesOrFunc(muiTableHeadRowProps, {
headerGroup,
table,
}),
...rest,
};
return (
<TableRow
{...tableRowProps}
sx={(theme) => ({
backgroundColor: baseBackgroundColor,
display: layoutMode?.startsWith('grid') ? 'flex' : undefined,
position:
enableStickyHeader && layoutMode === 'semantic'
? 'sticky'
: 'relative',
top: 0,
...(parseFromValuesOrFunc(tableRowProps?.sx, theme)),
})}
>
{virtualPaddingLeft ? (
<th style={{ display: 'flex', width: virtualPaddingLeft }} />
) : null}
{(virtualColumns ?? headerGroup.headers).map(
(headerOrVirtualHeader, staticColumnIndex) => {
let header = headerOrVirtualHeader;
if (columnVirtualizer) {
staticColumnIndex = (headerOrVirtualHeader)
.index;
header = headerGroup.headers[staticColumnIndex];
}
return header ? (
<DataTable_TableHeadCell
columnVirtualizer={columnVirtualizer}
header={header}
key={header.id}
staticColumnIndex={staticColumnIndex}
table={table}
/>
) : null;
},
)}
{virtualPaddingRight ? (
<th style={{ display: 'flex', width: virtualPaddingRight }} />
) : null}
</TableRow>
);
}
export default DataTable_TableHeadRow

View File

@@ -0,0 +1,19 @@
import { useMaterialReactTable } from "material-react-table";
import DataTable_Paper from "./table/Paper";
const isTableInstanceProp = (props) => props.table !== undefined;
const DataTable = (props) => {
let table;
if (isTableInstanceProp(props)) {
table = props.table;
} else {
// eslint-disable-next-line react-hooks/rules-of-hooks
table = useMaterialReactTable(props);
}
return <DataTable_Paper table={table} />;
};
export default DataTable;

View File

@@ -0,0 +1,50 @@
import { Box, IconButton, ListItemIcon, MenuItem } from "@mui/material";
const DataTable_ActionMenuItem = ({
icon,
label,
onOpenSubMenu,
table,
...rest
}) => {
const {
options: {
icons: { ArrowRightIcon },
},
} = table;
return (
<MenuItem
sx={{
alignItems: 'center',
justifyContent: 'space-between',
minWidth: '120px',
my: 0,
py: '6px',
}}
{...rest}
>
<Box
sx={{
alignItems: 'center',
display: 'flex',
}}
>
<ListItemIcon>{icon}</ListItemIcon>
{label}
</Box>
{onOpenSubMenu && (
<IconButton
onClick={onOpenSubMenu}
onMouseEnter={onOpenSubMenu}
size="small"
sx={{ p: 0 }}
>
<ArrowRightIcon />
</IconButton>
)}
</MenuItem>
);
}
export default DataTable_ActionMenuItem

View File

@@ -0,0 +1,100 @@
import { parseFromValuesOrFunc } from "@/core/utils/utils";
import { Menu } from "@mui/material";
import DataTable_ActionMenuItem from "./ActionMenuItem";
const DataTable_CellActionMenu = ({
table,
...rest
}) => {
const {
getState,
options: {
editDisplayMode,
enableClickToCopy,
enableEditing,
icons: { ContentCopy, EditIcon },
localization,
mrtTheme: { menuBackgroundColor },
renderCellActionMenuItems,
},
refs: { actionCellRef },
} = table;
const { actionCell, density } = getState();
const cell = actionCell || null;
const { row } = cell;
const { column } = cell;
const { columnDef } = column;
const handleClose = (event) => {
event?.stopPropagation();
table.setActionCell(null);
actionCellRef.current = null;
};
const internalMenuItems = [
(parseFromValuesOrFunc(enableClickToCopy, cell) === 'context-menu' ||
parseFromValuesOrFunc(columnDef.enableClickToCopy, cell) ===
'context-menu') && (
<DataTable_ActionMenuItem
icon={<ContentCopy />}
key={'mrt-copy'}
label={localization.copy}
onClick={(event) => {
event.stopPropagation();
navigator.clipboard.writeText(cell.getValue());
handleClose();
}}
table={table}
/>
),
parseFromValuesOrFunc(enableEditing, row) && editDisplayMode === 'cell' && (
<DataTable_ActionMenuItem
icon={<EditIcon />}
key={'mrt-edit'}
label={localization.edit}
onClick={() => {
openEditingCell({ cell, table });
handleClose();
}}
table={table}
/>
),
].filter(Boolean);
const renderActionProps = {
cell,
closeMenu: handleClose,
column,
internalMenuItems,
row,
table,
};
const menuItems =
columnDef.renderCellActionMenuItems?.(renderActionProps) ??
renderCellActionMenuItems?.(renderActionProps);
return (
(!!menuItems?.length || !!internalMenuItems?.length) && (
<Menu
MenuListProps={{
dense: density === 'compact',
sx: {
backgroundColor: menuBackgroundColor,
},
}}
anchorEl={actionCellRef.current}
disableScrollLock
onClick={(event) => event.stopPropagation()}
onClose={handleClose}
open={!!cell}
transformOrigin={{ horizontal: -100, vertical: 8 }}
{...rest}
>
{menuItems ?? internalMenuItems}
</Menu>
)
);
}
export default DataTable_CellActionMenu

View File

@@ -0,0 +1,140 @@
import { Box, Button, Divider, Menu } from "@mui/material";
import { useMemo, useState } from "react";
import DataTable_ShowHideColumnsMenuItems from "./ShowHideColumnsMenuItems";
const DataTable_ShowHideColumnsMenu = ({ anchorEl,
setAnchorEl,
table,
...rest
}) => {
const {
getAllColumns,
getAllLeafColumns,
getCenterLeafColumns,
getIsAllColumnsVisible,
getIsSomeColumnsPinned,
getIsSomeColumnsVisible,
getLeftLeafColumns,
getRightLeafColumns,
getState,
options: {
enableColumnOrdering,
enableColumnPinning,
enableHiding,
localization,
mrtTheme: { menuBackgroundColor },
},
} = table;
const { columnOrder, columnPinning, density } = getState();
const handleToggleAllColumns = (value) => {
getAllLeafColumns()
.filter((col) => col.columnDef.enableHiding !== false)
.forEach((col) => col.toggleVisibility(value));
};
const allColumns = useMemo(() => {
const columns = getAllColumns();
if (
columnOrder.length > 0 &&
!columns.some((col) => col.columnDef.columnDefType === 'group')
) {
return [
...getLeftLeafColumns(),
...Array.from(new Set(columnOrder)).map((colId) =>
getCenterLeafColumns().find((col) => col?.id === colId),
),
...getRightLeafColumns(),
].filter(Boolean);
}
return columns;
}, [
columnOrder,
columnPinning,
getAllColumns(),
getCenterLeafColumns(),
getLeftLeafColumns(),
getRightLeafColumns(),
]);
const isNestedColumns = allColumns.some(
(col) => col.columnDef.columnDefType === 'group',
);
const [hoveredColumn, setHoveredColumn] = useState(null);
return (
<Menu
MenuListProps={{
dense: density === 'compact',
sx: {
backgroundColor: menuBackgroundColor,
},
}}
anchorEl={anchorEl}
disableScrollLock
onClose={() => setAnchorEl(null)}
open={!!anchorEl}
{...rest}
>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
p: '0.5rem',
pt: 0,
}}
>
{enableHiding && (
<Button
disabled={!getIsSomeColumnsVisible()}
onClick={() => handleToggleAllColumns(false)}
>
{localization.hideAll}
</Button>
)}
{enableColumnOrdering && (
<Button
onClick={() =>
table.setColumnOrder(
getDefaultColumnOrderIds(table.options, true),
)
}
>
{localization.resetOrder}
</Button>
)}
{enableColumnPinning && (
<Button
disabled={!getIsSomeColumnsPinned()}
onClick={() => table.resetColumnPinning(true)}
>
{localization.unpinAll}
</Button>
)}
{enableHiding && (
<Button
disabled={getIsAllColumnsVisible()}
onClick={() => handleToggleAllColumns(true)}
>
{localization.showAll}
</Button>
)}
</Box>
<Divider />
{allColumns.map((column, index) => (
<DataTable_ShowHideColumnsMenuItems
allColumns={allColumns}
column={column}
hoveredColumn={hoveredColumn}
isNestedColumns={isNestedColumns}
key={`${index}-${column.id}`}
setHoveredColumn={setHoveredColumn}
table={table}
/>
))}
</Menu>
);
}
export default DataTable_ShowHideColumnsMenu

View File

@@ -0,0 +1,160 @@
import { getCommonTooltipProps, parseFromValuesOrFunc } from "@/core/utils/utils";
import { Box, FormControlLabel, MenuItem, Switch, Tooltip, Typography } from "@mui/material";
import { useRef, useState } from "react";
import DataTable_GrabHandleButton from "../buttons/GrabHandleButton";
const DataTable_ShowHideColumnsMenuItems = ({
allColumns,
column,
hoveredColumn,
isNestedColumns,
setHoveredColumn,
table,
...rest
}) => {
const {
getState,
options: {
enableColumnOrdering,
enableHiding,
localization,
mrtTheme: { draggingBorderColor },
},
setColumnOrder,
} = table;
const { columnOrder } = getState();
const { columnDef } = column;
const { columnDefType } = columnDef;
const switchChecked = column.getIsVisible();
const handleToggleColumnHidden = (column) => {
if (columnDefType === 'group') {
column?.columns?.forEach?.((childColumn) => {
childColumn.toggleVisibility(!switchChecked);
});
} else {
column.toggleVisibility();
}
};
const menuItemRef = useRef(null);
const [isDragging, setIsDragging] = useState(false);
const handleDragStart = (e) => {
setIsDragging(true);
try {
e.dataTransfer.setDragImage(menuItemRef.current, 0, 0);
} catch (e) {
console.error(e);
}
};
const handleDragEnd = (_e) => {
setIsDragging(false);
setHoveredColumn(null);
if (hoveredColumn) {
setColumnOrder(reorderColumn(column, hoveredColumn, columnOrder));
}
};
const handleDragEnter = (_e) => {
if (!isDragging && columnDef.enableColumnOrdering !== false) {
setHoveredColumn(column);
}
};
if (!columnDef.header || columnDef.visibleInShowHideMenu === false) {
return null;
}
return (
<>
<MenuItem
disableRipple
onDragEnter={handleDragEnter}
ref={menuItemRef}
{...rest}
sx={(theme) => ({
alignItems: 'center',
justifyContent: 'flex-start',
my: 0,
opacity: isDragging ? 0.5 : 1,
outline: isDragging
? `2px dashed ${theme.palette.grey[500]}`
: hoveredColumn?.id === column.id
? `2px dashed ${draggingBorderColor}`
: 'none',
outlineOffset: '-2px',
pl: `${(column.depth + 0.5) * 2}rem`,
py: '6px',
...(parseFromValuesOrFunc(rest?.sx, theme)),
})}
>
<Box
sx={{
display: 'flex',
flexWrap: 'nowrap',
gap: '8px',
}}
>
{columnDefType !== 'group' &&
enableColumnOrdering &&
!isNestedColumns &&
(columnDef.enableColumnOrdering !== false ? (
<DataTable_GrabHandleButton
onDragEnd={handleDragEnd}
onDragStart={handleDragStart}
table={table}
/>
) : (
<Box sx={{ width: '28px' }} />
))}
{enableHiding ? (
<FormControlLabel
checked={switchChecked}
componentsProps={{
typography: {
sx: {
mb: 0,
opacity: columnDefType !== 'display' ? 1 : 0.5,
},
},
}}
control={
<Tooltip
{...getCommonTooltipProps()}
title={localization.toggleVisibility}
>
<Switch />
</Tooltip>
}
disabled={!column.getCanHide()}
label={columnDef.header}
onChange={() => handleToggleColumnHidden(column)}
/>
) : (
<Typography sx={{ alignSelf: 'center' }}>
{columnDef.header}
</Typography>
)}
</Box>
</MenuItem>
{column.columns?.map((c, i) => (
<DataTable_ShowHideColumnsMenuItems
allColumns={allColumns}
column={c}
hoveredColumn={hoveredColumn}
isNestedColumns={isNestedColumns}
key={`${i}-${c.id}`}
setHoveredColumn={setHoveredColumn}
table={table}
/>
))}
</>
);
}
export default DataTable_ShowHideColumnsMenuItems;

View File

@@ -0,0 +1,78 @@
import { parseFromValuesOrFunc } from "@/core/utils/utils";
import { Paper } from "@mui/material";
import DataTable_BottomToolbar from "../toolbar/BottomToolbar";
import DataTable_TopToolbar from "../toolbar/TopToolbar";
import DataTable_TableContainer from "./TableContainer";
const DataTable_Paper = ({ table, ...rest }) => {
const {
getState,
options: {
enableBottomToolbar,
enableTopToolbar,
mrtTheme: { baseBackgroundColor },
muiTablePaperProps,
renderBottomToolbar,
renderTopToolbar,
},
refs: { tablePaperRef },
} = table;
const { isFullScreen } = getState();
const paperProps = {
...parseFromValuesOrFunc(muiTablePaperProps, { table }),
...rest,
};
return (
<Paper
elevation={0}
{...paperProps}
ref={(ref) => {
tablePaperRef.current = ref;
if (paperProps?.ref) {
//@ts-ignore
paperProps.ref.current = ref;
}
}}
style={{
...(isFullScreen
? {
bottom: 0,
height: '100dvh',
left: 0,
margin: 0,
maxHeight: '100dvh',
maxWidth: '100dvw',
padding: 0,
position: 'fixed',
right: 0,
top: 0,
width: '100dvw',
zIndex: 999,
}
: {}),
...paperProps?.style,
}}
sx={(theme) => ({
backgroundColor: baseBackgroundColor,
backgroundImage: 'unset',
overflow: 'hidden',
transition: 'all 100ms ease-in-out',
...(parseFromValuesOrFunc(paperProps?.sx, theme)),
})}
>
{enableTopToolbar &&
(parseFromValuesOrFunc(renderTopToolbar, { table }) ?? (
<DataTable_TopToolbar table={table} />
))}
<DataTable_TableContainer table={table} />
{enableBottomToolbar &&
(parseFromValuesOrFunc(renderBottomToolbar, { table }) ?? (
<DataTable_BottomToolbar table={table} />
))}
</Paper>
);
};
export default DataTable_Paper;

View File

@@ -0,0 +1,78 @@
import { parseCSSVarId, parseFromValuesOrFunc } from "@/core/utils/utils";
import { Table } from "@mui/material";
import { useMRT_ColumnVirtualizer } from "material-react-table";
import { useMemo } from "react";
import DataTable_TableBody, { Memo_DataTable_TableBody } from "../body/TableBody";
import DataTable_TableHead from "../head/TableHead";
const DataTable_Table = ({
table,
...rest
}) => {
const {
getFlatHeaders,
getState,
options: {
columns,
enableStickyHeader,
enableTableFooter,
enableTableHead,
layoutMode,
memoMode,
muiTableProps,
renderCaption,
},
} = table;
const { columnSizing, columnSizingInfo, columnVisibility, isFullScreen } =
getState();
const tableProps = {
...parseFromValuesOrFunc(muiTableProps, { table }),
...rest,
};
const Caption = parseFromValuesOrFunc(renderCaption, { table });
const columnSizeVars = useMemo(() => {
const headers = getFlatHeaders();
const colSizes = {};
for (let i = 0; i < headers.length; i++) {
const header = headers[i];
const colSize = header.getSize();
colSizes[`--header-${parseCSSVarId(header.id)}-size`] = colSize;
colSizes[`--col-${parseCSSVarId(header.column.id)}-size`] = colSize;
}
return colSizes;
}, [columns, columnSizing, columnSizingInfo, columnVisibility]);
const columnVirtualizer = useMRT_ColumnVirtualizer(table);
const commonTableGroupProps = {
columnVirtualizer,
table,
};
return (
<Table
stickyHeader={enableStickyHeader || isFullScreen}
{...tableProps}
style={{ ...columnSizeVars, ...tableProps?.style }}
sx={(theme) => ({
borderCollapse: 'separate',
display: layoutMode?.startsWith('grid') ? 'grid' : undefined,
position: 'relative',
...(parseFromValuesOrFunc(tableProps?.sx, theme)),
})}
>
{!!Caption && <caption>{Caption}</caption>}
{enableTableHead && <DataTable_TableHead {...commonTableGroupProps} />}
{memoMode === 'table-body' || columnSizingInfo.isResizingColumn ? (
<Memo_DataTable_TableBody {...commonTableGroupProps} />
) : (
<DataTable_TableBody {...commonTableGroupProps} />
)}
{/* {enableTableFooter && <MRT_TableFooter {...commonTableGroupProps} />} */}
</Table>
);
}
export default DataTable_Table

View File

@@ -0,0 +1,94 @@
import { parseFromValuesOrFunc } from "@/core/utils/utils";
import { TableContainer } from "@mui/material";
import { useEffect, useLayoutEffect, useState } from "react";
import DataTable_CellActionMenu from "../menus/CellActionMenu";
import DataTable_Table from "./Table";
import DataTable_TableLoadingOverlay from "./TableLoadingOverlay";
const useIsomorphicLayoutEffect =
typeof window !== 'undefined' ? useLayoutEffect : useEffect;
const DataTable_TableContainer = ({
table,
...rest
}) => {
const {
getState,
options: {
enableCellActions,
enableStickyHeader,
muiTableContainerProps,
},
refs: { bottomToolbarRef, tableContainerRef, topToolbarRef },
} = table;
const {
actionCell,
isFullScreen,
isLoading,
showLoadingOverlay,
} = getState();
const loading =
showLoadingOverlay !== false && (isLoading || showLoadingOverlay);
const [totalToolbarHeight, setTotalToolbarHeight] = useState(0);
const tableContainerProps = {
...parseFromValuesOrFunc(muiTableContainerProps, {
table,
}),
...rest,
};
useIsomorphicLayoutEffect(() => {
const topToolbarHeight =
typeof document !== 'undefined'
? topToolbarRef.current?.offsetHeight ?? 0
: 0;
const bottomToolbarHeight =
typeof document !== 'undefined'
? bottomToolbarRef?.current?.offsetHeight ?? 0
: 0;
setTotalToolbarHeight(topToolbarHeight + bottomToolbarHeight);
});
return (
<TableContainer
aria-busy={loading}
aria-describedby={loading ? 'mrt-progress' : undefined}
{...tableContainerProps}
ref={(node) => {
if (node) {
tableContainerRef.current = node;
if (tableContainerProps?.ref) {
//@ts-ignore
tableContainerProps.ref.current = node;
}
}
}}
style={{
maxHeight: isFullScreen
? `calc(100vh - ${totalToolbarHeight}px)`
: undefined,
...tableContainerProps?.style,
}}
sx={(theme) => ({
maxHeight: enableStickyHeader
? `clamp(350px, calc(100vh - ${totalToolbarHeight}px), 9999px)`
: undefined,
maxWidth: '100%',
overflow: 'auto',
position: 'relative',
...(parseFromValuesOrFunc(tableContainerProps?.sx, theme)),
})}
>
{loading ? <DataTable_TableLoadingOverlay table={table} /> : null}
<DataTable_Table table={table} />
{enableCellActions && actionCell && <DataTable_CellActionMenu table={table} />}
</TableContainer>
);
}
export default DataTable_TableContainer

View File

@@ -0,0 +1,48 @@
import { Box, CircularProgress } from "@mui/material";
const DataTable_TableLoadingOverlay = ({
table,
...rest
}) => {
const {
options: {
localization,
mrtTheme: { baseBackgroundColor },
muiCircularProgressProps,
},
} = table;
const circularProgressProps = {
...parseFromValuesOrFunc(muiCircularProgressProps, { table }),
...rest,
};
return (
<Box
sx={{
alignItems: 'center',
backgroundColor: alpha(baseBackgroundColor, 0.5),
bottom: 0,
display: 'flex',
justifyContent: 'center',
left: 0,
maxHeight: '100vh',
position: 'absolute',
right: 0,
top: 0,
width: '100%',
zIndex: 3,
}}
>
{circularProgressProps?.Component ?? (
<CircularProgress
aria-label={localization.noRecordsToDisplay}
id="mrt-progress"
{...circularProgressProps}
/>
)}
</Box>
);
}
export default DataTable_TableLoadingOverlay

View File

@@ -0,0 +1,88 @@
import { getCommonToolbarStyles, parseFromValuesOrFunc } from "@/core/utils/utils";
import { Box, alpha, useMediaQuery } from "@mui/material";
import DataTable_LinearProgressBar from "./LinearProgressBar";
import DataTable_TablePagination from "./TablePagination";
const DataTable_BottomToolbar = ({
table,
...rest
}) => {
const {
getState,
options: {
enablePagination,
muiBottomToolbarProps,
positionPagination,
renderBottomToolbarCustomActions,
},
refs: { bottomToolbarRef },
} = table;
const { isFullScreen } = getState();
const isMobile = useMediaQuery('(max-width:720px)');
const toolbarProps = {
...parseFromValuesOrFunc(muiBottomToolbarProps, { table }),
...rest,
};
const stackAlertBanner = isMobile || !!renderBottomToolbarCustomActions;
return (
<Box
{...toolbarProps}
ref={(node) => {
if (node) {
bottomToolbarRef.current = node;
if (toolbarProps?.ref) {
// @ts-ignore
toolbarProps.ref.current = node;
}
}
}}
sx={(theme) => ({
...getCommonToolbarStyles({ table, theme }),
bottom: isFullScreen ? '0' : undefined,
boxShadow: `0 1px 2px -1px ${alpha(
theme.palette.grey[700],
0.5,
)} inset`,
left: 0,
position: isFullScreen ? 'fixed' : 'relative',
right: 0,
...(parseFromValuesOrFunc(toolbarProps?.sx, theme)),
})}
>
<DataTable_LinearProgressBar isTopToolbar={false} table={table} />
<Box
sx={{
alignItems: 'center',
boxSizing: 'border-box',
display: 'flex',
justifyContent: 'space-between',
p: '0.5rem',
width: '100%',
}}
>
{renderBottomToolbarCustomActions ? (
renderBottomToolbarCustomActions({ table })
) : (
<span />
)}
<Box
sx={{
display: 'flex',
justifyContent: 'flex-end',
position: stackAlertBanner ? 'relative' : 'absolute',
right: 0,
top: 0,
}}
>
{enablePagination &&
['both', 'bottom'].includes(positionPagination ?? '') && (
<DataTable_TablePagination position="bottom" table={table} />
)}
</Box>
</Box>
</Box>
);
}
export default DataTable_BottomToolbar

View File

@@ -0,0 +1,44 @@
import { parseFromValuesOrFunc } from "@/core/utils/utils";
import { Collapse, LinearProgress } from "@mui/material";
const DataTable_LinearProgressBar = ({
isTopToolbar,
table,
...rest
}) => {
const {
getState,
options: { muiLinearProgressProps },
} = table;
const { isSaving, showProgressBars } = getState();
const linearProgressProps = {
...parseFromValuesOrFunc(muiLinearProgressProps, {
isTopToolbar,
table,
}),
...rest,
};
return (
<Collapse
in={showProgressBars !== false && (showProgressBars || isSaving)}
mountOnEnter
sx={{
bottom: isTopToolbar ? 0 : undefined,
position: 'absolute',
top: !isTopToolbar ? 0 : undefined,
width: '100%',
}}
unmountOnExit
>
<LinearProgress
aria-busy="true"
aria-label="Loading"
sx={{ position: 'relative' }}
{...linearProgressProps}
/>
</Collapse>
);
}
export default DataTable_LinearProgressBar

View File

@@ -0,0 +1,217 @@
import { flipIconStyles, getCommonTooltipProps, parseFromValuesOrFunc } from "@/core/utils/utils";
import { useTheme } from "@emotion/react";
import { Box, IconButton, InputLabel, MenuItem, Pagination, PaginationItem, Select, Tooltip, Typography, useMediaQuery } from "@mui/material";
const defaultRowsPerPage = [5, 10, 15, 20, 25, 30, 50, 100];
const DataTable_TablePagination = ({
position = 'bottom',
table,
...rest
}) => {
const theme = useTheme();
const isMobile = useMediaQuery('(max-width: 720px)');
const {
getState,
options: {
enableToolbarInternalActions,
icons: { ChevronLeftIcon, ChevronRightIcon, FirstPageIcon, LastPageIcon },
localization,
muiPaginationProps,
paginationDisplayMode,
},
} = table;
const {
pagination: { pageIndex = 0, pageSize = 10 },
showGlobalFilter,
} = getState();
const paginationProps = {
...parseFromValuesOrFunc(muiPaginationProps, {
table,
}),
...rest,
};
const totalRowCount = table.getRowCount();
const numberOfPages = table.getPageCount();
const showFirstLastPageButtons = numberOfPages > 2;
const firstRowIndex = pageIndex * pageSize;
const lastRowIndex = Math.min(pageIndex * pageSize + pageSize, totalRowCount);
const {
SelectProps = {},
disabled = false,
rowsPerPageOptions = defaultRowsPerPage,
showFirstButton = showFirstLastPageButtons,
showLastButton = showFirstLastPageButtons,
showRowsPerPage = true,
...restPaginationProps
} = paginationProps ?? {};
const disableBack = pageIndex <= 0 || disabled;
const disableNext = lastRowIndex >= totalRowCount || disabled;
if (isMobile && SelectProps?.native !== false) {
SelectProps.native = true;
}
const tooltipProps = getCommonTooltipProps();
return (
<Box
className="MuiTablePagination-root"
sx={{
alignItems: 'center',
display: 'flex',
flexWrap: 'wrap',
gap: '8px',
justifyContent: { md: 'space-between', sm: 'center' },
justifySelf: 'flex-end',
mt:
position === 'top' &&
enableToolbarInternalActions &&
!showGlobalFilter
? '3rem'
: undefined,
position: 'relative',
px: '8px',
py: '12px',
zIndex: 2,
}}
>
{showRowsPerPage && (
<Box sx={{ alignItems: 'center', display: 'flex', gap: '8px' }}>
<InputLabel htmlFor="mrt-rows-per-page" sx={{ mb: 0 }}>
{localization.rowsPerPage}
</InputLabel>
<Select
MenuProps={{ disableScrollLock: true }}
disableUnderline
disabled={disabled}
inputProps={{
'aria-label': localization.rowsPerPage,
id: 'mrt-rows-per-page',
}}
label={localization.rowsPerPage}
onChange={(event) =>
table.setPageSize(+(event.target.value))
}
sx={{ mb: 0 }}
value={pageSize}
variant="standard"
{...SelectProps}
>
{rowsPerPageOptions.map((option) => {
const value = typeof option !== 'number' ? option.value : option;
const label =
typeof option !== 'number' ? option.label : `${option}`;
return (
SelectProps?.children ??
(SelectProps?.native ? (
<option key={value} value={value}>
{label}
</option>
) : (
<MenuItem key={value} sx={{ m: 0 }} value={value}>
{label}
</MenuItem>
))
);
})}
</Select>
</Box>
)}
{paginationDisplayMode === 'pages' ? (
<Pagination
count={numberOfPages}
disabled={disabled}
onChange={(_e, newPageIndex) => table.setPageIndex(newPageIndex - 1)}
page={pageIndex + 1}
renderItem={(item) => (
<PaginationItem
slots={{
first: FirstPageIcon,
last: LastPageIcon,
next: ChevronRightIcon,
previous: ChevronLeftIcon,
}}
{...item}
/>
)}
showFirstButton={showFirstButton}
showLastButton={showLastButton}
{...restPaginationProps}
/>
) : paginationDisplayMode === 'default' ? (
<>
<Typography
align="center"
component="span"
sx={{ m: '0 4px', minWidth: '8ch' }}
variant="body2"
>{`${lastRowIndex === 0 ? 0 : (firstRowIndex + 1).toLocaleString()
}-${lastRowIndex.toLocaleString()} ${localization.of
} ${totalRowCount.toLocaleString()}`}</Typography>
<Box gap="xs">
{showFirstButton && (
<Tooltip {...tooltipProps} title={localization.goToFirstPage}>
<span>
<IconButton
aria-label={localization.goToFirstPage}
disabled={disableBack}
onClick={() => table.firstPage()}
size="small"
>
<FirstPageIcon {...flipIconStyles(theme)} />
</IconButton>
</span>
</Tooltip>
)}
<Tooltip {...tooltipProps} title={localization.goToPreviousPage}>
<span>
<IconButton
aria-label={localization.goToPreviousPage}
disabled={disableBack}
onClick={() => table.previousPage()}
size="small"
>
<ChevronLeftIcon {...flipIconStyles(theme)} />
</IconButton>
</span>
</Tooltip>
<Tooltip {...tooltipProps} title={localization.goToNextPage}>
<span>
<IconButton
aria-label={localization.goToNextPage}
disabled={disableNext}
onClick={() => table.nextPage()}
size="small"
>
<ChevronRightIcon {...flipIconStyles(theme)} />
</IconButton>
</span>
</Tooltip>
{showLastButton && (
<Tooltip {...tooltipProps} title={localization.goToLastPage}>
<span>
<IconButton
aria-label={localization.goToLastPage}
disabled={disableNext}
onClick={() => table.lastPage()}
size="small"
>
<LastPageIcon {...flipIconStyles(theme)} />
</IconButton>
</span>
</Tooltip>
)}
</Box>
</>
) : null}
</Box>
);
}
export default DataTable_TablePagination

View File

@@ -0,0 +1,47 @@
import { parseFromValuesOrFunc } from "@/core/utils/utils";
import { Box } from "@mui/material";
import DataTable_ShowHideColumnsButton from "../buttons/ShowHideColumnsButton";
import DataTable_ToggleFiltersButton from "../buttons/ToggleFiltersButton";
const DataTable_ToolbarInternalButtons = ({ table, ...rest }) => {
const {
options: {
columnFilterDisplayMode,
enableColumnFilters,
enableColumnOrdering,
enableColumnPinning,
enableFilters,
enableHiding,
renderToolbarInternalActions,
},
} = table;
return (
<Box
{...rest}
sx={(theme) => ({
alignItems: 'center',
display: 'flex',
zIndex: 3,
...(parseFromValuesOrFunc(rest?.sx, theme)),
})}
>
{renderToolbarInternalActions?.({
table,
}) ?? (
<>
{enableFilters &&
enableColumnFilters &&
columnFilterDisplayMode !== 'popover' && (
<DataTable_ToggleFiltersButton table={table} />
)}
{(enableHiding || enableColumnOrdering || enableColumnPinning) && (
<DataTable_ShowHideColumnsButton table={table} />
)}
</>
)}
</Box>
);
}
export default DataTable_ToolbarInternalButtons

View File

@@ -0,0 +1,86 @@
import { getCommonToolbarStyles, parseFromValuesOrFunc } from "@/core/utils/utils";
import { Box, useMediaQuery } from "@mui/material";
import DataTable_LinearProgressBar from "./LinearProgressBar";
import DataTable_TablePagination from "./TablePagination";
import DataTable_ToolbarInternalButtons from "./ToolbarInternalButtons";
const DataTable_TopToolbar = ({ table }) => {
const {
getState,
options: {
enablePagination,
enableToolbarInternalActions,
muiTopToolbarProps,
positionPagination,
renderTopToolbarCustomActions,
},
refs: { topToolbarRef },
} = table;
const { isFullScreen, showGlobalFilter } = getState();
const isMobile = useMediaQuery('(max-width:720px)');
const isTablet = useMediaQuery('(max-width:1024px)');
const toolbarProps = parseFromValuesOrFunc(muiTopToolbarProps, { table });
const stackAlertBanner =
isMobile ||
!!renderTopToolbarCustomActions ||
(showGlobalFilter && isTablet);
return (
<Box
{...toolbarProps}
ref={(ref) => {
topToolbarRef.current = ref;
if (toolbarProps?.ref) {
// @ts-ignore
toolbarProps.ref.current = ref;
}
}}
sx={(theme) => ({
...getCommonToolbarStyles({ table, theme }),
position: isFullScreen ? 'sticky' : 'relative',
top: isFullScreen ? '0' : undefined,
...(parseFromValuesOrFunc(toolbarProps?.sx, theme)),
})}
>
<Box
sx={{
alignItems: 'flex-start',
boxSizing: 'border-box',
display: 'flex',
gap: '0.5rem',
justifyContent: 'space-between',
p: '0.5rem',
position: stackAlertBanner ? 'relative' : 'absolute',
right: 0,
top: 0,
width: '100%',
}}
>
{renderTopToolbarCustomActions?.({ table }) ?? <span />}
{enableToolbarInternalActions && (
<Box
sx={{
alignItems: 'center',
display: 'flex',
flexWrap: 'wrap-reverse',
gap: '0.5rem',
justifyContent: 'flex-end',
}}
>
<DataTable_ToolbarInternalButtons table={table} />
</Box>
)}
</Box>
{enablePagination &&
['both', 'top'].includes(positionPagination ?? '') && (
<DataTable_TablePagination position="top" table={table} />
)}
<DataTable_LinearProgressBar isTopToolbar table={table} />
</Box>
);
}
export default DataTable_TopToolbar

View File

@@ -1,10 +1,11 @@
'use client'
import {createTheme} from "@mui/material";
import { createTheme } from "@mui/material";
const theme = createTheme({
direction: 'rtl',
typography: {
fontFamily: `IRANSansFaNum, sans-serif`,
fontSize: 12
},
});

162
src/core/utils/utils.js Normal file
View File

@@ -0,0 +1,162 @@
import { alpha, darken } from "@mui/material";
export const parseCSSVarId = (id) => id.replace(/[^a-zA-Z0-9]/g, '_');
export const parseFromValuesOrFunc = (
fn,
arg
) => (fn instanceof Function ? fn(arg) : fn);
export const getCommonToolbarStyles = ({
table,
}) => ({
alignItems: 'flex-start',
backgroundColor: table.options.mrtTheme.baseBackgroundColor,
display: 'grid',
flexWrap: 'wrap-reverse',
minHeight: '3.5rem',
overflow: 'hidden',
position: 'relative',
transition: 'all 150ms ease-in-out',
zIndex: 1,
});
export const getCommonTooltipProps = (
placement,
) => ({
disableInteractive: true,
enterDelay: 1000,
enterNextDelay: 1000,
placement,
});
export const flipIconStyles = (theme) =>
theme.direction === 'rtl'
? { style: { transform: 'scaleX(-1)' } }
: undefined;
export const getCommonPinnedCellStyles = ({
column,
table,
theme,
}) => {
const { baseBackgroundColor } = table.options.mrtTheme;
const isPinned = column?.getIsPinned();
return {
'&[data-pinned="true"]': {
'&:before': {
backgroundColor: alpha(
darken(
baseBackgroundColor,
theme.palette.mode === 'dark' ? 0.05 : 0.01,
),
0.97,
),
boxShadow: column
? isPinned === 'left' && column.getIsLastColumn(isPinned)
? `-4px 0 4px -4px ${alpha(theme.palette.grey[700], 0.5)} inset`
: isPinned === 'right' && column.getIsFirstColumn(isPinned)
? `4px 0 4px -4px ${alpha(theme.palette.grey[700], 0.5)} inset`
: undefined
: undefined,
...commonCellBeforeAfterStyles,
},
},
};
};
export const getCommonMRTCellStyles = ({
column,
header,
table,
tableCellProps,
theme,
}) => {
const {
getState,
options: { enableColumnVirtualization, layoutMode },
} = table;
const { draggingColumn } = getState();
const { columnDef } = column;
const { columnDefType } = columnDef;
const isColumnPinned =
columnDef.columnDefType !== 'group' && column.getIsPinned();
const widthStyles = {
minWidth: `max(calc(var(--${header ? 'header' : 'col'}-${parseCSSVarId(
header?.id ?? column.id,
)}-size) * 1px), ${columnDef.minSize ?? 30}px)`,
width: `calc(var(--${header ? 'header' : 'col'}-${parseCSSVarId(
header?.id ?? column.id,
)}-size) * 1px)`,
};
if (layoutMode === 'grid') {
widthStyles.flex = `${[0, false].includes(columnDef.grow)
? 0
: `var(--${header ? 'header' : 'col'}-${parseCSSVarId(
header?.id ?? column.id,
)}-size)`
} 0 auto`;
} else if (layoutMode === 'grid-no-grow') {
widthStyles.flex = `${+(columnDef.grow || 0)} 0 auto`;
}
const pinnedStyles = isColumnPinned
? {
...getCommonPinnedCellStyles({ column, table, theme }),
left:
isColumnPinned === 'left'
? `${column.getStart('left')}px`
: undefined,
opacity: 0.97,
position: 'sticky',
right:
isColumnPinned === 'right'
? `${column.getAfter('right')}px`
: undefined,
}
: {};
return {
backgroundColor: 'inherit',
backgroundImage: 'inherit',
display: layoutMode?.startsWith('grid') ? 'flex' : undefined,
justifyContent:
columnDefType === 'group'
? 'center'
: layoutMode?.startsWith('grid')
? tableCellProps.align
: undefined,
opacity:
table.getState().draggingColumn?.id === column.id ||
table.getState().hoveredColumn?.id === column.id
? 0.5
: 1,
position: 'relative',
transition: enableColumnVirtualization
? 'none'
: `padding 150ms ease-in-out`,
zIndex:
column.getIsResizing() || draggingColumn?.id === column.id
? 2
: columnDefType !== 'group' && isColumnPinned
? 1
: 0,
...pinnedStyles,
...widthStyles,
...(parseFromValuesOrFunc(tableCellProps?.sx, theme)),
};
};
export const commonCellBeforeAfterStyles = {
content: '""',
height: '100%',
left: 0,
position: 'absolute',
top: 0,
width: '100%',
zIndex: -1,
};