DataTable
The DataTable component simplifies rendering of tabular data. It uses CSS grid for layout and doesn't rely on native tables.
Primary table
To render a table, provide the list of columns with their titles using columns={columns}, and the list of rows using data={data}.
import type { DataTableData } from '@semcore/ui/data-table';
import { DataTable } from '@semcore/ui/data-table';
import React from 'react';
const Demo = () => {
return (
<DataTable
data={data}
aria-label='Basic table example'
defaultGridTemplateColumnWidth='auto'
wMax='800px'
headerProps={{
sticky: true,
}}
columns={[
{
name: 'keyword',
children: 'Keyword',
},
{
name: 'kd',
children: 'KD %',
},
{
name: 'cpc',
children: 'CPC',
},
{
name: 'hiddenColumn',
children: 'Empty',
},
{
name: 'vol',
children: 'Vol.',
},
]}
/>
);
};
const data: DataTableData = [
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: null,
cpc: '$0',
vol: 'n/a',
},
{
keyword: 'ebay buy',
kd: 75.89,
cpc: '$0',
vol: '21,644,290',
},
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: null,
cpc: '$0',
vol: 'n/a',
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
},
];
export default Demo;
Secondary table
Use the secondary table to display small amounts of data in a compact layout.
import { DataTable } from '@semcore/ui/data-table';
import React from 'react';
const Demo = () => {
return (
<DataTable
data={data}
use='secondary'
sort={['kd', 'desc']}
aria-label='Secondary'
columns={[
{ name: 'keyword', children: 'Keyword' },
{ name: 'kd', children: 'KD %' },
{ name: 'cpc', children: 'CPC' },
{ name: 'vol', children: 'Vol.' },
]}
/>
);
};
const data = [
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: '-',
cpc: '$0',
vol: 'n/a',
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
},
];
export default Demo;
Styles
Compact
Cell paddings can be reduced by adding the compact property.
import { DataTable } from '@semcore/ui/data-table';
import React from 'react';
const Demo = () => {
return (
<DataTable
data={data}
compact
aria-label='Compact'
columns={[
{ name: 'keyword', children: 'Keyword', gtcWidth: 'max-content' },
{ name: 'kd', children: 'KD %', gtcWidth: 'max-content' },
{ name: 'cpc', children: 'CPC', gtcWidth: 'max-content' },
{ name: 'vol', children: 'Vol.', gtcWidth: 'max-content' },
]}
/>
);
};
const data = [
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: '-',
cpc: '$0',
vol: 'n/a',
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
},
];
export default Demo;
Table in card
Use variant="card" when displaying a table in a card. Refer to the Card layout for tables example.
Borders
Add borders to specific columns using the borders property.
import { DataTable } from '@semcore/ui/data-table';
import React from 'react';
const Demo = () => {
return (
<DataTable
data={data}
aria-label='Borders'
headerProps={{
sticky: true,
}}
columns={[
{
name: 'keyword',
children: 'Keyword',
},
{
name: 'bordersGroup',
borders: 'both',
children: 'Organic Sessions',
columns: [
{
name: 'kd',
children: 'KD %',
},
{
name: 'cpc',
children: 'CPC',
},
{
name: 'vol',
children: 'Vol.',
},
],
},
{
name: 'other',
children: 'Other',
},
]}
/>
);
};
const data = [
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '1.25',
vol: '32,500,000',
other: 'ebay buy',
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '3.4',
vol: '65,457,920',
other: 'ebay buy',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '0.65',
vol: '47,354,640',
other: 'ebay buy',
},
{
keyword: 'ebay buy',
kd: '-',
cpc: '0',
vol: '2,456,789',
other: 'ebay buy',
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '0',
vol: '21,644,290',
other: 'ebay buy',
},
];
export default Demo;
Themes
You can use different themes for cells and rows.
import { Box } from '@semcore/ui/base-components';
import { DataTable } from '@semcore/ui/data-table';
import React from 'react';
const styles = ['success', 'info', 'muted', 'warning', 'danger'];
const Demo = () => {
const [data] = React.useState(generateData);
return (
<Box wMax={800}>
<DataTable
data={data}
aria-label='Example with themed rows'
columns={[
{ name: 'col_1', children: 'Theme', gtcWidth: '100px' },
{ name: 'col_2', children: 'Column 2', gtcWidth: '100px' },
{ name: 'col_3', children: 'Column 3', gtcWidth: '100px' },
{ name: 'col_4', children: 'Column 4', gtcWidth: '100px' },
{ name: 'col_5', children: 'Column 5', gtcWidth: '100px' },
]}
// @ts-ignore
rowProps={(_, index) => {
return {
theme: styles[index],
};
}}
/>
</Box>
);
};
const generateData = () =>
Array.from({ length: 5 }, (_, i) => ({
col_1: styles[i],
col_2: i,
col_3: i,
col_4: i,
col_5: i,
}));
export default Demo;
Header
Sticky header
Use the sticky and top props to make the table header sticky.
Scroll in the table header is useful for long tables, allowing users to scroll horizontally without having to scroll to the bottom of the table.
import { Box } from '@semcore/ui/base-components';
import { DataTable } from '@semcore/ui/data-table';
import React from 'react';
const Demo = () => {
const top = 0; // the height of the UI that should stick alongside the table header
return (
<>
<DataTable
data={data}
aria-label='Fixed header'
wMax={800}
hMax={200}
headerProps={{ sticky: true, top }}
columns={[
{ name: 'keyword', children: 'Keyword' },
{ name: 'kd', children: 'KD %' },
{ name: 'cpc', children: 'CPC' },
{ name: 'vol', children: 'Vol.' },
]}
/>
<h4>With horizontal scroll</h4>
<DataTable
data={data}
aria-label='Fixed header with scroll'
wMax={800}
hMax={200}
headerProps={{ sticky: true, top, withScrollBar: true }}
columns={[
{ name: 'keyword', children: 'Keyword', gtcWidth: '340px' },
{ name: 'kd', children: 'KD %', gtcWidth: '340px' },
{ name: 'cpc', children: 'CPC', gtcWidth: '340px' },
{ name: 'vol', children: 'Vol.', gtcWidth: '340px' },
]}
/>
</>
);
};
const data = [
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: '-',
cpc: '$0',
vol: 'n/a',
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
},
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: '-',
cpc: '$0',
vol: 'n/a',
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
},
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: '-',
cpc: '$0',
vol: 'n/a',
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
},
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: '-',
cpc: '$0',
vol: 'n/a',
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
},
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: '-',
cpc: '$0',
vol: 'n/a',
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
},
];
export default Demo;
Header customization
You can insert tooltips, selects, and other components into the table header using children and tag.
import { LinkTrigger } from '@semcore/ui/base-trigger';
import { DataTable } from '@semcore/ui/data-table';
import Select from '@semcore/ui/select';
import Tooltip from '@semcore/ui/tooltip';
import { Text } from '@semcore/ui/typography';
import React from 'react';
const Demo = () => {
return (
<DataTable
data={data}
aria-label='Customizing header'
columns={[
{
name: 'keyword',
tag: Tooltip,
title: 'Jesus Christ, Joe, fucking forget about it. I\'m Mr. Pink. Let\'s move on.',
tabIndex: 0,
children: (
<Text noWrap>
Keyword
{' '}
<Text color='text-secondary'>(1 - 100)</Text>
</Text>
),
},
{
name: 'kd',
children: () => {
const [isVisible, setIsVisible] = React.useState(false);
const selectOptions = [
{ value: 'kd', children: 'KD %', label: 'KD %' },
{ value: 'Traffic', children: 'Traffic', label: 'Traffic' },
];
return (
<Select
tag={LinkTrigger}
aria-label='Column'
color='text-primary'
style={{ fontSize: '12px' }}
visible={isVisible}
onVisibleChange={setIsVisible}
options={selectOptions}
defaultValue='kd'
onKeyDown={(e) => {
if (!isVisible && (e.key === 'ArrowDown' || e.key === 'ArrowUp')) {
return false;
}
if (
(e.key === 'ArrowLeft' ||
e.key === 'ArrowRight' ||
e.key === 'ArrowDown' ||
e.key === 'ArrowUp') &&
isVisible
) {
e.stopPropagation();
}
}}
/>
);
},
},
{
name: 'cpc',
tag: Tooltip,
title: 'Jesus Christ, Joe, fucking forget about it. I\'m Mr. Pink. Let\'s move on.',
tabIndex: 0,
children: 'CPC',
},
{
name: 'vol',
tag: Tooltip,
title: 'Jesus Christ, Joe, fucking forget about it. I\'m Mr. Pink. Let\'s move on.',
tabIndex: 0,
children: 'Vol.',
},
]}
/>
);
};
const data = [
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: '-',
cpc: '$0',
vol: 'n/a',
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
},
];
export default Demo;
Multi-level header
Create a multi-level header by nesting columns within each other.
TIP
name property isn't applicable for group columns.
import { DataTable } from '@semcore/ui/data-table';
import React from 'react';
const Demo = () => {
return (
<DataTable
data={data}
aria-label='Multi level header'
columns={[
{ name: 'keyword', children: 'Keyword' },
{
name: 'group',
children: 'Organic Sessions',
borders: 'both',
columns: [
{ name: 'kd', children: 'KD %', gtcWidth: 'max-content' },
{ name: 'cpc', children: 'CPC', gtcWidth: 'max-content' },
{ name: 'vol', children: 'Vol.', gtcWidth: 'max-content' },
],
},
]}
/>
);
};
const data = [
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: '-',
cpc: '$0',
vol: 'n/a',
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
},
];
export default Demo;
Columns
Column width
Control the column width with the gtcWidth prop.
import { DataTable } from '@semcore/ui/data-table';
import React from 'react';
const Demo = () => {
return (
<DataTable
data={data}
aria-label='Column size'
columns={[
{ name: 'keyword', children: 'Keyword', gtcWidth: 'minmax(min-content, 100px)' },
{ name: 'kd', children: 'KD %', gtcWidth: 'minmax(min-content, 100px)' },
{ name: 'cpc', children: 'CPC' },
{ name: 'vol', children: 'Vol.' },
]}
/>
);
};
const data = [
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: '-',
cpc: '$0',
vol: 'n/a',
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
},
];
export default Demo;
Column alignment
You can use justifyContent, alignItems, alignContent, and textAlign props to align content in columns.
import { DataTable } from '@semcore/ui/data-table';
import React from 'react';
const Demo = () => {
return (
<DataTable
data={data}
aria-label='Column alignment'
columns={[
{ name: 'keyword', children: 'Keyword' },
{
name: 'kd',
children: 'KD %',
justifyContent: 'flex-end',
textAlign: 'end',
},
{
name: 'cpc',
children: 'CPC',
justifyContent: 'flex-end',
textAlign: 'end',
},
{
name: 'vol',
children: 'Vol.',
justifyContent: 'flex-end',
textAlign: 'end',
},
]}
/>
);
};
const data = [
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: '-',
cpc: '$0',
vol: 'n/a',
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
},
];
export default Demo;
Fixed column
To fix table columns, use the fixed property.
TIP
If fixed columns aren't visible in the following example, try reducing the window width.
import { DataTable } from '@semcore/ui/data-table';
import React from 'react';
const Demo = () => {
return (
<DataTable
data={data}
aria-label='Fixed columns'
wMax={800}
hMax={400}
headerProps={{ sticky: true }}
columns={[
{
name: 'keyword',
children: 'keyword',
gtcWidth: '300px',
fixed: 'left',
},
{
name: 'kd',
children: 'KD %',
gtcWidth: '300px',
},
{
name: 'cpc',
children: 'CPC',
gtcWidth: '300px',
},
{
name: 'vol',
children: 'Vol.',
gtcWidth: '300px',
fixed: 'right',
},
]}
/>
);
};
const data = [
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: '-',
cpc: '$0',
vol: 'n/a',
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
},
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: '-',
cpc: '$0',
vol: 'n/a',
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
},
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: '-',
cpc: '$0',
vol: 'n/a',
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
},
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: '-',
cpc: '$0',
vol: 'n/a',
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
},
];
export default Demo;
Column grouping
Merge cells by combining column keys in the data. You can merge cells in a specific row, as shown in the following example, or in all rows.
import { DataTable } from '@semcore/ui/data-table';
import React from 'react';
const data = [
{
'keyword': 'ebay buy',
'kd/cpc/vol': 'These three columns are grouped.',
},
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
},
];
const Demo = () => {
return (
<DataTable
data={data}
aria-label='Columns merging'
columns={[
{ name: 'keyword', children: 'Keyword' },
{ name: 'kd', children: 'KD %' },
{ name: 'cpc', children: 'CPC' },
{ name: 'vol', children: 'Vol.' },
]}
/>
);
};
export default Demo;
Rows
Row grouping
Merge cells across rows using the [ROW_GROUP] key in the data.
import { DataTable, ROW_GROUP } from '@semcore/ui/data-table';
import React from 'react';
const data = [
{
keyword: 'ebay buy',
[ROW_GROUP]: [
{
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
},
{
kd: '-',
cpc: '$0',
vol: 'n/a',
},
{
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
},
],
},
{
keyword: 'www.ebay.com',
[ROW_GROUP]: [
{
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
],
},
];
const Demo = () => {
return (
<DataTable
data={data}
aria-label='Rows grouping'
columns={[
{ name: 'keyword', children: 'Keyword' },
{ name: 'kd', children: 'KD %' },
{ name: 'cpc', children: 'CPC' },
{ name: 'vol', children: 'Vol.' },
]}
/>
);
};
export default Demo;
Checkboxes and action bar
You can enable selecting rows with checkboxes with the selectedRows and onSelectedRowsChange props.
import { Box, Flex, Collapse, ScreenReaderOnly } from '@semcore/ui/base-components';
import Button from '@semcore/ui/button';
import { DataTable } from '@semcore/ui/data-table';
import Pagination from '@semcore/ui/pagination';
import { Text } from '@semcore/ui/typography';
import React from 'react';
type CheckboxExampleProps = { animationDuration: number; loading: boolean; sideIndents?: 'wide'; compact?: boolean };
const Demo = (props: CheckboxExampleProps) => {
const [selectedRows, setSelectedRows] = React.useState<string[]>([]);
const [selectedRowsDisplay, setSelectedRowsDisplay] = React.useState(0);
const [ariaMessage, setAriaMessage] = React.useState('');
const [currentPage, setCurrentPage] = React.useState(0);
const tableRef = React.useRef<HTMLDivElement>(null);
const handleChangeSelectedRows = (value: string[]) => {
setSelectedRows(value);
if (!selectedRows.length) setAriaMessage('Action bar appeared before the table');
if (value.length) setSelectedRowsDisplay(value.length);
};
const handleDeselectAll = () => {
setSelectedRows([]);
tableRef.current?.focus();
};
React.useEffect(() => {
const timer = setTimeout(() => setAriaMessage(''), 300);
return () => clearTimeout(timer);
}, [ariaMessage]);
const limit = 5;
const tableData = data.slice(currentPage * limit, currentPage * limit + limit);
return (
<>
<Box
// need this for FF
tabIndex={-1}
wMax={800}
h={250}
style={{ overflow: 'auto', scrollPaddingTop: selectedRows.length ? '44px' : undefined }}
>
<ScreenReaderOnly role='status' aria-live='polite'>
{ariaMessage}
</ScreenReaderOnly>
<Collapse
visible={!!selectedRows.length}
duration={props.animationDuration}
style={{ position: 'sticky', top: 0, zIndex: 50 }}
>
<Flex
role='region'
aria-label='Table action bar'
alignItems='center'
gap={6}
py={2}
px={3}
style={{
backgroundColor: 'var(--intergalactic-bg-primary-neutral, #ffffff)',
}}
>
<Text size={200}>
Selected rows:
{' '}
<Text bold>{selectedRowsDisplay}</Text>
</Text>
<Button use='tertiary' onClick={handleDeselectAll}>
Deselect all
</Button>
</Flex>
</Collapse>
<DataTable
data={tableData}
aria-label='Table example with selectable rows'
defaultGridTemplateColumnWidth='auto'
selectedRows={selectedRows}
onSelectedRowsChange={handleChangeSelectedRows}
ref={tableRef}
sideIndents={props.sideIndents}
loading={props.loading}
compact={props.compact}
headerProps={{
sticky: true,
top: selectedRows.length ? 44 : 0,
animationDuration: props.animationDuration,
}}
columns={[
{ name: 'keyword', children: 'Keyword' },
{ name: 'kd', children: 'KD %' },
{ name: 'cpc', children: 'CPC' },
{ name: 'vol', children: 'Vol.' },
]}
uniqueRowKey='id'
/>
</Box>
<Pagination
mt={4}
totalPages={Math.ceil(data.length / limit)}
currentPage={currentPage + 1}
onCurrentPageChange={(page) => setCurrentPage(page - 1)}
aria-label='Table with selectable rows pagination'
/>
</>
);
};
const data = [
{ id: '1', keyword: 'ebay buy', kd: '31.2', cpc: '$1.15', vol: '22,000' },
{ id: '2', keyword: 'amazon shoes', kd: '47', cpc: '$2.95', vol: '48,000' },
{ id: '3', keyword: 'www.nike.com', kd: '66.4', cpc: '$3.80', vol: 'n/a' },
{ id: '4', keyword: 'buy iphone 13', kd: '59', cpc: '$5.20', vol: '71,000' },
{ id: '5', keyword: 'adidas sale', kd: '40.2', cpc: '$1.85', vol: '19,500' },
{ id: '6', keyword: 'cheap flights expedia', kd: '52', cpc: '$4.10', vol: '35,800' },
{ id: '7', keyword: 'booking.com hotels', kd: '73', cpc: '$6.45', vol: 'n/a' },
{ id: '8', keyword: 'ubereats promo code', kd: '38', cpc: '$2.10', vol: '11,700' },
{ id: '9', keyword: 'buy ps5 online', kd: '64', cpc: '$5.95', vol: '44,200' },
{ id: '10', keyword: 'shopify login', kd: '25.8', cpc: '$0.65', vol: '13,600' },
{ id: '11', keyword: 'h&m online store', kd: '36', cpc: '$1.70', vol: '10,300' },
{ id: '12', keyword: 'buy macbook air', kd: '57.4', cpc: '$4.90', vol: '28,400' },
{ id: '13', keyword: 'www.zara.com', kd: '45', cpc: '$3.20', vol: 'n/a' },
{ id: '14', keyword: 'target clearance', kd: '33', cpc: '$1.25', vol: '12,900' },
{ id: '15', keyword: 'asos men jackets', kd: '41', cpc: '$2.55', vol: '6,800' },
{ id: '16', keyword: 'best buy coupons', kd: '48', cpc: '$3.70', vol: '17,100' },
{ id: '17', keyword: 'walmart near me', kd: '60.1', cpc: '$0.95', vol: '50,000' },
{ id: '18', keyword: 'netflix gift card', kd: '39', cpc: '$2.20', vol: '8,900' },
{ id: '19', keyword: 'www.apple.com', kd: '71', cpc: '$6.90', vol: 'n/a' },
{ id: '20', keyword: 'nike running shoes men', kd: '44', cpc: '$3.60', vol: '21,700' },
{ id: '21', keyword: 'download spotify premium', kd: '58', cpc: '$4.75', vol: '26,800' },
{ id: '22', keyword: 'buy dell laptop', kd: '53.1', cpc: '$5.40', vol: '19,600' },
{ id: '23', keyword: 'gap kids sale', kd: '34', cpc: '$1.10', vol: '5,300' },
];
export const defaultProps: CheckboxExampleProps = {
animationDuration: 200,
loading: false,
sideIndents: undefined,
compact: undefined,
};
Demo.defaultProps = defaultProps;
export default Demo;
Custom row rendering
import { DataTable } from '@semcore/ui/data-table';
import React from 'react';
const Demo = () => {
return (
<DataTable
data={data}
aria-label='Custom rows rendering'
hMax='500px'
totalRows={data.length}
sort={['keyword', 'asc']}
columns={[
{ name: 'keyword', children: 'Keyword', sortable: true },
{ name: 'tags', children: 'Tags' },
{ name: 'cpc', children: 'CPC' },
{ name: 'vol', children: 'Vol.' },
]}
virtualScroll={true}
renderCell={(props) => {
if (props.dataKey === 'tags') {
const tags = props.row[props.dataKey];
if (Array.isArray(tags)) {
return (
<div>
{tags.map((_, i) => {
return (
<div key={i}>
tag
{i + 1}
</div>
);
})}
</div>
);
}
return null;
}
return props.defaultRender();
}}
/>
);
};
const data = Array(100)
.fill(0)
.map((_, i) => ({
keyword: `keyword ${i}`,
tags: Array(Math.floor(Math.random() * 4))
.fill(0),
// .map((_, i) => <div key={i}>tag {i + 1}</div>),
cpc: Math.round(Math.random() * 10),
vol: Math.round(Math.random() * 1000000),
}));
export default Demo;
Cells
Access to cells
To customize the content of a table cell, use the renderCell prop. It receives props described in CellRenderProps.
You can return either a custom React element to override the rendering entirely, or an object that will be applied as props to the cell. If the returned object includes a children property, it will override the default cell content—otherwise, you can use it to apply custom attributes such as theming or data attributes.
import { ButtonLink } from '@semcore/ui/button';
import { DataTable } from '@semcore/ui/data-table';
import React from 'react';
const Demo = () => {
return (
<DataTable
data={data}
aria-label='Access to cells'
columns={[
{ name: 'keyword', children: 'Keyword' },
{ name: 'kd', children: 'KD,%' },
{ name: 'cpc', children: 'CPC' },
{ name: 'vol', children: 'Vol.' },
]}
renderCell={(props) => {
if (props.dataKey === 'keyword') {
return (
<ButtonLink
onClick={() => {
alert(`Click row
props: ${JSON.stringify(Object.keys(props), null, ' ')};
row: ${JSON.stringify(props.row, null, ' ')};
index: ${props.rowIndex};`);
}}
>
{props.value}
</ButtonLink>
);
}
if (props.dataKey === 'kd') {
return {
'data-test-id': 'kd cell',
};
}
return props.defaultRender();
}}
/>
);
};
const data = [
{
keyword: 'it must be link ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: '-',
cpc: '$0',
vol: 'n/a',
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
},
{
'keyword/kd/cpc': '434',
'vol': 'ebay buy',
},
];
export default Demo;
Access to set of cells
import { DataTable } from '@semcore/ui/data-table';
import Spin from '@semcore/ui/spin';
import React from 'react';
const Demo = () => {
return (
<DataTable
data={data}
aria-label='Access to set of cells'
columns={[
{ name: 'keyword', children: 'Keyword' },
{ name: 'kd', children: 'KD %' },
{ name: 'cpc', children: 'CPC' },
{ name: 'vol', children: 'Vol.' },
]}
renderCell={({ dataKey, row, defaultRender }) => {
const value = row[dataKey].toString();
return ['-', '$0', 'n/a'].includes(value) ? <Spin /> : defaultRender();
}}
/>
);
};
const data = [
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: '-',
cpc: '$0',
vol: 'n/a',
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
},
];
export default Demo;
Sorting
To enable column sorting:
- Set the
sortableproperty on the column. - Subscribe to the
onSortChangeevent. - Pass the
sortproperty to the table. - Sort the data provided in the
dataproperty.
import type { DataTableSort } from '@semcore/ui/data-table';
import { DataTable } from '@semcore/ui/data-table';
import Ellipsis from '@semcore/ui/ellipsis';
import React from 'react';
type SortableColumn = Exclude<keyof typeof data[0], 'keyword'>;
const Demo = () => {
const [sort, setSort] = React.useState<DataTableSort<keyof typeof data[0]>>(['kd', 'desc']);
const sortedData = React.useMemo(
() =>
[...data].sort((aRow, bRow) => {
const [prop, sortDirection] = sort;
const a = aRow[prop as SortableColumn];
const b = bRow[prop as SortableColumn];
if (a === b) return 0;
if (sortDirection === 'asc') return a > b ? 1 : -1;
else return a > b ? -1 : 1;
}),
[sort],
);
const numberFormat = React.useMemo(() => new Intl.NumberFormat('en-US'), []);
const currencyFormat = React.useMemo(
() => new Intl.NumberFormat('en-US', { currency: 'USD', style: 'currency' }),
[],
);
const handleSortChange: (sort: DataTableSort<string>, e?: React.SyntheticEvent) => void = (
newSort,
) => {
setSort(newSort as DataTableSort<SortableColumn>);
};
return (
<DataTable
data={sortedData}
sort={sort}
onSortChange={handleSortChange}
aria-label='Sorting'
columns={[
{ name: 'keyword', children: 'Keyword', justifyContent: 'left', sortable: true },
{
name: 'kd',
children: <Ellipsis>KD % and some another text long</Ellipsis>,
justifyContent: 'right',
gtcWidth: 'minmax(0, 68px)',
sortable: true,
},
{ name: 'cpc', children: 'CPC', gtcWidth: 'minmax(0, 60px)', sortable: 'asc' },
{
name: 'vol',
children: 'Vol.',
gtcWidth: 'minmax(0, 120px)',
justifyContent: 'left',
sortable: 'desc',
},
]}
renderCell={(props) => {
if (props.columnName === 'keyword') {
return props.defaultRender();
}
const rawValue = props.row[props.columnName as SortableColumn];
return typeof rawValue === 'number' && rawValue !== -1
? props.columnName === 'cpc'
? currencyFormat.format(rawValue)
: numberFormat.format(rawValue)
: 'n/a';
}}
/>
);
};
export default Demo;
const data = [
{
keyword: 'ebay buy',
kd: 77.8,
cpc: 1.25,
vol: 32500000,
},
{
keyword: 'www.ebay.com',
kd: 11.2,
cpc: 3.4,
vol: 65457920,
},
{
keyword: 'www.ebay.com',
kd: 10,
cpc: 0.65,
vol: 47354640,
},
{
keyword: 'ebay buy',
kd: -1,
cpc: 0,
vol: -1,
},
{
keyword: 'ebay buy',
kd: 75.89,
cpc: 0,
vol: 21644290,
},
];
Expanding column
changeSortSize allows the sorted column to grow in width to fit the sort icon.
import { DataTable } from '@semcore/ui/data-table';
import type { DataTableSort, DataTableProps } from '@semcore/ui/data-table';
import React from 'react';
type SortableColumn = Exclude<keyof typeof data[0], 'keyword'>;
export type SortTableProps = {
use: DataTableProps<typeof data, any, any>['use'];
};
const Demo = (props: SortTableProps) => {
const [sort, setSort] = React.useState<DataTableSort<keyof typeof data[0]>>(['kd', 'desc']);
const sortedData = React.useMemo(
() =>
[...data].sort((aRow, bRow) => {
const [prop, sortDirection] = sort;
const a = aRow[prop as SortableColumn];
const b = bRow[prop as SortableColumn];
if (a === b) return 0;
if (sortDirection === 'asc') return a - b;
else return b - a;
}),
[sort],
);
const numberFormat = React.useMemo(() => new Intl.NumberFormat('en-US'), []);
const currencyFormat = React.useMemo(
() => new Intl.NumberFormat('en-US', { currency: 'USD', style: 'currency' }),
[],
);
return (
<DataTable
data={sortedData}
sort={sort}
use={props.use}
onSortChange={setSort}
aria-label='Expanding sortable column'
columns={[
{ name: 'keyword', children: 'Keyword', justifyContent: 'left', sortable: true },
{
name: 'kd',
children: 'KD %',
justifyContent: 'right',
gtcWidth: 'minmax(0, 68px)',
style: { whiteSpace: 'nowrap' },
borders: 'both',
sortable: true,
changeSortSize: true,
},
{
name: 'cpc',
children: 'CPC',
gtcWidth: 'minmax(60px, 66px)',
borders: 'right',
sortable: true,
changeSortSize: true,
},
{
name: 'vol',
children: 'Vol.',
gtcWidth: 'minmax(0, 120px)',
justifyContent: 'left',
sortable: true,
},
]}
renderCell={(props) => {
if (props.columnName === 'keyword') {
return props.defaultRender();
}
const rawValue = props.row[props.columnName as SortableColumn];
return typeof rawValue === 'number' && rawValue !== -1
? props.columnName === 'cpc'
? currencyFormat.format(rawValue)
: numberFormat.format(rawValue)
: 'n/a';
}}
/>
);
};
export const defaultTableProps: SortTableProps = {
use: 'primary',
};
Demo.defaultProps = defaultTableProps;
export default Demo;
const data = [
{
keyword: 'ebay buy',
kd: 77.8,
cpc: 1.25,
vol: 32500000,
},
{
keyword: 'www.ebay.com',
kd: 11.2,
cpc: 3.4,
vol: 65457920,
},
{
keyword: 'www.ebay.com',
kd: 10,
cpc: 0.65,
vol: 47354640,
},
{
keyword: 'ebay buy',
kd: -1,
cpc: 0,
vol: -1,
},
{
keyword: 'ebay buy',
kd: 75.89,
cpc: 0,
vol: 21644290,
},
];
Scroll
Basic scroll
<DataTable/> inherits all Box properties, such as wMax and hMax, which can be used to enable internal scroll.
By default, horizontal scrolling is displayed at the bottom of the table, but it can also be added to the table header. Scroll in the table header is useful for long tables, allowing users to scroll horizontally without having to scroll to the end of the table. For examples, refer to Sticky header.
import { DataTable } from '@semcore/ui/data-table';
import React from 'react';
const Demo = () => {
return (
<DataTable
data={data}
aria-label='Scroll inside'
hMax={200}
wMax={400}
columns={[
{ name: 'keyword', children: 'Keyword', gtcWidth: '150px' },
{ name: 'kd', children: 'KD %', gtcWidth: '150px' },
{ name: 'cpc', children: 'CPC', gtcWidth: '150px' },
{ name: 'vol', children: 'Vol.', gtcWidth: '150px' },
]}
/>
);
};
const data = [
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: '-',
cpc: '$0',
vol: 'n/a',
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
},
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: '-',
cpc: '$0',
vol: 'n/a',
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
},
];
export default Demo;
Virtual scroll with constant row height
Enable scroll virtualization using the virtualScroll property. Passing rowHeight as its subproperty will ensure the best performance.
import { DataTable } from '@semcore/ui/data-table';
import React from 'react';
const keyword = ['ebay buy', 'www.ebay.com', 'ebay buy'];
const kd = ['77.8', '10', '11.2', '-', '75.89'];
const cpc = ['$3.4', '$0.65', '$1.25', '$0', '$0'];
const vol = ['32,500,000', '65,457,920', '47,354,640', 'n/a', '21,644,290'];
const data = Array(10000)
.fill(0)
.map((_, index) => ({
id: `#${index + 1}`,
keyword: keyword[Math.floor(keyword.length * Math.random())],
// [ROW_GROUP]: [
// {
kd: kd[Math.floor(kd.length * Math.random())],
cpc: cpc[Math.floor(cpc.length * Math.random())],
vol: vol[Math.floor(vol.length * Math.random())],
// },
// ],
}));
const Demo = () => {
return (
<DataTable
data={data}
totalRows={10000}
aria-label='Virtual scroll'
h={400}
virtualScroll={{ rowHeight: 45 }}
headerProps={{ sticky: true }}
columns={[
{ name: 'id', children: 'ID' },
{ name: 'keyword', children: 'Keyword', gtcWidth: '300px' },
{
name: 'group',
children: 'Organic Sessions',
columns: [
{ name: 'kd', children: 'KD %' },
{ name: 'cpc', children: 'CPC' },
{ name: 'vol', children: 'Vol.' },
],
},
]}
/>
);
};
export default Demo;
Virtual scroll with variable row height
Omit rowHeight for tables with variable row heights.
import { DataTable } from '@semcore/ui/data-table';
import React from 'react';
const keyword = [
'ebay buy',
'www.ebay.com',
'ebay buy',
'some long long long long long long text for test multi rows in table with virtualizarion some long long long long long long text for test multi rows in table with virtualizarion some long long long long long long text for test multi rows in table with virtualizarion some long long long long long long text for test multi rows in table with virtualizarion some long long long long long long text for test multi rows in table with virtualizarion some long long long long long long text for test multi rows in table with virtualizarion some long long long long long long text for test multi rows in table with virtualizarion',
];
const kd = ['77.8', '10', '11.2', '-', '75.89'];
const cpc = ['$3.4', '$0.65', '$1.25', '$0', '$0'];
const vol = ['32,500,000', '65,457,920', '47,354,640', 'n/a', '21,644,290'];
const data = Array(10000)
.fill(0)
.map((_, index) => ({
id: `#${index + 1}`,
keyword: index < 3 ? keyword[3] : keyword[Math.floor(keyword.length * Math.random())],
kd: kd[Math.floor(kd.length * Math.random())],
cpc: cpc[Math.floor(cpc.length * Math.random())],
vol: vol[Math.floor(vol.length * Math.random())],
}));
const Demo = () => {
return (
<DataTable
data={data}
totalRows={10000}
aria-label='Virtual scroll with variable row height'
h={400}
virtualScroll
headerProps={{ sticky: true }}
columns={[
{ name: 'id', children: 'ID' },
{ name: 'keyword', children: 'Keyword', gtcWidth: '300px' },
{
name: 'group',
children: 'Organic Sessions',
columns: [
{ name: 'kd', children: 'KD %' },
{ name: 'cpc', children: 'CPC' },
{ name: 'vol', children: 'Vol.' },
],
},
]}
/>
);
};
export default Demo;
Pagination
Avoid placing Pagination inside the table, as the pagination component has a nav landmark assigned to it.
import { Flex } from '@semcore/ui/base-components';
import { DataTable } from '@semcore/ui/data-table';
import Pagination from '@semcore/ui/pagination';
import Select from '@semcore/ui/select';
import React from 'react';
const Demo = () => {
const [limit, setLimit] = React.useState(10);
const [currentPage, setCurrentPage] = React.useState(0);
const numberFormat = React.useMemo(() => new Intl.NumberFormat('en-US'), []);
const currencyFormat = React.useMemo(
() => new Intl.NumberFormat('en-US', { currency: 'USD', style: 'currency' }),
[],
);
const numLim = Number(limit);
const tableData: typeof data = [];
let index = 0;
for (let i = 0; i < 10; i++) {
tableData.push(...data.map((item) => {
index++;
return {
...item,
keyword: `${index} ${item.keyword}`,
};
}));
}
return (
<>
<DataTable
data={tableData.slice(currentPage * numLim, currentPage * numLim + numLim)}
aria-label='Pagination'
h='auto'
columns={[
{ name: 'keyword', children: 'Keyword', justifyContent: 'left' },
{
name: 'kd',
children: 'KD %',
justifyContent: 'right',
gtcWidth: 'minmax(fit-content, 68px)',
},
{ name: 'cpc', children: 'CPC', gtcWidth: 'minmax(fit-content, 60px)' },
{
name: 'vol',
children: 'Vol.',
gtcWidth: 'minmax(fit-content, 120px)',
justifyContent: 'left',
},
]}
renderCell={(props) => {
const { column, row } = props;
if (!row) return props.defaultRender();
const value = row[column.name];
if (column.name === 'keyword') {
return props.defaultRender();
}
if (typeof value !== 'number' || value === -1) {
return 'n/a';
}
if (column.name === 'cpc') {
return currencyFormat.format(value);
}
return numberFormat.format(value);
}}
/>
<Flex gap={4} mt={4}>
<Pagination
totalPages={Math.ceil(tableData.length / numLim)}
currentPage={currentPage + 1}
onCurrentPageChange={(page) => setCurrentPage(page - 1)}
/>
<Select
aria-label='Table rows on the page'
value={numLim}
onChange={setLimit}
options={[{ value: 3, children: 3 }, { value: 5, children: 5 }, { value: 8, children: 8 }, { value: 10, children: 10 }]}
/>
</Flex>
</>
);
};
export default Demo;
const data = [
{
keyword: 'ebay buy',
kd: 77.8,
cpc: 1.25,
vol: 32500000,
},
{
keyword: 'www.ebay.com',
kd: 11.2,
cpc: 3.4,
vol: 65457920,
},
{
keyword: 'www.ebay.com',
kd: 10,
cpc: 0.65,
vol: 47354640,
},
{
keyword: 'ebay buy',
kd: -1,
cpc: 0,
vol: -1,
},
{
keyword: 'ebay buy last',
kd: 75.89,
cpc: 0,
vol: 21644290,
},
];
Table states
Initial loading (Skeleton)
Add a skeleton to the table by directly substituting the cell content.
import { ScreenReaderOnly } from '@semcore/ui/base-components';
import Button from '@semcore/ui/button';
import { DataTable } from '@semcore/ui/data-table';
import Skeleton from '@semcore/ui/skeleton';
import React from 'react';
const Demo = () => {
const [loading, setLoading] = React.useState(true);
const [message, setMessage] = React.useState('');
React.useEffect(() => {
const timer = setTimeout(() => {
setMessage('');
}, 300);
return () => {
clearTimeout(timer);
};
}, [message]);
const toggleLoading = () => {
setLoading(!loading);
setMessage(loading ? 'Data loaded' : 'Loading started');
};
return (
<>
<ScreenReaderOnly role='status' aria-live='polite'>
{message}
</ScreenReaderOnly>
<DataTable
data={data}
aria-label='Loading using Skeleton'
h='auto'
columns={[
{ name: 'keyword', children: 'Keyword' },
{ name: 'kd', children: 'KD %' },
{ name: 'cpc', children: 'CPC' },
{ name: 'vol', children: 'Vol.' },
]}
renderCell={(props) => {
if (loading) {
return (
<Skeleton height={17}>
<Skeleton.Text y='5' width='60%' />
</Skeleton>
);
}
return props.defaultRender();
}}
/>
<Button onClick={toggleLoading} mt={3}>
{loading ? 'Stop loading' : 'Start loading'}
</Button>
</>
);
};
const data = [
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
];
export default Demo;
Updating table (SpinContainer)
SpinContainer is the default loading state for the table and can be enabled by the loading prop.
import { ScreenReaderOnly } from '@semcore/ui/base-components';
import Button from '@semcore/ui/button';
import { DataTable } from '@semcore/ui/data-table';
import React from 'react';
const Demo = (): any => {
const [loading, setLoading] = React.useState(true);
const [message, setMessage] = React.useState('');
React.useEffect(() => {
const timer = setTimeout(() => {
setMessage('');
}, 300);
return () => {
clearTimeout(timer);
};
}, [message]);
const toggleLoading = () => {
setLoading(!loading);
setMessage(loading ? 'Data loaded' : 'Loading started');
};
return (
<>
<ScreenReaderOnly role='status' aria-live='polite'>
{message}
</ScreenReaderOnly>
<DataTable
data={data}
aria-label='Loading using SpinContainer'
loading={loading}
h='auto'
columns={[
{ name: 'keyword', children: 'Keyword' },
{ name: 'kd', children: 'KD %' },
{ name: 'cpc', children: 'CPC' },
{ name: 'vol', children: 'Vol.' },
]}
/>
<Button onClick={toggleLoading} mt={3}>
{loading ? 'Stop loading' : 'Start loading'}
</Button>
</>
);
};
const data = [
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: '-',
cpc: '$0',
vol: 'n/a',
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
},
];
export default Demo;
Limited data
You can hide the limited data with a blurred overlay by using the limit prop, and add your own message for this table state.
import { Flex } from '@semcore/ui/base-components';
import Button from '@semcore/ui/button';
import { DataTable } from '@semcore/ui/data-table';
import type { DataTableData } from '@semcore/ui/data-table';
import { Text } from '@semcore/ui/typography';
import React from 'react';
export type LimitedModeExampleProps = {
rowsLimit?: number;
columnsLimit?: number;
};
const Demo = (props: LimitedModeExampleProps) => {
const { rowsLimit, columnsLimit } = props;
return (
<DataTable
data={data}
aria-label='Limited table example'
defaultGridTemplateColumnWidth='auto'
wMax='800px'
limit={{
fromRow: rowsLimit,
fromColumn: columnsLimit,
renderOverlay() {
return (
<Flex alignItems='center' direction='column' gap={3} py={6} wMax={320} style={{ textWrap: 'balance' }}>
<Text size={300} fontWeight='bold' textAlign='center' id='limited_rows_title'>You've reached your report limit for today</Text>
<Text size={200} textAlign='center' id='limited_rows_description'>
To increase your daily report limit,upgrade to a Guru plan.
</Text>
<Button
theme='success'
use='primary'
aria-describedby='limited_rows_title limited_rows_description'
>
Upgrade to Guru
</Button>
</Flex>
);
},
}}
headerProps={{
sticky: true,
}}
columns={[
{
name: 'keyword',
children: 'Keyword',
},
{
name: 'kd',
children: 'KD %',
},
{
name: 'cpc',
children: 'CPC',
},
{
name: 'hiddenColumn',
children: 'Empty',
},
{
name: 'vol',
children: 'Vol.',
},
]}
/>
);
};
const data: DataTableData = [
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: null,
cpc: '$0',
vol: 'n/a',
},
{
keyword: 'ebay buy',
kd: 75.89,
cpc: '$0',
vol: '21,644,290',
},
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: null,
cpc: '$0',
vol: 'n/a',
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
},
];
export const limitedModeDefaultProps: LimitedModeExampleProps = {
rowsLimit: 3,
columnsLimit: undefined,
};
Demo.defaultProps = limitedModeDefaultProps;
export default Demo;
Empty state
DataTable has a default empty state based on WidgetEmpty which is rendered automatically if the data is empty. You can customize the empty state using the renderEmptyData prop.
import Button from '@semcore/ui/button';
import { DataTable } from '@semcore/ui/data-table';
import { NoData } from '@semcore/ui/widget-empty';
import React from 'react';
const Demo = () => {
return (
<DataTable
data={[]}
renderEmptyData={() => (
<NoData type='nothing-found' my={7} mx='auto'>
<Button mt={4}>Clear filters</Button>
</NoData>
)}
aria-label='Empty table example'
defaultGridTemplateColumnWidth='auto'
wMax='800px'
headerProps={{
sticky: true,
}}
columns={[
{
name: 'keyword',
children: 'keyword',
},
{
name: 'kd',
children: 'KD %',
},
{
name: 'cpc',
children: 'CPC',
},
{
name: 'hiddenColumn',
children: 'HC',
},
{
name: 'vol',
children: 'Vol.',
},
]}
/>
);
};
export default Demo;
Accordion in table
Render expandable rows using the [ACCORDION] key in the data.
import type { DataTableProps } from '@semcore/ui/data-table';
import { DataTable, ACCORDION } from '@semcore/ui/data-table';
import React from 'react';
export type TableInTableProps = {
accordionMode: DataTableProps<typeof data, any, any>['accordionMode'];
onAccordionToggle?: DataTableProps<typeof data, any, any>['onAccordionToggle'];
};
const Demo = (props: TableInTableProps) => {
return (
<DataTable
data={data}
aria-label='Parent'
columns={[
{ name: 'keyword', children: 'Keyword' },
{ name: 'kd', children: 'KD %' },
{ name: 'cpc', children: 'CPC' },
{ name: 'vol', children: 'Vol.' },
]}
accordionMode={props.accordionMode}
onAccordionToggle={props.onAccordionToggle}
/>
);
};
export const tableInTableDefaultProps: TableInTableProps = {
accordionMode: 'independent',
};
Demo.defaultProps = tableInTableDefaultProps;
const data = [
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
[ACCORDION]: [
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: '-',
cpc: '$0',
vol: 'n/a',
},
],
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
[ACCORDION]: [
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: '-',
cpc: '$0',
vol: 'n/a',
},
],
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: '-',
cpc: '$0',
vol: 'n/a',
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
},
];
export default Demo;
Custom accordion content
You can also set a single cell as the accordion trigger, and customize the accordion content.
import { Plot, Line, XAxis, YAxis, ResponsiveContainer, minMax } from '@semcore/ui/d3-chart';
import type { DataTableData } from '@semcore/ui/data-table';
import { DataTable, ACCORDION } from '@semcore/ui/data-table';
import { scaleLinear } from 'd3-scale';
import React from 'react';
export type AccordionInTableProps = {
loading: boolean;
};
const Demo = (props: AccordionInTableProps) => {
return (
<DataTable
loading={props.loading}
data={data}
aria-label='Accordion inside table'
h='100%'
defaultGridTemplateColumnWidth='1fr'
columns={[
{ name: 'keyword', children: 'Keyword', gtcWidth: 'minmax(20%, 50%)' },
{
name: 'group',
children: 'Organic Sessions',
borders: 'both',
columns: [
{ name: 'kd', children: 'KD %' },
{ name: 'cpc', children: 'CPC' },
{ name: 'vol', children: 'Vol.' },
],
},
]}
/>
);
};
export const accordionInsideTableDefaultProps = {
loading: false,
};
Demo.defaultProps = accordionInsideTableDefaultProps;
const ChartExample = () => {
const [[width, height], setSize] = React.useState([600, 300]);
const MARGIN = 40;
const [dataChart, setDataChart] = React.useState<any[]>([]);
React.useEffect(() => {
const dataChart = Array(20)
.fill({})
.map((d, i) => ({
x: i,
y: Math.random() * 10,
}));
setDataChart(dataChart);
}, []);
const xScale = scaleLinear()
.range([MARGIN, width - MARGIN])
.domain(minMax(dataChart, 'x'));
const yScale = scaleLinear()
.range([height - MARGIN, MARGIN])
.domain([0, 10]);
return (
<ResponsiveContainer onResize={setSize} h={300} w='100%' style={{ background: '#fff' }}>
<Plot
data={dataChart}
scale={[xScale, yScale]}
width={width}
height={height}
style={{ background: '#fff' }}
>
<YAxis>
<YAxis.Ticks />
<YAxis.Grid />
</YAxis>
<XAxis>
<XAxis.Ticks />
</XAxis>
<Line x='x' y='y'>
<Line.Dots display />
</Line>
</Plot>
</ResponsiveContainer>
);
};
const data: DataTableData = [
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
[ACCORDION]: <ChartExample />,
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: {
toString: () => '65,457,920',
[ACCORDION]: <ChartExample />,
},
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
[ACCORDION]: <ChartExample />,
},
{
keyword: 'ebay buy',
kd: '-',
cpc: '$0',
vol: 'n/a',
[ACCORDION]: <ChartExample />,
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
[ACCORDION]: <ChartExample />,
},
];
export default Demo;
Accordion with fixed column
import { DataTable, ACCORDION } from '@semcore/ui/data-table';
import type { DataTableData, DataTableProps } from '@semcore/ui/data-table';
import React from 'react';
export type AccordionInTableProps = {
loading: boolean;
};
const Demo = (props: AccordionInTableProps) => {
return (
<DataTable
data={data}
aria-label='Parent with fixed column'
h='100%'
loading={props.loading}
wMax={600}
columns={[
{ name: 'keyword', children: 'Keyword', gtcWidth: '400px', fixed: 'left' },
{ name: 'kd', children: 'KD %', gtcWidth: '150px' },
{ name: 'cpc', children: 'CPC', gtcWidth: '150px' },
{ name: 'vol', children: 'Vol.', gtcWidth: '100px' },
]}
/>
);
};
export const accordionTableDefaultProps = {
loading: false,
};
Demo.defaultProps = accordionTableDefaultProps;
const data = [
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
[ACCORDION]: [
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: '-',
cpc: '$0',
vol: 'n/a',
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
},
],
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: '-',
cpc: '$0',
vol: 'n/a',
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
},
];
export default Demo;
Export to image
import { Flex } from '@semcore/ui/base-components';
import Button from '@semcore/ui/button';
import { DataTable } from '@semcore/ui/data-table';
import DropdownMenu from '@semcore/ui/dropdown-menu';
import FileExportM from '@semcore/ui/icon/FileExport/m';
import React from 'react';
const extensions = ['png', 'jpeg', 'webp'];
const Demo = () => {
const tableRef = React.useRef<HTMLDivElement>(null);
const ref = React.useRef<SVGForeignObjectElement>(null);
const width = 500;
const height = 300;
const downloadImage = React.useCallback(
(extention: string) => async () => {
if (tableRef.current) {
ref.current?.append(tableRef.current.cloneNode(true));
const svgElement = ref.current?.parentElement;
if (svgElement) {
let svgText = svgElementToSvgText(svgElement);
svgText = svgText.replace('xmlns="http://www.w3.org/1999/xhtml"', '');
svgText = svgText.replace(/(\w+)?:?xlink=/g, 'xmlns:xlink='); // Fix root xlink without namespace
svgText = svgText.replace(/NS\d+:href/g, 'xlink:href'); // Safari NS namespace fix
const downloadUrl = await svgText2DownloadUrl(svgText, 2 * width, 2 * height, extention);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = `image.${extention}`;
link.dispatchEvent(
new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window,
}),
);
setTimeout(() => {
link.remove();
}, 100);
}
}
},
[],
);
return (
<Flex>
<div style={{ display: 'none' }}>
<svg xmlns='http://www.w3.org/2000/svg' width='500' height='300' aria-hidden='true'>
<foreignObject width='100%' height='100%' ref={ref} />
</svg>
</div>
<DataTable
data={data}
aria-label='Export in image'
ref={tableRef}
w={500}
columns={[
{ name: 'keyword', children: 'Keyword' },
{ name: 'kd', children: 'KD %' },
{ name: 'cpc', children: 'CPC' },
{ name: 'vol', children: 'Vol.' },
]}
/>
<DropdownMenu>
<DropdownMenu.Trigger tag={Button} ml={4}>
<Button.Addon>
<FileExportM />
</Button.Addon>
<Button.Text>Export</Button.Text>
</DropdownMenu.Trigger>
<DropdownMenu.Popper wMax='257px' aria-label='Extensions'>
<DropdownMenu.List>
{extensions.map((name) => (
<DropdownMenu.Item key={name} onClick={downloadImage(name)}>
{name}
</DropdownMenu.Item>
))}
</DropdownMenu.List>
</DropdownMenu.Popper>
</DropdownMenu>
</Flex>
);
};
const data = [
{
keyword: 'ebay buy',
kd: '77.8',
cpc: '$1.25',
vol: '32,500,000',
},
{
keyword: 'www.ebay.com',
kd: '11.2',
cpc: '$3.4',
vol: '65,457,920',
},
{
keyword: 'www.ebay.com',
kd: '10',
cpc: '$0.65',
vol: '47,354,640',
},
{
keyword: 'ebay buy',
kd: '-',
cpc: '$0',
vol: 'n/a',
},
{
keyword: 'ebay buy',
kd: '75.89',
cpc: '$0',
vol: '21,644,290',
},
];
const getCSSStyles = (parentElement: Element) => {
const selectorTextArr: string[] = [];
for (let c = 0; c < parentElement.classList.length; c++) {
if (!selectorTextArr.includes(`.${parentElement.classList[c]}`))
selectorTextArr.push(`.${parentElement.classList[c]}`);
}
// Add Children element Ids and Classes to the list
const nodes = parentElement.getElementsByTagName('*');
for (let i = 0; i < nodes.length; i++) {
const id = nodes[i].id;
if (!selectorTextArr.includes(`#${id}`)) selectorTextArr.push(`#${id}`);
const classes = nodes[i].classList;
for (let c = 0; c < classes.length; c++)
if (!selectorTextArr.includes(`.${classes[c]}`)) selectorTextArr.push(`.${classes[c]}`);
}
// Extract CSS Rules
let extractedCSSText = '';
for (let i = 0; i < document.styleSheets.length; i++) {
const s = document.styleSheets[i];
try {
if (!s.cssRules) continue;
} catch (e: any) {
if (e.name !== 'SecurityError') throw e; // for Firefox
continue;
}
const cssRules: any = s.cssRules;
for (let r = 0; r < cssRules.length; r++) {
if (
cssRules[r].selectorText &&
selectorTextArr.some((s) => cssRules[r].selectorText.includes(s))
)
extractedCSSText += cssRules[r].cssText;
}
}
return extractedCSSText;
};
const appendCSS = (cssText: string, element: Element) => {
const styleElement = document.createElement('style');
styleElement.setAttribute('type', 'text/css');
styleElement.innerHTML = cssText;
const refNode = element.hasChildNodes() ? element.children[0] : null;
element.insertBefore(styleElement, refNode);
};
const svgElementToSvgText = (svgNode: Element) => {
svgNode.setAttribute('xlink', 'http://www.w3.org/1999/xlink');
const cssStyleText = getCSSStyles(svgNode);
appendCSS(cssStyleText, svgNode);
const serializer = new XMLSerializer();
const svgString = serializer.serializeToString(svgNode);
return svgString;
};
const svgText2DownloadUrl = async (svg: string, width: number, height: number, format: string) =>
new Promise<string>((resolve, reject) => {
const imgsrc = `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svg)))}`;
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.width = width;
canvas.height = height;
const image = new Image();
image.onload = function () {
context?.clearRect(0, 0, width, height);
context?.drawImage(image, 0, 0, width, height);
const img = canvas.toDataURL(`image/${format}`);
resolve(img);
};
image.onerror = reject;
image.src = imgsrc;
});
export default Demo;