Dynamic table
A dynamic table displays rows of data with built-in pagination, sorting, and re-ordering functionality. interface President {
id: number;
name: string;
party: string;
term: string;
}
// applied as rows in the form
const rows = presidents.map((president: President, index: number) => ({
key: `row-${index}-${president.name}`,
cells: [
{
key: createKey(president.name),
content: (
<NameWrapper>
<AvatarWrapper>
<Avatar name={president.name} size="medium" />
</AvatarWrapper>
<Link href="https://atlassian.design">{president.name}</Link>
</NameWrapper>
),
},
{
key: createKey(president.party),
content: president.party,
},
{
key: president.id,
content: president.term,
},
]
})
Uncontrolled
Dynamic table manages sorting, pagination, loading, and drag and drop state management by default. If this functionality isn't needed, use the native HTML table element.
Comment | Actions | |||
---|---|---|---|---|
George Washington | None, Federalist | 1789-1797 | 0 | |
John Adams | Federalist | 1797-1801 | 1 | |
Thomas Jefferson | Democratic-Republican | 1801-1809 | 2 | |
James Madison | Democratic-Republican | 1809-1817 | 3 | |
James Monroe | Democratic-Republican | 1817-1825 | 4 |
import React from 'react';
import DynamicTable from '@atlaskit/dynamic-table';
import { head, rows } from './content/sample-data';
export default function TableUncontrolled() {
return (
<DynamicTable
head={head}
rows={rows}
rowsPerPage={5}
defaultPage={1}
loadingSpinnerSize="large"
isRankable
testId="table"
/>
);
}
Controlled
In a controlled dynamic table, you need to manage sorting, drag and drop, and pagination on your own. If you require this functionality, use the stateless dynamic table component.
Comment | Actions | |||
---|---|---|---|---|
Dwight David Eisenhower | Republican | 1953-1961 | 23 | |
Harry S Truman | Democrat | 1945-1953 | 22 | |
Franklin Delano Roosevelt | Democrat | 1933-1945 | 21 | |
Herbert C. Hoover | Republican | 1929-1933 | 20 | |
Calvin Coolidge | Republican | 1923-1929 | 19 |
import React, { useState } from 'react';
import Banner from '@atlaskit/banner';
import ButtonGroup from '@atlaskit/button/button-group';
import Button from '@atlaskit/button/new';
import { DynamicTableStateless } from '@atlaskit/dynamic-table';
import WarningIcon from '@atlaskit/icon/glyph/warning';
import { head, rows } from './content/sample-data';
export default function TableControlled() {
const [pageNumber, setPageNumber] = useState(3);
const navigateTo = (pageNumber: number) => {
setPageNumber(pageNumber);
};
return (
<>
<Banner appearance="warning" icon={<WarningIcon label="" secondaryColor="inherit" />}>
This is a stateless table example, which doesn't have pagination support. To navigate pages,
use the "Previous page" and "Next page" buttons.
</Banner>
<ButtonGroup label="Paging navigation">
<Button isDisabled={pageNumber === 1} onClick={() => navigateTo(pageNumber - 1)}>
Previous Page
</Button>
<Button isDisabled={pageNumber === 5} onClick={() => navigateTo(pageNumber + 1)}>
Next Page
</Button>
</ButtonGroup>
<DynamicTableStateless
head={head}
rows={rows}
rowsPerPage={5}
page={pageNumber}
loadingSpinnerSize="large"
isLoading={false}
isFixedSize
sortKey="term"
sortOrder="DESC"
onSort={() => console.log('onSort')}
onSetPage={() => console.log('onSetPage')}
/>
</>
);
}
Sorting
Sorting a dynamic table takes place using the key
set on each cell. Note that the type of key
will affect the sorted result. For example, numeric and string keys will result in different
orderings. Avoid using objects or React nodes as keys.
1 | d |
2 | c |
3 | a |
4 | b |
import React, { type FC } from 'react';
import DynamicTable from '@atlaskit/dynamic-table';
const caption = 'Example sorting with DynamicTable';
const head = {
cells: [
{
key: 'number',
content: 'Number',
isSortable: true,
},
{
key: 'string',
content: 'String',
isSortable: true,
},
],
};
const rows = [
[1, 'd'],
[2, 'c'],
[3, 'a'],
[4, 'b'],
].map(([number, letter]) => ({
key: number.toString(),
cells: [
{
key: number,
content: number,
},
{
key: letter,
content: letter,
},
],
}));
const SortingExample: FC = () => (
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
<div style={{ maxWidth: 800 }}>
<DynamicTable
caption={caption}
head={head}
rows={rows}
isFixedSize
defaultSortKey="number"
defaultSortOrder="ASC"
/>
</div>
);
export default SortingExample;
Loading states
Dynamic table uses a spinner to denote loading state. This is toggled by the isLoading
prop.
Table content is set to 20% opacity in this loading state, using the opacity.loading
token.
Comment | Actions | |||
---|---|---|---|---|
George Washington | None, Federalist | 1789-1797 | 0 | |
John Adams | Federalist | 1797-1801 | 1 | |
Thomas Jefferson | Democratic-Republican | 1801-1809 | 2 | |
James Madison | Democratic-Republican | 1809-1817 | 3 | |
James Monroe | Democratic-Republican | 1817-1825 | 4 |
import React, { useState } from 'react';
import Button from '@atlaskit/button/new';
import { DynamicTableStateless } from '@atlaskit/dynamic-table';
import { head, rows } from './content/sample-data';
const LoadingPartialPageExample = () => {
const [isLoading, setIsLoading] = useState(false);
return (
<div>
<Button onClick={() => setIsLoading((loading) => !loading)}>Toggle loading</Button>
<DynamicTableStateless
head={head}
rows={rows}
rowsPerPage={5}
page={1}
isLoading={isLoading}
/>
</div>
);
};
export default LoadingPartialPageExample;
Empty state
Use the emptyView
prop to show an empty state in the dynamic table. Empty states communicate that
the table has no content to show. If there's an action that people must take to create or show table
content, add this to the empty state so they know how to proceed. See
empty state guidelines for more guidance.
Comment | Actions |
---|
The table is empty and this is the empty view
import React from 'react';
import { DynamicTableStateless } from '@atlaskit/dynamic-table';
import { head } from './content/sample-data';
const EmptyViewExample = () => (
<DynamicTableStateless
head={head}
emptyView={<h2>The table is empty and this is the empty view</h2>}
/>
);
export default EmptyViewExample;
Pagination
You can enable or disable pagination with the rowsPerPage
prop. If the rowsPerPage
prop is set
and the number of rows exceed one page, the pagination component
will show below the table.
Comment | Actions | |||
---|---|---|---|---|
George Washington | None, Federalist | 1789-1797 | 0 | |
John Adams | Federalist | 1797-1801 | 1 | |
Thomas Jefferson | Democratic-Republican | 1801-1809 | 2 | |
James Madison | Democratic-Republican | 1809-1817 | 3 | |
James Monroe | Democratic-Republican | 1817-1825 | 4 |
import React from 'react';
import DynamicTable from '@atlaskit/dynamic-table';
import { head, rows } from './content/sample-data';
export default () => {
return (
<DynamicTable
caption="List of US Presidents"
head={head}
rows={rows}
rowsPerPage={5}
defaultPage={1}
isFixedSize
defaultSortKey="term"
defaultSortOrder="ASC"
onSort={() => console.log('onSort')}
onSetPage={() => console.log('onSetPage')}
/>
);
};
Drag and drop
Drag and drop functionality is built into the dynamic table. You can enable it using the
isRankable
prop. This lets people drag rows and rank them in different orders.
Comment | Actions | |||
---|---|---|---|---|
George Washington | None, Federalist | 1789-1797 | 0 | |
John Adams | Federalist | 1797-1801 | 1 | |
Thomas Jefferson | Democratic-Republican | 1801-1809 | 2 | |
James Madison | Democratic-Republican | 1809-1817 | 3 | |
James Monroe | Democratic-Republican | 1817-1825 | 4 |
import React, { useCallback, useState } from 'react';
import DynamicTable from '@atlaskit/dynamic-table';
import Toggle from '@atlaskit/toggle';
import { createHead, rows } from './content/sample-data';
export default function DynamicTableRankableExample() {
const [isFixedSize, setIsFixedSize] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const onToggleFixedChange = useCallback(() => {
setIsFixedSize((isFixedSize) => !isFixedSize);
}, [setIsFixedSize]);
const onLoadingChange = useCallback(() => {
setIsLoading((isLoading) => !isLoading);
}, [setIsLoading]);
return (
<div>
<div>
<Toggle label="Fixed size" onChange={onToggleFixedChange} isChecked={isFixedSize} />
Fixed size
</div>
<div>
<Toggle label="Loading" onChange={onLoadingChange} isChecked={isLoading} />
Loading
</div>
<DynamicTable
caption="List of US Presidents"
head={createHead(isFixedSize)}
rows={rows}
rowsPerPage={5}
defaultPage={1}
isRankable
isLoading={isLoading}
onRankStart={(params) => console.log('onRankStart', params)}
onRankEnd={(params) => console.log('onRankEnd', params)}
onSort={() => console.log('onSort')}
onSetPage={() => console.log('onSetPage')}
testId="my-table"
/>
</div>
);
}
Overflow
Larger tables or tables that cannot be constrained easily can use horizontal scroll. This isn't supported directly by dynamic table, but the component can be easily extended to support this.
Be mindful that horizontally scrolling tables can cause accessibility issues if there isn't enough visual affordance to indicate that the table has a scroll. For this reason, we recommend finding ways to simplify the table before opting for a horizontal scroll solution.
Comment | Actions | |||
---|---|---|---|---|
George Washington | None, Federalist | 1789-1797 | 0 | |
John Adams | Federalist | 1797-1801 | 1 | |
Thomas Jefferson | Democratic-Republican | 1801-1809 | 2 | |
James Madison | Democratic-Republican | 1809-1817 | 3 | |
James Monroe | Democratic-Republican | 1817-1825 | 4 | |
John Quincy Adams | Democratic-Republican | 1825-1829 | 5 | |
Andrew Jackson | Democrat | 1829-1837 | 6 | |
Martin van Buren | Democrat | 1837-1841 | 7 | |
William H. Harrison | Whig | 1841 | 8 | |
John Tyler | Whig | 1841-1845 | 9 |
/**
* @jsxRuntime classic
* @jsx jsx
*/
import { css, jsx } from '@compiled/react';
import DynamicTable from '@atlaskit/dynamic-table';
import { head, rows } from './content/sample-data';
const wrapperStyles = css({
overflowX: 'auto',
});
const overflowStyles = css({
width: 1000,
});
const HeadlessExample = () => (
<div css={wrapperStyles}>
<div css={overflowStyles}>
<DynamicTable head={head} rows={rows.slice(0, 10)} />
</div>
</div>
);
export default HeadlessExample;
Custom column span
Individual cells can use colSpan
to make cells span across more than one column.
Time | Monday | Tuesday | Wednesday | Thursday | Friday |
---|---|---|---|---|---|
9:00 | Math | History | Science | Computing | Math |
12:00 | LUNCH | ||||
13:00 | Science | History | Psychology | Computing | Business |
import React from 'react';
import DynamicTable from '@atlaskit/dynamic-table';
const days = ['Time', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'];
const head = {
cells: days.map((day) => ({
key: day,
content: day,
})),
};
const rows = [
{
key: `morning-row`,
cells: ['9:00', 'Math', 'History', 'Science', 'Computing', 'Math'].map((content, index) => ({
key: index,
content,
})),
},
{
key: 'midday-row',
cells: [
{
key: 0,
content: '12:00',
},
{
key: 1,
content: 'LUNCH',
colSpan: 5,
},
],
},
{
key: 'afternoon-row',
cells: ['13:00', 'Science', 'History', 'Psychology', 'Computing', 'Business'].map(
(content, index) => ({
key: index,
content,
}),
),
},
];
const CustomColSpanExample = () => (
<DynamicTable
caption="Class timetable"
head={head}
rows={rows}
loadingSpinnerSize="large"
isLoading={false}
isFixedSize
/>
);
export default CustomColSpanExample;
Highlighted row
You can highlight rows with highlightedRowIndex
. Highlights provide additional visual prominence
to a row. For example, you could use highlighted rows to show new rows that are added to a table.
Keep in mind that people with visual disabilities may not notice when rows are highlighted, so don’t rely on highlights alone to convey information. Never use highlighted rows to indicate that a person has selected or focused on the row.
Comment | Actions | |||
---|---|---|---|---|
George Washington | None, Federalist | 1789-1797 | 0 | |
John Adams | Federalist | 1797-1801 | 1 | |
Thomas Jefferson | Democratic-Republican | 1801-1809 | 2 | |
James Madison | Democratic-Republican | 1809-1817 | 3 | |
James Monroe | Democratic-Republican | 1817-1825 | 4 | |
John Quincy Adams | Democratic-Republican | 1825-1829 | 5 | |
Andrew Jackson | Democrat | 1829-1837 | 6 | |
Martin van Buren | Democrat | 1837-1841 | 7 | |
William H. Harrison | Whig | 1841 | 8 | |
John Tyler | Whig | 1841-1845 | 9 |
import React from 'react';
import DynamicTable from '@atlaskit/dynamic-table';
import { head, rows } from './content/sample-data';
const rowsWithHighlightedRow = [...rows];
rowsWithHighlightedRow[6].isHighlighted = true;
export default () => (
<DynamicTable
head={head}
rows={rowsWithHighlightedRow}
rowsPerPage={10}
page={1}
testId="the-table"
/>
);
Interactive row
Rows can be interactive if you provide an onClick
or onKeydown
handler to the row.
Click in a row to highlight it
Comment | Actions | |||
---|---|---|---|---|
George Washington | None, Federalist | 1789-1797 | 0 | |
John Adams | Federalist | 1797-1801 | 1 | |
Thomas Jefferson | Democratic-Republican | 1801-1809 | 2 | |
James Madison | Democratic-Republican | 1809-1817 | 3 | |
James Monroe | Democratic-Republican | 1817-1825 | 4 | |
John Quincy Adams | Democratic-Republican | 1825-1829 | 5 | |
Andrew Jackson | Democrat | 1829-1837 | 6 | |
Martin van Buren | Democrat | 1837-1841 | 7 | |
William H. Harrison | Whig | 1841 | 8 | |
John Tyler | Whig | 1841-1845 | 9 |
import React from 'react';
import { DynamicTableStateless } from '@atlaskit/dynamic-table';
import type { RowType } from '@atlaskit/dynamic-table/types';
import { rows as allRows, head } from './content/sample-data';
const rows = allRows.slice(0, 10);
const extendRows = (
rows: Array<RowType>,
onClick: (e: React.MouseEvent, rowIndex: number) => void,
) => {
return rows.map((row, index) => ({
...row,
onClick: (e: React.MouseEvent) => onClick(e, index),
}));
};
interface StatelessState {
highlightedRowIndex?: number[];
}
class RegularStatelessExample extends React.Component<{}, StatelessState> {
state = {
highlightedRowIndex: [],
};
onRowClick = (e: React.MouseEvent, rowIndex: number) => {
this.setState(({ highlightedRowIndex }) => {
const newHighlightedRowIndex = [...(highlightedRowIndex || [])];
const existingIndex = newHighlightedRowIndex.indexOf(rowIndex);
if (existingIndex > -1) {
newHighlightedRowIndex.splice(existingIndex, 1);
} else {
newHighlightedRowIndex.push(rowIndex);
}
return { highlightedRowIndex: newHighlightedRowIndex };
});
};
render() {
return (
<DynamicTableStateless
head={head}
highlightedRowIndex={this.state.highlightedRowIndex}
rows={extendRows(rows, this.onRowClick)}
/>
);
}
}
export default () => {
return (
<>
<h4>Click in a row to highlight it</h4>
<RegularStatelessExample />
</>
);
};