Skip to content

Commit

Permalink
feat: Table loading skeleton (#663)
Browse files Browse the repository at this point in the history
  • Loading branch information
maciaszczykm authored Nov 21, 2024
1 parent fb05211 commit 10258af
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 90 deletions.
29 changes: 29 additions & 0 deletions src/components/table/Skeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import styled from 'styled-components'

function SkeletonUnstyled({ ...props }) {
return (
<div {...props}>
<span />
</div>
)
}

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',
},
}))
224 changes: 134 additions & 90 deletions src/components/table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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
Expand Down Expand Up @@ -126,6 +129,8 @@ function TableRef(
{
data,
columns,
loading = false,
loadingSkeletonRows = 10,
hideHeader = false,
getRowCanExpand,
renderExpanded,
Expand Down Expand Up @@ -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]

Expand Down Expand Up @@ -362,111 +378,139 @@ function TableRef(
))}
</Thead>
<Tbody>
{paddingTop > 0 && (
<FillerRows
columns={columns}
rows={rows}
height={paddingTop}
position="top"
stickyColumn={stickyColumn}
clickable={!!onRowClick}
fillLevel={fillLevel}
/>
)}
{rows.map((maybeRow) => {
const i = maybeRow.index
const isLoaderRow = i > tableRows.length - 1
const row: Row<unknown> | 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 (
<Fragment key={key}>
{loading ? (
<>
{skeletonRows.map((_, i) => (
<Tr
key={key}
onClick={(e) => 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 ? (
<TdLoading
key={i}
{columns.map((_, j) => (
<Td
key={j}
$fillLevel={fillLevel}
$firstRow={i === 0}
$padCells={padCells}
$loose={loose}
$stickyColumn={stickyColumn}
$truncateColumn={false}
$center={false}
colSpan={columns.length}
>
<div>Loading</div>
<Spinner color={theme.colors['text-xlight']} />
</TdLoading>
) : (
row?.getVisibleCells().map((cell) => (
<Td
key={cell.id}
<Skeleton />
</Td>
))}
</Tr>
))}
</>
) : (
<>
{paddingTop > 0 && (
<FillerRows
columns={columns}
rows={rows}
height={paddingTop}
position="top"
stickyColumn={stickyColumn}
clickable={!!onRowClick}
fillLevel={fillLevel}
/>
)}
{rows.map((maybeRow) => {
const i = maybeRow.index
const isLoaderRow = i > tableRows.length - 1
const row: Row<unknown> | null = isRow(maybeRow)
? maybeRow
: isLoaderRow
? null
: tableRows[maybeRow.index]
const key = row?.id ?? maybeRow.index

return (
<Fragment key={key}>
<Tr
key={key}
onClick={(e) => 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 ? (
<TdLoading
key={i}
$fillLevel={fillLevel}
$firstRow={i === 0}
$padCells={padCells}
$loose={loose}
$stickyColumn={stickyColumn}
$truncateColumn={false}
$center={false}
colSpan={columns.length}
>
<div>Loading</div>
<Spinner color={theme.colors['text-xlight']} />
</TdLoading>
) : (
row?.getVisibleCells().map((cell) => (
<Td
key={cell.id}
$fillLevel={fillLevel}
$firstRow={i === 0}
$padCells={padCells}
$loose={loose}
$stickyColumn={stickyColumn}
$highlight={
cell.column?.columnDef?.meta?.highlight
}
$truncateColumn={
cell.column?.columnDef?.meta?.truncate
}
$center={cell.column?.columnDef?.meta?.center}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</Td>
))
)}
</Tr>
{row?.getIsExpanded() && (
<Tr
$fillLevel={fillLevel}
$firstRow={i === 0}
$padCells={padCells}
$loose={loose}
$stickyColumn={stickyColumn}
$highlight={cell.column?.columnDef?.meta?.highlight}
$truncateColumn={
cell.column?.columnDef?.meta?.truncate
}
$center={cell.column?.columnDef?.meta?.center}
$raised={i % 2 === 1}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</Td>
))
)}
</Tr>
{row?.getIsExpanded() && (
<Tr
$fillLevel={fillLevel}
$raised={i % 2 === 1}
>
<TdExpand />
<TdExpand colSpan={row.getVisibleCells().length - 1}>
{renderExpanded({ row })}
</TdExpand>
</Tr>
)}
</Fragment>
)
})}
{paddingBottom > 0 && (
<FillerRows
rows={rows}
columns={columns}
height={paddingBottom}
position="bottom"
stickyColumn={stickyColumn}
fillLevel={fillLevel}
/>
<TdExpand />
<TdExpand colSpan={row.getVisibleCells().length - 1}>
{renderExpanded({ row })}
</TdExpand>
</Tr>
)}
</Fragment>
)
})}
{paddingBottom > 0 && (
<FillerRows
rows={rows}
columns={columns}
height={paddingBottom}
position="bottom"
stickyColumn={stickyColumn}
fillLevel={fillLevel}
/>
)}
</>
)}
</Tbody>
</T>
{isEmpty(rows) && (
{isEmpty(rows) && !loading && (
<EmptyState
message="No results match your query"
{...emptyStateProps}
Expand Down
10 changes: 10 additions & 0 deletions src/stories/Table.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,16 @@ Empty.args = {
columns,
}

export const Loading = Template.bind({})
Loading.args = {
fillLevel: 0,
width: '900px',
height: '400px',
data: [],
columns,
loading: true,
}

export const Highlighted = Template.bind({})
Highlighted.args = {
fillLevel: 0,
Expand Down

0 comments on commit 10258af

Please sign in to comment.