mirror of
https://github.com/johndoe6345789/postgres.git
synced 2026-04-24 13:55:00 +00:00
Create atomic component library and Storybook stories
Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
46
src/components/admin/ConfirmDialog.stories.tsx
Normal file
46
src/components/admin/ConfirmDialog.stories.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { fn } from '@storybook/test';
|
||||
import ConfirmDialog from './ConfirmDialog';
|
||||
|
||||
const meta = {
|
||||
title: 'Admin/ConfirmDialog',
|
||||
component: ConfirmDialog,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
onConfirm: { action: 'confirm' },
|
||||
onCancel: { action: 'cancel' },
|
||||
},
|
||||
} satisfies Meta<typeof ConfirmDialog>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
// Stories based on features.json storybookStories.ConfirmDialog
|
||||
export const Default: Story = {
|
||||
name: 'Default',
|
||||
args: {
|
||||
open: true,
|
||||
title: 'Confirm Action',
|
||||
message: 'Are you sure you want to proceed?',
|
||||
confirmLabel: 'Confirm',
|
||||
cancelLabel: 'Cancel',
|
||||
onConfirm: fn(),
|
||||
onCancel: fn(),
|
||||
},
|
||||
};
|
||||
|
||||
export const DeleteWarning: Story = {
|
||||
name: 'Delete Warning',
|
||||
args: {
|
||||
open: true,
|
||||
title: 'Delete Item',
|
||||
message: 'This action cannot be undone. Are you sure you want to delete this item?',
|
||||
confirmLabel: 'Delete',
|
||||
cancelLabel: 'Cancel',
|
||||
onConfirm: fn(),
|
||||
onCancel: fn(),
|
||||
},
|
||||
};
|
||||
@@ -1,13 +1,13 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
} from '@mui/material';
|
||||
import Button from '../atoms/Button';
|
||||
|
||||
type ConfirmDialogProps = {
|
||||
open: boolean;
|
||||
@@ -35,10 +35,8 @@ export default function ConfirmDialog({
|
||||
<DialogContentText>{message}</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onCancel}>{cancelLabel}</Button>
|
||||
<Button onClick={onConfirm} color="error" variant="contained">
|
||||
{confirmLabel}
|
||||
</Button>
|
||||
<Button onClick={onCancel} text={cancelLabel} />
|
||||
<Button onClick={onConfirm} color="error" variant="contained" text={confirmLabel} />
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
66
src/components/admin/DataGrid.stories.tsx
Normal file
66
src/components/admin/DataGrid.stories.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { fn } from '@storybook/test';
|
||||
import DataGrid from './DataGrid';
|
||||
|
||||
const meta = {
|
||||
title: 'Admin/DataGrid',
|
||||
component: DataGrid,
|
||||
parameters: {
|
||||
layout: 'padded',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
onEdit: { action: 'edit' },
|
||||
onDelete: { action: 'delete' },
|
||||
},
|
||||
} satisfies Meta<typeof DataGrid>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
// Stories based on features.json storybookStories.DataGrid
|
||||
export const Default: Story = {
|
||||
name: 'Default',
|
||||
args: {
|
||||
columns: [
|
||||
{ name: 'id', label: 'ID' },
|
||||
{ name: 'name', label: 'Name' },
|
||||
{ name: 'email', label: 'Email' },
|
||||
],
|
||||
rows: [
|
||||
{ id: 1, name: 'John Doe', email: 'john@example.com' },
|
||||
{ id: 2, name: 'Jane Smith', email: 'jane@example.com' },
|
||||
{ id: 3, name: 'Bob Johnson', email: 'bob@example.com' },
|
||||
],
|
||||
primaryKey: 'id',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithActions: Story = {
|
||||
name: 'With Edit/Delete Actions',
|
||||
args: {
|
||||
columns: [
|
||||
{ name: 'id', label: 'ID' },
|
||||
{ name: 'name', label: 'Name' },
|
||||
{ name: 'status', label: 'Status' },
|
||||
],
|
||||
rows: [
|
||||
{ id: 1, name: 'Active User', status: 'active' },
|
||||
{ id: 2, name: 'Pending User', status: 'pending' },
|
||||
],
|
||||
onEdit: fn(),
|
||||
onDelete: fn(),
|
||||
primaryKey: 'id',
|
||||
},
|
||||
};
|
||||
|
||||
export const Empty: Story = {
|
||||
name: 'Empty State',
|
||||
args: {
|
||||
columns: [
|
||||
{ name: 'id', label: 'ID' },
|
||||
{ name: 'name', label: 'Name' },
|
||||
],
|
||||
rows: [],
|
||||
},
|
||||
};
|
||||
@@ -1,9 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import {
|
||||
IconButton,
|
||||
IconButton as MuiIconButton,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -13,6 +11,7 @@ import {
|
||||
TableRow,
|
||||
Tooltip,
|
||||
} from '@mui/material';
|
||||
import IconButton from '../atoms/IconButton';
|
||||
|
||||
type DataGridProps = {
|
||||
columns: Array<{ name: string; label?: string }>;
|
||||
@@ -54,16 +53,12 @@ export default function DataGrid({ columns, rows, onEdit, onDelete, primaryKey =
|
||||
<TableCell>
|
||||
{onEdit && (
|
||||
<Tooltip title="Edit">
|
||||
<IconButton size="small" onClick={() => onEdit(row)}>
|
||||
<EditIcon fontSize="small" />
|
||||
</IconButton>
|
||||
<IconButton size="small" onClick={() => onEdit(row)} icon="Edit" />
|
||||
</Tooltip>
|
||||
)}
|
||||
{onDelete && (
|
||||
<Tooltip title="Delete">
|
||||
<IconButton size="small" color="error" onClick={() => onDelete(row)}>
|
||||
<DeleteIcon fontSize="small" />
|
||||
</IconButton>
|
||||
<IconButton size="small" color="error" onClick={() => onDelete(row)} icon="Delete" />
|
||||
</Tooltip>
|
||||
)}
|
||||
</TableCell>
|
||||
|
||||
65
src/components/atoms/Button.stories.tsx
Normal file
65
src/components/atoms/Button.stories.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import Button from './Button';
|
||||
|
||||
const meta = {
|
||||
title: 'Atoms/Button',
|
||||
component: Button,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: 'select',
|
||||
options: ['text', 'outlined', 'contained'],
|
||||
},
|
||||
color: {
|
||||
control: 'select',
|
||||
options: ['default', 'primary', 'secondary', 'error', 'warning', 'info', 'success'],
|
||||
},
|
||||
size: {
|
||||
control: 'select',
|
||||
options: ['small', 'medium', 'large'],
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof Button>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
// Stories based on features.json storybookStories
|
||||
export const Primary: Story = {
|
||||
name: 'Primary Button',
|
||||
args: {
|
||||
variant: 'contained',
|
||||
color: 'primary',
|
||||
text: 'Click Me',
|
||||
},
|
||||
};
|
||||
|
||||
export const Secondary: Story = {
|
||||
name: 'Secondary Button',
|
||||
args: {
|
||||
variant: 'outlined',
|
||||
color: 'secondary',
|
||||
text: 'Cancel',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithIcon: Story = {
|
||||
name: 'With Icon',
|
||||
args: {
|
||||
variant: 'contained',
|
||||
startIcon: 'Add',
|
||||
text: 'Add Item',
|
||||
},
|
||||
};
|
||||
|
||||
export const Loading: Story = {
|
||||
name: 'Loading State',
|
||||
args: {
|
||||
variant: 'contained',
|
||||
disabled: true,
|
||||
text: 'Loading...',
|
||||
},
|
||||
};
|
||||
35
src/components/atoms/Button.tsx
Normal file
35
src/components/atoms/Button.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
'use client';
|
||||
|
||||
import { Button as MuiButton, ButtonProps as MuiButtonProps } from '@mui/material';
|
||||
import * as Icons from '@mui/icons-material';
|
||||
|
||||
export type ButtonProps = Omit<MuiButtonProps, 'startIcon' | 'endIcon'> & {
|
||||
text?: string;
|
||||
startIcon?: keyof typeof Icons;
|
||||
endIcon?: keyof typeof Icons;
|
||||
};
|
||||
|
||||
/**
|
||||
* Atomic Button component
|
||||
* Wraps Material-UI Button with icon support from features.json
|
||||
*/
|
||||
export default function Button({
|
||||
text,
|
||||
children,
|
||||
startIcon,
|
||||
endIcon,
|
||||
...props
|
||||
}: ButtonProps) {
|
||||
const StartIconComponent = startIcon ? Icons[startIcon] : null;
|
||||
const EndIconComponent = endIcon ? Icons[endIcon] : null;
|
||||
|
||||
return (
|
||||
<MuiButton
|
||||
{...props}
|
||||
startIcon={StartIconComponent ? <StartIconComponent /> : undefined}
|
||||
endIcon={EndIconComponent ? <EndIconComponent /> : undefined}
|
||||
>
|
||||
{text || children}
|
||||
</MuiButton>
|
||||
);
|
||||
}
|
||||
53
src/components/atoms/Icon.stories.tsx
Normal file
53
src/components/atoms/Icon.stories.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import Icon from './Icon';
|
||||
|
||||
const meta = {
|
||||
title: 'Atoms/Icon',
|
||||
component: Icon,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
color: {
|
||||
control: 'select',
|
||||
options: ['inherit', 'primary', 'secondary', 'action', 'disabled', 'error'],
|
||||
},
|
||||
fontSize: {
|
||||
control: 'select',
|
||||
options: ['small', 'medium', 'large', 'inherit'],
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof Icon>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Storage: Story = {
|
||||
args: {
|
||||
name: 'Storage',
|
||||
color: 'primary',
|
||||
},
|
||||
};
|
||||
|
||||
export const Code: Story = {
|
||||
args: {
|
||||
name: 'Code',
|
||||
fontSize: 'large',
|
||||
},
|
||||
};
|
||||
|
||||
export const AccountTree: Story = {
|
||||
args: {
|
||||
name: 'AccountTree',
|
||||
color: 'secondary',
|
||||
},
|
||||
};
|
||||
|
||||
export const Speed: Story = {
|
||||
args: {
|
||||
name: 'Speed',
|
||||
color: 'error',
|
||||
fontSize: 'large',
|
||||
},
|
||||
};
|
||||
23
src/components/atoms/Icon.tsx
Normal file
23
src/components/atoms/Icon.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
'use client';
|
||||
|
||||
import { SvgIconProps } from '@mui/material';
|
||||
import * as Icons from '@mui/icons-material';
|
||||
|
||||
export type IconProps = SvgIconProps & {
|
||||
name: keyof typeof Icons;
|
||||
};
|
||||
|
||||
/**
|
||||
* Atomic Icon component
|
||||
* Renders Material-UI icons by name from features.json
|
||||
*/
|
||||
export default function Icon({ name, ...props }: IconProps) {
|
||||
const IconComponent = Icons[name];
|
||||
|
||||
if (!IconComponent) {
|
||||
console.warn(`Icon "${name}" not found in Material Icons`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return <IconComponent {...props} />;
|
||||
}
|
||||
52
src/components/atoms/IconButton.stories.tsx
Normal file
52
src/components/atoms/IconButton.stories.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import IconButton from './IconButton';
|
||||
|
||||
const meta = {
|
||||
title: 'Atoms/IconButton',
|
||||
component: IconButton,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
color: {
|
||||
control: 'select',
|
||||
options: ['default', 'primary', 'secondary', 'error', 'warning', 'info', 'success'],
|
||||
},
|
||||
size: {
|
||||
control: 'select',
|
||||
options: ['small', 'medium', 'large'],
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof IconButton>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Edit: Story = {
|
||||
args: {
|
||||
icon: 'Edit',
|
||||
color: 'primary',
|
||||
},
|
||||
};
|
||||
|
||||
export const Delete: Story = {
|
||||
args: {
|
||||
icon: 'Delete',
|
||||
color: 'error',
|
||||
},
|
||||
};
|
||||
|
||||
export const Add: Story = {
|
||||
args: {
|
||||
icon: 'Add',
|
||||
color: 'primary',
|
||||
},
|
||||
};
|
||||
|
||||
export const Settings: Story = {
|
||||
args: {
|
||||
icon: 'Settings',
|
||||
size: 'large',
|
||||
},
|
||||
};
|
||||
27
src/components/atoms/IconButton.tsx
Normal file
27
src/components/atoms/IconButton.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
'use client';
|
||||
|
||||
import { IconButton as MuiIconButton, IconButtonProps as MuiIconButtonProps } from '@mui/material';
|
||||
import * as Icons from '@mui/icons-material';
|
||||
|
||||
export type IconButtonProps = Omit<MuiIconButtonProps, 'children'> & {
|
||||
icon: keyof typeof Icons;
|
||||
};
|
||||
|
||||
/**
|
||||
* Atomic IconButton component
|
||||
* Wraps Material-UI IconButton with icon name from features.json
|
||||
*/
|
||||
export default function IconButton({ icon, ...props }: IconButtonProps) {
|
||||
const IconComponent = Icons[icon];
|
||||
|
||||
if (!IconComponent) {
|
||||
console.warn(`Icon "${icon}" not found in Material Icons`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<MuiIconButton {...props}>
|
||||
<IconComponent />
|
||||
</MuiIconButton>
|
||||
);
|
||||
}
|
||||
64
src/components/atoms/TextField.stories.tsx
Normal file
64
src/components/atoms/TextField.stories.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import TextField from './TextField';
|
||||
|
||||
const meta = {
|
||||
title: 'Atoms/TextField',
|
||||
component: TextField,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: 'select',
|
||||
options: ['standard', 'outlined', 'filled'],
|
||||
},
|
||||
type: {
|
||||
control: 'select',
|
||||
options: ['text', 'email', 'password', 'number', 'tel', 'url'],
|
||||
},
|
||||
size: {
|
||||
control: 'select',
|
||||
options: ['small', 'medium'],
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof TextField>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
label: 'Text Field',
|
||||
placeholder: 'Enter text...',
|
||||
variant: 'outlined',
|
||||
},
|
||||
};
|
||||
|
||||
export const Email: Story = {
|
||||
args: {
|
||||
label: 'Email',
|
||||
type: 'email',
|
||||
placeholder: 'user@example.com',
|
||||
variant: 'outlined',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithError: Story = {
|
||||
args: {
|
||||
label: 'Name',
|
||||
error: true,
|
||||
helperText: 'This field is required',
|
||||
variant: 'outlined',
|
||||
},
|
||||
};
|
||||
|
||||
export const Multiline: Story = {
|
||||
args: {
|
||||
label: 'Description',
|
||||
multiline: true,
|
||||
rows: 4,
|
||||
placeholder: 'Enter description...',
|
||||
variant: 'outlined',
|
||||
},
|
||||
};
|
||||
15
src/components/atoms/TextField.tsx
Normal file
15
src/components/atoms/TextField.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
'use client';
|
||||
|
||||
import { TextField as MuiTextField, TextFieldProps as MuiTextFieldProps } from '@mui/material';
|
||||
|
||||
export type TextFieldProps = MuiTextFieldProps & {
|
||||
// Additional props from features.json
|
||||
};
|
||||
|
||||
/**
|
||||
* Atomic TextField component
|
||||
* Wraps Material-UI TextField with features.json configuration
|
||||
*/
|
||||
export default function TextField(props: TextFieldProps) {
|
||||
return <MuiTextField {...props} />;
|
||||
}
|
||||
53
src/components/atoms/Typography.stories.tsx
Normal file
53
src/components/atoms/Typography.stories.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import Typography from './Typography';
|
||||
|
||||
const meta = {
|
||||
title: 'Atoms/Typography',
|
||||
component: Typography,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: 'select',
|
||||
options: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'subtitle1', 'subtitle2', 'body1', 'body2', 'caption', 'button', 'overline'],
|
||||
},
|
||||
align: {
|
||||
control: 'select',
|
||||
options: ['left', 'center', 'right', 'justify'],
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof Typography>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Heading1: Story = {
|
||||
args: {
|
||||
variant: 'h1',
|
||||
text: 'Heading 1',
|
||||
},
|
||||
};
|
||||
|
||||
export const Heading4: Story = {
|
||||
args: {
|
||||
variant: 'h4',
|
||||
text: 'Heading 4',
|
||||
},
|
||||
};
|
||||
|
||||
export const Body: Story = {
|
||||
args: {
|
||||
variant: 'body1',
|
||||
text: 'This is body text with regular weight and size.',
|
||||
},
|
||||
};
|
||||
|
||||
export const Caption: Story = {
|
||||
args: {
|
||||
variant: 'caption',
|
||||
color: 'text.secondary',
|
||||
text: 'Caption text - smaller and secondary color',
|
||||
},
|
||||
};
|
||||
16
src/components/atoms/Typography.tsx
Normal file
16
src/components/atoms/Typography.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
'use client';
|
||||
|
||||
import { Typography as MuiTypography, TypographyProps as MuiTypographyProps } from '@mui/material';
|
||||
|
||||
export type TypographyProps = Omit<MuiTypographyProps, 'children'> & {
|
||||
text?: string;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Atomic Typography component
|
||||
* Wraps Material-UI Typography with text prop from features.json
|
||||
*/
|
||||
export default function Typography({ text, children, ...props }: TypographyProps) {
|
||||
return <MuiTypography {...props}>{text || children}</MuiTypography>;
|
||||
}
|
||||
47
src/components/atoms/index.ts
Normal file
47
src/components/atoms/index.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
// Atomic component library - exported from features.json componentProps
|
||||
export { default as Button } from './Button';
|
||||
export { default as TextField } from './TextField';
|
||||
export { default as Typography } from './Typography';
|
||||
export { default as IconButton } from './IconButton';
|
||||
export { default as Icon } from './Icon';
|
||||
|
||||
// Re-export commonly used Material-UI components for consistency
|
||||
export {
|
||||
Box,
|
||||
Grid,
|
||||
Paper,
|
||||
Card,
|
||||
CardContent,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Select,
|
||||
MenuItem,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Checkbox,
|
||||
FormControlLabel,
|
||||
AppBar,
|
||||
Toolbar,
|
||||
Drawer,
|
||||
Alert,
|
||||
CircularProgress,
|
||||
Pagination,
|
||||
Tabs,
|
||||
Tab,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
ListItemIcon,
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
AccordionDetails,
|
||||
Chip,
|
||||
} from '@mui/material';
|
||||
Reference in New Issue
Block a user