feat: add Storybook with component stories
Installs @storybook/nextjs-vite. Stories co-located with components, grouped by layer (Shared/Entities/Widgets). Multi-variant cases use render functions instead of one story per variant/size.
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { Badge } from './Badge'
|
||||
|
||||
const meta: Meta<typeof Badge> = {
|
||||
title: 'Shared/Badge',
|
||||
component: Badge,
|
||||
}
|
||||
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof Badge>
|
||||
|
||||
export const AllVariants: Story = {
|
||||
render: () => (
|
||||
<div className="flex gap-3 p-8 bg-ochre-clay">
|
||||
<Badge variant="default">Default</Badge>
|
||||
<Badge variant="primary">Primary</Badge>
|
||||
<Badge variant="secondary">Secondary</Badge>
|
||||
<Badge variant="outline">Outline</Badge>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { Button } from './Button'
|
||||
|
||||
const meta: Meta<typeof Button> = {
|
||||
title: 'Shared/Button',
|
||||
component: Button,
|
||||
}
|
||||
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof Button>
|
||||
|
||||
export const AllVariants: Story = {
|
||||
render: () => (
|
||||
<div className="flex gap-4 flex-wrap p-8 bg-ochre-clay">
|
||||
<Button variant="primary" size="md">Primary</Button>
|
||||
<Button variant="secondary" size="md">Secondary</Button>
|
||||
<Button variant="outline" size="md">Outline</Button>
|
||||
<Button variant="ghost" size="md">Ghost</Button>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
export const Sizes: Story = {
|
||||
render: () => (
|
||||
<div className="flex gap-4 items-center flex-wrap p-8 bg-ochre-clay">
|
||||
<Button variant="primary" size="sm">Small</Button>
|
||||
<Button variant="primary" size="md">Medium</Button>
|
||||
<Button variant="primary" size="lg">Large</Button>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
variant: 'primary',
|
||||
disabled: true,
|
||||
children: 'Disabled',
|
||||
},
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div className="p-8 bg-ochre-clay">
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from './Card'
|
||||
|
||||
const meta: Meta<typeof Card> = {
|
||||
title: 'Shared/Card',
|
||||
component: Card,
|
||||
}
|
||||
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof Card>
|
||||
|
||||
export const AllBackgrounds: Story = {
|
||||
render: () => (
|
||||
<div className="flex gap-6 flex-wrap p-8 bg-white">
|
||||
<Card background="ochre" className="w-64">
|
||||
<CardHeader>
|
||||
<CardTitle>Ochre Card</CardTitle>
|
||||
<CardDescription>Background ochre-clay variant</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter>Footer content</CardFooter>
|
||||
</Card>
|
||||
<Card background="slate" className="w-64">
|
||||
<CardHeader>
|
||||
<CardTitle>Slate Card</CardTitle>
|
||||
<CardDescription>Background slate-indigo variant</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter>Footer content</CardFooter>
|
||||
</Card>
|
||||
<Card background="white" className="w-64">
|
||||
<CardHeader>
|
||||
<CardTitle>White Card</CardTitle>
|
||||
<CardDescription>Background white variant</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter>Footer content</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
export const NoPadding: Story = {
|
||||
render: () => (
|
||||
<div className="p-8 bg-ochre-clay">
|
||||
<Card noPadding className="w-64 overflow-hidden">
|
||||
<div className="h-40 bg-slate-indigo flex items-center justify-center text-ochre-clay">
|
||||
Image placeholder
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
export const FullComposition: Story = {
|
||||
render: () => (
|
||||
<div className="p-8 bg-white max-w-md">
|
||||
<Card background="ochre">
|
||||
<CardHeader>
|
||||
<CardTitle>Full Composition</CardTitle>
|
||||
<CardDescription>A card using all available slot components</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>This is the main body content of the card, placed inside CardContent.</p>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<span className="text-sm opacity-70">Card footer</span>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { Input, Textarea } from './Input'
|
||||
|
||||
const meta: Meta<typeof Input> = {
|
||||
title: 'Shared/Input',
|
||||
component: Input,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div className="p-8 bg-ochre-clay max-w-sm">
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
}
|
||||
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof Input>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {},
|
||||
}
|
||||
|
||||
export const WithLabel: Story = {
|
||||
args: {
|
||||
label: 'Email address',
|
||||
},
|
||||
}
|
||||
|
||||
export const WithError: Story = {
|
||||
args: {
|
||||
label: 'Email',
|
||||
error: 'This field is required',
|
||||
},
|
||||
}
|
||||
|
||||
export const WithPlaceholder: Story = {
|
||||
args: {
|
||||
placeholder: 'Enter your email',
|
||||
type: 'email',
|
||||
},
|
||||
}
|
||||
|
||||
export const TextareaStory: Story = {
|
||||
name: 'Textarea',
|
||||
render: () => (
|
||||
<div className="p-8 bg-ochre-clay max-w-sm">
|
||||
<Textarea label="Message" rows={4} />
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
export const TextareaWithError: Story = {
|
||||
render: () => (
|
||||
<div className="p-8 bg-ochre-clay max-w-sm">
|
||||
<Textarea label="Message" error="Too short" rows={4} />
|
||||
</div>
|
||||
),
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { Section, Container } from './Section'
|
||||
|
||||
const meta: Meta<typeof Section> = {
|
||||
title: 'Shared/Section',
|
||||
component: Section,
|
||||
}
|
||||
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof Section>
|
||||
|
||||
export const AllBackgrounds: Story = {
|
||||
render: () => (
|
||||
<div>
|
||||
<Section background="ochre" className="py-12">
|
||||
<Container>
|
||||
<h2>Ochre Section</h2>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
|
||||
</Container>
|
||||
</Section>
|
||||
<Section background="slate" className="py-12">
|
||||
<Container>
|
||||
<h2>Slate Section</h2>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
|
||||
</Container>
|
||||
</Section>
|
||||
<Section background="white" className="py-12">
|
||||
<Container>
|
||||
<h2>White Section</h2>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
|
||||
</Container>
|
||||
</Section>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
export const Bordered: Story = {
|
||||
render: () => (
|
||||
<Section background="ochre" bordered className="py-12">
|
||||
<Container>
|
||||
<h2>Bordered Section</h2>
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
|
||||
</Container>
|
||||
</Section>
|
||||
),
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { SectionAccordion } from './SectionAccordion'
|
||||
|
||||
const meta: Meta<typeof SectionAccordion> = {
|
||||
title: 'Shared/SectionAccordion',
|
||||
component: SectionAccordion,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div className="p-8 bg-ochre-clay">
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
}
|
||||
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof SectionAccordion>
|
||||
|
||||
export const Active: Story = {
|
||||
args: {
|
||||
number: '01',
|
||||
title: 'Biography',
|
||||
id: 'bio',
|
||||
isActive: true,
|
||||
onClick: () => {},
|
||||
children: (
|
||||
<p>This is the expanded section content. It is visible because isActive is true.</p>
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
export const Collapsed: Story = {
|
||||
args: {
|
||||
number: '02',
|
||||
title: 'Work',
|
||||
id: 'work',
|
||||
isActive: false,
|
||||
onClick: () => console.log('section clicked'),
|
||||
children: (
|
||||
<p>This content is hidden in collapsed state.</p>
|
||||
),
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { TechStackGrid, TechStackBrick } from './TechStack'
|
||||
|
||||
const meta: Meta<typeof TechStackGrid> = {
|
||||
title: 'Shared/TechStack',
|
||||
component: TechStackGrid,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div className="p-8 bg-ochre-clay">
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
}
|
||||
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof TechStackGrid>
|
||||
|
||||
export const Grid: Story = {
|
||||
args: {
|
||||
skills: [
|
||||
'React',
|
||||
'TypeScript',
|
||||
'Next.js',
|
||||
'Go',
|
||||
'PostgreSQL',
|
||||
'Redis',
|
||||
'Docker',
|
||||
'Kubernetes',
|
||||
'Tailwind',
|
||||
'Figma',
|
||||
'GraphQL',
|
||||
'Rust',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export const SingleBrick: Story = {
|
||||
render: () => (
|
||||
<div className="p-8 bg-ochre-clay inline-block">
|
||||
<TechStackBrick name="TypeScript" />
|
||||
</div>
|
||||
),
|
||||
}
|
||||
Reference in New Issue
Block a user