Files
liqa/client/src/ui/Table/Table.tsx
T
ars9 077cd6ad5f feat(client): add Pagination component and integrate with Table and pages
- 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
2026-04-09 10:43:45 +03:00

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>
);
}