feat(client): add UI kit, data layer, app shell, and Table component
- design token system (Atlantis, Kimberly, Powder Ash palette) - Button, Badge, Input, Select, Card, SidePanel, Breadcrumbs components - generic typed Table<T> component with loading/empty states - API data layer: typed fetch client for environments, keys, sessions, scenarios - Vite dev proxy targeting server on port 13000 - App shell with SidePanel nav and four entity pages (Environments, Keys, Sessions, Scenarios) - Storybook config with dark/light theme toggle and a11y addon
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import type { ReactNode } from 'react';
|
||||
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;
|
||||
}
|
||||
|
||||
export function Table<T>({
|
||||
columns,
|
||||
data,
|
||||
rowKey,
|
||||
loading = false,
|
||||
emptyMessage = 'No data.',
|
||||
className,
|
||||
}: TableProps<T>) {
|
||||
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>
|
||||
) : data.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={columns.length} className={styles.empty}>
|
||||
{emptyMessage}
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
data.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>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user