import { ReactNode } from 'react'; import { Cell, Row, flexRender } from '@tanstack/react-table'; import clsx from 'clsx'; import { ChevronDown } from 'lucide-react'; import { Icon } from '@@/Icon'; import { DefaultType } from './types'; interface Props { row: Row; renderSubRow(row: Row): ReactNode; expandOnClick?: boolean; } export function CardExpandableListRow({ row, renderSubRow, expandOnClick, }: Props) { const cells = row.getVisibleCells(); const canExpand = row.getCanExpand(); const isExpanded = canExpand && row.getIsExpanded(); const { selectCell, dataCells } = groupCells(cells); const { titleCell, actionCells, metaCells } = splitDataCells(dataCells); const isCardClickable = !!expandOnClick && canExpand; function handleCardClick(e: React.MouseEvent | React.KeyboardEvent) { if (!isCardClickable) return; const target = e.target as HTMLElement; if (target.closest('[data-card-expandable-stop]')) { return; } row.toggleExpanded(); } return (
{ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleCardClick(e); } } : undefined } role={isCardClickable ? 'button' : undefined} tabIndex={isCardClickable ? 0 : undefined} >
{selectCell && (
{flexRender( selectCell.column.columnDef.cell, selectCell.getContext() )}
)} {canExpand && ( )} {titleCell && (
{flexRender( titleCell.column.columnDef.cell, titleCell.getContext() )}
)} {actionCells.length > 0 && (
{actionCells.map((cell) => (
{flexRender(cell.column.columnDef.cell, cell.getContext())}
))}
)}
{metaCells.length > 0 && (
{metaCells.map((cell) => { const label = getColumnHeaderText(cell); return (
{label && {label}} {flexRender(cell.column.columnDef.cell, cell.getContext())}
); })}
)}
{isExpanded && (
{renderSubRow(row)}
)}
); } function groupCells(cells: Cell[]) { let selectCell: Cell | undefined; const dataCells: Cell[] = []; for (const cell of cells) { if (cell.column.id === 'select') { selectCell = cell; } else if (cell.column.id !== 'expand') { dataCells.push(cell); } } return { selectCell, dataCells }; } function splitDataCells(cells: Cell[]) { if (cells.length === 0) { return { titleCell: undefined, actionCells: [] as Cell[], metaCells: [] as Cell[], }; } const titleCell = cells[0]; const remaining = cells.slice(1); let actionStart = remaining.length; for (let i = remaining.length - 1; i >= 0; i--) { if (isActionLikeColumn(remaining[i])) { actionStart = i; } else { break; } } return { titleCell, metaCells: remaining.slice(0, actionStart), actionCells: remaining.slice(actionStart), }; } function isActionLikeColumn(cell: Cell) { const { id } = cell.column; if (id === 'actions' || id.endsWith('-actions') || id.endsWith('_actions')) { return true; } const header = cell.column.columnDef.header; if (header == null) { return true; } if (typeof header === 'string' && header.trim() === '') { return true; } return false; } function getColumnHeaderText(cell: Cell) { const header = cell.column.columnDef.header; return typeof header === 'string' && header.trim() !== '' ? header : null; }