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
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||
import { useState } from 'react';
|
||||
import { Pagination } from '../../src/ui/Pagination/Pagination';
|
||||
|
||||
const meta: Meta<typeof Pagination> = {
|
||||
title: 'UI/Pagination',
|
||||
component: Pagination,
|
||||
parameters: { layout: 'padded' },
|
||||
tags: ['autodocs'],
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Pagination>;
|
||||
|
||||
function Controlled(args: React.ComponentProps<typeof Pagination>) {
|
||||
const [page, setPage] = useState(args.page);
|
||||
const [pageSize, setPageSize] = useState(args.pageSize);
|
||||
return (
|
||||
<Pagination
|
||||
{...args}
|
||||
page={page}
|
||||
pageSize={pageSize}
|
||||
onPageChange={setPage}
|
||||
onPageSizeChange={args.onPageSizeChange ? setPageSize : undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const FewPages: Story = {
|
||||
render: (args) => <Controlled {...args} />,
|
||||
args: { page: 1, pageSize: 10, total: 30 },
|
||||
};
|
||||
|
||||
export const ManyPages: Story = {
|
||||
render: (args) => <Controlled {...args} />,
|
||||
args: { page: 5, pageSize: 10, total: 200 },
|
||||
};
|
||||
|
||||
export const LastPage: Story = {
|
||||
render: (args) => <Controlled {...args} />,
|
||||
args: { page: 20, pageSize: 10, total: 200 },
|
||||
};
|
||||
|
||||
export const SinglePage: Story = {
|
||||
render: (args) => <Controlled {...args} />,
|
||||
args: { page: 1, pageSize: 10, total: 7 },
|
||||
};
|
||||
|
||||
export const WithPageSizeSelector: Story = {
|
||||
render: (args) => <Controlled {...args} />,
|
||||
args: {
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
total: 150,
|
||||
onPageSizeChange: () => {},
|
||||
},
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
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',
|
||||
@@ -15,53 +16,74 @@ interface User {
|
||||
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' },
|
||||
{ id: 2, name: 'Bob Nguyen', email: 'bob@example.com', status: 'inactive' },
|
||||
{ id: 3, name: 'Carol Santos', email: 'carol@example.com', status: 'active' },
|
||||
{ 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: {
|
||||
data: users,
|
||||
rowKey: (u) => u.id,
|
||||
emptyMessage: 'No users found.',
|
||||
columns: [
|
||||
{ key: 'id', header: 'ID', render: (u) => u.id, width: 60 },
|
||||
{ key: 'name', header: 'Name', render: (u) => u.name },
|
||||
{ key: 'email', header: 'Email', render: (u) => u.email },
|
||||
{
|
||||
key: 'status',
|
||||
header: 'Status',
|
||||
width: 110,
|
||||
render: (u) => (
|
||||
<Badge variant={u.status === 'active' ? 'success' : 'neutral'}>{u.status}</Badge>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
args: { ...columns, data: users },
|
||||
};
|
||||
|
||||
export const Loading: StoryObj<typeof Table<User>> = {
|
||||
args: {
|
||||
...Default.args,
|
||||
loading: true,
|
||||
},
|
||||
args: { ...columns, data: users, loading: true },
|
||||
};
|
||||
|
||||
export const Empty: StoryObj<typeof Table<User>> = {
|
||||
args: {
|
||||
...Default.args,
|
||||
data: [],
|
||||
emptyMessage: 'No users found.',
|
||||
},
|
||||
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: {
|
||||
...Default.args,
|
||||
data: [users[0]],
|
||||
...columns,
|
||||
data: manyUsers,
|
||||
pageSize: 10,
|
||||
pageSizeOptions: [10, 25, 50],
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user