Skip to content

Instantly share code, notes, and snippets.

@rawnly
Last active February 7, 2023 16:24
Show Gist options
  • Save rawnly/d6c3d7a99f4fb9a7fec5b43fefcb8c36 to your computer and use it in GitHub Desktop.
Save rawnly/d6c3d7a99f4fb9a7fec5b43fefcb8c36 to your computer and use it in GitHub Desktop.
import { ArrowNarrowDownIcon, ArrowNarrowUpIcon } from '@smartfish/icons';
import { Column, flexRender, type Row, type Table } from '@tanstack/react-table';
import clsx from 'clsx';
import { useMemo } from 'react';
import { forwardRef } from 'react';
interface ICondensedTableProps<T> {
table: Table<T>;
onRowClick?: (row: Row<T>) => void;
stickyHeader?: boolean;
verticalLines?: boolean;
condensed?: boolean;
uppercaseHeadings?: boolean;
variant?: 'striped' | 'plain';
emptyMessage?: string;
loading?: boolean;
stickyFooter?: boolean;
hasFooter?: boolean;
}
function Table<T>(
{ table, onRowClick, loading: isLoading = false, ...options }: ICondensedTableProps<T>,
ref: React.ForwardedRef<HTMLTableElement>
) {
const {
variant = 'striped',
condensed = false,
verticalLines = false,
stickyHeader = false,
uppercaseHeadings = false,
emptyMessage = 'No data',
stickyFooter = false,
hasFooter = false,
} = options;
const rows = useMemo(() => table.getRowModel().rows, [table]);
const headerGroups = useMemo(() => table.getHeaderGroups(), [table]);
const footerGroups = useMemo(() => table.getFooterGroups(), [table]);
const visibleFlatColumns = useMemo(() => table.getVisibleFlatColumns(), [table]);
return (
<>
<table
ref={ref}
className={clsx('min-w-full', {
'divide-y rx-divide-neutral-6': !stickyHeader,
})}
>
<thead
className={clsx({
'rx-bg-neutral-3': !stickyHeader,
'rx-backdrop-blur bg-neutral-3/50 dark:bg-neutralDark-3/50': stickyHeader, // style
'sticky top-0 z-50': stickyHeader, // positioning
})}
>
{headerGroups.map(group => (
<tr
key={group.id}
className={clsx({
'divide-x rx-divide-neutral-6': verticalLines,
})}
>
{group.headers.map(header => (
<th
scope="col"
key={header.id}
onClick={header.column.getToggleSortingHandler()}
colSpan={header.column.columnDef.meta?.colSpan ?? header.colSpan}
className={clsx('whitespace-nowrap text-left text-sm font-semibold rx-text-neutral-11', {
'py-3.5 px-2': header.index > 0 && header.index < group.headers.length - 1 && condensed,
'py-3.5 px-4': header.index > 0 && header.index < group.headers.length - 1 && !condensed,
'sticky top-0 z-10': stickyHeader,
'relative hover:cursor-pointer hover:opacity-80': header.column.getCanSort(),
uppercase: uppercaseHeadings,
'py-3.5 pl-4 pr-3 sm:pl-6': header.index === 0,
'pr-4 pl-3 sm:pr-6': header.index === group.headers.length - 1,
'sticky inset-x-0 z-50 border-x rx-border-neutral-6': header.column.getIsPinned(),
'rx-bg-neutral-3': !stickyHeader,
})}
>
<div>
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
<div
className={clsx('absolute top-4', {
'-left-4': header.index !== 0,
'-left-0': header.index === 0,
})}
>
{
{
desc: <ArrowNarrowUpIcon className="h-4" />,
asc: <ArrowNarrowDownIcon className="h-4" />,
}[header.column.getIsSorted().toString()]
}
</div>
</div>
</th>
))}
</tr>
))}
</thead>
<tbody className="divide-y overflow-y-scroll rx-divide-neutral-3">
{isLoading && (
<>
<tr
className={clsx('skeleton-no-border border-0', {
'divide-x rx-divide-neutral-6': verticalLines,
})}
>
{visibleFlatColumns.map(col => (
<TableCell condensed={condensed} key={col.id} column={col} />
))}
</tr>
<tr
className={clsx('skeleton-no-border border-0', {
'divide-x rx-divide-neutral-6': verticalLines,
'rx-bg-neutral-2': variant === 'striped',
})}
>
{visibleFlatColumns.map(col => (
<TableCell condensed={condensed} key={col.id} column={col} />
))}
</tr>
<tr
className={clsx('skeleton-no-border border-0', {
'divide-x rx-divide-neutral-6': verticalLines,
})}
>
{visibleFlatColumns.map(col => (
<TableCell condensed={condensed} key={col.id} column={col} />
))}
</tr>
</>
)}
{!isLoading &&
rows.map((row, idx) => (
<tr
key={row.id}
data-row-id={row.id}
onClick={onRowClick ? () => onRowClick(row) : row.getToggleSelectedHandler()}
className={clsx('transition-colors duration-150 rx-bg-neutral-1', {
'rx-bg-neutral-2': idx % 2 === 0 && variant === 'striped' && rows.length !== 1,
'rx-bg-neutral-3': row.getIsSelected(),
'hover:cursor-pointer hover:rx-bg-neutral-2': row.getCanSelect() || !!onRowClick,
'divide-x rx-divide-neutral-6': verticalLines,
})}
>
{row.getVisibleCells().map((cell, idx) => (
<TableCell
key={cell.id ?? idx}
isFirst={idx === 0}
isLast={idx === row.getVisibleCells().length - 1}
condensed={condensed}
column={cell.column}
>
{(typeof cell.column.columnDef.cell === 'function'
? cell.column.columnDef.cell(cell.getContext())
: cell.column.columnDef.cell) || <span className="font-mono tabular-nums opacity-50">--</span>}
</TableCell>
))}
</tr>
))}
</tbody>
{!isLoading && hasFooter && rows.length > 0 && (
<tfoot
className={clsx({
'rx-bg-neutral-3': !stickyFooter,
'rx-backdrop-blur bg-neutral-3/50 dark:bg-neutralDark-3/50': stickyFooter, // style
'sticky bottom-0 z-50': stickyFooter, // positioning
})}
>
{footerGroups.map(group => (
<tr key={group.id}>
{group.headers.map((header, idx) => {
const value = header.isPlaceholder
? ''
: flexRender(header.column.columnDef.footer, header.getContext());
return (
<TableCell
column={header.column}
isFirst={idx === 0}
isLast={group.headers.length - 1 === idx}
condensed={condensed}
key={header.id}
>
{value ? <>{value}</> : <span className="font-mono tabular-nums opacity-50">--</span>}
</TableCell>
);
})}
</tr>
))}
</tfoot>
)}
</table>
{rows.length === 0 && !isLoading && (
<div data-row-id={'empty'} key="empty-row" className="flex w-full items-center justify-center p-4 text-center">
<p className="opacity-50">{emptyMessage}</p>
</div>
)}
</>
);
}
const TableCell = ({
isLast,
isFirst,
condensed = false,
children,
column,
}: React.PropsWithChildren<{
condensed?: boolean;
column: Column<any, unknown>;
isLast?: boolean;
isFirst?: boolean;
}>) => {
return (
<td
className={clsx('whitespace-nowrap text-sm rx-text-neutral-11', {
// padding condensed
'px-2 py-3': !isLast && !isFirst && condensed,
// padding regular
'py-3.5 px-4': !isLast && !isFirst && !condensed,
// padding regular first & last
// 'py-3.5 px-4': (isFirst || isLast) && !condensed,
'px-4': (isLast || isFirst) && condensed,
// sempre
'sm:pl-6': isFirst,
'sm:pr-6': isLast,
'font-mono tabular-nums': column.columnDef.meta?.dataType === 'accounting',
'sticky left-0 border-r rx-bg-neutral-3 rx-border-neutral-6': column.getIsPinned(),
})}
>
{children ?? 'n.d'}
</td>
);
};
export default forwardRef(Table);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment