Files
greyhaven-design-system/stories/Form/Form.stories.tsx
2026-04-13 15:33:00 -05:00

212 lines
5.2 KiB
TypeScript

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 />,
}