- add ContextMenu component with click-outside/Escape dismiss, align prop, and danger variant - replace EnvironmentsPage table with responsive card grid - add cog button to each card opening a context menu with view and delete actions - add EnvironmentDetailPage at /environments/:id with meta grid, URLs card, and delete - add Breadcrumbs to all pages; detail page uses two-item trail with delete button inline - add Table stories for pagination and Timestamp column
108 lines
2.6 KiB
TypeScript
108 lines
2.6 KiB
TypeScript
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
import { Table } from '../../src/ui/Table/Table';
|
|
import { Badge } from '../../src/ui/Badge/Badge';
|
|
import { Timestamp } from '../../src/ui/Timestamp/Timestamp';
|
|
|
|
const meta: Meta<typeof Table> = {
|
|
title: 'UI/Table',
|
|
component: Table,
|
|
tags: ['autodocs'],
|
|
parameters: { layout: 'padded' },
|
|
};
|
|
export default meta;
|
|
|
|
interface User {
|
|
id: number;
|
|
name: string;
|
|
email: string;
|
|
status: 'active' | 'inactive';
|
|
updatedAt: string;
|
|
}
|
|
|
|
const daysAgo = (n: number) => new Date(Date.now() - n * 86_400_000).toISOString();
|
|
|
|
const users: User[] = [
|
|
{
|
|
id: 1,
|
|
name: 'Alice Müller',
|
|
email: 'alice@example.com',
|
|
status: 'active',
|
|
updatedAt: daysAgo(1),
|
|
},
|
|
{
|
|
id: 2,
|
|
name: 'Bob Nguyen',
|
|
email: 'bob@example.com',
|
|
status: 'inactive',
|
|
updatedAt: daysAgo(5),
|
|
},
|
|
{
|
|
id: 3,
|
|
name: 'Carol Santos',
|
|
email: 'carol@example.com',
|
|
status: 'active',
|
|
updatedAt: daysAgo(30),
|
|
},
|
|
];
|
|
|
|
const manyUsers: User[] = Array.from({ length: 47 }, (_, i) => ({
|
|
id: i + 1,
|
|
name: `User ${i + 1}`,
|
|
email: `user${i + 1}@example.com`,
|
|
status: i % 3 === 0 ? 'inactive' : 'active',
|
|
updatedAt: daysAgo(i),
|
|
}));
|
|
|
|
const columns: StoryObj<typeof Table<User>>['args'] = {
|
|
rowKey: (u: User) => u.id,
|
|
emptyMessage: 'No users found.',
|
|
columns: [
|
|
{ key: 'id', header: 'ID', render: (u: User) => u.id, width: 60 },
|
|
{ key: 'name', header: 'Name', render: (u: User) => u.name },
|
|
{ key: 'email', header: 'Email', render: (u: User) => u.email },
|
|
{
|
|
key: 'status',
|
|
header: 'Status',
|
|
width: 110,
|
|
render: (u: User) => (
|
|
<Badge variant={u.status === 'active' ? 'success' : 'neutral'}>{u.status}</Badge>
|
|
),
|
|
},
|
|
{
|
|
key: 'updated',
|
|
header: 'Updated',
|
|
width: 140,
|
|
render: (u: User) => <Timestamp value={u.updatedAt} />,
|
|
},
|
|
],
|
|
};
|
|
|
|
export const Default: StoryObj<typeof Table<User>> = {
|
|
args: { ...columns, data: users },
|
|
};
|
|
|
|
export const Loading: StoryObj<typeof Table<User>> = {
|
|
args: { ...columns, data: users, loading: true },
|
|
};
|
|
|
|
export const Empty: StoryObj<typeof Table<User>> = {
|
|
args: { ...columns, data: [] },
|
|
};
|
|
|
|
export const SingleRow: StoryObj<typeof Table<User>> = {
|
|
args: { ...columns, data: [users[0]] },
|
|
};
|
|
|
|
export const WithPagination: StoryObj<typeof Table<User>> = {
|
|
args: { ...columns, data: manyUsers, pageSize: 10 },
|
|
};
|
|
|
|
export const WithPaginationAndSizeSelector: StoryObj<typeof Table<User>> = {
|
|
args: {
|
|
...columns,
|
|
data: manyUsers,
|
|
pageSize: 10,
|
|
pageSizeOptions: [10, 25, 50],
|
|
},
|
|
};
|