icflorescu / mantine-datatable

The table component for your Mantine data-rich applications, supporting asynchronous data loading, column sorting, custom cell data rendering, context menus, nesting, Gmail-style batch row selection, dark theme, and more.
https://icflorescu.github.io/mantine-datatable/
MIT License
957 stars 69 forks source link

Nested Table Headers Overlap Parent Table Headers on Scroll in Mantine DataTable #660

Closed pfo-omicsstudio closed 3 days ago

pfo-omicsstudio commented 1 month ago

Describe the bug When using nested tables, the table headers do not render in the correct order. Specifically, the headers of the nested table(s) appear above the header of the parent table.

To Reproduce Using the following sample code, expand one of the rows and scroll down in the parent datatable. You should immediately notice the header of the nested table overlapping the header of the parent table:

import { Box } from "@mantine/core";
import { DataTable } from "mantine-datatable";
import { useState } from "react";

// Utility function to generate random strings
const getRandomString = (length: number) => {
  const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
  return Array.from({ length }, () => characters.charAt(Math.floor(Math.random() * characters.length))).join("");
};

// Generate gibberish employees, departments, and companies
const generateEmployees = (departmentId: string, count: number) => {
  return Array.from({ length: count }, () => ({
    id: getRandomString(8),
    firstName: getRandomString(5),
    lastName: getRandomString(7),
    birthDate: new Date(
      1980 + Math.floor(Math.random() * 30),
      Math.floor(Math.random() * 12),
      Math.floor(Math.random() * 28),
    ),
    department: { id: departmentId },
  }));
};

const generateDepartments = (companyId: string, count: number) => {
  return Array.from({ length: count }, () => ({
    id: getRandomString(8),
    name: `Dept-${getRandomString(5)}`,
    company: { id: companyId },
    employees: 0,
  }));
};

const generateCompanies = (count: number) => {
  return Array.from({ length: count }, () => ({
    id: getRandomString(8),
    name: `Company-${getRandomString(5)}`,
    employees: 0,
  }));
};

// Create companies, departments, and employees
const companies = generateCompanies(20);
const departments = companies.flatMap((company) => generateDepartments(company.id, Math.floor(Math.random() * 5) + 2));
const employees = departments.flatMap((department) =>
  generateEmployees(department.id, Math.floor(Math.random() * 10) + 1),
);

// Update employees count for departments and companies
departments.forEach((department) => {
  department.employees = employees.filter((employee) => employee.department.id === department.id).length;
});
companies.forEach((company) => {
  company.employees = departments
    .filter((department) => department.company.id === company.id)
    .reduce((sum, department) => sum + department.employees, 0);
});

export default function NestedTablesExample() {
  const [expandedCompanyIds, setExpandedCompanyIds] = useState<string[]>([]);

  return (
    <DataTable
      idAccessor="name"
      highlightOnHover
      height={400}
      columns={[
        {
          accessor: "name",
          title: "Company ",
          noWrap: true,
          render: ({ name }) => (
            <>
              <span>{name}</span>
            </>
          ),
        },
        { accessor: "employees", title: "Employees", textAlign: "right", width: 200 },
      ]}
      records={companies}
      rowExpansion={{
        allowMultiple: true,
        expanded: { recordIds: expandedCompanyIds, onRecordIdsChange: setExpandedCompanyIds },
        content: (company) => (
          <DataTable
            withColumnBorders
            columns={[
              {
                accessor: "name",
                noWrap: true,
                render: ({ name }) => (
                  <Box component="span" ml={20}>
                    <span>{name}</span>
                  </Box>
                ),
              },
              { accessor: "employees", textAlign: "right", width: 200 },
            ]}
            records={departments.filter((department) => department.company.id === company.record.id)}
          />
        ),
      }}
    />
  );
}

Expected behavior Thee headers of the nested table(s) should render behind the header of the parent table.

Screenshots image

If the gif is too crunchy, you can find the video itself on gyazo.com here: https://gyazo.com/d8484ac8d4ab2763adcfd957b2feb9c1

Desktop:

icflorescu commented 3 days ago

Hey Patrick, This is actually not a bug, you can adjust the zIndex of the nested tables to fit your needs. In your example, setting style={{ zIndex: 0 }} on the nested tables will solve the issue.