feat(client): add ContextMenu component, environment cards, detail page, and breadcrumbs
- 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
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||
import { Settings, Trash2, ExternalLink } from 'lucide-react';
|
||||
import { Button } from '../../src/ui/Button/Button';
|
||||
import { ContextMenu } from '../../src/ui/ContextMenu/ContextMenu';
|
||||
|
||||
const meta: Meta<typeof ContextMenu> = {
|
||||
title: 'UI/ContextMenu',
|
||||
component: ContextMenu,
|
||||
parameters: { layout: 'centered' },
|
||||
tags: ['autodocs'],
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof ContextMenu>;
|
||||
|
||||
const trigger = (
|
||||
<Button variant="ghost" size="sm">
|
||||
<Settings size={16} />
|
||||
</Button>
|
||||
);
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
trigger,
|
||||
items: [
|
||||
{ label: 'View details', icon: <ExternalLink size={14} />, onClick: () => {} },
|
||||
{ label: 'Delete', icon: <Trash2 size={14} />, variant: 'danger', onClick: () => {} },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const AlignLeft: Story = {
|
||||
args: {
|
||||
trigger,
|
||||
align: 'left',
|
||||
items: [
|
||||
{ label: 'View details', icon: <ExternalLink size={14} />, onClick: () => {} },
|
||||
{ label: 'Delete', icon: <Trash2 size={14} />, variant: 'danger', onClick: () => {} },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const ManyItems: Story = {
|
||||
args: {
|
||||
trigger,
|
||||
items: [
|
||||
{ label: 'View details', icon: <ExternalLink size={14} />, onClick: () => {} },
|
||||
{ label: 'Edit', icon: <Settings size={14} />, onClick: () => {} },
|
||||
{ label: 'Duplicate', onClick: () => {} },
|
||||
{ label: 'Delete', icon: <Trash2 size={14} />, variant: 'danger', onClick: () => {} },
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -22,9 +22,27 @@ interface User {
|
||||
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) },
|
||||
{
|
||||
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) => ({
|
||||
|
||||
Reference in New Issue
Block a user