Table
Import
Table
: The wrapper that provides props, state, and context to its children.TableHeader
: The header of the table.TableHeaderRow
: The header row of the table.TableHeaderCell
: The header cell of the table.TableBody
: The body of the table.TableRow
: The body row of the table.TableCell
: The body cell of the table.
import {Table,TableHeader,TableHeaderRow,TableHeaderCell,TableBody,TableRow,TableCell,} from '@trendmicro/react-styled-ui';
Usage
EDITABLE EXAMPLE<Table><TableHeader><TableHeaderRow><TableHeaderCell width="240px">Event Type</TableHeaderCell><TableHeaderCell width="136px" textAlign="right">Detections</TableHeaderCell></TableHeaderRow></TableHeader><TableBody><TableRow><TableCell width="240px">Virus/Malware</TableCell><TableCell width="136px" textAlign="right">634</TableCell></TableRow><TableRow><TableCell width="240px">Virus/Malware</TableCell><TableCell width="136px" textAlign="right">778</TableCell></TableRow><TableRow><TableCell width="240px">URL Filtering</TableCell><TableCell width="136px" textAlign="right">598</TableCell></TableRow></TableBody></Table>
Variants
Use the variant
prop to change the visual style of the Table. You can set the value to default
, outline
.
default
: Only add bottom border into cells, including table header.outline
: Adds borders on all sides of the table and cells.
EDITABLE EXAMPLE<Stack direction="row" spacing="4x">{["default", "outline"].map(variant => (<Stack key={variant} direction="column" spacing="4x" shouldWrapChildren><Text size="xl">{variant}</Text><Table variant={variant}><TableHeader><TableHeaderRow><TableHeaderCell width="240px">Event Type</TableHeaderCell><TableHeaderCell width="136px" textAlign="right">Detections</TableHeaderCell></TableHeaderRow></TableHeader><TableBody><TableRow><TableCell width="240px">Virus/Malware</TableCell><TableCell width="136px" textAlign="right">634</TableCell></TableRow><TableRow><TableCell width="240px">Virus/Malware</TableCell><TableCell width="136px" textAlign="right">778</TableCell></TableRow><TableRow><TableCell width="240px">URL Filtering</TableCell><TableCell width="136px" textAlign="right">598</TableCell></TableRow></TableBody></Table></Stack>))}</Stack>
Sizes
Use the size
prop to change the padding of the <TableCell>
. You can set the value to sm
, md
, or lg
.
EDITABLE EXAMPLEfunction Sample() {const shadowLeftRef = React.useRef(null);const shadowRightRef = React.useRef(null);const handleUpdate = (props) => {const { scrollLeft, scrollWidth, clientWidth } = props;const shadowLeftOpacity = 1 / 20 * Math.min(scrollLeft, 20);const rightScrollLeft = scrollWidth - clientWidth;const shadowRightOpacity = 1 / 20 * (rightScrollLeft - Math.max(scrollLeft, rightScrollLeft - 20));shadowLeftRef.current.style.opacity = shadowLeftOpacity;shadowRightRef.current.style.opacity = shadowRightOpacity;};return (<Box position="relative"><Scrollbar onUpdate={handleUpdate} width="100%" height={248}><Flex>{["sm", "md", "lg"].map(size => (<Box flex="initial" mr="4x" key={size}><Text display="block" size="xl" mb="4x">Size: {size}</Text><Table variant="outline" size={size}><TableHeader><TableHeaderRow><TableHeaderCell width="240px">Event Type</TableHeaderCell><TableHeaderCell width="136px" textAlign="right">Detections</TableHeaderCell></TableHeaderRow></TableHeader><TableBody><TableRow><TableCell width="240px">Virus/Malware</TableCell><TableCell width="136px" textAlign="right">634</TableCell></TableRow><TableRow><TableCell width="240px">Virus/Malware</TableCell><TableCell width="136px" textAlign="right">778</TableCell></TableRow><TableRow><TableCell width="240px">URL Filtering</TableCell><TableCell width="136px" textAlign="right">598</TableCell></TableRow></TableBody></Table></Box>))}</Flex></Scrollbar><Boxref={shadowLeftRef}position="absolute"top="0"bottom="0"left="0"width="24px"background="linear-gradient(to right, rgba(33, 33, 33, 1) 0%, rgba(255, 255, 255, 0) 100%)"/><Boxref={shadowRightRef}position="absolute"top="0"bottom="0"right="0"width="24px"background="linear-gradient(to left, rgba(33, 33, 33, 1) 0%, rgba(255, 255, 255, 0) 100%)"/></Box>);}
Hoverable
Enable a hover state on TableRows.
EDITABLE EXAMPLEconst HoverableTableRow = (props) => {return (<TableRow_hover={{bg: 'rgba(255, 255, 255, 0.12)'}}{...props}/>);};function HoverableTable() {return (<Table><TableHeader><TableHeaderRow><TableHeaderCell width="240px">Event Type</TableHeaderCell><TableHeaderCell width="136px" textAlign="right">Detections</TableHeaderCell></TableHeaderRow></TableHeader><TableBody><HoverableTableRow><TableCell width="240px">Virus/Malware</TableCell><TableCell width="136px" textAlign="right">634</TableCell></HoverableTableRow><HoverableTableRow><TableCell width="240px">Virus/Malware</TableCell><TableCell width="136px" textAlign="right">778</TableCell></HoverableTableRow><HoverableTableRow><TableCell width="240px">URL Filtering</TableCell><TableCell width="136px" textAlign="right">598</TableCell></HoverableTableRow></TableBody></Table>);}render(<HoverableTable />);
Custom tables
In the following examples, we demonstrate how to use react-table
with the Table component.
import { useTable, useBlockLayout, useRowSelect, useSortBy } from 'react-table';
Base table
EDITABLE EXAMPLEfunction BaseTable() {const columns = React.useMemo(() => [{Header: 'Event Type',accessor: 'eventType',width: 240,},{Header: 'Affected Devices',accessor: 'affectedDevices',width: 140,customProps: {textAlign: 'right',},},{Header: 'Detections',accessor: 'detections',width: 136,customProps: {textAlign: 'right',},},], []);const data = React.useMemo(() => [{ id: 1, eventType: 'Virus/Malware', affectedDevices: 20, detections: 634 },{ id: 2, eventType: 'Spyware/Grayware', affectedDevices: 20, detections: 634 },{ id: 3, eventType: 'URL Filtering', affectedDevices: 15, detections: 598 },{ id: 4, eventType: 'Web Reputation', affectedDevices: 15, detections: 598 },{ id: 5, eventType: 'Network Virus', affectedDevices: 15, detections: 497 },{ id: 6, eventType: 'Application Control', affectedDevices: 0, detections: 0 }], []);const {getTableProps,getTableBodyProps,headerGroups,rows,prepareRow,} = useTable({columns,data,},useBlockLayout,);return (<Table {...getTableProps()}><TableHeader>{headerGroups.map(headerGroup => (<TableHeaderRow {...headerGroup.getHeaderGroupProps()}>{headerGroup.headers.map(column => (<TableHeaderCell{...column.getHeaderProps()}{...column.customProps}>{column.render('Header')}</TableHeaderCell>))}</TableHeaderRow>))}</TableHeader><TableBody {...getTableBodyProps()}>{rows.map((row, i) => {prepareRow(row);return (<TableRow{...row.getRowProps()}_hover={{bg: 'rgba(255, 255, 255, 0.12)'}}>{row.cells.map(cell => {return (<TableCell{...cell.getCellProps()}{...cell.column.customProps}>{cell.render('Cell')}</TableCell>);})}</TableRow>);})}</TableBody></Table>);}render(<BaseTable />);
Selectable table
EDITABLE EXAMPLEconst IndeterminateCheckbox = React.forwardRef(({ indeterminate, ...rest }, ref) => {const defaultRef = React.useRef();const resolvedRef = ref || defaultRef;return (<Checkboxref={resolvedRef}indeterminate={indeterminate}{...rest}/>);});function SelectableTable() {const columns = React.useMemo(() => [{Header: 'Event Type',accessor: 'eventType',width: 240,},{Header: 'Affected Devices',accessor: 'affectedDevices',width: 140,customProps: {textAlign: 'right',},},{Header: 'Detections',accessor: 'detections',width: 136,customProps: {textAlign: 'right',},},], []);const data = React.useMemo(() => [{ id: 1, eventType: 'Virus/Malware', affectedDevices: 20, detections: 634 },{ id: 2, eventType: 'Spyware/Grayware', affectedDevices: 20, detections: 634 },{ id: 3, eventType: 'URL Filtering', affectedDevices: 15, detections: 598 },{ id: 4, eventType: 'Web Reputation', affectedDevices: 15, detections: 598 },{ id: 5, eventType: 'Network Virus', affectedDevices: 15, detections: 497 },{ id: 6, eventType: 'Application Control', affectedDevices: 0, detections: 0 }], []);const {getTableProps,getTableBodyProps,headerGroups,rows,prepareRow,selectedFlatRows,state: { selectedRowIds },} = useTable({columns,data,},useBlockLayout,useRowSelect,hooks => {hooks.visibleColumns.push(columns => [{id: 'selection',Header: ({ getToggleAllRowsSelectedProps }) => (<Flex height="100%" alignItems="center"><IndeterminateCheckbox {...getToggleAllRowsSelectedProps()} /></Flex>),Cell: ({ row }) => (<Flex height="100%" alignItems="center"><IndeterminateCheckbox {...row.getToggleRowSelectedProps()} /></Flex>),width: 48,},...columns,]);});return (<><Table {...getTableProps()}><TableHeader>{headerGroups.map(headerGroup => (<TableHeaderRow {...headerGroup.getHeaderGroupProps()}>{headerGroup.headers.map(column => (<TableHeaderCell{...column.getHeaderProps()}{...column.customProps}>{column.render('Header')}</TableHeaderCell>))}</TableHeaderRow>))}</TableHeader><TableBody {...getTableBodyProps()}>{rows.slice(0, 10).map((row, i) => {prepareRow(row);const isChecked = Object.keys(selectedRowIds).indexOf(row.id) >= 0;return (<TableRow{...row.getRowProps()}{...isChecked && { bg: 'rgba(255, 255, 255, 0.08)' }}_hover={{bg: 'rgba(255, 255, 255, 0.12)'}}>{row.cells.map(cell => {return (<TableCell{...cell.getCellProps()}{...cell.column.customProps}>{cell.render('Cell')}</TableCell>);})}</TableRow>);})}</TableBody></Table><p>Selected Rows: {Object.keys(selectedRowIds).length}</p><pre><code>{JSON.stringify({selectedRowIds: selectedRowIds,'selectedFlatRows[].original': selectedFlatRows.map(d => d.original),},null,2)}</code></pre></>);}render(<SelectableTable />);
Sortable table
EDITABLE EXAMPLEfunction SortableTable() {const columns = React.useMemo(() => [{Header: 'Event Type',accessor: 'eventType',width: 240,},{Header: 'Affected Devices',accessor: 'affectedDevices',width: 176,customProps: {textAlign: 'right',justifyContent: 'flex-end',},},{Header: 'Detections',accessor: 'detections',width: 136,customProps: {textAlign: 'right',justifyContent: 'flex-end',},},], []);const data = React.useMemo(() => [{ id: 1, eventType: 'Virus/Malware', affectedDevices: 20, detections: 634 },{ id: 2, eventType: 'Spyware/Grayware', affectedDevices: 20, detections: 634 },{ id: 3, eventType: 'URL Filtering', affectedDevices: 15, detections: 598 },{ id: 4, eventType: 'Web Reputation', affectedDevices: 15, detections: 598 },{ id: 5, eventType: 'Network Virus', affectedDevices: 15, detections: 497 },{ id: 6, eventType: 'Application Control', affectedDevices: 0, detections: 0 }], []);const {getTableProps,getTableBodyProps,headerGroups,rows,prepareRow,} = useTable({columns,data,// useSortBydisableSortRemove: true,},useBlockLayout,useSortBy,);return (<Table {...getTableProps()}><TableHeader>{headerGroups.map(headerGroup => (<TableHeaderRow {...headerGroup.getHeaderGroupProps()}>{headerGroup.headers.map(column => (<TableHeaderCell{...column.getHeaderProps(column.getSortByToggleProps())}{...column.isSorted && {color: 'rgba(255, 255, 255, 1)'}}_hover={{bg: 'rgba(255, 255, 255, 0.12)'}}><FlexalignItems="center"{...column.customProps}><Boxoverflow="hidden"textOverflow="ellipsis"whiteSpace="nowrap">{column.render('Header')}</Box>{column.isSorted && column.isSortedDesc && (<Icon icon="sort-down" size={20} ml="1x" />)}{column.isSorted && !column.isSortedDesc && (<Icon icon="sort-up" size={20} ml="1x" />)}</Flex></TableHeaderCell>))}</TableHeaderRow>))}</TableHeader><TableBody {...getTableBodyProps()}>{rows.map((row, i) => {prepareRow(row);return (<TableRow{...row.getRowProps()}_hover={{bg: 'rgba(255, 255, 255, 0.12)'}}>{row.cells.map(cell => {return (<TableCell{...cell.getCellProps()}{...cell.column.customProps}>{cell.render('Cell')}</TableCell>);})}</TableRow>);})}</TableBody></Table>);}render(<SortableTable />);
Resizable table
EDITABLE EXAMPLEfunction ResizableTable() {const { colorMode } = useColorMode();const hoveringStyle = {'dark': {borderRight: 1,borderColor: 'gray:50',},'light': {borderRight: 1,borderColor: 'gray:70',}}[colorMode];const resizingStyle = {'dark': {borderRight: 1,borderColor: 'gray:50',boxShadow: '4px 0px 0px 0px rgba(255, 255, 255, 0.12)',},'light': {borderRight: 1,borderColor: 'gray:70',boxShadow: '4px 0px 0px 0px rgba(0, 0, 0, 0.12)',},}[colorMode];const columns = React.useMemo(() => [{Header: 'Event Type',accessor: 'eventType',width: 240,},{Header: 'Affected Devices',accessor: 'affectedDevices',width: 144,customProps: {textAlign: 'right',justifyContent: 'flex-end',},},{Header: 'Detections',accessor: 'detections',width: 136,customProps: {textAlign: 'right',justifyContent: 'flex-end',},},], []);const data = React.useMemo(() => [{ id: 1, eventType: 'Virus/Malware', affectedDevices: 20, detections: 634 },{ id: 2, eventType: 'Spyware/Grayware', affectedDevices: 20, detections: 634 },{ id: 3, eventType: 'URL Filtering', affectedDevices: 15, detections: 598 },{ id: 4, eventType: 'Web Reputation', affectedDevices: 15, detections: 598 },{ id: 5, eventType: 'Network Virus', affectedDevices: 15, detections: 497 },{ id: 6, eventType: 'Application Control', affectedDevices: 0, detections: 0 }], []);const {getTableProps,getTableBodyProps,headerGroups,rows,prepareRow,resetResizing,} = useTable({columns,data,},useBlockLayout,useResizeColumns,);const [hoveredResizerId, setHoveredResizerId] = React.useState();const handleEnterResizer = (cellId) => (e) => {setHoveredResizerId(cellId);};const handleLeaveResizer = (e) => {setHoveredResizerId(null);};return (<Stack spacing="4x" shouldWrapChildren><Button onClick={resetResizing}>Reset Resizing</Button><Tablevariant="outline"{...getTableProps()}><TableHeader>{headerGroups.map(headerGroup => {return (<TableHeaderRow {...headerGroup.getHeaderGroupProps()}>{headerGroup.headers.map((column, index) => {const cellId = column.id;const isLastChild = (index === headerGroup.headers.length - 1);const isResizing = column.isResizing;return (<TableHeaderCell {...column.getHeaderProps()}><Flex alignItems="center" height="100%" {...column.customProps}><Boxoverflow="hidden"textOverflow="ellipsis">{column.render('Header')}</Box></Flex>{!isLastChild && (<Box{...column.getResizerProps()} // Use column.getResizerProps to hook up the events correctlydisplay="inline-block"width="8px"height="calc(100% + 2px)" // 2px is border-bottom width of the table headerposition="absolute"right="-8px"top="0"zIndex={1}onMouseEnter={handleEnterResizer(cellId)}onMouseLeave={handleLeaveResizer}><Boxwidth="4px"height="100%"marginLeft="-3px"{...(!isResizing && hoveredResizerId === cellId) && hoveringStyle}{...isResizing && resizingStyle}/></Box>)}</TableHeaderCell>);})}</TableHeaderRow>);})}</TableHeader><TableBody {...getTableBodyProps()}>{rows.map((row, i) => {prepareRow(row);return (<TableRow{...row.getRowProps()}_hover={{bg: 'rgba(255, 255, 255, 0.12)'}}>{row.cells.map((cell, index) => {const cellId = cell.column.id;const isLastChild = (index === row.cells.length - 1);const isResizing = cell.column.isResizing;return (<TableCell{...cell.getCellProps()}{...cell.column.customProps}position="relative"><Boxoverflow="hidden"textOverflow="ellipsis">{cell.render('Cell')}</Box>{!isLastChild && (<Boxdisplay="inline-block"width="8px"height="calc(100% + 1px)" // 1px is border-bottom width of the table rowposition="absolute"right="-8px"top="0"zIndex={1}><Boxwidth="4px"height="100%"marginLeft="-3px"{...(!isResizing && hoveredResizerId === cellId) && hoveringStyle}{...isResizing && resizingStyle}/></Box>)}</TableCell>);})}</TableRow>);})}</TableBody></Table></Stack>);}render(<ResizableTable />);
Scrollable table
EDITABLE EXAMPLEconst CustomScrollbar = ({children,...rest}) => {const [colorMode] = useColorMode();const backgroundColor = {dark: 'gray:70',light: 'gray:30',}[colorMode];return (<Scrollbar{...rest}>{({ScrollView,HorizontalTrack,VerticalTrack,HorizontalThumb,VerticalThumb,getScrollViewProps,getHorizontalTrackProps,getVerticalTrackProps,getHorizontalThumbProps,getVerticalThumbProps,}) => (<><ScrollView {...getScrollViewProps()}>{children}</ScrollView><HorizontalTrack{...getHorizontalTrackProps()}backgroundColor={backgroundColor}><HorizontalThumb {...getHorizontalThumbProps()} /></HorizontalTrack><VerticalTrack{...getVerticalTrackProps()}backgroundColor={backgroundColor}><VerticalThumb {...getVerticalThumbProps()} /></VerticalTrack></>)}</Scrollbar>);}function ScrollableTable() {const tableHeaderRef = React.createRef();const [isHorizontalScrollbarVisible, setHorizontalScrollbarVisible] = React.useState(false);const [isVerticalScrollbarVisible, setVerticalScrollbarVisible] = React.useState(false);const columns = React.useMemo(() => [{Header: 'Event Type',accessor: 'eventType',},{Header: 'Affected Devices',accessor: 'affectedDevices',customProps: {textAlign: 'right',},},{Header: 'Detections',accessor: 'detections',customProps: {textAlign: 'right',},},], []);const data = React.useMemo(() => [{ id: 1, eventType: 'Virus/Malware', affectedDevices: 20, detections: 634 },{ id: 2, eventType: 'Spyware/Grayware', affectedDevices: 20, detections: 634 },{ id: 3, eventType: 'URL Filtering', affectedDevices: 15, detections: 598 },{ id: 4, eventType: 'Web Reputation', affectedDevices: 15, detections: 598 },{ id: 5, eventType: 'Network Virus', affectedDevices: 15, detections: 497 },{ id: 6, eventType: 'Application Control', affectedDevices: 0, detections: 0 }], []);const onScroll = (e) => {const scrollLeft = e.target.scrollLeft;if (!!tableHeaderRef && tableHeaderRef.current.scrollLeft !== scrollLeft) {tableHeaderRef.current.scrollLeft = scrollLeft;}};const onUpdate = (props) => {const { scrollWidth, clientWidth, scrollHeight, clientHeight } = props;const _isHorizontalScrollbarVisible = (scrollWidth > clientWidth);const _isVerticalScrollbarVisible = (scrollHeight > clientHeight);if (_isHorizontalScrollbarVisible !== isHorizontalScrollbarVisible) {setHorizontalScrollbarVisible(_isHorizontalScrollbarVisible);}if (_isVerticalScrollbarVisible !== isVerticalScrollbarVisible) {setVerticalScrollbarVisible(_isVerticalScrollbarVisible);}};const {getTableProps,getTableBodyProps,headerGroups,rows,prepareRow} = useTable({columns,data,},useBlockLayout,);return (<Tablevariant="outline"width={400}height={200}{...getTableProps()}><TableHeader ref={tableHeaderRef}>{headerGroups.map(headerGroup => {const { style, ...props } = headerGroup.getHeaderGroupProps();let headerWidth = style.width;if (isVerticalScrollbarVisible) {headerWidth = `calc(${style.width} + 8px)`; // 8px is scrollbar width}return (<TableHeaderRow style={{...style, width: headerWidth}} {...props}>{headerGroup.headers.map((column, index) => (<TableHeaderCellkey={column.accessor}{...column.getHeaderProps()}{...column.customProps}{...(isVerticalScrollbarVisible && index === headerGroup.headers.length - 1) && {borderRight: 0,}}>{column.render('Header')}</TableHeaderCell>))}{isVerticalScrollbarVisible && (<TableHeaderCell width="2x" padding={0} borderLeft={0} />)}</TableHeaderRow>);})}</TableHeader><CustomScrollbaronScroll={onScroll}onUpdate={onUpdate}><TableBody {...getTableBodyProps()}>{rows.map((row, index) => {prepareRow(row);return (<TableRow{...row.getRowProps()}key={index}_hover={{bg: 'rgba(255, 255, 255, 0.12)'}}>{row.cells.map(cell => {return (<TableCellkey={cell.id}{...cell.getCellProps()}{...cell.column.customProps}>{cell.render('Cell')}</TableCell>);})}</TableRow>);})}</TableBody></CustomScrollbar></Table>);}render(<ScrollableTable />);
Full width table
In this example, we use react-virtualized
to get full width.
import { AutoSizer } from 'react-virtualized';
EDITABLE EXAMPLEfunction FullWidthTable() {const columns = React.useMemo(() => [{Header: 'Event Type',id: 'eventType',accessor: 'eventType',width: 'auto',},{Header: 'Affected Devices',id: 'affectedDevices',accessor: 'affectedDevices',width: 140,customProps: {textAlign: 'right',},},{Header: 'Detections',id: 'detections',accessor: 'detections',width: '10%',customProps: {textAlign: 'right',},},], []);const data = React.useMemo(() => [{ id: 1, eventType: 'Virus/Malware', affectedDevices: 20, detections: 634 },{ id: 2, eventType: 'Spyware/Grayware', affectedDevices: 20, detections: 634 },{ id: 3, eventType: 'URL Filtering', affectedDevices: 15, detections: 598 },{ id: 4, eventType: 'Web Reputation', affectedDevices: 15, detections: 598 },{ id: 5, eventType: 'Network Virus', affectedDevices: 15, detections: 497 },{ id: 6, eventType: 'Application Control', affectedDevices: 0, detections: 0 }], []);const getCalculatedColumns = ({ initColumns, tableWidth }) => {const columns = initColumns.map(column => {let columnWidth = column.width;if (typeof columnWidth === 'string') {const lastChar = columnWidth.substr(columnWidth.length - 1);if (lastChar === '%') {columnWidth = tableWidth * (parseFloat(columnWidth) / 100);return {...column,width: columnWidth};}if (columnWidth === 'auto') {return {...column,width: 0};}}return column;});const customWidthColumns = columns.filter(column => !!column.width);const totalCustomWidth = customWidthColumns.reduce((accumulator, column) => accumulator + column.width, 0);let defaultCellWidth = (tableWidth - totalCustomWidth) / (columns.length - customWidthColumns.length);defaultCellWidth = defaultCellWidth <= 0 ? 150 : defaultCellWidth;return columns.map(column => {if (!!column.width) {return column;}return {...column,width: defaultCellWidth};});};const {getTableProps,getTableBodyProps,headerGroups,rows,prepareRow,} = useTable({columns,data,},useBlockLayout,);return (<Box height={300} overflow="auto"><AutoSizer>{({ height, width }) => {if (height === 0 || width === 0) {return null;}const newColumns = getCalculatedColumns({ initColumns: columns, tableWidth: width });return (<Table {...getTableProps()}><TableHeader>{headerGroups.map(headerGroup => (<TableHeaderRow {...headerGroup.getHeaderGroupProps()}>{headerGroup.headers.map(column => {const columnId = column.id;const _column = newColumns.filter(column => column.id === columnId);const _columnWidth = _column[0].width;return (<TableHeaderCellwidth={_columnWidth}{...column.getHeaderProps()}{...column.customProps}>{column.render('Header')}</TableHeaderCell>);})}</TableHeaderRow>))}</TableHeader><TableBody {...getTableBodyProps()}>{rows.map((row, i) => {prepareRow(row);return (<TableRow{...row.getRowProps()}_hover={{bg: 'rgba(255, 255, 255, 0.12)'}}>{row.cells.map(cell => {const columnId = cell.column.id;const _column = newColumns.filter(column => column.id === columnId);const _columnWidth = _column[0].width;return (<TableCellwidth={_columnWidth}{...cell.getCellProps()}{...cell.column.customProps}>{cell.render('Cell')}</TableCell>);})}</TableRow>);})}</TableBody></Table>);}}</AutoSizer></Box>);}render(<FullWidthTable />);
Row reordering
By react-movable
In this example, we use react-movable
to move table rows.
import { List, arrayMove } from 'react-movable';
EDITABLE EXAMPLEfunction ReactMovableExample() {const { colorMode } = useColorMode();const tableProps = {'dark': {backgroundColor: 'gray:80',},'light': {backgroundColor: 'gray:10',}}[colorMode];const rowProps = {'dark': {backgroundColor: 'gray:100',},'light': {backgroundColor: 'white',},}[colorMode];const [items, setItems] = React.useState([{ id: 1, eventType: 'Virus/Malware', affectedDevices: 20, detections: 634 },{ id: 2, eventType: 'Spyware/Grayware', affectedDevices: 20, detections: 634 },{ id: 3, eventType: 'URL Filtering', affectedDevices: 15, detections: 598 },{ id: 4, eventType: 'Web Reputation', affectedDevices: 15, detections: 598 },{ id: 5, eventType: 'Network Virus', affectedDevices: 15, detections: 497 },{ id: 6, eventType: 'Application Control', affectedDevices: 0, detections: 0 }]);return (<ReactMovable.Listvalues={items}onChange={({ oldIndex, newIndex }) =>setItems(ReactMovable.arrayMove(items, oldIndex, newIndex))}renderList={({ children, props, isDragged }) => (<Table{...tableProps}style={{cursor: isDragged ? 'grabbing' : undefined,}}><TableHeader><TableHeaderRow{...rowProps}><TableHeaderCell width="240px">Event Type</TableHeaderCell><TableHeaderCell width="140px" textAlign="right">Affected Devices</TableHeaderCell><TableHeaderCell width="136px" textAlign="right">Detections</TableHeaderCell></TableHeaderRow></TableHeader><TableBody {...props}>{children}</TableBody></Table>)}renderItem={({ value, props, isDragged, isSelected }) => {const row = (<TableRow{...props}{...rowProps}style={{...props.style,cursor: isDragged ? 'grabbing' : 'grab',}}><TableCell width="240px">{value.eventType}</TableCell><TableCell width="140px" textAlign="right">{value.affectedDevices}</TableCell><TableCell width="136px" textAlign="right">{value.detections}</TableCell></TableRow>);return isDragged ? (<Table style={{ ...props.style }}><TableBody>{row}</TableBody></Table>) : (row);}}/>);}render(<ReactMovableExample />);
By react-dnd
In this example, we use react-dnd
to move table rows.
import { DndProvider, useDrop, useDragLayer } from 'react-dnd';
import { HTML5Backend, getEmptyImage } from 'react-dnd-html5-backend';
import update from 'immutability-helper';
EDITABLE EXAMPLEconst ItemTypes = {TR: 'tr',};const TR = ({ id, row, index, moveTr, ...otherProps }) => {const ref = React.useRef(null);const [, drop] = ReactDND.useDrop({accept: ItemTypes.TR,hover(item, monitor) {if (!ref.current) {return;}const dragIndex = item.index;const hoverIndex = index;// Don't replace items with themselvesif (dragIndex === hoverIndex) {return;}// Determine rectangle on screenconst hoverBoundingRect = ref.current ? ref.current.getBoundingClientRect() : {};// Get vertical middleconst hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;// Determine mouse positionconst clientOffset = monitor.getClientOffset();// Get pixels to the topconst hoverClientY = clientOffset.y - hoverBoundingRect.top;// Only perform the move when the mouse has crossed half of the items height// When dragging downwards, only move when the cursor is below 50%// When dragging upwards, only move when the cursor is above 50%// Dragging downwardsif (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {return;}// Dragging upwardsif (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {return;}// Time to actually perform the actionmoveTr(dragIndex, hoverIndex);// Note: we're mutating the monitor item here!// Generally it's better to avoid mutations,// but it's good here for the sake of performance// to avoid expensive index searches.item.index = hoverIndex;},});const [{ isDragging }, drag, preview] = ReactDND.useDrag({type: ItemTypes.TR,item: { id, index, row },collect: (monitor) => ({isDragging: monitor.isDragging(),}),});drag(drop(ref));React.useEffect(() => {preview(ReactDNDHtml5backend.getEmptyImage(), { captureDraggingState: true });}, []);return (<TableRow{...otherProps}ref={ref}style={{cursor: 'move',opacity: isDragging ? 0 : 1,}}><TableCell width="240px">{row.eventType}</TableCell><TableCell width="140px" textAlign="right">{row.affectedDevices}</TableCell><TableCell width="136px" textAlign="right">{row.detections}</TableCell></TableRow>);};const CustomDragLayer = (props) => {const { itemType, isDragging, item, initialOffset, currentOffset, } = ReactDND.useDragLayer((monitor) => ({item: monitor.getItem(),itemType: monitor.getItemType(),initialOffset: monitor.getInitialSourceClientOffset(),currentOffset: monitor.getSourceClientOffset(),isDragging: monitor.isDragging(),}));const layerStyles = {position: 'fixed',pointerEvents: 'none',zIndex: 100,left: 0,top: 0,};const getItemStyles = (initialOffset, currentOffset) => {if (!initialOffset || !currentOffset) {return {display: 'none',};}const { x, y } = currentOffset;const transform = `translate(${x}px, ${y}px)`;return {transform,WebkitTransform: transform,};};if (!isDragging) {return null;}if (itemType === ItemTypes.TR) {const row = item.row;return (<Box style={layerStyles}><Box style={getItemStyles(initialOffset, currentOffset)}><TableRow {...props}><TableCell width="240px">{row.eventType}</TableCell><TableCell width="140px" textAlign="right">{row.affectedDevices}</TableCell><TableCell width="136px" textAlign="right">{row.detections}</TableCell></TableRow></Box></Box>);}return null;};function ReactDNDExample() {const [items, setItems] = React.useState([{ id: 1, eventType: 'Virus/Malware', affectedDevices: 20, detections: 634 },{ id: 2, eventType: 'Spyware/Grayware', affectedDevices: 20, detections: 634 },{ id: 3, eventType: 'URL Filtering', affectedDevices: 15, detections: 598 },{ id: 4, eventType: 'Web Reputation', affectedDevices: 15, detections: 598 },{ id: 5, eventType: 'Network Virus', affectedDevices: 15, detections: 497 },{ id: 6, eventType: 'Application Control', affectedDevices: 0, detections: 0 }]);const moveTr = React.useCallback((dragIndex, hoverIndex) => {const dragCard = items[dragIndex];setItems(update(items, {$splice: [[dragIndex, 1],[hoverIndex, 0, dragCard],],}));}, [items]);const { colorMode } = useColorMode();const tableProps = {'dark': {backgroundColor: 'gray:80',},'light': {backgroundColor: 'gray:10',}}[colorMode];const rowProps = {'dark': {backgroundColor: 'gray:100',},'light': {backgroundColor: 'white',},}[colorMode];return (<Table{...tableProps}><TableHeader><TableHeaderRow{...rowProps}><TableHeaderCell width="240px">Event Type</TableHeaderCell><TableHeaderCell width="140px" textAlign="right">Affected Devices</TableHeaderCell><TableHeaderCell width="136px" textAlign="right">Detections</TableHeaderCell></TableHeaderRow></TableHeader><TableBody>{items.map((item, i) => (<TR {...rowProps} key={item.id} index={i} id={item.id} row={item} moveTr={moveTr}/>))}<CustomDragLayer {...rowProps} /></TableBody></Table>);}render(<ReactDND.DndProvider backend={ReactDNDHtml5backend.HTML5Backend}><ReactDNDExample /></ReactDND.DndProvider>);
Column reordering
In this example, we use react-beautiful-dnd
to move table columns.
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
EDITABLE EXAMPLEfunction ReorderColumnTable() {const columns = React.useMemo(() => [{Header: 'Event Type',accessor: 'eventType',width: 240,},{Header: 'Affected Devices',accessor: 'affectedDevices',width: 144,customProps: {textAlign: 'right',},},{Header: 'Detections',accessor: 'detections',width: 136,customProps: {textAlign: 'right',},},], []);const data = React.useMemo(() => [{ id: 1, eventType: 'Virus/Malware', affectedDevices: 20, detections: 634 },{ id: 2, eventType: 'Spyware/Grayware', affectedDevices: 20, detections: 634 },{ id: 3, eventType: 'URL Filtering', affectedDevices: 15, detections: 598 },{ id: 4, eventType: 'Web Reputation', affectedDevices: 15, detections: 598 },{ id: 5, eventType: 'Network Virus', affectedDevices: 15, detections: 497 },{ id: 6, eventType: 'Application Control', affectedDevices: 0, detections: 0 }], []);const { colorMode } = useColorMode();const { colors } = useTheme();const columnPlaceholderProps = {'dark': {backgroundColor: 'gray:90',color: 'gray:50',},'light': {backgroundColor: 'gray:10',color: 'gray:30',}}[colorMode];const columnDraggerProps = {'dark': {backgroundColor: 'gray:80',color: 'gray:30',boxShadow: 'dark.md',border: `1px solid ${colors['gray:60']}`,},'light': {backgroundColor: 'gray:10',color: 'gray:80',boxShadow: 'light.md',border: `1px solid ${colors['gray:20']}`,}}[colorMode];const {getTableProps,getTableBodyProps,headerGroups,rows,prepareRow,allColumns,setColumnOrder,} = useTable({columns,data,defaultColumn,},useColumnOrder,useBlockLayout,);const currentColOrder = React.useRef();const [placeholderProps, setPlaceholderProps] = React.useState({});const onDragEnd = (result) => {setPlaceholderProps({});// dropped outside the listif (!result.destination) {return;}};const onDragUpdate = (dragUpdateObj, b) => {if(!dragUpdateObj.destination){return;}const draggableId = dragUpdateObj.draggableId;const destinationIndex = dragUpdateObj.destination.index;const queryAttr = "data-rbd-drag-handle-draggable-id";const domQuery = `[${queryAttr}='${draggableId}']`;const draggedDOM = document.querySelector(domQuery);if (!draggedDOM) {return;}const { clientHeight, clientWidth, offsetWidth } = draggedDOM;const clientX = Array.from(draggedDOM.parentNode.children).slice(0, destinationIndex).reduce((total, curr) => {return total + curr.clientWidth;}, 0);setPlaceholderProps({clientHeight,clientWidth: offsetWidth,clientY: 2,clientX: clientX,content: draggedDOM.innerHTML,});const colOrder = [...currentColOrder.current];const sIndex = dragUpdateObj.source.index;const dIndex = dragUpdateObj.destination && dragUpdateObj.destination.index;if (typeof sIndex === "number" && typeof dIndex === "number") {colOrder.splice(sIndex, 1);colOrder.splice(dIndex, 0, dragUpdateObj.draggableId);setColumnOrder(colOrder);}};const onDragStart = () => {currentColOrder.current = allColumns.map(o => o.id);};return (<Table {...getTableProps()}><TableHeader>{headerGroups.map((headerGroup, index) => (<rbd.DragDropContextkey={index}onDragStart={onDragStart}onDragUpdate={onDragUpdate}onDragEnd={onDragEnd}><rbd.Droppable droppableId="droppable-table-header" direction="horizontal">{(droppableProvided, droppableSnapshot) => {return (<TableHeaderRowref={droppableProvided.innerRef}{...droppableProvided.droppableProps}{...headerGroup.getHeaderGroupProps()}>{headerGroup.headers.map((column, index) => (<rbd.Draggablekey={column.id}draggableId={column.id}index={index}isDragDisabled={!column.accessor}>{(provided, snapshot) => {const { style: headerStyle, ...columnHeaderProps } = column.getHeaderProps();const columnHeaderStyle = {...headerStyle,...provided.draggableProps.style,};return (<TableHeaderCell{...provided.draggableProps}{...provided.dragHandleProps}{...columnHeaderProps}{...column.customProps}{...snapshot.isDragging && columnDraggerProps}ref={provided.innerRef}userSelect="none"style={columnHeaderStyle}>{column.render("Header")}</TableHeaderCell>);}}</rbd.Draggable>))}{droppableProvided.placeholder}<TableHeaderCellposition="absolute"top={placeholderProps.clientY}left={placeholderProps.clientX}height={placeholderProps.clientHeight}width={placeholderProps.clientWidth}display={!!placeholderProps.clientWidth ? 'block' : 'none' }{...columnPlaceholderProps}>{ placeholderProps.content }</TableHeaderCell></TableHeaderRow>);}}</rbd.Droppable></rbd.DragDropContext>))}</TableHeader><TableBody {...getTableBodyProps()}>{rows.map((row, i) => {prepareRow(row);return (<TableRow {...row.getRowProps()}>{row.cells.map(cell => {return (<TableCell{...cell.getCellProps()}{...cell.column.customProps}>{cell.render('Cell')}</TableCell>);})}</TableRow>);})}</TableBody></Table>);}// The resetServerContext function should be used when server side rendering (SSR).// It ensures context state does not persist across multiple renders on the server// which would result in client/server markup mismatches after multiple requests are rendered on the server.rbd.resetServerContext();render(<ReorderColumnTable />);
Props
Table
Name | Type | Default | Description |
---|---|---|---|
size | string | 'md' | The size of TableCells. One of: 'sm' , 'md' , 'lg' |
variant | string | 'default' | The variant of the table style to use. One of: 'default' , 'outline' |
TableHeader
TableHeaderRow
TableHeaderCell
Name | Type | Default | Description |
---|---|---|---|
width | string | number | 150 |
TableBody
TableRow
TableCell
Name | Type | Default | Description |
---|---|---|---|
width | string | number | 150 |