import React from 'react'; import { clsx } from 'clsx'; export interface TableProps extends React.HTMLAttributes { children: React.ReactNode; } /** * Table container using divs for better responsiveness and control */ export const Table = React.forwardRef( ({ className, children, ...props }, ref) => { return (
{children}
); } ); Table.displayName = 'Table'; export interface TableHeaderProps extends React.HTMLAttributes { children: React.ReactNode; } /** * Table header container */ export const TableHeader = React.forwardRef( ({ className, children, ...props }, ref) => { return (
{children}
); } ); TableHeader.displayName = 'TableHeader'; export interface TableRowProps extends React.HTMLAttributes { children: React.ReactNode; selected?: boolean; interactive?: boolean; } /** * Table row using div */ export const TableRow = React.forwardRef( ({ className, children, selected, interactive = false, ...props }, ref) => { return (
{children}
); } ); TableRow.displayName = 'TableRow'; export interface TableHeadProps extends React.HTMLAttributes { children: React.ReactNode; sortable?: boolean; sorted?: 'asc' | 'desc' | null; onSort?: () => void; width?: string | number; minWidth?: string | number; } /** * Table header cell */ export const TableHead = React.forwardRef( ( { className, children, sortable = false, sorted = null, onSort, width, minWidth, ...props }, ref ) => { const style: React.CSSProperties = {}; if (width) style.width = typeof width === 'number' ? `${width}px` : width; if (minWidth) style.minWidth = typeof minWidth === 'number' ? `${minWidth}px` : minWidth; return (
{children} {sortable && ( {sorted === 'asc' ? '↑' : sorted === 'desc' ? '↓' : '⇅'} )}
); } ); TableHead.displayName = 'TableHead'; export interface TableCellProps extends React.HTMLAttributes { children: React.ReactNode; width?: string | number; minWidth?: string | number; align?: 'left' | 'center' | 'right'; } /** * Table cell */ export const TableCell = React.forwardRef( ({ className, children, width, minWidth, align = 'left', ...props }, ref) => { const style: React.CSSProperties = {}; if (width) style.width = typeof width === 'number' ? `${width}px` : width; if (minWidth) style.minWidth = typeof minWidth === 'number' ? `${minWidth}px` : minWidth; return (
{children}
); } ); TableCell.displayName = 'TableCell'; export interface TableBodyProps extends React.HTMLAttributes { children: React.ReactNode; } /** * Table body container */ export const TableBody = React.forwardRef( ({ className, children, ...props }, ref) => { return (
{children}
); } ); TableBody.displayName = 'TableBody'; export interface TableEmptyProps extends React.HTMLAttributes { message?: string; description?: string; action?: React.ReactNode; } /** * Empty state for table */ export const TableEmpty = React.forwardRef( ({ className, message = 'No data available', description, action, ...props }, ref) => { return (

{message}

{description && (

{description}

)} {action &&
{action}
}
); } ); TableEmpty.displayName = 'TableEmpty'; /** * Responsive table wrapper that switches to card view on mobile */ export interface ResponsiveTableProps { data: T[]; columns: Array<{ key: string; header: string; render: (item: T, index: number) => React.ReactNode; width?: string | number; minWidth?: string | number; sortable?: boolean; align?: 'left' | 'center' | 'right'; }>; onSort?: (key: string, direction: 'asc' | 'desc') => void; sortedColumn?: string | null; sortDirection?: 'asc' | 'desc' | null; emptyMessage?: string; emptyDescription?: string; emptyAction?: React.ReactNode; selectedRows?: Set; onRowSelect?: (id: string) => void; getRowId?: (item: T) => string; renderMobileCard?: (item: T, index: number) => React.ReactNode; isLoading?: boolean; loadingRows?: number; className?: string; } export function ResponsiveTable({ data, columns, onSort, sortedColumn = null, sortDirection = null, emptyMessage = 'No data available', emptyDescription, emptyAction, selectedRows, onRowSelect, getRowId, renderMobileCard, isLoading = false, loadingRows = 5, className, }: ResponsiveTableProps) { const handleSort = (key: string) => { if (!onSort) return; const newDirection = sortedColumn === key && sortDirection === 'asc' ? 'desc' : 'asc'; onSort(key, newDirection); }; const gridTemplateColumns = columns .map((col) => { if (col.width) { return typeof col.width === 'number' ? `${col.width}px` : col.width; } return '1fr'; }) .join(' '); if (isLoading) { // Loading skeleton return ( {columns.map((column) => (
))} {Array.from({ length: loadingRows }).map((_, index) => ( {columns.map((column) => (
))} ))}
); } if (data.length === 0) { return (
); } return ( <> {/* Desktop Table View */}
{columns.map((column) => ( handleSort(column.key) : undefined} width={column.width} minWidth={column.minWidth} align={column.align} > {column.header} ))} {data.map((item, index) => { const rowId = getRowId ? getRowId(item) : String(index); const isSelected = selectedRows?.has(rowId) ?? false; return ( onRowSelect(rowId) : undefined} style={{ gridTemplateColumns }} className="grid" > {columns.map((column) => ( {column.render(item, index)} ))} ); })}
{/* Mobile Card View */}
{data.map((item, index) => { if (renderMobileCard) { return {renderMobileCard(item, index)}; } // Default mobile card rendering return (
{columns.map((column) => (
{column.header}
{column.render(item, index)}
))}
); })}
); }