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:
2026-04-09 00:11:55 +03:00
parent 5cc16725fb
commit 9916ef5aaf
47 changed files with 3910 additions and 10 deletions
+80
View File
@@ -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>
);
}