import React, { Fragment, useEffect, useMemo, useState } from 'react'; import { storybookExcludedControlParams, StoryMetaType, } from '@lg-tools/storybook-utils'; import { StoryFn, StoryObj } from '@storybook/react'; import { Badge } from '@leafygreen-ui/badge'; import { Button } from '@leafygreen-ui/button'; import { Checkbox } from '@leafygreen-ui/checkbox'; import { css, cx } from '@leafygreen-ui/emotion'; import { Icon } from '@leafygreen-ui/icon'; import { IconButton } from '@leafygreen-ui/icon-button'; import { Pagination, PaginationProps } from '@leafygreen-ui/pagination'; import { Tooltip } from '@leafygreen-ui/tooltip'; import { VerticalAlignment } from './Table/Table.types'; import { makeData, makeKitchenSinkData, Person, } from './utils/makeData.testutils'; import { DynamicDataComponent } from './utils/stories.testutils'; import { AnyDict } from './utils/types'; import { Cell, ExpandedContent, flexRender, HeaderCell, type HeaderGroup, HeaderRow, type LeafyGreenTableCell, type LeafyGreenTableRow, type LGColumnDef, Row, Table, TableBody, TableHead, type TableProps, useLeafyGreenTable, } from '.'; type StoryTableProps = TableProps; const meta: StoryMetaType = { title: 'Composition/Data Display/Table', component: Table, argTypes: { shouldAlternateRowColor: { control: 'boolean', defaultValue: false }, shouldTruncate: { control: 'boolean', defaultValue: true }, verticalAlignment: { options: Object.values(VerticalAlignment), control: { type: 'radio' }, }, }, args: { verticalAlignment: VerticalAlignment.Top, shouldTruncate: true, shouldAlternateRowColor: false, }, parameters: { default: 'LiveExample', controls: { exclude: [ ...storybookExcludedControlParams, 'table', 'children', 'value', 'onSelectChange', 'tableContainerClassName', 'baseFontSize', ], }, // This is needed as a workaround to make arg spreading performant // https://github.com/storybookjs/storybook/issues/11657 docs: { source: { type: 'code' }, }, }, }; export default meta; const Template: StoryFn = args => { const data = makeData(false, 100); const columns = Object.keys(data[0]).filter( x => x !== 'renderExpandedContent' && x !== 'subRows', ); return ( {columns.map((columnName: string) => ( {columnName} ))} {data.map((row: AnyDict) => ( {Object.keys(row).map((cellKey: string, index: number) => { return ( {index === 6 ? ( {row[cellKey]} } > {"I'm leafy, you're leafy"} ) : ( row[cellKey] )} ); })} ))}
); }; export const LiveExample: StoryFn = args => { const tableContainerRef = React.useRef(null); const [data] = useState(() => makeKitchenSinkData(10)); const columns = React.useMemo>>( () => [ { accessorKey: 'dateCreated', header: 'Date Created', enableSorting: true, cell: info => (info.getValue() as Date).toLocaleDateString('en-us', { year: 'numeric', month: 'short', day: 'numeric', }), }, { accessorKey: 'frequency', header: 'Frequency', }, { accessorKey: 'clusterType', header: 'Cluster Type', }, { accessorKey: 'encryptorEnabled', header: 'Encryptor', cell: info => ( {info.getValue() ? 'Enabled' : 'Not enabled'} ), }, { accessorKey: 'mdbVersion', header: 'MongoDB Version', enableSorting: true, size: 90, }, { id: 'actions', header: '', size: 120, cell: _ => { return ( <> ); }, }, ], [], ); const table = useLeafyGreenTable({ data, columns, }); const { rows } = table.getRowModel(); return ( {table.getHeaderGroups().map((headerGroup: HeaderGroup) => ( {headerGroup.headers.map((header, index) => { return ( {flexRender( header.column.columnDef.header, header.getContext(), )} ); })} ))} {rows.map((row: LeafyGreenTableRow) => { const isExpandedContent = row.isExpandedContent ?? false; return ( {!isExpandedContent && ( {row.getVisibleCells().map(cell => { return ( {flexRender( cell.column.columnDef.cell, cell.getContext(), )} ); })} )} {isExpandedContent && } ); })}
); }; export const HundredsOfRows: StoryFn = args => { const tableContainerRef = React.useRef(null); const [data] = useState(() => makeKitchenSinkData(200)); const startTime = performance.now(); // Capture start time useEffect(() => { const endTime = performance.now(); // eslint-disable-next-line no-console console.log(`Table rendered in ${endTime - startTime} ms`); }, [startTime]); // Runs after the first render const columns = React.useMemo>>( () => [ { accessorKey: 'dateCreated', header: 'Date Created', enableSorting: true, cell: info => (info.getValue() as Date).toLocaleDateString('en-us', { year: 'numeric', month: 'short', day: 'numeric', }), }, { accessorKey: 'frequency', header: 'Frequency', }, { accessorKey: 'clusterType', header: 'Cluster Type', }, { accessorKey: 'encryptorEnabled', header: 'Encryptor', cell: info => ( {info.getValue() ? 'Enabled' : 'Not enabled'} ), }, { accessorKey: 'mdbVersion', header: 'MongoDB Version', enableSorting: true, size: 90, }, { id: 'actions', header: '', size: 120, cell: _ => { return ( <> ); }, }, ], [], ); const table = useLeafyGreenTable({ data, columns, }); const { rows } = table.getRowModel(); return ( {table.getHeaderGroups().map((headerGroup: HeaderGroup) => ( {headerGroup.headers.map((header, index) => { return ( {flexRender( header.column.columnDef.header, header.getContext(), )} ); })} ))} {rows.map((row: LeafyGreenTableRow) => { const isExpandedContent = row.isExpandedContent ?? false; return ( {!isExpandedContent && ( {row.getVisibleCells().map(cell => { return ( {flexRender( cell.column.columnDef.cell, cell.getContext(), )} ); })} )} {isExpandedContent && } ); })}
); }; HundredsOfRows.parameters = { chromatic: { disableSnapshot: true, }, }; export const DynamicData: StoryObj = { render: DynamicDataComponent, parameters: { chromatic: { disableSnapshot: true, }, }, }; const defaultColumnVisibility = { firstName: true, lastName: true, age: false, status: false, }; export const ColumnVisibility: StoryFn = args => { const [data] = useState(() => makeData(false, 10)); const columns = useMemo>>( () => [ { id: 'firstName', accessorFn: entry => entry.firstName, enableSorting: true, enableHiding: true, }, { id: 'lastName', accessorFn: entry => entry.lastName, enableSorting: true, enableHiding: true, }, { id: 'age', accessorFn: entry => entry.age, enableSorting: true, enableHiding: true, }, { id: 'status', accessorFn: entry => entry.status, enableSorting: true, enableHiding: true, }, ], [], ); const table = useLeafyGreenTable({ data, columns, initialState: { columnVisibility: defaultColumnVisibility }, }); const { rows } = table.getRowModel(); return (
{table.getAllColumns().map(column => { if (!column.getCanHide()) return null; return ( ); })} {table.getHeaderGroups().map((headerGroup: HeaderGroup) => ( {headerGroup.headers.map(header => { return ( {flexRender( header.column.columnDef.header, header.getContext(), )} ); })} ))} {rows.map((row: LeafyGreenTableRow) => { return ( {row.getVisibleCells().map(cell => { return ( {flexRender( cell.column.columnDef.cell, cell.getContext(), )} ); })} ); })}
); }; ColumnVisibility.parameters = { chromatic: { disableSnapshot: true, }, }; export const WithButtons: StoryFn = args => { const tableContainerRef = React.useRef(null); const [data] = useState(() => makeKitchenSinkData(300)); const columns = React.useMemo>>( () => [ { accessorKey: 'dateCreated', header: 'Date Created', enableSorting: true, cell: info => (info.getValue() as Date).toLocaleDateString('en-us', { year: 'numeric', month: 'short', day: 'numeric', }), }, { accessorKey: 'frequency', header: 'Frequency', }, { accessorKey: 'clusterType', header: 'Cluster Type', }, { accessorKey: 'encryptorEnabled', header: 'Encryptor', cell: info => ( {info.getValue() ? 'Enabled' : 'Not enabled'} ), }, { accessorKey: 'mdbVersion', header: 'MongoDB Version', enableSorting: true, size: 90, }, { id: 'actions', header: '', size: 120, cell: _ => { return ( Button}> {"I'm leafy, you're leafy"} ); }, }, ], [], ); const table = useLeafyGreenTable({ data, columns, }); const { rows } = table.getRowModel(); return ( {table.getHeaderGroups().map((headerGroup: HeaderGroup) => ( {headerGroup.headers.map((header, index) => { return ( {flexRender( header.column.columnDef.header, header.getContext(), )} ); })} ))} {rows.map((row: LeafyGreenTableRow) => { const isExpandedContent = row.isExpandedContent ?? false; return ( {!isExpandedContent && ( {row.getVisibleCells().map(cell => { return ( {flexRender( cell.column.columnDef.cell, cell.getContext(), )} ); })} )} {isExpandedContent && } ); })}
); }; WithButtons.parameters = { chromatic: { disableSnapshot: true, }, }; export const NoTruncation = LiveExample.bind({}); NoTruncation.args = { shouldTruncate: false, }; export const Basic = Template.bind({}); export const ZebraStripes = Template.bind({}); ZebraStripes.args = { shouldAlternateRowColor: true, }; export const NestedRows: StoryFn = args => { const tableContainerRef = React.useRef(null); const [data] = React.useState(() => makeData(false, 200, 5, 3)); const columns = React.useMemo>>( () => [ { accessorKey: 'id', header: 'ID', size: 60, }, { accessorKey: 'firstName', header: 'First Name', cell: info => info.getValue(), }, { accessorFn: row => row.lastName, id: 'lastName', cell: info => info.getValue(), header: () => Last Name, }, { accessorKey: 'age', header: () => 'Age', size: 50, align: 'right', }, { accessorKey: 'visits', header: () => Visits, size: 50, align: 'right', }, { accessorKey: 'status', header: 'Status', size: 90, align: 'right', }, ], [], ); const table = useLeafyGreenTable({ data, columns, }); const { rows } = table.getRowModel(); return ( {table.getHeaderGroups().map((headerGroup: HeaderGroup) => ( {headerGroup.headers.map(header => { return ( {flexRender( header.column.columnDef.header, header.getContext(), )} ); })} ))} {rows.map((row: LeafyGreenTableRow) => { return ( {row .getVisibleCells() .map((cell: LeafyGreenTableCell) => { return ( {flexRender( cell.column.columnDef.cell, cell.getContext(), )} ); })} ); })}
); }; export const ExpandableContent: StoryFn = args => { const tableContainerRef = React.useRef(null); const data = React.useState(() => makeData(true, 200))[0]; const columns = React.useMemo>>( () => [ { accessorKey: 'id', header: 'ID', size: 60, }, { accessorKey: 'firstName', header: 'First Name', cell: info => info.getValue(), }, { accessorFn: row => row.lastName, id: 'lastName', cell: info => info.getValue(), header: () => Last Name, }, { accessorKey: 'age', header: () => 'Age', size: 50, }, { accessorKey: 'visits', header: () => Visits, size: 50, }, { accessorKey: 'status', header: 'Status', size: 90, }, ], [], ); const table = useLeafyGreenTable({ data, columns, }); const { rows } = table.getRowModel(); return ( {table.getHeaderGroups().map((headerGroup: HeaderGroup) => ( {headerGroup.headers.map(header => { return ( {flexRender( header.column.columnDef.header, header.getContext(), )} ); })} ))} {rows.map((row: LeafyGreenTableRow) => { const isExpandedContent = row.isExpandedContent ?? false; return ( {!isExpandedContent && ( {row.getVisibleCells().map(cell => { return ( {flexRender( cell.column.columnDef.cell, cell.getContext(), )} ); })} )} {isExpandedContent && } ); })}
); }; export const SortableRows: StoryFn = args => { const tableContainerRef = React.useRef(null); const data = React.useState(() => makeData(false, 100))[0]; const columns = React.useMemo>>( () => [ { accessorKey: 'id', header: 'ID', size: 60, }, { accessorKey: 'firstName', header: 'First Name', cell: info => info.getValue(), enableSorting: true, }, { accessorFn: row => row.lastName, id: 'lastName', cell: info => info.getValue(), header: () => Last Name, enableSorting: true, }, { accessorKey: 'age', header: () => 'Age', size: 50, enableSorting: true, }, { accessorKey: 'visits', header: () => Visits, size: 50, enableSorting: true, }, { accessorKey: 'status', header: 'Status', size: 90, }, ], [], ); const table = useLeafyGreenTable({ data, columns, }); const { rows } = table.getRowModel(); return ( {table.getHeaderGroups().map((headerGroup: HeaderGroup) => ( {headerGroup.headers.map(header => { return ( {flexRender( header.column.columnDef.header, header.getContext(), )} ); })} ))} {rows.map((row: LeafyGreenTableRow) => { return ( {row.getVisibleCells().map(cell => { return ( {flexRender(cell.column.columnDef.cell, cell.getContext())} ); })} ); })}
); }; export const SelectableRows: StoryFn = args => { const tableContainerRef = React.useRef(null); const data = React.useState(() => makeData(false, 100))[0]; const [rowSelection, setRowSelection] = React.useState({}); const columns = React.useMemo>>( () => [ { accessorKey: 'id', header: 'ID', size: 100, }, { accessorKey: 'firstName', header: 'First Name', cell: info => info.getValue(), }, { accessorFn: row => row.lastName, id: 'lastName', cell: info => info.getValue(), header: () => Last Name, }, { accessorKey: 'age', header: () => 'Age', size: 50, }, { accessorKey: 'visits', header: () => Visits, size: 50, }, { accessorKey: 'status', header: 'Status', size: 140, }, ], [], ); const table = useLeafyGreenTable({ data, columns, state: { rowSelection, }, onRowSelectionChange: setRowSelection, hasSelectableRows: true, }); const { rows } = table.getRowModel(); return (
{table.getHeaderGroups().map((headerGroup: HeaderGroup) => ( {headerGroup.headers.map(header => { return ( {flexRender( header.column.columnDef.header, header.getContext(), )} ); })} ))} {rows.map((row: LeafyGreenTableRow) => { return ( {row.getVisibleCells().map(cell => { return ( {flexRender( cell.column.columnDef.cell, cell.getContext(), )} ); })} ); })}
); }; export const SelectableRowsNoSelectAll: StoryFn = args => { const tableContainerRef = React.useRef(null); const data = React.useState(() => makeData(false, 100))[0]; const [rowSelection, setRowSelection] = React.useState({}); const columns = React.useMemo>>( () => [ { accessorKey: 'id', header: 'ID', size: 100, }, { accessorKey: 'firstName', header: 'First Name', cell: info => info.getValue(), }, { accessorFn: row => row.lastName, id: 'lastName', cell: info => info.getValue(), header: () => Last Name, }, { accessorKey: 'age', header: () => 'Age', size: 50, }, { accessorKey: 'visits', header: () => Visits, size: 50, }, { accessorKey: 'status', header: 'Status', size: 140, }, ], [], ); const table = useLeafyGreenTable({ data, columns, state: { rowSelection, }, onRowSelectionChange: setRowSelection, hasSelectableRows: true, allowSelectAll: false, }); const { rows } = table.getRowModel(); return (
{table.getHeaderGroups().map((headerGroup: HeaderGroup) => ( {headerGroup.headers.map(header => { return ( {flexRender( header.column.columnDef.header, header.getContext(), )} ); })} ))} {rows.map((row: LeafyGreenTableRow) => { return ( {row.getVisibleCells().map(cell => { return ( {flexRender( cell.column.columnDef.cell, cell.getContext(), )} ); })} ); })}
); }; export const WithPagination: StoryFn = ({ darkMode, ...rest }) => { const tableContainerRef = React.useRef(null); const data = React.useState(() => makeData(false, 10000))[0]; const columns = React.useMemo>>( () => [ { accessorKey: 'id', header: 'ID', size: 100, }, { accessorKey: 'firstName', header: 'First Name', cell: info => info.getValue(), }, { accessorFn: row => row.lastName, id: 'lastName', cell: info => info.getValue(), header: () => Last Name, }, { accessorKey: 'age', header: () => 'Age', size: 50, }, { accessorKey: 'visits', header: () => Visits, size: 50, }, { accessorKey: 'status', header: 'Status', size: 140, }, ], [], ); const table = useLeafyGreenTable({ data, columns, withPagination: true, }); const { rows } = table.getRowModel(); return (
{table.getHeaderGroups().map((headerGroup: HeaderGroup) => ( {headerGroup.headers.map(header => { return ( {flexRender( header.column.columnDef.header, header.getContext(), )} ); })} ))} {rows.map((row: LeafyGreenTableRow) => { return ( {row.getVisibleCells().map(cell => { return ( {flexRender( cell.column.columnDef.cell, cell.getContext(), )} ); })} ); })}
{ table.setPageSize(Number(value)); }) as PaginationProps['onItemsPerPageOptionChange'] } numTotalItems={data.length} currentPage={table.getState().pagination.pageIndex + 1} onCurrentPageOptionChange={ ((value, _) => { table.setPageIndex(Number(value) - 1); }) as PaginationProps['onCurrentPageOptionChange'] } onBackArrowClick={() => table.previousPage()} onForwardArrowClick={() => table.nextPage()} darkMode={darkMode} />
); }; // TODO: Will address in a separate PR - removing from stories and will test TS in spec files. // TODO: create a sandbox to demonstrate styled components // export const StyledComponents: StoryFn = args => { // const tableContainerRef = React.useRef(null); // const [data] = useState(() => makeKitchenSinkData(5)); // const columns = React.useMemo>>( // () => [ // { // accessorKey: 'dateCreated', // header: 'Date Created', // enableSorting: true, // cell: info => // (info.getValue() as Date).toLocaleDateString('en-us', { // year: 'numeric', // month: 'short', // day: 'numeric', // }), // }, // { // accessorKey: 'frequency', // header: 'Frequency', // }, // { // accessorKey: 'clusterType', // header: 'Cluster Type', // }, // { // accessorKey: 'encryptorEnabled', // header: 'Encryptor', // // eslint-disable-next-line react/display-name // cell: info => ( // // {info.getValue() ? 'Enabled' : 'Not enabled'} // // ), // }, // { // accessorKey: 'mdbVersion', // header: 'MongoDB Version', // enableSorting: true, // size: 90, // }, // { // id: 'actions', // header: '', // size: 90, // // eslint-disable-next-line react/display-name // cell: _ => { // return ( // <> // // // // // // // // // // // ); // }, // }, // ], // [], // ); // const table = useLeafyGreenTable({ // data, // columns, // }); // const { rows } = table.getRowModel(); // FIXME: // proptypes error. The other components don't have proptypes but should have them // const StyledCell = styled(Cell)` // color: grey; // ` as typeof Cell; // const StyledRow = styled(Row)` // background: snow; // ` as typeof Row; // const StyledHeaderRow = styled(HeaderRow)` // background: whitesmoke; // ` as typeof HeaderRow; // const StyledHeaderCell = styled(HeaderCell)` // color: black; // ` as typeof HeaderCell; // const StyledExpandedContent = styled(ExpandedContent)` // td > div { // background: whitesmoke; // } // ` as typeof ExpandedContent; // return ( // // // {table // .getHeaderGroups() // .map((headerGroup: HeaderGroup) => ( // // {headerGroup.headers.map(header => { // return ( // // {flexRender( // header.column.columnDef.header, // header.getContext(), // )} // // ); // })} // // ))} // // // {rows.map((row: LeafyGreenTableRow) => { // const isExpandedContent = row.isExpandedContent ?? false; // return ( // // {!isExpandedContent && ( // // {row.getVisibleCells().map(cell => { // return ( // // {flexRender( // cell.column.columnDef.cell, // cell.getContext(), // )} // // ); // })} // // )} // {isExpandedContent && } // // ); // })} // //
// ); // };