design system token v0.1
This commit is contained in:
111
stories/Composition/CTASection.stories.tsx
Normal file
111
stories/Composition/CTASection.stories.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import { CTASection } from '@/components/ui/cta-section'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
const meta = {
|
||||
title: 'Composition/CTASection',
|
||||
component: CTASection,
|
||||
tags: ['autodocs'],
|
||||
parameters: { layout: 'fullscreen' },
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: 'select',
|
||||
options: ['centered', 'left-aligned'],
|
||||
},
|
||||
background: {
|
||||
control: 'select',
|
||||
options: ['default', 'muted', 'accent', 'subtle'],
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof CTASection>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
const defaultActions = (
|
||||
<>
|
||||
<Button size="lg">Get Started</Button>
|
||||
<Button size="lg" variant="outline">Contact Sales</Button>
|
||||
</>
|
||||
)
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
heading: 'Ready to get started?',
|
||||
description: 'Join thousands of teams building better products with our design system.',
|
||||
actions: defaultActions,
|
||||
},
|
||||
}
|
||||
|
||||
export const Centered: Story = {
|
||||
args: {
|
||||
variant: 'centered',
|
||||
background: 'muted',
|
||||
heading: 'Start building today',
|
||||
description: 'Free for open source. Affordable for teams.',
|
||||
actions: defaultActions,
|
||||
},
|
||||
}
|
||||
|
||||
export const LeftAligned: Story = {
|
||||
args: {
|
||||
variant: 'left-aligned',
|
||||
background: 'muted',
|
||||
heading: 'Need help getting started?',
|
||||
description: 'Our team is ready to help you integrate the design system into your project.',
|
||||
actions: (
|
||||
<>
|
||||
<Button size="lg">Talk to us</Button>
|
||||
</>
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
export const AccentBackground: Story = {
|
||||
args: {
|
||||
variant: 'centered',
|
||||
background: 'accent',
|
||||
heading: 'Upgrade your workflow',
|
||||
description: 'Take your team to the next level with our premium plan.',
|
||||
actions: (
|
||||
<>
|
||||
<Button size="lg" variant="secondary">Start Free Trial</Button>
|
||||
<Button
|
||||
size="lg"
|
||||
variant="outline"
|
||||
className="border-primary-foreground/20 text-primary-foreground hover:bg-primary-foreground/10"
|
||||
>
|
||||
Learn More
|
||||
</Button>
|
||||
</>
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
export const SubtleBackground: Story = {
|
||||
args: {
|
||||
variant: 'centered',
|
||||
background: 'subtle',
|
||||
heading: 'Stay in the loop',
|
||||
description: 'Subscribe to our newsletter for the latest updates and releases.',
|
||||
actions: (
|
||||
<Button size="lg">Subscribe</Button>
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
export const DefaultBackground: Story = {
|
||||
args: {
|
||||
variant: 'centered',
|
||||
background: 'default',
|
||||
heading: 'Questions? We have answers.',
|
||||
description: 'Check out our documentation or reach out to our support team.',
|
||||
actions: (
|
||||
<>
|
||||
<Button size="lg">View Docs</Button>
|
||||
<Button size="lg" variant="ghost">Contact Support</Button>
|
||||
</>
|
||||
),
|
||||
},
|
||||
}
|
||||
92
stories/Composition/Footer.stories.tsx
Normal file
92
stories/Composition/Footer.stories.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import { Footer } from '@/components/ui/footer'
|
||||
import { Logo } from '@/components/ui/logo'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
const meta = {
|
||||
title: 'Composition/Footer',
|
||||
component: Footer,
|
||||
tags: ['autodocs'],
|
||||
parameters: { layout: 'fullscreen' },
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: 'select',
|
||||
options: ['minimal', 'full'],
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof Footer>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Minimal: Story = {
|
||||
args: {
|
||||
variant: 'minimal',
|
||||
logo: <Logo size="sm" />,
|
||||
copyright: <>© 2026 Greyhaven. All rights reserved.</>,
|
||||
actions: (
|
||||
<div className="flex gap-4 text-sm text-muted-foreground">
|
||||
<a href="#" className="hover:text-foreground transition-colors">Privacy</a>
|
||||
<a href="#" className="hover:text-foreground transition-colors">Terms</a>
|
||||
<a href="#" className="hover:text-foreground transition-colors">Contact</a>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
export const Full: Story = {
|
||||
args: {
|
||||
variant: 'full',
|
||||
logo: <Logo size="md" />,
|
||||
copyright: <>© 2026 Greyhaven. All rights reserved.</>,
|
||||
linkGroups: [
|
||||
{
|
||||
title: 'Product',
|
||||
links: [
|
||||
{ label: 'Features', href: '#' },
|
||||
{ label: 'Pricing', href: '#' },
|
||||
{ label: 'Changelog', href: '#' },
|
||||
{ label: 'Docs', href: '#' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Company',
|
||||
links: [
|
||||
{ label: 'About', href: '#' },
|
||||
{ label: 'Blog', href: '#' },
|
||||
{ label: 'Careers', href: '#' },
|
||||
{ label: 'Contact', href: '#' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Legal',
|
||||
links: [
|
||||
{ label: 'Privacy Policy', href: '#' },
|
||||
{ label: 'Terms of Service', href: '#' },
|
||||
{ label: 'Cookie Policy', href: '#' },
|
||||
],
|
||||
},
|
||||
],
|
||||
actions: (
|
||||
<div className="flex gap-2">
|
||||
<Button variant="ghost" size="sm">Twitter</Button>
|
||||
<Button variant="ghost" size="sm">GitHub</Button>
|
||||
<Button variant="ghost" size="sm">Discord</Button>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
export const MinimalNoCopyright: Story = {
|
||||
args: {
|
||||
variant: 'minimal',
|
||||
logo: <Logo size="sm" />,
|
||||
actions: (
|
||||
<div className="flex gap-4 text-sm text-muted-foreground">
|
||||
<a href="#" className="hover:text-foreground transition-colors">Docs</a>
|
||||
<a href="#" className="hover:text-foreground transition-colors">GitHub</a>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
}
|
||||
111
stories/Composition/Hero.stories.tsx
Normal file
111
stories/Composition/Hero.stories.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import { Hero } from '@/components/ui/hero'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
const meta = {
|
||||
title: 'Composition/Hero',
|
||||
component: Hero,
|
||||
tags: ['autodocs'],
|
||||
parameters: { layout: 'fullscreen' },
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: 'select',
|
||||
options: ['centered', 'left-aligned', 'split'],
|
||||
},
|
||||
background: {
|
||||
control: 'select',
|
||||
options: ['default', 'muted', 'accent', 'dark'],
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof Hero>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
const defaultActions = (
|
||||
<>
|
||||
<Button size="lg">Get Started</Button>
|
||||
<Button size="lg" variant="outline">Learn More</Button>
|
||||
</>
|
||||
)
|
||||
|
||||
export const Centered: Story = {
|
||||
args: {
|
||||
variant: 'centered',
|
||||
heading: 'Build better products with Greyhaven',
|
||||
subheading:
|
||||
'A modern design system that helps you create consistent, accessible, and beautiful user interfaces.',
|
||||
actions: defaultActions,
|
||||
},
|
||||
}
|
||||
|
||||
export const LeftAligned: Story = {
|
||||
args: {
|
||||
variant: 'left-aligned',
|
||||
heading: 'Ship faster with confidence',
|
||||
subheading:
|
||||
'Pre-built components, design tokens, and patterns so your team can focus on what matters.',
|
||||
actions: defaultActions,
|
||||
},
|
||||
}
|
||||
|
||||
export const Split: Story = {
|
||||
args: {
|
||||
variant: 'split',
|
||||
heading: 'Design meets engineering',
|
||||
subheading:
|
||||
'Bridging the gap between design and code with a shared language of components and tokens.',
|
||||
actions: defaultActions,
|
||||
media: (
|
||||
<div className="w-full aspect-video bg-muted rounded-lg flex items-center justify-center text-muted-foreground">
|
||||
Image / Media Placeholder
|
||||
</div>
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
export const MutedBackground: Story = {
|
||||
args: {
|
||||
variant: 'centered',
|
||||
background: 'muted',
|
||||
heading: 'Welcome to the platform',
|
||||
subheading: 'Everything you need to build and scale your project.',
|
||||
actions: defaultActions,
|
||||
},
|
||||
}
|
||||
|
||||
export const AccentBackground: Story = {
|
||||
args: {
|
||||
variant: 'centered',
|
||||
background: 'accent',
|
||||
heading: 'Start building today',
|
||||
subheading: 'Join thousands of developers using our design system.',
|
||||
actions: defaultActions,
|
||||
},
|
||||
}
|
||||
|
||||
export const DarkBackground: Story = {
|
||||
args: {
|
||||
variant: 'centered',
|
||||
background: 'dark',
|
||||
heading: 'The future of design systems',
|
||||
subheading: 'A bold new approach to building consistent user interfaces at scale.',
|
||||
actions: (
|
||||
<>
|
||||
<Button size="lg" variant="secondary">Get Started</Button>
|
||||
<Button size="lg" variant="outline" className="border-background/20 text-background hover:bg-background/10">
|
||||
Learn More
|
||||
</Button>
|
||||
</>
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
export const WithoutActions: Story = {
|
||||
args: {
|
||||
variant: 'centered',
|
||||
heading: 'A hero section without action buttons',
|
||||
subheading: 'Sometimes you just need a heading and description.',
|
||||
},
|
||||
}
|
||||
167
stories/Composition/PageLayout.stories.tsx
Normal file
167
stories/Composition/PageLayout.stories.tsx
Normal file
@@ -0,0 +1,167 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import { PageLayout } from '@/components/ui/page-layout'
|
||||
import { Navbar, NavbarLink } from '@/components/ui/navbar'
|
||||
import { Footer } from '@/components/ui/footer'
|
||||
import { Hero } from '@/components/ui/hero'
|
||||
import { Section } from '@/components/ui/section'
|
||||
import { CTASection } from '@/components/ui/cta-section'
|
||||
import { Logo } from '@/components/ui/logo'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription,
|
||||
CardContent,
|
||||
} from '@/components/ui/card'
|
||||
|
||||
const meta = {
|
||||
title: 'Composition/PageLayout',
|
||||
component: PageLayout,
|
||||
tags: ['autodocs'],
|
||||
parameters: { layout: 'fullscreen' },
|
||||
} satisfies Meta<typeof PageLayout>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
const navLinks = (
|
||||
<>
|
||||
<NavbarLink href="#" active>Home</NavbarLink>
|
||||
<NavbarLink href="#">Features</NavbarLink>
|
||||
<NavbarLink href="#">Pricing</NavbarLink>
|
||||
<NavbarLink href="#">Docs</NavbarLink>
|
||||
</>
|
||||
)
|
||||
|
||||
const navActions = (
|
||||
<>
|
||||
<Button variant="ghost" size="sm">Log in</Button>
|
||||
<Button size="sm">Sign up</Button>
|
||||
</>
|
||||
)
|
||||
|
||||
const sampleNavbar = (
|
||||
<Navbar
|
||||
variant="solid"
|
||||
logo={<Logo size="sm" />}
|
||||
actions={navActions}
|
||||
>
|
||||
{navLinks}
|
||||
</Navbar>
|
||||
)
|
||||
|
||||
const sampleFooter = (
|
||||
<Footer
|
||||
variant="minimal"
|
||||
logo={<Logo size="sm" />}
|
||||
copyright={<>© 2026 Greyhaven. All rights reserved.</>}
|
||||
actions={
|
||||
<div className="flex gap-4 text-sm text-muted-foreground">
|
||||
<a href="#" className="hover:text-foreground transition-colors">Privacy</a>
|
||||
<a href="#" className="hover:text-foreground transition-colors">Terms</a>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)
|
||||
|
||||
export const FullPage: Story = {
|
||||
args: {
|
||||
navbar: sampleNavbar,
|
||||
footer: sampleFooter,
|
||||
children: (
|
||||
<>
|
||||
<Hero
|
||||
variant="centered"
|
||||
heading="Build something great"
|
||||
subheading="A complete design system for modern web applications."
|
||||
actions={
|
||||
<>
|
||||
<Button size="lg">Get Started</Button>
|
||||
<Button size="lg" variant="outline">View Docs</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Section
|
||||
title="Features"
|
||||
description="Everything you need to build beautiful interfaces."
|
||||
>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{['Components', 'Tokens', 'Patterns'].map((title) => (
|
||||
<Card key={title}>
|
||||
<CardHeader>
|
||||
<CardTitle>{title}</CardTitle>
|
||||
<CardDescription>
|
||||
Pre-built {title.toLowerCase()} for rapid development.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Fully customizable {title.toLowerCase()} that follow best practices.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
<CTASection
|
||||
background="muted"
|
||||
heading="Ready to start?"
|
||||
description="Get up and running in minutes."
|
||||
actions={<Button size="lg">Get Started Free</Button>}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
export const WithSidebar: Story = {
|
||||
args: {
|
||||
navbar: sampleNavbar,
|
||||
footer: sampleFooter,
|
||||
sidebar: (
|
||||
<nav className="p-4 space-y-2">
|
||||
<h3 className="font-semibold text-sm mb-4">Navigation</h3>
|
||||
{['Dashboard', 'Projects', 'Team', 'Settings'].map((item) => (
|
||||
<a
|
||||
key={item}
|
||||
href="#"
|
||||
className="block px-3 py-2 text-sm rounded-md text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
|
||||
>
|
||||
{item}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
),
|
||||
children: (
|
||||
<Section title="Dashboard" description="Overview of your workspace.">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{['Revenue', 'Users', 'Orders', 'Growth'].map((metric) => (
|
||||
<Card key={metric}>
|
||||
<CardHeader>
|
||||
<CardTitle>{metric}</CardTitle>
|
||||
<CardDescription>Last 30 days</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-2xl font-bold">1,234</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
export const ContentOnly: Story = {
|
||||
args: {
|
||||
children: (
|
||||
<Section title="Standalone Content" description="A page layout with no navbar or footer.">
|
||||
<p className="text-muted-foreground">
|
||||
This demonstrates the PageLayout component with only content, no navbar, sidebar, or footer.
|
||||
</p>
|
||||
</Section>
|
||||
),
|
||||
},
|
||||
}
|
||||
135
stories/Composition/Section.stories.tsx
Normal file
135
stories/Composition/Section.stories.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import { Section } from '@/components/ui/section'
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription,
|
||||
CardContent,
|
||||
} from '@/components/ui/card'
|
||||
|
||||
const meta = {
|
||||
title: 'Composition/Section',
|
||||
component: Section,
|
||||
tags: ['autodocs'],
|
||||
parameters: { layout: 'fullscreen' },
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: 'select',
|
||||
options: ['default', 'highlighted', 'accent'],
|
||||
},
|
||||
width: {
|
||||
control: 'select',
|
||||
options: ['narrow', 'default', 'wide', 'full'],
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof Section>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
const sampleCards = (
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{['Design', 'Develop', 'Deploy'].map((title) => (
|
||||
<Card key={title}>
|
||||
<CardHeader>
|
||||
<CardTitle>{title}</CardTitle>
|
||||
<CardDescription>Description for the {title.toLowerCase()} phase.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Content explaining the {title.toLowerCase()} process in detail.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
title: 'Our Process',
|
||||
description: 'How we build great products from concept to delivery.',
|
||||
children: sampleCards,
|
||||
},
|
||||
}
|
||||
|
||||
export const Highlighted: Story = {
|
||||
args: {
|
||||
variant: 'highlighted',
|
||||
title: 'Featured Section',
|
||||
description: 'This section uses a highlighted background to stand out.',
|
||||
children: sampleCards,
|
||||
},
|
||||
}
|
||||
|
||||
export const Accent: Story = {
|
||||
args: {
|
||||
variant: 'accent',
|
||||
title: 'Accent Section',
|
||||
description: 'A subtle accent background to differentiate this area.',
|
||||
children: sampleCards,
|
||||
},
|
||||
}
|
||||
|
||||
export const Narrow: Story = {
|
||||
args: {
|
||||
width: 'narrow',
|
||||
title: 'Narrow Section',
|
||||
description: 'Constrained width for focused reading.',
|
||||
children: (
|
||||
<p className="text-muted-foreground">
|
||||
This is a narrow section with max-w-3xl. Useful for text-heavy content that
|
||||
benefits from shorter line lengths for readability.
|
||||
</p>
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
export const Wide: Story = {
|
||||
args: {
|
||||
width: 'wide',
|
||||
title: 'Wide Section',
|
||||
description: 'Extended width for content-rich layouts.',
|
||||
children: sampleCards,
|
||||
},
|
||||
}
|
||||
|
||||
export const Full: Story = {
|
||||
args: {
|
||||
width: 'full',
|
||||
variant: 'highlighted',
|
||||
title: 'Full Width Section',
|
||||
description: 'Spans the full width of the viewport.',
|
||||
children: sampleCards,
|
||||
},
|
||||
}
|
||||
|
||||
export const NoHeader: Story = {
|
||||
args: {
|
||||
children: sampleCards,
|
||||
},
|
||||
}
|
||||
|
||||
export const AllCombinations: Story = {
|
||||
render: () => (
|
||||
<div>
|
||||
{(['default', 'highlighted', 'accent'] as const).map((variant) =>
|
||||
(['narrow', 'default', 'wide'] as const).map((width) => (
|
||||
<Section
|
||||
key={`${variant}-${width}`}
|
||||
variant={variant}
|
||||
width={width}
|
||||
title={`${variant} / ${width}`}
|
||||
description={`Section with variant="${variant}" and width="${width}".`}
|
||||
>
|
||||
<div className="h-20 rounded-lg border-2 border-dashed border-muted-foreground/25 flex items-center justify-center text-sm text-muted-foreground">
|
||||
Content area
|
||||
</div>
|
||||
</Section>
|
||||
)),
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
}
|
||||
88
stories/Data/Progress.stories.tsx
Normal file
88
stories/Data/Progress.stories.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
|
||||
const meta = {
|
||||
title: 'Data/Progress',
|
||||
component: Progress,
|
||||
tags: ['autodocs'],
|
||||
parameters: { layout: 'centered' },
|
||||
argTypes: {
|
||||
value: {
|
||||
control: { type: 'range', min: 0, max: 100, step: 1 },
|
||||
},
|
||||
},
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div className="w-100">
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
} satisfies Meta<typeof Progress>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
value: 60,
|
||||
},
|
||||
}
|
||||
|
||||
export const Empty: Story = {
|
||||
args: {
|
||||
value: 0,
|
||||
},
|
||||
}
|
||||
|
||||
export const Quarter: Story = {
|
||||
args: {
|
||||
value: 25,
|
||||
},
|
||||
}
|
||||
|
||||
export const Half: Story = {
|
||||
args: {
|
||||
value: 50,
|
||||
},
|
||||
}
|
||||
|
||||
export const ThreeQuarters: Story = {
|
||||
args: {
|
||||
value: 75,
|
||||
},
|
||||
}
|
||||
|
||||
export const Complete: Story = {
|
||||
args: {
|
||||
value: 100,
|
||||
},
|
||||
}
|
||||
|
||||
export const AllStages: Story = {
|
||||
render: () => (
|
||||
<div className="flex flex-col gap-4 w-100">
|
||||
<div className="space-y-1">
|
||||
<span className="text-sm text-muted-foreground">0%</span>
|
||||
<Progress value={0} />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<span className="text-sm text-muted-foreground">25%</span>
|
||||
<Progress value={25} />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<span className="text-sm text-muted-foreground">50%</span>
|
||||
<Progress value={50} />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<span className="text-sm text-muted-foreground">75%</span>
|
||||
<Progress value={75} />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<span className="text-sm text-muted-foreground">100%</span>
|
||||
<Progress value={100} />
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
117
stories/Data/Table.stories.tsx
Normal file
117
stories/Data/Table.stories.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableFooter,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableCaption,
|
||||
} from '@/components/ui/table'
|
||||
|
||||
const meta = {
|
||||
title: 'Data/Table',
|
||||
component: Table,
|
||||
tags: ['autodocs'],
|
||||
parameters: { layout: 'centered' },
|
||||
} satisfies Meta<typeof Table>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
const invoices = [
|
||||
{ invoice: 'INV001', status: 'Paid', method: 'Credit Card', amount: '$250.00' },
|
||||
{ invoice: 'INV002', status: 'Pending', method: 'PayPal', amount: '$150.00' },
|
||||
{ invoice: 'INV003', status: 'Unpaid', method: 'Bank Transfer', amount: '$350.00' },
|
||||
{ invoice: 'INV004', status: 'Paid', method: 'Credit Card', amount: '$450.00' },
|
||||
{ invoice: 'INV005', status: 'Paid', method: 'PayPal', amount: '$550.00' },
|
||||
]
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<div className="w-150">
|
||||
<Table>
|
||||
<TableCaption>A list of your recent invoices.</TableCaption>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-25">Invoice</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Method</TableHead>
|
||||
<TableHead className="text-right">Amount</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{invoices.map((invoice) => (
|
||||
<TableRow key={invoice.invoice}>
|
||||
<TableCell className="font-medium">{invoice.invoice}</TableCell>
|
||||
<TableCell>{invoice.status}</TableCell>
|
||||
<TableCell>{invoice.method}</TableCell>
|
||||
<TableCell className="text-right">{invoice.amount}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TableCell colSpan={3}>Total</TableCell>
|
||||
<TableCell className="text-right">$1,750.00</TableCell>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</Table>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
export const Simple: Story = {
|
||||
render: () => (
|
||||
<div className="w-100">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Role</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>Alice</TableCell>
|
||||
<TableCell>Engineer</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>Bob</TableCell>
|
||||
<TableCell>Designer</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>Charlie</TableCell>
|
||||
<TableCell>Manager</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
export const Empty: Story = {
|
||||
render: () => (
|
||||
<div className="w-100">
|
||||
<Table>
|
||||
<TableCaption>No data available.</TableCaption>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Email</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell colSpan={3} className="text-center text-muted-foreground h-24">
|
||||
No results found.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
71
stories/Feedback/Alert.stories.tsx
Normal file
71
stories/Feedback/Alert.stories.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import { Terminal, AlertCircle } from 'lucide-react'
|
||||
|
||||
import { Alert, AlertTitle, AlertDescription } from '@/components/ui/alert'
|
||||
|
||||
const meta = {
|
||||
title: 'Feedback/Alert',
|
||||
component: Alert,
|
||||
tags: ['autodocs'],
|
||||
parameters: { layout: 'centered' },
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: 'select',
|
||||
options: ['default', 'destructive'],
|
||||
},
|
||||
},
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div className="w-125">
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
} satisfies Meta<typeof Alert>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<Alert>
|
||||
<Terminal className="size-4" />
|
||||
<AlertTitle>Heads up!</AlertTitle>
|
||||
<AlertDescription>
|
||||
You can add components to your app using the CLI.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
),
|
||||
}
|
||||
|
||||
export const Destructive: Story = {
|
||||
render: () => (
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="size-4" />
|
||||
<AlertTitle>Error</AlertTitle>
|
||||
<AlertDescription>
|
||||
Your session has expired. Please log in again.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
),
|
||||
}
|
||||
|
||||
export const WithoutIcon: Story = {
|
||||
render: () => (
|
||||
<Alert>
|
||||
<AlertTitle>Note</AlertTitle>
|
||||
<AlertDescription>
|
||||
This alert has no icon, just a title and description.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
),
|
||||
}
|
||||
|
||||
export const TitleOnly: Story = {
|
||||
render: () => (
|
||||
<Alert>
|
||||
<Terminal className="size-4" />
|
||||
<AlertTitle>A simple alert with only a title.</AlertTitle>
|
||||
</Alert>
|
||||
),
|
||||
}
|
||||
63
stories/Feedback/Skeleton.stories.tsx
Normal file
63
stories/Feedback/Skeleton.stories.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
|
||||
const meta = {
|
||||
title: 'Feedback/Skeleton',
|
||||
component: Skeleton,
|
||||
tags: ['autodocs'],
|
||||
parameters: { layout: 'centered' },
|
||||
} satisfies Meta<typeof Skeleton>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
className: 'h-4 w-62.5',
|
||||
},
|
||||
}
|
||||
|
||||
export const Circle: Story = {
|
||||
args: {
|
||||
className: 'size-12 rounded-full',
|
||||
},
|
||||
}
|
||||
|
||||
export const CardSkeleton: Story = {
|
||||
render: () => (
|
||||
<div className="flex items-center space-x-4">
|
||||
<Skeleton className="size-12 rounded-full" />
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-4 w-62.5" />
|
||||
<Skeleton className="h-4 w-50" />
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
export const FormSkeleton: Story = {
|
||||
render: () => (
|
||||
<div className="space-y-4 w-75">
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-4 w-20" />
|
||||
<Skeleton className="h-9 w-full" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-4 w-30" />
|
||||
<Skeleton className="h-9 w-full" />
|
||||
</div>
|
||||
<Skeleton className="h-9 w-25" />
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
export const TextBlock: Story = {
|
||||
render: () => (
|
||||
<div className="space-y-2 w-87.5">
|
||||
<Skeleton className="h-4 w-full" />
|
||||
<Skeleton className="h-4 w-full" />
|
||||
<Skeleton className="h-4 w-3/4" />
|
||||
</div>
|
||||
),
|
||||
}
|
||||
54
stories/Feedback/Spinner.stories.tsx
Normal file
54
stories/Feedback/Spinner.stories.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import { Spinner } from '@/components/ui/spinner'
|
||||
|
||||
const meta = {
|
||||
title: 'Feedback/Spinner',
|
||||
component: Spinner,
|
||||
tags: ['autodocs'],
|
||||
parameters: { layout: 'centered' },
|
||||
} satisfies Meta<typeof Spinner>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {}
|
||||
|
||||
export const Small: Story = {
|
||||
args: {
|
||||
className: 'size-3 animate-spin',
|
||||
},
|
||||
}
|
||||
|
||||
export const Large: Story = {
|
||||
args: {
|
||||
className: 'size-8 animate-spin',
|
||||
},
|
||||
}
|
||||
|
||||
export const ExtraLarge: Story = {
|
||||
args: {
|
||||
className: 'size-12 animate-spin',
|
||||
},
|
||||
}
|
||||
|
||||
export const WithText: Story = {
|
||||
render: () => (
|
||||
<div className="flex items-center gap-2">
|
||||
<Spinner />
|
||||
<span className="text-sm text-muted-foreground">Loading...</span>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
export const Sizes: Story = {
|
||||
render: () => (
|
||||
<div className="flex items-center gap-4">
|
||||
<Spinner className="size-3 animate-spin" />
|
||||
<Spinner />
|
||||
<Spinner className="size-6 animate-spin" />
|
||||
<Spinner className="size-8 animate-spin" />
|
||||
<Spinner className="size-12 animate-spin" />
|
||||
</div>
|
||||
),
|
||||
}
|
||||
211
stories/Form/Form.stories.tsx
Normal file
211
stories/Form/Form.stories.tsx
Normal file
@@ -0,0 +1,211 @@
|
||||
import React from 'react'
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { z } from 'zod'
|
||||
|
||||
import {
|
||||
Form,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
const meta = {
|
||||
title: 'Form/Form',
|
||||
component: Form,
|
||||
tags: ['autodocs'],
|
||||
parameters: { layout: 'centered' },
|
||||
} satisfies Meta<typeof Form>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
const profileSchema = z.object({
|
||||
username: z
|
||||
.string()
|
||||
.min(2, { message: 'Username must be at least 2 characters.' })
|
||||
.max(30, { message: 'Username must not be longer than 30 characters.' }),
|
||||
email: z.string().email({ message: 'Please enter a valid email address.' }),
|
||||
})
|
||||
|
||||
type ProfileValues = z.infer<typeof profileSchema>
|
||||
|
||||
function ProfileForm() {
|
||||
const form = useForm<ProfileValues>({
|
||||
resolver: zodResolver(profileSchema),
|
||||
defaultValues: {
|
||||
username: '',
|
||||
email: '',
|
||||
},
|
||||
})
|
||||
|
||||
function onSubmit(data: ProfileValues) {
|
||||
alert(JSON.stringify(data, null, 2))
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="w-100 space-y-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="username"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="johndoe" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
This is your public display name.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="email" placeholder="john@example.com" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
We will never share your email with anyone.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit">Submit</Button>
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <ProfileForm />,
|
||||
}
|
||||
|
||||
const loginSchema = z.object({
|
||||
email: z.string().email({ message: 'Invalid email address.' }),
|
||||
password: z
|
||||
.string()
|
||||
.min(8, { message: 'Password must be at least 8 characters.' }),
|
||||
})
|
||||
|
||||
type LoginValues = z.infer<typeof loginSchema>
|
||||
|
||||
function LoginForm() {
|
||||
const form = useForm<LoginValues>({
|
||||
resolver: zodResolver(loginSchema),
|
||||
defaultValues: {
|
||||
email: '',
|
||||
password: '',
|
||||
},
|
||||
})
|
||||
|
||||
function onSubmit(data: LoginValues) {
|
||||
alert(JSON.stringify(data, null, 2))
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="w-100 space-y-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="email" placeholder="you@example.com" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="password" placeholder="Enter password" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit" className="w-full">
|
||||
Sign in
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
export const Login: Story = {
|
||||
render: () => <LoginForm />,
|
||||
}
|
||||
|
||||
function PrefilledErrorForm() {
|
||||
const form = useForm<ProfileValues>({
|
||||
resolver: zodResolver(profileSchema),
|
||||
defaultValues: {
|
||||
username: 'a',
|
||||
email: 'not-an-email',
|
||||
},
|
||||
})
|
||||
|
||||
// Trigger validation on mount
|
||||
React.useEffect(() => {
|
||||
form.trigger()
|
||||
}, [form])
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form className="w-100 space-y-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="username"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="email" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit">Submit</Button>
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
export const WithErrors: Story = {
|
||||
render: () => <PrefilledErrorForm />,
|
||||
}
|
||||
87
stories/Layout/Accordion.stories.tsx
Normal file
87
stories/Layout/Accordion.stories.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import {
|
||||
Accordion,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
AccordionContent,
|
||||
} from '@/components/ui/accordion'
|
||||
|
||||
const meta = {
|
||||
title: 'Layout/Accordion',
|
||||
component: Accordion,
|
||||
tags: ['autodocs'],
|
||||
parameters: { layout: 'centered' },
|
||||
} satisfies Meta<typeof Accordion>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Single: Story = {
|
||||
render: () => (
|
||||
<Accordion type="single" collapsible className="w-100">
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger>Is it accessible?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Yes. It adheres to the WAI-ARIA design pattern.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-2">
|
||||
<AccordionTrigger>Is it styled?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Yes. It comes with default styles that match the other components.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-3">
|
||||
<AccordionTrigger>Is it animated?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Yes. It uses CSS animations for smooth open and close transitions.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
),
|
||||
}
|
||||
|
||||
export const Multiple: Story = {
|
||||
render: () => (
|
||||
<Accordion type="multiple" className="w-100">
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger>What is Greyhaven?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Greyhaven is a design system built with Radix UI and Tailwind CSS.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-2">
|
||||
<AccordionTrigger>How do I install it?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
You can install it via npm or pnpm. Check the documentation for details.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-3">
|
||||
<AccordionTrigger>Can I customize themes?</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Absolutely. The design system uses CSS custom properties for full theme control.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
),
|
||||
}
|
||||
|
||||
export const DefaultOpen: Story = {
|
||||
render: () => (
|
||||
<Accordion type="single" defaultValue="item-1" collapsible className="w-100">
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger>Open by default</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
This accordion item is open by default.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-2">
|
||||
<AccordionTrigger>Click to open</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
This accordion item starts closed.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
),
|
||||
}
|
||||
97
stories/Layout/Card.stories.tsx
Normal file
97
stories/Layout/Card.stories.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription,
|
||||
CardContent,
|
||||
CardFooter,
|
||||
CardAction,
|
||||
} from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
const meta = {
|
||||
title: 'Layout/Card',
|
||||
component: Card,
|
||||
tags: ['autodocs'],
|
||||
parameters: { layout: 'centered' },
|
||||
} satisfies Meta<typeof Card>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<Card className="w-87.5">
|
||||
<CardHeader>
|
||||
<CardTitle>Card Title</CardTitle>
|
||||
<CardDescription>Card description goes here.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>Card content with some example text to demonstrate the layout.</p>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button>Action</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
),
|
||||
}
|
||||
|
||||
export const WithAction: Story = {
|
||||
render: () => (
|
||||
<Card className="w-87.5">
|
||||
<CardHeader>
|
||||
<CardTitle>Notifications</CardTitle>
|
||||
<CardDescription>You have 3 unread messages.</CardDescription>
|
||||
<CardAction>
|
||||
<Button variant="outline" size="sm">Mark all read</Button>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>Here are your latest notifications.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
),
|
||||
}
|
||||
|
||||
export const Simple: Story = {
|
||||
render: () => (
|
||||
<Card className="w-87.5">
|
||||
<CardHeader>
|
||||
<CardTitle>Simple Card</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>A card with just a title and content.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
),
|
||||
}
|
||||
|
||||
export const WithFooter: Story = {
|
||||
render: () => (
|
||||
<Card className="w-87.5">
|
||||
<CardHeader>
|
||||
<CardTitle>Create project</CardTitle>
|
||||
<CardDescription>Deploy your new project in one click.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>Configure your project settings below.</p>
|
||||
</CardContent>
|
||||
<CardFooter className="flex justify-between">
|
||||
<Button variant="outline">Cancel</Button>
|
||||
<Button>Deploy</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
),
|
||||
}
|
||||
|
||||
export const ContentOnly: Story = {
|
||||
render: () => (
|
||||
<Card className="w-87.5">
|
||||
<CardContent>
|
||||
<p>A minimal card with only content, no header or footer.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
),
|
||||
}
|
||||
59
stories/Layout/Separator.stories.tsx
Normal file
59
stories/Layout/Separator.stories.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
|
||||
const meta = {
|
||||
title: 'Layout/Separator',
|
||||
component: Separator,
|
||||
tags: ['autodocs'],
|
||||
parameters: { layout: 'centered' },
|
||||
argTypes: {
|
||||
orientation: {
|
||||
control: 'select',
|
||||
options: ['horizontal', 'vertical'],
|
||||
},
|
||||
decorative: { control: 'boolean' },
|
||||
},
|
||||
} satisfies Meta<typeof Separator>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Horizontal: Story = {
|
||||
args: {
|
||||
orientation: 'horizontal',
|
||||
},
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div className="w-75">
|
||||
<div className="space-y-1">
|
||||
<h4 className="text-sm font-medium leading-none">Greyhaven Design System</h4>
|
||||
<p className="text-sm text-muted-foreground">An open-source UI component library.</p>
|
||||
</div>
|
||||
<Story />
|
||||
<div className="flex h-5 items-center space-x-4 text-sm">
|
||||
<div>Blog</div>
|
||||
<div>Docs</div>
|
||||
<div>Source</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
],
|
||||
}
|
||||
|
||||
export const Vertical: Story = {
|
||||
args: {
|
||||
orientation: 'vertical',
|
||||
},
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div className="flex h-5 items-center space-x-4 text-sm">
|
||||
<div>Blog</div>
|
||||
<Story />
|
||||
<div>Docs</div>
|
||||
<Story />
|
||||
<div>Source</div>
|
||||
</div>
|
||||
),
|
||||
],
|
||||
}
|
||||
109
stories/Navigation/Breadcrumb.stories.tsx
Normal file
109
stories/Navigation/Breadcrumb.stories.tsx
Normal file
@@ -0,0 +1,109 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbList,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
BreadcrumbEllipsis,
|
||||
} from '@/components/ui/breadcrumb'
|
||||
|
||||
const meta = {
|
||||
title: 'Navigation/Breadcrumb',
|
||||
component: Breadcrumb,
|
||||
tags: ['autodocs'],
|
||||
parameters: { layout: 'centered' },
|
||||
} satisfies Meta<typeof Breadcrumb>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="#">Home</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="#">Components</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
),
|
||||
}
|
||||
|
||||
export const WithEllipsis: Story = {
|
||||
render: () => (
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="#">Home</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbEllipsis />
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="#">Components</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
),
|
||||
}
|
||||
|
||||
export const TwoLevels: Story = {
|
||||
render: () => (
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="#">Dashboard</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Settings</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
),
|
||||
}
|
||||
|
||||
export const DeepNesting: Story = {
|
||||
render: () => (
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="#">Home</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="#">Products</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="#">Electronics</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href="#">Laptops</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>MacBook Pro</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
),
|
||||
}
|
||||
94
stories/Navigation/Navbar.stories.tsx
Normal file
94
stories/Navigation/Navbar.stories.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import { Navbar, NavbarLink } from '@/components/ui/navbar'
|
||||
import { Logo } from '@/components/ui/logo'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
const meta = {
|
||||
title: 'Navigation/Navbar',
|
||||
component: Navbar,
|
||||
tags: ['autodocs'],
|
||||
parameters: { layout: 'fullscreen' },
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: 'select',
|
||||
options: ['solid', 'transparent', 'minimal'],
|
||||
},
|
||||
},
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div className="min-h-[200px]">
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
} satisfies Meta<typeof Navbar>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
const navLinks = (
|
||||
<>
|
||||
<NavbarLink href="#" active>Home</NavbarLink>
|
||||
<NavbarLink href="#">About</NavbarLink>
|
||||
<NavbarLink href="#">Services</NavbarLink>
|
||||
<NavbarLink href="#">Contact</NavbarLink>
|
||||
</>
|
||||
)
|
||||
|
||||
const navActions = (
|
||||
<>
|
||||
<Button variant="ghost" size="sm">Log in</Button>
|
||||
<Button size="sm">Sign up</Button>
|
||||
</>
|
||||
)
|
||||
|
||||
export const Solid: Story = {
|
||||
args: {
|
||||
variant: 'solid',
|
||||
logo: <Logo size="sm" />,
|
||||
actions: navActions,
|
||||
children: navLinks,
|
||||
},
|
||||
}
|
||||
|
||||
export const Transparent: Story = {
|
||||
args: {
|
||||
variant: 'transparent',
|
||||
logo: <Logo size="sm" />,
|
||||
actions: navActions,
|
||||
children: navLinks,
|
||||
},
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div className="min-h-[200px] bg-gradient-to-br from-primary/20 to-primary/5">
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
}
|
||||
|
||||
export const Minimal: Story = {
|
||||
args: {
|
||||
variant: 'minimal',
|
||||
logo: <Logo size="sm" />,
|
||||
actions: navActions,
|
||||
children: navLinks,
|
||||
},
|
||||
}
|
||||
|
||||
export const WithoutActions: Story = {
|
||||
args: {
|
||||
variant: 'solid',
|
||||
logo: <Logo size="sm" />,
|
||||
children: navLinks,
|
||||
},
|
||||
}
|
||||
|
||||
export const LogoOnly: Story = {
|
||||
args: {
|
||||
variant: 'solid',
|
||||
logo: <Logo size="sm" />,
|
||||
actions: navActions,
|
||||
},
|
||||
}
|
||||
72
stories/Overlay/AlertDialog.stories.tsx
Normal file
72
stories/Overlay/AlertDialog.stories.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogTrigger,
|
||||
AlertDialogContent,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
} from '@/components/ui/alert-dialog'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
const meta = {
|
||||
title: 'Overlay/AlertDialog',
|
||||
component: AlertDialog,
|
||||
tags: ['autodocs'],
|
||||
parameters: { layout: 'centered' },
|
||||
} satisfies Meta<typeof AlertDialog>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="outline">Delete Account</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action cannot be undone. This will permanently delete your account
|
||||
and remove your data from our servers.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction>Continue</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
),
|
||||
}
|
||||
|
||||
export const Destructive: Story = {
|
||||
render: () => (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="destructive">Delete Project</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Delete project?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This will permanently delete the project and all associated data.
|
||||
This action cannot be reversed.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction className="bg-destructive text-white hover:bg-destructive/90">
|
||||
Yes, delete project
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
),
|
||||
}
|
||||
106
stories/Overlay/Dialog.stories.tsx
Normal file
106
stories/Overlay/Dialog.stories.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogClose,
|
||||
} from '@/components/ui/dialog'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
|
||||
const meta = {
|
||||
title: 'Overlay/Dialog',
|
||||
component: Dialog,
|
||||
tags: ['autodocs'],
|
||||
parameters: { layout: 'centered' },
|
||||
} satisfies Meta<typeof Dialog>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline">Open Dialog</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Dialog Title</DialogTitle>
|
||||
<DialogDescription>
|
||||
This is a dialog description. It provides context about the dialog content.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="py-4">
|
||||
<p>Dialog body content goes here.</p>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button variant="outline">Cancel</Button>
|
||||
</DialogClose>
|
||||
<Button>Confirm</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
),
|
||||
}
|
||||
|
||||
export const WithForm: Story = {
|
||||
render: () => (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button>Edit Profile</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit profile</DialogTitle>
|
||||
<DialogDescription>
|
||||
Make changes to your profile here. Click save when you are done.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="name">Name</Label>
|
||||
<Input id="name" defaultValue="John Doe" />
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="username">Username</Label>
|
||||
<Input id="username" defaultValue="@johndoe" />
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="submit">Save changes</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
),
|
||||
}
|
||||
|
||||
export const NoCloseButton: Story = {
|
||||
render: () => (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline">Open (no close button)</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent showCloseButton={false}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>No Close Button</DialogTitle>
|
||||
<DialogDescription>
|
||||
This dialog has no close button in the corner.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button>Got it</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
),
|
||||
}
|
||||
99
stories/Overlay/Tooltip.stories.tsx
Normal file
99
stories/Overlay/Tooltip.stories.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import { Plus } from 'lucide-react'
|
||||
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipTrigger,
|
||||
TooltipContent,
|
||||
} from '@/components/ui/tooltip'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
const meta = {
|
||||
title: 'Overlay/Tooltip',
|
||||
component: Tooltip,
|
||||
tags: ['autodocs'],
|
||||
parameters: { layout: 'centered' },
|
||||
} satisfies Meta<typeof Tooltip>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="outline">Hover me</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>This is a tooltip</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
),
|
||||
}
|
||||
|
||||
export const Top: Story = {
|
||||
render: () => (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="outline">Top</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top">
|
||||
<p>Tooltip on top</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
),
|
||||
}
|
||||
|
||||
export const Right: Story = {
|
||||
render: () => (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="outline">Right</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">
|
||||
<p>Tooltip on right</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
),
|
||||
}
|
||||
|
||||
export const Bottom: Story = {
|
||||
render: () => (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="outline">Bottom</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">
|
||||
<p>Tooltip on bottom</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
),
|
||||
}
|
||||
|
||||
export const Left: Story = {
|
||||
render: () => (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="outline">Left</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="left">
|
||||
<p>Tooltip on left</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
),
|
||||
}
|
||||
|
||||
export const WithIconButton: Story = {
|
||||
render: () => (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button size="icon" variant="outline">
|
||||
<Plus className="size-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Add item</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
),
|
||||
}
|
||||
170
stories/Primitives/Badge.stories.tsx
Normal file
170
stories/Primitives/Badge.stories.tsx
Normal file
@@ -0,0 +1,170 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
|
||||
const meta = {
|
||||
title: 'Primitives/Badge',
|
||||
component: Badge,
|
||||
tags: ['autodocs'],
|
||||
parameters: { layout: 'centered' },
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: 'select',
|
||||
options: [
|
||||
'default',
|
||||
'secondary',
|
||||
'muted',
|
||||
'destructive',
|
||||
'outline',
|
||||
'success',
|
||||
'warning',
|
||||
'info',
|
||||
'tag',
|
||||
'value',
|
||||
'whatsapp',
|
||||
'email',
|
||||
'telegram',
|
||||
'zulip',
|
||||
'platform',
|
||||
],
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof Badge>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
children: 'Badge',
|
||||
variant: 'default',
|
||||
},
|
||||
}
|
||||
|
||||
export const Secondary: Story = {
|
||||
args: {
|
||||
children: 'Secondary',
|
||||
variant: 'secondary',
|
||||
},
|
||||
}
|
||||
|
||||
export const Muted: Story = {
|
||||
args: {
|
||||
children: 'Muted',
|
||||
variant: 'muted',
|
||||
},
|
||||
}
|
||||
|
||||
export const Destructive: Story = {
|
||||
args: {
|
||||
children: 'Destructive',
|
||||
variant: 'destructive',
|
||||
},
|
||||
}
|
||||
|
||||
export const Outline: Story = {
|
||||
args: {
|
||||
children: 'Outline',
|
||||
variant: 'outline',
|
||||
},
|
||||
}
|
||||
|
||||
export const Success: Story = {
|
||||
args: {
|
||||
children: 'Success',
|
||||
variant: 'success',
|
||||
},
|
||||
}
|
||||
|
||||
export const Warning: Story = {
|
||||
args: {
|
||||
children: 'Warning',
|
||||
variant: 'warning',
|
||||
},
|
||||
}
|
||||
|
||||
export const Info: Story = {
|
||||
args: {
|
||||
children: 'Info',
|
||||
variant: 'info',
|
||||
},
|
||||
}
|
||||
|
||||
export const Tag: Story = {
|
||||
args: {
|
||||
children: 'Tag',
|
||||
variant: 'tag',
|
||||
},
|
||||
}
|
||||
|
||||
export const Value: Story = {
|
||||
args: {
|
||||
children: '42',
|
||||
variant: 'value',
|
||||
},
|
||||
}
|
||||
|
||||
export const Whatsapp: Story = {
|
||||
args: {
|
||||
children: 'WhatsApp',
|
||||
variant: 'whatsapp',
|
||||
},
|
||||
}
|
||||
|
||||
export const Email: Story = {
|
||||
args: {
|
||||
children: 'Email',
|
||||
variant: 'email',
|
||||
},
|
||||
}
|
||||
|
||||
export const Telegram: Story = {
|
||||
args: {
|
||||
children: 'Telegram',
|
||||
variant: 'telegram',
|
||||
},
|
||||
}
|
||||
|
||||
export const Zulip: Story = {
|
||||
args: {
|
||||
children: 'Zulip',
|
||||
variant: 'zulip',
|
||||
},
|
||||
}
|
||||
|
||||
export const Platform: Story = {
|
||||
args: {
|
||||
children: 'Platform',
|
||||
variant: 'platform',
|
||||
},
|
||||
}
|
||||
|
||||
export const AllVariants: Story = {
|
||||
render: () => (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{(
|
||||
[
|
||||
'default',
|
||||
'secondary',
|
||||
'muted',
|
||||
'destructive',
|
||||
'outline',
|
||||
'success',
|
||||
'warning',
|
||||
'info',
|
||||
'tag',
|
||||
'value',
|
||||
'whatsapp',
|
||||
'email',
|
||||
'telegram',
|
||||
'zulip',
|
||||
'platform',
|
||||
] as const
|
||||
).map((variant) => (
|
||||
<Badge key={variant} variant={variant}>
|
||||
{variant}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
}
|
||||
171
stories/Primitives/Button.stories.tsx
Normal file
171
stories/Primitives/Button.stories.tsx
Normal file
@@ -0,0 +1,171 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import { ChevronRight, Mail, Loader2, Plus } from 'lucide-react'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
const meta = {
|
||||
title: 'Primitives/Button',
|
||||
component: Button,
|
||||
tags: ['autodocs'],
|
||||
parameters: { layout: 'centered' },
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: 'select',
|
||||
options: ['default', 'secondary', 'outline', 'ghost', 'link', 'destructive'],
|
||||
},
|
||||
size: {
|
||||
control: 'select',
|
||||
options: ['default', 'sm', 'lg', 'icon', 'icon-sm', 'icon-lg'],
|
||||
},
|
||||
disabled: { control: 'boolean' },
|
||||
asChild: { control: 'boolean' },
|
||||
},
|
||||
} satisfies Meta<typeof Button>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
children: 'Button',
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
},
|
||||
}
|
||||
|
||||
export const Secondary: Story = {
|
||||
args: {
|
||||
children: 'Secondary',
|
||||
variant: 'secondary',
|
||||
},
|
||||
}
|
||||
|
||||
export const Outline: Story = {
|
||||
args: {
|
||||
children: 'Outline',
|
||||
variant: 'outline',
|
||||
},
|
||||
}
|
||||
|
||||
export const Ghost: Story = {
|
||||
args: {
|
||||
children: 'Ghost',
|
||||
variant: 'ghost',
|
||||
},
|
||||
}
|
||||
|
||||
export const Link: Story = {
|
||||
args: {
|
||||
children: 'Link',
|
||||
variant: 'link',
|
||||
},
|
||||
}
|
||||
|
||||
export const Destructive: Story = {
|
||||
args: {
|
||||
children: 'Delete',
|
||||
variant: 'destructive',
|
||||
},
|
||||
}
|
||||
|
||||
export const Small: Story = {
|
||||
args: {
|
||||
children: 'Small',
|
||||
size: 'sm',
|
||||
},
|
||||
}
|
||||
|
||||
export const Large: Story = {
|
||||
args: {
|
||||
children: 'Large',
|
||||
size: 'lg',
|
||||
},
|
||||
}
|
||||
|
||||
export const Icon: Story = {
|
||||
args: {
|
||||
size: 'icon',
|
||||
children: <Plus className="size-4" />,
|
||||
'aria-label': 'Add',
|
||||
},
|
||||
}
|
||||
|
||||
export const IconSmall: Story = {
|
||||
args: {
|
||||
size: 'icon-sm',
|
||||
children: <Plus className="size-4" />,
|
||||
'aria-label': 'Add',
|
||||
},
|
||||
}
|
||||
|
||||
export const IconLarge: Story = {
|
||||
args: {
|
||||
size: 'icon-lg',
|
||||
children: <Plus className="size-4" />,
|
||||
'aria-label': 'Add',
|
||||
},
|
||||
}
|
||||
|
||||
export const WithIcon: Story = {
|
||||
args: {
|
||||
children: (
|
||||
<>
|
||||
<Mail /> Login with Email
|
||||
</>
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
export const WithTrailingIcon: Story = {
|
||||
args: {
|
||||
children: (
|
||||
<>
|
||||
Next <ChevronRight />
|
||||
</>
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
export const Loading: Story = {
|
||||
args: {
|
||||
disabled: true,
|
||||
children: (
|
||||
<>
|
||||
<Loader2 className="animate-spin" /> Please wait
|
||||
</>
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
children: 'Disabled',
|
||||
disabled: true,
|
||||
},
|
||||
}
|
||||
|
||||
export const AsChild: Story = {
|
||||
args: {
|
||||
asChild: true,
|
||||
children: <a href="#">Link styled as Button</a>,
|
||||
},
|
||||
}
|
||||
|
||||
export const AllVariants: Story = {
|
||||
render: () => (
|
||||
<div className="flex flex-col gap-4">
|
||||
{(['default', 'secondary', 'outline', 'ghost', 'link', 'destructive'] as const).map(
|
||||
(variant) => (
|
||||
<div key={variant} className="flex items-center gap-2">
|
||||
<span className="w-24 text-sm text-muted-foreground">{variant}</span>
|
||||
<Button variant={variant} size="sm">Small</Button>
|
||||
<Button variant={variant} size="default">Default</Button>
|
||||
<Button variant={variant} size="lg">Large</Button>
|
||||
<Button variant={variant} size="icon"><Plus className="size-4" /></Button>
|
||||
<Button variant={variant} disabled>Disabled</Button>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
}
|
||||
88
stories/Primitives/Input.stories.tsx
Normal file
88
stories/Primitives/Input.stories.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
|
||||
const meta = {
|
||||
title: 'Primitives/Input',
|
||||
component: Input,
|
||||
tags: ['autodocs'],
|
||||
parameters: { layout: 'centered' },
|
||||
argTypes: {
|
||||
type: {
|
||||
control: 'select',
|
||||
options: ['text', 'email', 'password', 'number', 'search', 'tel', 'url', 'file'],
|
||||
},
|
||||
disabled: { control: 'boolean' },
|
||||
placeholder: { control: 'text' },
|
||||
},
|
||||
} satisfies Meta<typeof Input>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
placeholder: 'Enter text...',
|
||||
type: 'text',
|
||||
},
|
||||
}
|
||||
|
||||
export const Email: Story = {
|
||||
args: {
|
||||
type: 'email',
|
||||
placeholder: 'email@example.com',
|
||||
},
|
||||
}
|
||||
|
||||
export const Password: Story = {
|
||||
args: {
|
||||
type: 'password',
|
||||
placeholder: 'Enter password...',
|
||||
},
|
||||
}
|
||||
|
||||
export const File: Story = {
|
||||
args: {
|
||||
type: 'file',
|
||||
},
|
||||
}
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
placeholder: 'Disabled input',
|
||||
disabled: true,
|
||||
},
|
||||
}
|
||||
|
||||
export const WithLabel: Story = {
|
||||
render: () => (
|
||||
<div className="grid w-full max-w-sm gap-2">
|
||||
<Label htmlFor="with-label-input">Email</Label>
|
||||
<Input id="with-label-input" type="email" placeholder="email@example.com" />
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
export const ErrorState: Story = {
|
||||
render: () => (
|
||||
<div className="grid w-full max-w-sm gap-2">
|
||||
<Label htmlFor="error-input">Email</Label>
|
||||
<Input
|
||||
id="error-input"
|
||||
type="email"
|
||||
placeholder="email@example.com"
|
||||
aria-invalid="true"
|
||||
defaultValue="invalid-email"
|
||||
/>
|
||||
<p className="text-sm text-destructive">Please enter a valid email address.</p>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
export const WithDefaultValue: Story = {
|
||||
args: {
|
||||
type: 'text',
|
||||
defaultValue: 'Hello world',
|
||||
},
|
||||
}
|
||||
103
stories/Primitives/Toggle.stories.tsx
Normal file
103
stories/Primitives/Toggle.stories.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import { Bold, Italic, Underline } from 'lucide-react'
|
||||
|
||||
import { Toggle } from '@/components/ui/toggle'
|
||||
|
||||
const meta = {
|
||||
title: 'Primitives/Toggle',
|
||||
component: Toggle,
|
||||
tags: ['autodocs'],
|
||||
parameters: { layout: 'centered' },
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: 'select',
|
||||
options: ['default', 'outline'],
|
||||
},
|
||||
size: {
|
||||
control: 'select',
|
||||
options: ['default', 'sm', 'lg'],
|
||||
},
|
||||
disabled: { control: 'boolean' },
|
||||
},
|
||||
} satisfies Meta<typeof Toggle>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
children: <Bold className="size-4" />,
|
||||
'aria-label': 'Toggle bold',
|
||||
},
|
||||
}
|
||||
|
||||
export const Outline: Story = {
|
||||
args: {
|
||||
variant: 'outline',
|
||||
children: <Italic className="size-4" />,
|
||||
'aria-label': 'Toggle italic',
|
||||
},
|
||||
}
|
||||
|
||||
export const Small: Story = {
|
||||
args: {
|
||||
size: 'sm',
|
||||
children: <Bold className="size-4" />,
|
||||
'aria-label': 'Toggle bold',
|
||||
},
|
||||
}
|
||||
|
||||
export const Large: Story = {
|
||||
args: {
|
||||
size: 'lg',
|
||||
children: <Bold className="size-4" />,
|
||||
'aria-label': 'Toggle bold',
|
||||
},
|
||||
}
|
||||
|
||||
export const WithText: Story = {
|
||||
args: {
|
||||
children: (
|
||||
<>
|
||||
<Italic className="size-4" />
|
||||
Italic
|
||||
</>
|
||||
),
|
||||
'aria-label': 'Toggle italic',
|
||||
},
|
||||
}
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
disabled: true,
|
||||
children: <Bold className="size-4" />,
|
||||
'aria-label': 'Toggle bold',
|
||||
},
|
||||
}
|
||||
|
||||
export const Pressed: Story = {
|
||||
args: {
|
||||
defaultPressed: true,
|
||||
children: <Bold className="size-4" />,
|
||||
'aria-label': 'Toggle bold',
|
||||
},
|
||||
}
|
||||
|
||||
export const AllVariants: Story = {
|
||||
render: () => (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="w-20 text-sm text-muted-foreground">default</span>
|
||||
<Toggle size="sm" aria-label="Bold"><Bold className="size-4" /></Toggle>
|
||||
<Toggle size="default" aria-label="Italic"><Italic className="size-4" /></Toggle>
|
||||
<Toggle size="lg" aria-label="Underline"><Underline className="size-4" /></Toggle>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="w-20 text-sm text-muted-foreground">outline</span>
|
||||
<Toggle variant="outline" size="sm" aria-label="Bold"><Bold className="size-4" /></Toggle>
|
||||
<Toggle variant="outline" size="default" aria-label="Italic"><Italic className="size-4" /></Toggle>
|
||||
<Toggle variant="outline" size="lg" aria-label="Underline"><Underline className="size-4" /></Toggle>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
Reference in New Issue
Block a user