No Preview

Sorry, but you either have no stories or none are selected somehow.

If the problem persists, check the browser console, or the terminal you've run Storybook from.

The component failed to render properly, likely due to a configuration issue in Storybook. Here are some common causes and how you can address them:

  1. Missing Context/Providers: You can use decorators to supply specific contexts or providers, which are sometimes necessary for components to render correctly. For detailed instructions on using decorators, please visit the Decorators documentation.
  2. Misconfigured Webpack or Vite: Verify that Storybook picks up all necessary settings for loaders, plugins, and other relevant parameters. You can find step-by-step guides for configuring Webpack or Vite with Storybook.
  3. Missing Environment Variables: Your Storybook may require specific environment variables to function as intended. You can set up custom environment variables as outlined in the Environment Variables documentation.

InteractiveTable

Alpha

The InteractiveTable is used to display and select data efficiently. It allows for the display and modification of detailed information. With additional functionality it allows for batch editing, as needed by your feature's users.

It is a wrapper around React Table, for more information, refer to the official documentation.

When to use

The InteractiveTable can be used to allow users to perform administrative tasks workflows.

When not to use

Avoid using the InteractiveTable where mobile or responsiveness may be a requirement. Consider an alternative pattern where the user is presented with a summary list and can click/tap to an individual page for each row in that list.

Usage

NameDescriptionDefault
renderExpandedRow
Render function for the expanded row. if not provided, the tables rows will not be expandable.
((row: TableData) => ReactNode)
-
showExpandAll
Whether to show the "Expand all" button. Depends on renderExpandedRow to be provided. Defaults to false.
boolean
false
className
string
-
columns*
Table's columns definition. Must be memoized.
Column<TableData>[]
-
data*
The data to display in the table. Must be memoized.
TableData[]
-
getRowId*
Must return a unique id for each row
((originalRow: TableData, relativeIndex: number, parent?: Row<TableData> | undefined) => string) | undefined
-
headerTooltips
Optional tooltips for the table headers. The key must match the column id.
Record<string, InteractiveTableHeaderTooltip>
-
pageSize

Number of rows per page. A value of zero disables pagination. Defaults to 0. A React hooks error will be thrown if pageSize goes from greater than 0 to 0 or vice versa. If enabling pagination, make sure pageSize remains a non-zero value.

number
0
fetchData

A custom function to fetch data when the table is sorted. If not provided, the table will be sorted client-side. It's important for this function to have a stable identity, e.g. being wrapped into useCallback to prevent unnecessary re-renders of the table.

FetchDataFunc<TableData>
-
initialSortBy
Optional way to set how the table is sorted from the beginning. Must be memoized.
SortingRule<TableData>[]
[]

About columns and data Props

To avoid unnecessary rerenders, columns and data must be memoized.

Columns are rendered in the same order defined in the columns prop. Each Cell's content is automatically rendered by matching the id of the column to the key of each object in the data array prop.

Example
interface TableData { projectName: string; repository: string; } const columns = useMemo<Array<Column<TableData>>>( () => [ id: 'projectName' header: "Project Name" ], [ id: 'repository', header: "Repository" ], [] ); const data = useMemo<Array<TableData>>( () => [ { projectName: 'Grafana', repository: 'https://github.com/grafana/grafana', } ], [ { projectName: 'Loki'; repository: 'https://github.com/grafana/loki'; } ], [] );

Examples

With row expansion

Individual rows can be expanded to display additional details or reconfigure properties previously defined when the row was created. The expanded row area should be used to unclutter the primary presentation of data, carefully consider what the user needs to know at first glance and what can be hidden behind the Row Expander button.

In general, data-types that are consistent across all dataset are in the primary table, variances are pushed to the expanded section for each individual row.

SonniStill sortable!Legend
HansonGiraudeauX5
WhitmanSeabridge
AledaFrimanX5
CullenKobpacMontero
FitzButterwickFox
JordonHarringtonElantra

Row expansion is enabled whenever the renderExpanded prop is provided. The renderExpanded function is called with the row's data and should return a ReactNode.

