Search code examples
reactjsmaterial-uimui-datatable

How to show a scroll shadow MUI Tables with sticky header


I have an MUI table with sticky headers. I want that whenever the table is at the top, it shows a shadow at the top below the header. When the scroll is totally at the bottom, it shows a shadow at the bottom. When it is in the middle position, it shows both shadows. How can I do it?

enter image description here


Solution

  • After a lot of trying, this is how I managed to do it

    import * as React from "react";
    import Table from "@mui/material/Table";
    import TableBody from "@mui/material/TableBody";
    import TableCell from "@mui/material/TableCell";
    import TableContainer from "@mui/material/TableContainer";
    import TableHead from "@mui/material/TableHead";
    import TableRow from "@mui/material/TableRow";
    import Paper from "@mui/material/Paper";
    
    
    export default function DenseTable() {
      const headerRef = React.useRef<HTMLTableSectionElement>(null);
      const headerHeight = headerRef.current?.offsetHeight ?? 0;
      const tableContainerRef = React.useRef<HTMLDivElement>(null);
      const [isAtTop, setIsAtTop] = React.useState(true);
      const [isAtBottom, setIsAtBottom] = React.useState(false);
    
      React.useEffect(() => {
        const handleScroll = () => {
          if (tableContainerRef.current) {
            const { scrollTop, scrollHeight, clientHeight } =
              tableContainerRef.current;
            setIsAtTop(scrollTop === 0);
    
            // + 1 for some tolerance
            setIsAtBottom(scrollTop + clientHeight >= scrollHeight - 1);
          }
        };
    
        const container = tableContainerRef.current;
        container?.addEventListener("scroll", handleScroll);
    
        return () => {
          container?.removeEventListener("scroll", handleScroll);
        };
      }, []);
    
      return (
        <TableContainer
          component={Paper}
          sx={{ height: "300px" }}
          ref={tableContainerRef}
        >
          <div
            style={{
              zIndex: 100,
              content: '""',
              position: "sticky",
              // no idea where the -40px come from
              marginTop: "calc(-100% - 40px)",
              top: 0,
              left: 0,
              right: 0,
              height: "100%",
              pointerEvents: "none",
            }}
          >
            {!isAtTop && (
              <div
                style={{
                  "--header-height": `${headerHeight}px`,
                  content: '""',
                  position: "absolute",
                  background:
                    "linear-gradient(180deg, rgba(20, 20, 20, 0.6) 0%, rgba(20, 20, 20, 0.6) 10%, rgba(30, 30, 30, 0) 100%)",
                  top: "calc(var(--header-height) - 1px)",
                  left: 0,
                  width: "100%",
                  height: "10px",
                }}
                className="shadow-top"
              />
            )}
            {!isAtBottom && (
              <div
                style={{
                  content: '""',
                  position: "absolute",
                  background:
                    "linear-gradient(0deg, rgba(20, 20, 20, 0.6) 0%, rgba(20, 20, 20, 0.6) 10%, rgba(30, 30, 30, 0) 100%)",
                  bottom: 0,
                  left: 0,
                  width: "100%",
                  height: "10px",
                }}
                className="shadow-bottom"
              />
            )}
          </div>
          <Table
            stickyHeader
            sx={{ minWidth: 650 }}
            size="small"
            aria-label="a dense table"
          >
            <TableHead ref={headerRef}>
              ...
            </TableHead>
            <TableBody>
              ...
            </TableBody>
          </Table>
        </TableContainer>
      );
    }
    
    

    I still don't know how to calculate the - 40px. Happy to receive comments about it.

    Here the codesandbox for it.