- add Pagination component with prev/next, ellipsis page range, and optional page-size selector - add Pagination and Table pagination stories (WithPagination, WithPaginationAndSizeSelector) - extend Table with optional pageSize and pageSizeOptions props for built-in client-side pagination - add Timestamp column to Table story fixtures - enable pagination on all four pages with pageSize=10 and size selector - add pagination i18n keys to en.json
115 lines
3.1 KiB
TypeScript
115 lines
3.1 KiB
TypeScript
import { useState, type ReactNode } from 'react';
|
|
import { Pagination } from '../Pagination/Pagination';
|
|
import styles from './Table.module.css';
|
|
|
|
export interface TableColumn<T> {
|
|
key: string;
|
|
header: ReactNode;
|
|
render: (row: T) => ReactNode;
|
|
width?: string | number;
|
|
align?: 'left' | 'center' | 'right';
|
|
}
|
|
|
|
export interface TableProps<T> {
|
|
columns: TableColumn<T>[];
|
|
data: T[];
|
|
rowKey: (row: T) => string | number;
|
|
loading?: boolean;
|
|
emptyMessage?: string;
|
|
className?: string;
|
|
/** Enable built-in client-side pagination with this default page size */
|
|
pageSize?: number;
|
|
/** Offer a page-size selector when pagination is enabled */
|
|
pageSizeOptions?: number[];
|
|
}
|
|
|
|
export function Table<T>({
|
|
columns,
|
|
data,
|
|
rowKey,
|
|
loading = false,
|
|
emptyMessage = 'No data.',
|
|
className,
|
|
pageSize: defaultPageSize,
|
|
pageSizeOptions,
|
|
}: TableProps<T>) {
|
|
const [page, setPage] = useState(1);
|
|
const [pageSize, setPageSize] = useState(defaultPageSize ?? 0);
|
|
|
|
const paginated = defaultPageSize !== undefined && !loading;
|
|
const visibleData = paginated ? data.slice((page - 1) * pageSize, page * pageSize) : data;
|
|
|
|
// Reset to page 1 when data changes and current page would be empty
|
|
const lastPage = pageSize > 0 ? Math.ceil(data.length / pageSize) : 1;
|
|
const safePage = Math.min(page, Math.max(1, lastPage));
|
|
|
|
return (
|
|
<div className={`${styles.wrapper} ${className ?? ''}`}>
|
|
<table className={styles.table}>
|
|
<thead>
|
|
<tr>
|
|
{columns.map((col) => (
|
|
<th
|
|
key={col.key}
|
|
className={styles.th}
|
|
style={{
|
|
width: col.width,
|
|
textAlign: col.align ?? 'left',
|
|
}}
|
|
>
|
|
{col.header}
|
|
</th>
|
|
))}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{loading ? (
|
|
<tr>
|
|
<td colSpan={columns.length} className={styles.empty}>
|
|
Loading…
|
|
</td>
|
|
</tr>
|
|
) : visibleData.length === 0 ? (
|
|
<tr>
|
|
<td colSpan={columns.length} className={styles.empty}>
|
|
{emptyMessage}
|
|
</td>
|
|
</tr>
|
|
) : (
|
|
visibleData.map((row) => (
|
|
<tr key={rowKey(row)} className={styles.tr}>
|
|
{columns.map((col) => (
|
|
<td
|
|
key={col.key}
|
|
className={styles.td}
|
|
style={{ textAlign: col.align ?? 'left' }}
|
|
>
|
|
{col.render(row)}
|
|
</td>
|
|
))}
|
|
</tr>
|
|
))
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
{paginated && data.length > 0 && (
|
|
<Pagination
|
|
page={safePage}
|
|
pageSize={pageSize}
|
|
total={data.length}
|
|
onPageChange={setPage}
|
|
onPageSizeChange={
|
|
pageSizeOptions
|
|
? (size) => {
|
|
setPageSize(size);
|
|
setPage(1);
|
|
}
|
|
: undefined
|
|
}
|
|
pageSizeOptions={pageSizeOptions}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|