UNPKG

@etsoo/materialui

Version:

TypeScript Material-UI Implementation

331 lines (330 loc) 14.3 kB
import { jsx as _jsx } from "react/jsx-runtime"; import { css } from "@emotion/css"; import { GridAlignGet, ScrollerGrid, useCombinedRefs } from "@etsoo/react"; import { Utils } from "@etsoo/shared"; import React from "react"; import { DataGridRenderers } from "./DataGridRenderers"; import { useTheme } from "@mui/material/styles"; import Box from "@mui/material/Box"; import TableSortLabel from "@mui/material/TableSortLabel"; import Checkbox from "@mui/material/Checkbox"; import Paper from "@mui/material/Paper"; // Borders const boldBorder = "2px solid rgba(224, 224, 224, 1)"; const thinBorder = "1px solid rgba(224, 224, 224, 1)"; // Scroll bar size const scrollbarSize = 16; // Minimum width const minWidth = 120; const createGridStyle = (alternatingColors, selectedColor, hoverColor) => { return css({ ".DataGridEx-Selected": { backgroundColor: selectedColor }, ".DataGridEx-Hover:not(.DataGridEx-Selected)": { backgroundColor: hoverColor }, "& .DataGridEx-Cell0:not(.DataGridEx-Hover):not(.DataGridEx-Selected)": { backgroundColor: alternatingColors[0] }, "& .DataGridEx-Cell1:not(.DataGridEx-Hover):not(.DataGridEx-Selected)": { backgroundColor: alternatingColors[1] }, "& .DataGridEx-Cell-Border": { borderBottom: thinBorder } }); }; const rowItems = (div, callback) => { const row = div.dataset["row"]; if (div.parentElement == null || row == null) return; doRowItems(div.parentElement, parseFloat(row), callback); }; const doRowItems = (parent, rowIndex, callback) => { if (parent == null || rowIndex == null) return; parent ?.querySelectorAll(`div[data-row="${rowIndex}"]`) .forEach((rowItem) => { callback(rowItem); }); }; /** * Extended datagrid columns calculation * @param columns * @returns Total width and unset items */ export function DataGridExCalColumns(columns) { return columns.reduce((previousValue, currentItem) => { previousValue.total += currentItem.width ?? currentItem.minWidth ?? minWidth; if (currentItem.width == null) previousValue.unset++; return previousValue; }, { total: 0, unset: 0 }); } /** * Extended DataGrid with VariableSizeGrid * @param props Props * @returns Component */ export function DataGridEx(props) { // Theme const theme = useTheme(); const defaultHeaderRenderer = (states) => { const { orderBy } = states.queryPaging; return (_jsx(Box, { className: "DataGridEx-Header", display: "flex", alignItems: "center", borderBottom: boldBorder, fontWeight: 500, minWidth: widthCalculator.total, height: headerHeight, children: columns.map((column, index) => { // Destruct const { align, field, header, headerCellRenderer, sortable, sortAsc = true, type } = column; // Header text const headerText = header ?? field; // Cell props const cellProps = {}; // Sortable let sortLabel; if (headerCellRenderer) { sortLabel = headerCellRenderer({ cellProps, column, columnIndex: checkable ? index - 1 : index, // Ignore the checkbox case, states }); } else if (sortable && field != null) { const active = orderBy?.some((o) => o.field.toUpperCase() === field.toUpperCase()); sortLabel = (_jsx(TableSortLabel, { active: active, direction: sortAsc ? "asc" : "desc", onClick: (_event) => { if (active) column.sortAsc = !sortAsc; handleSort(field, column.sortAsc); }, children: headerText })); } else { sortLabel = headerText; } return (_jsx(Box, { textAlign: GridAlignGet(align, type), width: columnWidth(index), children: _jsx(Box, { className: "DataGridEx-Cell", onMouseEnter: handleMouseEnter, ...cellProps, children: sortLabel }) }, field ?? index.toString())); }) })); }; function defaultFooterRenderer(rows, states) { return (_jsx(Box, { className: "DataGridEx-Footer", display: "flex", alignItems: "center", borderTop: thinBorder, marginTop: "1px", minWidth: widthCalculator.total, height: bottomHeight - 1, children: columns.map((column, index) => { // Destruct const { align, field, type } = column; // Cell props const cellProps = {}; // Cell const cell = footerItemRenderer ? footerItemRenderer(rows, { column, index: checkable ? index - 1 : index, // Ignore the checkbox case states, cellProps, checkable }) : undefined; return (_jsx(Box, { textAlign: GridAlignGet(align, type), width: columnWidth(index), children: _jsx(Box, { className: "DataGridEx-Cell", onMouseEnter: handleMouseEnter, ...cellProps, children: cell }) }, "bottom-" + (field ?? index.toString()))); }) })); } // Destruct const { alternatingColors = [theme.palette.grey[100], undefined], borderRowsCount, bottomHeight = 53, checkable = false, className, columns, defaultOrderBy, height, headerHeight = 56, headerRenderer = defaultHeaderRenderer, footerRenderer = defaultFooterRenderer, footerItemRenderer = DataGridRenderers.defaultFooterItemRenderer, hideFooter = false, hoverColor = "#f6f9fb", idField = "id", mRef = React.createRef(), onClick, onDoubleClick, selectable = true, selectedColor = "#edf4fb", width, ...rest } = props; if (checkable) { const cbColumn = { field: "selected", // Avoid validation from data model header: "", sortable: false, width: 50, cellRenderer: ({ cellProps, data, selected }) => { cellProps.sx = { padding: "4px!important" }; return (_jsx(Checkbox, { color: "primary", checked: selected, onChange: (_event, checked) => { refs.current.ref?.selectItem(data, checked); } })); }, headerCellRenderer: ({ cellProps, states }) => { // 2 = border height const hpad = (headerHeight - 42) / 2; cellProps.sx = { padding: `${hpad}px 4px ${hpad - 1}px 4px!important` }; return (_jsx(Checkbox, { color: "primary", indeterminate: states.selectedItems.length > 0 && states.selectedItems.length < states.loadedItems, checked: states.selectedItems.length > 0, onChange: (_event, checked) => refs.current.ref?.selectAll(checked) })); } }; // Update to the latest version if (columns[0].field === "selected") { columns[0] = cbColumn; } else { columns.unshift(cbColumn); } } const refs = React.useRef({}); const mRefLocal = useCombinedRefs(mRef, (ref) => { if (ref == null) return; refs.current.ref = ref; }); // New sort const handleSort = (field, asc) => { reset({ queryPaging: { orderBy: [{ field, desc: !(asc ?? true) }] } }); }; // Reset const reset = (add) => { refs.current.ref?.reset(add); }; // Show hover tooltip for trucated text const handleMouseEnter = (event) => { const div = event.currentTarget; const { innerText, offsetWidth, scrollWidth } = div; if (offsetWidth < scrollWidth) { div.title = innerText; } else { div.title = ""; } }; // selectedRowIndex state const selectedRowIndex = React.useRef(-1); const handleMouseDown = (event) => { const div = event.currentTarget; const row = div.dataset["row"]; if (div.parentElement == null || row == null) return; const rowIndex = parseFloat(row); // No change if (isNaN(rowIndex) || rowIndex === selectedRowIndex.current) return; if (selectedRowIndex.current != -1) { doRowItems(div.parentElement, selectedRowIndex.current, (preDiv) => { preDiv.classList.remove("DataGridEx-Selected"); }); } rowItems(div, (currentDiv) => { currentDiv.classList.add("DataGridEx-Selected"); }); selectedRowIndex.current = rowIndex; }; const handleMouseOver = (event) => { rowItems(event.currentTarget, (div) => { div.classList.add("DataGridEx-Hover"); }); }; const handleMouseOut = (event) => { rowItems(event.currentTarget, (div) => { div.classList.remove("DataGridEx-Hover"); }); }; /** * Item renderer */ const itemRenderer = ({ columnIndex, rowIndex, style, data, selectedItems, setItems }) => { // Column const { align, cellRenderer = DataGridRenderers.defaultCellRenderer, cellBoxStyle, field, type, valueFormatter, renderProps } = columns[columnIndex]; // Props const formatProps = { data, field, rowIndex, columnIndex }; let rowClass = `DataGridEx-Cell${rowIndex % 2}`; if (borderRowsCount != null && borderRowsCount > 0 && (rowIndex + 1) % borderRowsCount === 0) { rowClass += ` DataGridEx-Cell-Border`; } // Selected const selected = data != null && (selectedRowIndex.current === rowIndex || selectedItems.some((selectedItem) => selectedItem != null && selectedItem[idField] === data[idField])); if (selected) { rowClass += ` DataGridEx-Selected`; } // Box style const boxStyle = data == null || cellBoxStyle == null ? undefined : typeof cellBoxStyle === "function" ? cellBoxStyle(data) : cellBoxStyle; const cellProps = { className: "DataGridEx-Cell", textAlign: GridAlignGet(align, type), sx: { ...boxStyle } }; const child = cellRenderer({ data, field, formattedValue: valueFormatter ? valueFormatter(formatProps) : undefined, selected, type, rowIndex, columnIndex, cellProps, renderProps: typeof renderProps === "function" ? renderProps(data) : renderProps, setItems }); return (_jsx("div", { className: rowClass, style: style, "data-row": rowIndex, "data-column": columnIndex, onMouseDown: selectable && !checkable ? handleMouseDown : undefined, onMouseOver: selectable ? handleMouseOver : undefined, onMouseOut: selectable ? handleMouseOut : undefined, onClick: (event) => onClick && data != null && onClick(event, data), onDoubleClick: (event) => onDoubleClick && data != null && onDoubleClick(event, data), children: _jsx(Box, { ...cellProps, onMouseEnter: handleMouseEnter, children: child }) })); }; // Column width calculator const widthCalculator = React.useMemo(() => DataGridExCalColumns(columns), [columns]); // Column width const columnWidth = React.useCallback((index) => { // Ignore null case if (width == null) return 0; // Column const column = columns[index]; if (column.width != null) return column.width; // More space const leftWidth = width - widthCalculator.total - (width < 800 ? 0 : scrollbarSize); // Shared width const sharedWidth = leftWidth > 0 ? leftWidth / widthCalculator.unset : 0; return (column.minWidth || minWidth) + sharedWidth; }, [columns, width]); // Table const table = React.useMemo(() => { return (_jsx(ScrollerGrid, { className: Utils.mergeClasses("DataGridEx-Body", "DataGridEx-CustomBar", className, createGridStyle(alternatingColors, selectedColor, hoverColor)), columnCount: columns.length, columnWidth: columnWidth, defaultOrderBy: defaultOrderBy, height: height - headerHeight - (hideFooter ? 0 : bottomHeight + 1) - scrollbarSize, headerRenderer: headerRenderer, idField: idField, itemRenderer: itemRenderer, footerRenderer: hideFooter ? undefined : footerRenderer, width: Math.max(width ?? 0, widthCalculator.total), mRef: mRefLocal, ...rest })); }, [width]); return (_jsx(Paper, { sx: { fontSize: "0.875rem", height, "& .DataGridEx-Cell": { padding: 2, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, "& .DataGridEx-CustomBar": { "@media (min-width: 800px)": { "::-webkit-scrollbar": { width: scrollbarSize, height: scrollbarSize, backgroundColor: "#f6f6f6" }, "::-webkit-scrollbar-thumb": { backgroundColor: "rgba(0,0,0,0.4)", borderRadius: "2px" }, "::-webkit-scrollbar-track-piece:start": { background: "transparent" }, "::-webkit-scrollbar-track-piece:end": { background: "transparent" } } } }, children: _jsx("div", { className: "DataGridEx-CustomBar", style: { width, overflowX: "auto", overflowY: "hidden" }, children: table }) })); }