2025-09-27 16:31:02 +02:00
|
|
|
import React, { memo, forwardRef } from 'react';
|
2025-09-23 23:30:27 +02:00
|
|
|
import { flexRender } from '@tanstack/react-table';
|
|
|
|
|
import type { TableColumn } from './DataTable.types';
|
2025-09-26 18:51:45 +02:00
|
|
|
import type { Row } from '@tanstack/react-table';
|
2025-09-25 22:13:39 +02:00
|
|
|
import { TableCell, TableRow } from '../Table';
|
|
|
|
|
import { Checkbox } from '../Checkbox';
|
|
|
|
|
import { Skeleton } from '../Skeleton';
|
2025-09-23 23:30:27 +02:00
|
|
|
import { cn } from '~/utils';
|
|
|
|
|
|
|
|
|
|
export const SelectionCheckbox = memo(
|
|
|
|
|
({
|
|
|
|
|
checked,
|
|
|
|
|
onChange,
|
|
|
|
|
ariaLabel,
|
|
|
|
|
}: {
|
|
|
|
|
checked: boolean;
|
|
|
|
|
onChange: (value: boolean) => void;
|
|
|
|
|
ariaLabel: string;
|
|
|
|
|
}) => (
|
|
|
|
|
<div
|
|
|
|
|
role="button"
|
|
|
|
|
tabIndex={0}
|
|
|
|
|
onKeyDown={(e) => {
|
|
|
|
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
onChange(!checked);
|
|
|
|
|
}
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
}}
|
|
|
|
|
className="flex h-full w-[30px] items-center justify-center"
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
onChange(!checked);
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<Checkbox checked={checked} onCheckedChange={onChange} aria-label={ariaLabel} />
|
|
|
|
|
</div>
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
SelectionCheckbox.displayName = 'SelectionCheckbox';
|
|
|
|
|
|
2025-09-27 16:31:02 +02:00
|
|
|
const TableRowComponent = <TData extends Record<string, unknown>>(
|
|
|
|
|
{
|
|
|
|
|
row,
|
|
|
|
|
virtualIndex,
|
|
|
|
|
style,
|
|
|
|
|
selected,
|
|
|
|
|
}: {
|
|
|
|
|
row: Row<TData>;
|
|
|
|
|
virtualIndex?: number;
|
|
|
|
|
style?: React.CSSProperties;
|
|
|
|
|
selected: boolean;
|
|
|
|
|
},
|
|
|
|
|
ref: React.Ref<HTMLTableRowElement>,
|
|
|
|
|
) => (
|
2025-09-23 23:30:27 +02:00
|
|
|
<TableRow
|
2025-09-27 16:31:02 +02:00
|
|
|
ref={ref}
|
|
|
|
|
data-state={selected ? 'selected' : undefined}
|
2025-09-23 23:30:27 +02:00
|
|
|
data-index={virtualIndex}
|
|
|
|
|
className="border-none hover:bg-surface-secondary"
|
2025-09-27 16:31:02 +02:00
|
|
|
style={style}
|
2025-09-23 23:30:27 +02:00
|
|
|
>
|
|
|
|
|
{row.getVisibleCells().map((cell) => {
|
2025-09-25 00:24:57 +02:00
|
|
|
const meta = cell.column.columnDef.meta as
|
|
|
|
|
| { className?: string; desktopOnly?: boolean }
|
|
|
|
|
| undefined;
|
|
|
|
|
const isDesktopOnly = meta?.desktopOnly;
|
2025-09-23 23:30:27 +02:00
|
|
|
return (
|
|
|
|
|
<TableCell
|
|
|
|
|
key={cell.id}
|
2025-09-25 00:24:57 +02:00
|
|
|
className={cn(
|
|
|
|
|
'truncate p-3',
|
|
|
|
|
cell.column.id === 'select' && 'p-1',
|
|
|
|
|
meta?.className,
|
|
|
|
|
isDesktopOnly && 'hidden md:table-cell',
|
|
|
|
|
)}
|
2025-09-23 23:30:27 +02:00
|
|
|
>
|
|
|
|
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
|
|
|
</TableCell>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</TableRow>
|
|
|
|
|
);
|
|
|
|
|
|
2025-09-27 16:31:02 +02:00
|
|
|
type ForwardTableRowComponentType = <TData extends Record<string, unknown>>(
|
|
|
|
|
props: {
|
|
|
|
|
row: Row<TData>;
|
|
|
|
|
virtualIndex?: number;
|
|
|
|
|
style?: React.CSSProperties;
|
|
|
|
|
} & React.RefAttributes<HTMLTableRowElement>,
|
|
|
|
|
) => JSX.Element;
|
|
|
|
|
|
|
|
|
|
const ForwardTableRowComponent = forwardRef(TableRowComponent) as ForwardTableRowComponentType;
|
|
|
|
|
|
|
|
|
|
interface GenericRowProps {
|
|
|
|
|
row: Row<Record<string, unknown>>;
|
|
|
|
|
virtualIndex?: number;
|
|
|
|
|
style?: React.CSSProperties;
|
|
|
|
|
selected: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-23 23:30:27 +02:00
|
|
|
export const MemoizedTableRow = memo(
|
2025-09-27 16:31:02 +02:00
|
|
|
ForwardTableRowComponent as (props: GenericRowProps) => JSX.Element,
|
|
|
|
|
(prev: GenericRowProps, next: GenericRowProps) =>
|
|
|
|
|
prev.row.original === next.row.original && prev.selected === next.selected,
|
2025-09-23 23:30:27 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
export const SkeletonRows = memo(
|
|
|
|
|
<TData extends Record<string, unknown>, TValue>({
|
|
|
|
|
count = 10,
|
|
|
|
|
columns,
|
|
|
|
|
}: {
|
|
|
|
|
count?: number;
|
|
|
|
|
columns: TableColumn<TData, TValue>[];
|
|
|
|
|
}) => (
|
|
|
|
|
<>
|
|
|
|
|
{Array.from({ length: count }, (_, index) => (
|
|
|
|
|
<TableRow key={`skeleton-${index}`} className="h-[56px] border-b border-border-light">
|
|
|
|
|
{columns.map((column) => {
|
|
|
|
|
const columnKey = String(
|
|
|
|
|
column.id ?? ('accessorKey' in column && column.accessorKey) ?? '',
|
|
|
|
|
);
|
2025-09-25 00:24:57 +02:00
|
|
|
const meta = column.meta as { className?: string; desktopOnly?: boolean } | undefined;
|
2025-09-23 23:30:27 +02:00
|
|
|
return (
|
2025-09-25 00:24:57 +02:00
|
|
|
<TableCell
|
|
|
|
|
key={columnKey}
|
|
|
|
|
className={cn(
|
|
|
|
|
'px-2 py-2 md:px-3',
|
|
|
|
|
meta?.className,
|
|
|
|
|
meta?.desktopOnly && 'hidden md:table-cell',
|
|
|
|
|
)}
|
|
|
|
|
>
|
2025-09-23 23:30:27 +02:00
|
|
|
<Skeleton className="h-6 w-full" />
|
|
|
|
|
</TableCell>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</TableRow>
|
|
|
|
|
))}
|
|
|
|
|
</>
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
SkeletonRows.displayName = 'SkeletonRows';
|