interface TableData { datasource: string; repo: string; description: string; } const tableData: TableData[] = [ //... ]; const columns: Array<Column<TableData>> = [ //... ]; const ExpandedCell = ({ description }: TableData) => { return <p>{description}</p>; }; export const MyComponent = () => { return ( <InteractiveTable columns={columns} data={tableData} getRowId={(r) => r.datasource} renderExpandedRow={ExpandedCell} showExpandAll /> ); };

Custom Cell Rendering

Individual cells can be rendered using custom content dy defining a cell property on the column definition.

SonniStill sortable!Legend
HansonGiraudeauX5
WhitmanSeabridge
AledaFrimanX5
CullenKobpacMontero
FitzButterwickFox
JordonHarringtonElantra
interface TableData { datasource: string; repo: string; } const RepoCell = ({ row: { original: { repo }, }, }: CellProps<WithCustomCellData, void>) => { return ( <LinkButton href={repo} size="sm" icon="external-link-alt"> Open on GitHub </LinkButton> ); }; const tableData: WithCustomCellData[] = [ { datasource: 'Prometheus', repo: 'https://github.com/prometheus/prometheus', }, { datasource: 'Loki', repo: 'https://github.com/grafana/loki', }, { datasource: 'Tempo', repo: 'https://github.com/grafana/tempo', }, ]; const columns: Array<Column<WithCustomCellData>> = [ { id: 'datasource', header: 'Data Source' }, { id: 'repo', header: 'Repo', cell: RepoCell }, ]; export const MyComponent = () => { return <InteractiveTable columns={columns} data={tableData} getRowId={(r) => r.datasource} />; };

With pagination

The table can be rendered with pagination controls by passing in the pageSize property. All data must be provided as only client side pagination is supported.

SonniStill sortable!Legend
HansonGiraudeauX5
WhitmanSeabridge
AledaFrimanX5
CullenKobpacMontero
FitzButterwickFox
JordonHarringtonElantra
interface WithPaginationData { id: string; firstName: string; lastName: string; car: string; age: number; } export const MyComponent = () => { const pageableData: WithPaginationData[] = [ { id: '48a3926a-e82c-4c26-b959-3a5f473e186e', firstName: 'Brynne', lastName: 'Denisevich', car: 'Cougar', age: 47 }, { id: 'cf281390-adbf-4407-8cf3-a52e012f63e6', firstName: 'Aldridge', lastName: 'Shirer', car: 'Viper RT/10', age: 74, }, // ... { id: 'b9b0b559-acc1-4bd8-b052-160ecf3e4f68', firstName: 'Ermanno', lastName: 'Sinott', car: 'Thunderbird', age: 26, }, ]; const columns: Array<Column<WithPaginationData>> = [ { id: 'firstName', header: 'First name' }, { id: 'lastName', header: 'Last name' }, { id: 'car', header: 'Car', sortType: 'string' }, { id: 'age', header: 'Age', sortType: 'number' }, ]; return <InteractiveTable columns={columns} data={pageableData} getRowId={(r) => r.id} pageSize={15} />; };

With header tooltips

It may be useful to render a tooltip on the header of a column to provide additional information about the data in that column.

SonniStill sortable!Legend
HansonGiraudeauX5
WhitmanSeabridge
AledaFrimanX5
CullenKobpacMontero
FitzButterwickFox
JordonHarringtonElantra
interface WithPaginationData { id: string; firstName: string; lastName: string; car: string; age: number; } export const MyComponent = () => { const pageableData: WithPaginationData[] = [ { id: '48a3926a-e82c-4c26-b959-3a5f473e186e', firstName: 'Brynne', lastName: 'Denisevich', car: 'Cougar', age: 47 }, { id: 'cf281390-adbf-4407-8cf3-a52e012f63e6', firstName: 'Aldridge', lastName: 'Shirer', car: 'Viper RT/10', age: 74, }, // ... { id: 'b9b0b559-acc1-4bd8-b052-160ecf3e4f68', firstName: 'Ermanno', lastName: 'Sinott', car: 'Thunderbird', age: 26, }, ]; const columns: Array<Column<WithPaginationData>> = [ { id: 'firstName', header: 'First name' }, { id: 'lastName', header: 'Last name' }, { id: 'car', header: 'Car', sortType: 'string' }, { id: 'age', header: 'Age', sortType: 'number' }, ]; const headerToolTips = { age: { content: 'The number of years since the person was born' }, lastName: { content: () => { return ( <> <h4>Here is an h4</h4> <div>Some content</div> <div>Some more content</div> </> ); }, iconName: 'plus-square', }, }; return ( <InteractiveTable columns={columns} data={pageableData} getRowId={(r) => r.id} headerToolTips={headerToolTips} /> ); };

With controlled sorting

The default sorting can be changed to controlled sorting by passing in the fetchData function, which is called whenever the sorting changes and should return the sorted data. This is useful when the sorting is done server side. It is important to memoize the fetchData function to prevent unnecessary rerenders and the possibility of an infinite render loop.

interface WithPaginationData { id: string; firstName: string; lastName: string; car: string; age: number; } export const WithControlledSort: StoryFn<typeof InteractiveTable> = (args) => { const columns: Array<Column<WithPaginationData>> = [ { id: 'firstName', header: 'First name', sortType: 'string' }, { id: 'lastName', header: 'Last name', sortType: 'string' }, { id: 'car', header: 'Car', sortType: 'string' }, { id: 'age', header: 'Age' }, ]; const [data, setData] = useState(pageableData); // In production the function will most likely make an API call to fetch the sorted data const fetchData = useCallback(({ sortBy }: FetchDataArgs<WithPaginationData>) => { if (!sortBy?.length) { return setData(pageableData); } setTimeout(() => { const newData = [...pageableData]; newData.sort((a, b) => { const sort = sortBy[0]; const aData = a[sort.id as keyof Omit<WithPaginationData, 'age'>]; const bData = b[sort.id as keyof Omit<WithPaginationData, 'age'>]; if (sort.desc) { return bData.localeCompare(aData); } return aData.localeCompare(bData); }); setData(newData); }, 300); }, []); return <InteractiveTable columns={columns} data={data} getRowId={(r) => r.id} pageSize={15} fetchData={fetchData} />; };