diff --git a/src/components/table/Skeleton.tsx b/src/components/table/Skeleton.tsx new file mode 100644 index 00000000..c8015af4 --- /dev/null +++ b/src/components/table/Skeleton.tsx @@ -0,0 +1,29 @@ +import styled from 'styled-components' + +function SkeletonUnstyled({ ...props }) { + return ( +
+ +
+ ) +} + +export const Skeleton = styled(SkeletonUnstyled)(({ theme }) => ({ + '@keyframes moving-gradient': { + '0%': { backgroundPosition: '-250px 0' }, + '100%': { backgroundPosition: '250px 0' }, + }, + maxWidth: '400px', + width: '100%', + span: { + borderRadius: theme.borderRadiuses.medium, + maxWidth: '400px', + width: 'unset', + minWidth: '150px', + display: 'block', + height: '12px', + background: `linear-gradient(to right, ${theme.colors.border} 20%, ${theme.colors['border-fill-two']} 50%, ${theme.colors.border} 80%)`, + backgroundSize: '500px 100px', + animation: 'moving-gradient 2s infinite linear forwards', + }, +})) diff --git a/src/components/table/Table.tsx b/src/components/table/Table.tsx index c6c4c67b..c15b8d91 100644 --- a/src/components/table/Table.tsx +++ b/src/components/table/Table.tsx @@ -39,6 +39,7 @@ import { Spinner } from '../Spinner' import { tableFillLevelToBg, tableFillLevelToBorderColor } from './colors' import { FillerRows } from './FillerRows' import { useIsScrolling, useOnVirtualSliceChange } from './hooks' +import { Skeleton } from './Skeleton' import { SortIndicator } from './SortIndicator' import { T } from './T' import { Tbody } from './Tbody' @@ -50,6 +51,8 @@ import { Tr } from './Tr' export type TableProps = DivProps & { data: any[] columns: any[] + loading?: boolean + loadingSkeletonRows?: number hideHeader?: boolean padCells?: boolean fillLevel?: TableFillLevel @@ -126,6 +129,8 @@ function TableRef( { data, columns, + loading = false, + loadingSkeletonRows = 10, hideHeader = false, getRowCanExpand, renderExpanded, @@ -260,11 +265,22 @@ function TableRef( const headerGroups = useMemo(() => table.getHeaderGroups(), [table]) const rows = virtualizeRows ? virtualRows : tableRows + + const skeletonRows = useMemo( + () => Array(loadingSkeletonRows).fill({}), + [loadingSkeletonRows] + ) + const gridTemplateColumns = useMemo( () => fixedGridTemplateColumns ?? getGridTemplateCols(columns), [columns, fixedGridTemplateColumns] ) + const isRaised = useCallback( + (i: number) => rowBg === 'raised' || (rowBg === 'stripes' && i % 2 === 1), + [rowBg] + ) + useEffect(() => { const lastItem = virtualRows[virtualRows.length - 1] @@ -362,111 +378,139 @@ function TableRef( ))} - {paddingTop > 0 && ( - - )} - {rows.map((maybeRow) => { - const i = maybeRow.index - const isLoaderRow = i > tableRows.length - 1 - const row: Row | null = isRow(maybeRow) - ? maybeRow - : isLoaderRow - ? null - : tableRows[maybeRow.index] - const key = row?.id ?? maybeRow.index - const raised = - rowBg === 'raised' || (rowBg === 'stripes' && i % 2 === 1) - - return ( - + {loading ? ( + <> + {skeletonRows.map((_, i) => ( onRowClick?.(e, row)} + key={i} $fillLevel={fillLevel} - $raised={raised} - $highlighted={row?.id === highlightedRowId} - $selectable={row?.getCanSelect() ?? false} - $selected={row?.getIsSelected() ?? false} - $clickable={!!onRowClick} - // data-index is required for virtual scrolling to work - data-index={row?.index} - {...(virtualizeRows - ? { ref: rowVirtualizer.measureElement } - : {})} + $raised={isRaised(i)} > - {isNil(row) && isLoaderRow ? ( - ( + -
Loading
- -
- ) : ( - row?.getVisibleCells().map((cell) => ( - + + ))} + + ))} + + ) : ( + <> + {paddingTop > 0 && ( + + )} + {rows.map((maybeRow) => { + const i = maybeRow.index + const isLoaderRow = i > tableRows.length - 1 + const row: Row | null = isRow(maybeRow) + ? maybeRow + : isLoaderRow + ? null + : tableRows[maybeRow.index] + const key = row?.id ?? maybeRow.index + + return ( + + onRowClick?.(e, row)} + $fillLevel={fillLevel} + $raised={isRaised(i)} + $highlighted={row?.id === highlightedRowId} + $selectable={row?.getCanSelect() ?? false} + $selected={row?.getIsSelected() ?? false} + $clickable={!!onRowClick} + // data-index is required for virtual scrolling to work + data-index={row?.index} + {...(virtualizeRows + ? { ref: rowVirtualizer.measureElement } + : {})} + > + {isNil(row) && isLoaderRow ? ( + +
Loading
+ +
+ ) : ( + row?.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + )) + )} + + {row?.getIsExpanded() && ( + - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - - )) - )} - - {row?.getIsExpanded() && ( - - - - {renderExpanded({ row })} - - - )} -
- ) - })} - {paddingBottom > 0 && ( - + + + {renderExpanded({ row })} + + + )} +
+ ) + })} + {paddingBottom > 0 && ( + + )} + )} - {isEmpty(rows) && ( + {isEmpty(rows) && !loading && (