I have a Material-UI Table with a fixed header in a React project (reference: https://material-ui.com/components/tables/#fixed-header). I would like to hide the scrollbar in the table header (which overlaps it), while still showing it in the table body.
I have mostly tried changing the overflow
property in the TableContainer
, Table
, TableHead
, and TableBody
components, though with poor results. Searching around a bit, it seemed that this problem could be solved setting the display
property of the aforementioned components to block
. However, this approach let me only move the scrollbar from the TableContainer
component to the Table
component, not solving the issue and messing up the table layout.
Edit
Working example:
import * as React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import { styled } from '@mui/material/styles';
import TableCell from '@mui/material/TableCell';
import Paper from '@mui/material/Paper';
import { AutoSizer, Column, Table } from 'react-virtualized';
const classes = {
flexContainer: 'ReactVirtualizedDemo-flexContainer',
tableRow: 'ReactVirtualizedDemo-tableRow',
tableRowHover: 'ReactVirtualizedDemo-tableRowHover',
tableCell: 'ReactVirtualizedDemo-tableCell',
noClick: 'ReactVirtualizedDemo-noClick',
};
const styles = ({ theme }) => ({
// temporary right-to-left patch, waiting for
// https://github.com/bvaughn/react-virtualized/issues/454
'& .ReactVirtualized__Table__headerRow': {
...(theme.direction === 'rtl' && {
paddingLeft: '0 !important',
}),
...(theme.direction !== 'rtl' && {
paddingRight: undefined,
}),
},
[`& .${classes.flexContainer}`]: {
display: 'flex',
alignItems: 'center',
boxSizing: 'border-box',
},
[`& .${classes.tableRow}`]: {
cursor: 'pointer',
},
[`& .${classes.tableRowHover}`]: {
'&:hover': {
backgroundColor: theme.palette.grey[200],
},
},
[`& .${classes.tableCell}`]: {
flex: 1,
},
[`& .${classes.noClick}`]: {
cursor: 'initial',
},
});
class MuiVirtualizedTable extends React.PureComponent {
static defaultProps = {
headerHeight: 48,
rowHeight: 48,
};
getRowClassName = ({ index }) => {
const { onRowClick } = this.props;
return clsx(classes.tableRow, classes.flexContainer, {
[classes.tableRowHover]: index !== -1 && onRowClick != null,
});
};
cellRenderer = ({ cellData, columnIndex }) => {
const { columns, rowHeight, onRowClick } = this.props;
return (
<TableCell
component="div"
className={clsx(classes.tableCell, classes.flexContainer, {
[classes.noClick]: onRowClick == null,
})}
variant="body"
style={{ height: rowHeight }}
align={
(columnIndex != null && columns[columnIndex].numeric) || false
? 'right'
: 'left'
}
>
{cellData}
</TableCell>
);
};
headerRenderer = ({ label, columnIndex }) => {
const { headerHeight, columns } = this.props;
return (
<TableCell
component="div"
className={clsx(classes.tableCell, classes.flexContainer, classes.noClick)}
variant="head"
style={{ height: headerHeight }}
align={columns[columnIndex].numeric || false ? 'right' : 'left'}
>
<span>{label}</span>
</TableCell>
);
};
render() {
const { columns, rowHeight, headerHeight, ...tableProps } = this.props;
return (
<AutoSizer>
{({ height, width }) => (
<Table
height={height}
width={width}
rowHeight={rowHeight}
gridStyle={{
direction: 'inherit',
}}
headerHeight={headerHeight}
{...tableProps}
rowClassName={this.getRowClassName}
>
{columns.map(({ dataKey, ...other }, index) => {
return (
<Column
key={dataKey}
headerRenderer={(headerProps) =>
this.headerRenderer({
...headerProps,
columnIndex: index,
})
}
className={classes.flexContainer}
cellRenderer={this.cellRenderer}
dataKey={dataKey}
{...other}
/>
);
})}
</Table>
)}
</AutoSizer>
);
}
}
MuiVirtualizedTable.propTypes = {
columns: PropTypes.arrayOf(
PropTypes.shape({
dataKey: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
numeric: PropTypes.bool,
width: PropTypes.number.isRequired,
}),
).isRequired,
headerHeight: PropTypes.number,
onRowClick: PropTypes.func,
rowHeight: PropTypes.number,
};
const VirtualizedTable = styled(MuiVirtualizedTable)(styles);
// ---
const sample = [
['Frozen yoghurt', 159, 6.0, 24, 4.0],
['Ice cream sandwich', 237, 9.0, 37, 4.3],
['Eclair', 262, 16.0, 24, 6.0],
['Cupcake', 305, 3.7, 67, 4.3],
['Gingerbread', 356, 16.0, 49, 3.9],
];
function createData(id, dessert, calories, fat, carbs, protein) {
return { id, dessert, calories, fat, carbs, protein };
}
const rows = [];
for (let i = 0; i < 200; i += 1) {
const randomSelection = sample[Math.floor(Math.random() * sample.length)];
rows.push(createData(i, ...randomSelection));
}
export default function ReactVirtualizedTable() {
return (
<Paper style={{ height: 400, width: '100%' }}>
<VirtualizedTable
rowCount={rows.length}
rowGetter={({ index }) => rows[index]}
columns={[
{
width: 200,
label: 'Dessert',
dataKey: 'dessert',
},
{
width: 120,
label: 'Calories\u00A0(g)',
dataKey: 'calories',
numeric: true,
},
{
width: 120,
label: 'Fat\u00A0(g)',
dataKey: 'fat',
numeric: true,
},
{
width: 120,
label: 'Carbs\u00A0(g)',
dataKey: 'carbs',
numeric: true,
},
{
width: 120,
label: 'Protein\u00A0(g)',
dataKey: 'protein',
numeric: true,
},
]}
/>
</Paper>
);
}