mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
add stories
This commit is contained in:
1 parent
92527c209b
commit
30931062bd
40 files changed
+3136
-7
No files matched your search
@@ -14,7 +14,7 @@ const config: any = {
|
||||
'../src/components/Dropdown/Dropdown.stories.tsx',
|
||||
// '../src/components/Form/Form.stories.tsx',
|
||||
|
||||
'../src/components/shadcn/stories/**/*.tsx',
|
||||
'../src/components/shadcn/stories/**/*.stories.tsx',
|
||||
],
|
||||
addons: [
|
||||
'storybook-dark-mode',
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Button } from './../../Button'
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
@@ -9,13 +8,13 @@ import {
|
||||
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
const meta: Meta<typeof Button> = {
|
||||
const meta: Meta<typeof Accordion> = {
|
||||
/* 👇 The title prop is optional.
|
||||
* See https://storybook.js.org/docs/react/configure/overview#configure-story-loading
|
||||
* to learn how to generate automatic titles
|
||||
*/
|
||||
title: 'shadcn/Accordian',
|
||||
component: Button,
|
||||
component: Accordion,
|
||||
}
|
||||
|
||||
export const Neutral = (args: any) => (
|
||||
@@ -41,7 +40,7 @@ export const Neutral = (args: any) => (
|
||||
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof Button>
|
||||
type Story = StoryObj<typeof Accordion>
|
||||
|
||||
/*
|
||||
*👇 Render functions are a framework specific feature to allow you control on how the component renders.
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Alert, AlertDescription, AlertTitle } from '@ui/components/shadcn/ui/alert'
|
||||
import { AlertCircle, Terminal } from 'lucide-react'
|
||||
import { Meta } from '@storybook/react'
|
||||
|
||||
const meta: Meta<typeof Alert> = {
|
||||
title: 'shadcn/Alert',
|
||||
component: Alert,
|
||||
}
|
||||
|
||||
export const Default = () => (
|
||||
<Alert>
|
||||
<Terminal className="h-4 w-4" />
|
||||
<AlertTitle>Heads up!</AlertTitle>
|
||||
<AlertDescription>You can add components to your app using the cli.</AlertDescription>
|
||||
</Alert>
|
||||
)
|
||||
|
||||
Default.storyName = 'Default'
|
||||
|
||||
export const Destructive = () => (
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertTitle>Error</AlertTitle>
|
||||
<AlertDescription>Your session has expired. Please log in again.</AlertDescription>
|
||||
</Alert>
|
||||
)
|
||||
|
||||
Destructive.storyName = 'Destructive'
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,38 @@
|
||||
import React from 'react'
|
||||
import { Meta } from '@storybook/react'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogTrigger,
|
||||
AlertDialogContent,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
} from '@ui/components/shadcn/ui/alert-dialog'
|
||||
|
||||
const meta: Meta<typeof AlertDialog> = {
|
||||
title: 'shadcn/AlertDialog',
|
||||
component: AlertDialog,
|
||||
}
|
||||
|
||||
export const Default = () => (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger>Open Dialog</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Dialog Title</AlertDialogTitle>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogDescription>This is the dialog description.</AlertDialogDescription>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction>OK</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
|
||||
Default.storyName = 'Default'
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,32 @@
|
||||
import React from 'react'
|
||||
import { Meta } from '@storybook/react'
|
||||
import { AspectRatio } from '@ui/components/shadcn/ui/aspect-ratio'
|
||||
|
||||
const meta: Meta<typeof AspectRatio> = {
|
||||
title: 'shadcn/AspectRatio',
|
||||
component: AspectRatio,
|
||||
}
|
||||
|
||||
export const Default = () => (
|
||||
<AspectRatio ratio={16 / 9} className="bg-overlay p-3 border-overlay rounded-lg">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80"
|
||||
alt="Photo by Drew Beamer"
|
||||
className="rounded-md"
|
||||
/>
|
||||
{/*
|
||||
|
||||
You could also use Next/Image in here like this:
|
||||
|
||||
<Image
|
||||
src="https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80"
|
||||
alt="Photo by Drew Beamer"
|
||||
fill
|
||||
className="rounded-md object-cover"
|
||||
/>
|
||||
|
||||
*/}
|
||||
</AspectRatio>
|
||||
)
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from 'react'
|
||||
import { Meta } from '@storybook/react'
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@ui/components/shadcn/ui/avatar'
|
||||
|
||||
const meta: Meta<typeof Avatar> = {
|
||||
title: 'Shadcn/Avatar',
|
||||
component: Avatar,
|
||||
}
|
||||
|
||||
export const Default = () => (
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
||||
<AvatarFallback>CN</AvatarFallback>
|
||||
</Avatar>
|
||||
)
|
||||
|
||||
Default.storyName = 'Default'
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Badge } from '@ui/components/shadcn/ui/badge'
|
||||
import { Meta } from '@storybook/react'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/Badge',
|
||||
component: Badge,
|
||||
}
|
||||
|
||||
export const Default = () => <Badge>Badge</Badge>
|
||||
|
||||
Default.storyName = 'Default'
|
||||
|
||||
export const Secondary = () => <Badge variant="secondary">Secondary</Badge>
|
||||
|
||||
Secondary.storyName = 'Secondary'
|
||||
|
||||
export const Outline = () => <Badge variant="outline">Outline</Badge>
|
||||
|
||||
Outline.storyName = 'Outline'
|
||||
|
||||
export const Destructive = () => <Badge variant="destructive">Destructive</Badge>
|
||||
|
||||
Destructive.storyName = 'Destructive'
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,81 @@
|
||||
import { Button } from '@ui/components/shadcn/ui/button'
|
||||
import { Meta } from '@storybook/react'
|
||||
import { ChevronRight, Loader2, Mail } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'Button',
|
||||
component: Button,
|
||||
}
|
||||
|
||||
export const Default = () => <Button>Button</Button>
|
||||
|
||||
Default.storyName = 'Default'
|
||||
|
||||
export const Secondary = () => <Button variant="secondary">Secondary</Button>
|
||||
|
||||
Secondary.storyName = 'Secondary'
|
||||
|
||||
export const Destructive = () => <Button variant="destructive">Destructive</Button>
|
||||
|
||||
Destructive.storyName = 'Destructive'
|
||||
|
||||
export const Outline = () => <Button variant="outline">Outline</Button>
|
||||
|
||||
Outline.storyName = 'Outline'
|
||||
|
||||
export const Ghost = () => <Button variant="ghost">Ghost</Button>
|
||||
|
||||
Ghost.storyName = 'Ghost'
|
||||
|
||||
export const LinkButton = () => (
|
||||
<Link href="/">
|
||||
<Button variant="link">Link</Button>
|
||||
</Link>
|
||||
)
|
||||
|
||||
LinkButton.storyName = 'Link'
|
||||
|
||||
export const Icon = () => (
|
||||
<Button>
|
||||
<ChevronRight />
|
||||
</Button>
|
||||
)
|
||||
|
||||
Icon.storyName = 'Icon'
|
||||
|
||||
export const IconOnly = () => (
|
||||
<Button>
|
||||
<Mail className="mr-2 h-4 w-4" /> Login with Email
|
||||
</Button>
|
||||
)
|
||||
|
||||
IconOnly.storyName = 'Icon Only'
|
||||
|
||||
export const Loading = () => (
|
||||
<Button disabled>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Please wait
|
||||
</Button>
|
||||
)
|
||||
|
||||
Loading.storyName = 'Loading'
|
||||
|
||||
export const WithIcon = () => (
|
||||
<Button>
|
||||
<Mail className="mr-2" />
|
||||
Login with Email
|
||||
</Button>
|
||||
)
|
||||
|
||||
WithIcon.storyName = 'With Icon'
|
||||
|
||||
export const AsChild = () => (
|
||||
<Button>
|
||||
<Link href="/">Login</Link>
|
||||
</Button>
|
||||
)
|
||||
|
||||
AsChild.storyName = 'As Child'
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,114 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { Meta } from '@storybook/react'
|
||||
import { Button } from '@ui/components/shadcn/ui/button'
|
||||
import { Calendar } from '@ui/components/shadcn/ui/calendar'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@ui/components/shadcn/ui/form'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@ui/components/shadcn/ui/popover'
|
||||
import { toast } from '@ui/components/shadcn/ui/use-toast'
|
||||
import { cn } from '@ui/lib/utils'
|
||||
import { format } from 'date-fns'
|
||||
import { CalendarIcon } from 'lucide-react'
|
||||
import * as React from 'react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import * as z from 'zod'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/Calendar',
|
||||
component: Calendar,
|
||||
}
|
||||
|
||||
export const Default = () => {
|
||||
const [date, setDate] = React.useState<Date | undefined>(new Date())
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center">
|
||||
<Calendar mode="single" selected={date} onSelect={setDate} className="rounded-md border" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// export const withForm = () => {
|
||||
// const FormSchema = z.object({
|
||||
// dob: z.date({
|
||||
// required_error: 'A date of birth is required.',
|
||||
// }),
|
||||
// })
|
||||
|
||||
// const form = useForm<z.infer<typeof FormSchema>>({
|
||||
// resolver: zodResolver(FormSchema),
|
||||
// })
|
||||
|
||||
// function onSubmit(data: z.infer<typeof FormSchema>) {
|
||||
// toast({
|
||||
// title: 'You submitted the following values:',
|
||||
// description: (
|
||||
// <pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
|
||||
// <code className="text-white">{JSON.stringify(data, null, 2)}</code>
|
||||
// </pre>
|
||||
// ),
|
||||
// })
|
||||
// }
|
||||
|
||||
// return (
|
||||
// <Form {...form}>
|
||||
// <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="dob"
|
||||
// render={({ field }) => (
|
||||
// <FormItem className="flex flex-col">
|
||||
// <FormLabel>Date of birth</FormLabel>
|
||||
// <Popover>
|
||||
// <PopoverTrigger asChild>
|
||||
// <FormControl>
|
||||
// <Button
|
||||
// variant={'outline'}
|
||||
// className={cn(
|
||||
// 'w-[240px] pl-3 text-left font-normal',
|
||||
// !field.value && 'text-muted-foreground'
|
||||
// )}
|
||||
// >
|
||||
// {field.value ? format(field.value, 'PPP') : <span>Pick a date</span>}
|
||||
// <CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
||||
// </Button>
|
||||
// </FormControl>
|
||||
// </PopoverTrigger>
|
||||
// <PopoverContent className="w-auto p-0" align="start">
|
||||
// <Calendar
|
||||
// mode="single"
|
||||
// selected={field.value}
|
||||
// onSelect={(e) => {
|
||||
// // @mildtomato - guard in case returns undefined
|
||||
// if (e) {
|
||||
// field.onChange(e)
|
||||
// }
|
||||
// }}
|
||||
// disabled={(date) => date > new Date() || date < new Date('1900-01-01')}
|
||||
// initialFocus
|
||||
// />
|
||||
// </PopoverContent>
|
||||
// </Popover>
|
||||
// <FormDescription>Your date of birth is used to calculate your age.</FormDescription>
|
||||
// <FormMessage />
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
// <Button type="submit" variant={'secondary'}>
|
||||
// Submit
|
||||
// </Button>
|
||||
// </form>
|
||||
// </Form>
|
||||
// )
|
||||
// }
|
||||
|
||||
// withForm.storyName = 'With Form'
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,145 @@
|
||||
import { Meta } from '@storybook/react'
|
||||
import { Button } from '@ui/components/shadcn/ui/button'
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@ui/components/shadcn/ui/card'
|
||||
import { Input } from '@ui/components/shadcn/ui/input'
|
||||
import { Label } from '@ui/components/shadcn/ui/label'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@ui/components/shadcn/ui/select'
|
||||
import { Switch } from '@ui/components/shadcn/ui/switch'
|
||||
import { cn } from '@ui/lib/utils'
|
||||
import { BellRing, Check } from 'lucide-react'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/Card',
|
||||
component: Card,
|
||||
}
|
||||
|
||||
export const Default = () => {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Card Title</CardTitle>
|
||||
<CardDescription>Card Description</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p>Card Content</p>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<p>Card Footer</p>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export const CardWithForm = () => {
|
||||
return (
|
||||
<Card className="w-[350px]">
|
||||
<CardHeader>
|
||||
<CardTitle>Create project</CardTitle>
|
||||
<CardDescription>Deploy your new project in one-click.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form>
|
||||
<div className="grid w-full items-center gap-4">
|
||||
<div className="flex flex-col space-y-1.5">
|
||||
<Label htmlFor="name">Name</Label>
|
||||
<Input id="name" placeholder="Name of your project" />
|
||||
</div>
|
||||
<div className="flex flex-col space-y-1.5">
|
||||
<Label htmlFor="name">Framework</Label>
|
||||
<Select>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select" />
|
||||
<SelectContent position="popper">
|
||||
<SelectItem value="next">Next.js</SelectItem>
|
||||
<SelectItem value="sveltekit">SvelteKit</SelectItem>
|
||||
<SelectItem value="astro">Astro</SelectItem>
|
||||
<SelectItem value="nuxt">Nuxt.js</SelectItem>
|
||||
</SelectContent>
|
||||
</SelectTrigger>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
<CardFooter className="flex justify-between">
|
||||
<Button variant="outline">Cancel</Button>
|
||||
<Button>Deploy</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
type CardProps = React.ComponentProps<typeof Card>
|
||||
|
||||
export const CardDemo = ({ className, ...props }: CardProps) => {
|
||||
const notifications = [
|
||||
{
|
||||
title: 'Your call has been confirmed.',
|
||||
description: '1 hour ago',
|
||||
},
|
||||
{
|
||||
title: 'You have a new message!',
|
||||
description: '1 hour ago',
|
||||
},
|
||||
{
|
||||
title: 'Your subscription is expiring soon!',
|
||||
description: '2 hours ago',
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<Card className={cn('w-[380px]', className)} {...props}>
|
||||
<CardHeader>
|
||||
<CardTitle>Notifications</CardTitle>
|
||||
<CardDescription>You have 3 unread messages.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-4">
|
||||
<div className=" flex items-center space-x-4 rounded-md border p-4">
|
||||
<BellRing />
|
||||
<div className="flex-1 space-y-1">
|
||||
<p className="text-sm font-medium leading-none">Push Notifications</p>
|
||||
<p className="text-sm text-muted-foreground">Send notifications to device.</p>
|
||||
</div>
|
||||
<Switch />
|
||||
</div>
|
||||
<div>
|
||||
{notifications.map((notification, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="mb-4 grid grid-cols-[25px_1fr] items-start pb-4 last:mb-0 last:pb-0"
|
||||
>
|
||||
<span className="flex h-2 w-2 translate-y-1 rounded-full bg-sky-500" />
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium leading-none">{notification.title}</p>
|
||||
<p className="text-sm text-muted-foreground">{notification.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button className="w-full">
|
||||
<Check className="mr-2 h-4 w-4" /> Mark all as read
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
CardWithForm.storyName = 'Card with form'
|
||||
CardDemo.storyName = 'Card demo'
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,233 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { Meta } from '@storybook/react'
|
||||
import { Button } from '@ui/components/shadcn/ui/button'
|
||||
import { Checkbox } from '@ui/components/shadcn/ui/checkbox'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@ui/components/shadcn/ui/form'
|
||||
import { toast } from '@ui/components/shadcn/ui/use-toast'
|
||||
import Link from 'next/link'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import * as z from 'zod'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/Checkbox',
|
||||
component: Checkbox,
|
||||
}
|
||||
|
||||
export const Default = () => {
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox id="terms" />
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Accept terms and conditions
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function CheckboxWithText() {
|
||||
return (
|
||||
<div className="items-top flex space-x-2">
|
||||
<Checkbox id="terms1" />
|
||||
<div className="grid gap-1.5 leading-none">
|
||||
<label
|
||||
htmlFor="terms1"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Accept terms and conditions
|
||||
</label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
You agree to our Terms of Service and Privacy Policy.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function CheckboxDisabled() {
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox id="terms2" disabled />
|
||||
<label
|
||||
htmlFor="terms2"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Accept terms and conditions
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function CheckboxReactHookFormSingle() {
|
||||
const FormSchema = z.object({
|
||||
mobile: z.boolean().default(false).optional(),
|
||||
})
|
||||
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: {
|
||||
mobile: true,
|
||||
},
|
||||
})
|
||||
|
||||
function onSubmit(data: z.infer<typeof FormSchema>) {
|
||||
toast({
|
||||
title: 'You submitted the following values:',
|
||||
description: (
|
||||
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
|
||||
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
|
||||
</pre>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="mobile"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value}
|
||||
onCheckedChange={(e) => {
|
||||
if (e) {
|
||||
field.onChange
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="space-y-1 leading-none">
|
||||
<FormLabel>Use different settings for my mobile devices</FormLabel>
|
||||
<FormDescription>
|
||||
You can manage your mobile notifications in the{' '}
|
||||
<Link href="/examples/forms">mobile settings</Link> page.
|
||||
</FormDescription>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit">Submit</Button>
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
export function CheckboxReactHookFormMultiple() {
|
||||
const FormSchema = z.object({
|
||||
items: z.array(z.string()).refine((value) => value.some((item) => item), {
|
||||
message: 'You have to select at least one item.',
|
||||
}),
|
||||
})
|
||||
const items = [
|
||||
{
|
||||
id: 'recents',
|
||||
label: 'Recents',
|
||||
},
|
||||
{
|
||||
id: 'home',
|
||||
label: 'Home',
|
||||
},
|
||||
{
|
||||
id: 'applications',
|
||||
label: 'Applications',
|
||||
},
|
||||
{
|
||||
id: 'desktop',
|
||||
label: 'Desktop',
|
||||
},
|
||||
{
|
||||
id: 'downloads',
|
||||
label: 'Downloads',
|
||||
},
|
||||
{
|
||||
id: 'documents',
|
||||
label: 'Documents',
|
||||
},
|
||||
] as const
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: {
|
||||
items: ['recents', 'home'],
|
||||
},
|
||||
})
|
||||
|
||||
function onSubmit(data: z.infer<typeof FormSchema>) {
|
||||
toast({
|
||||
title: 'You submitted the following values:',
|
||||
description: (
|
||||
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
|
||||
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
|
||||
</pre>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="items"
|
||||
render={() => (
|
||||
<FormItem>
|
||||
<div className="mb-4">
|
||||
<FormLabel className="text-base">Sidebar</FormLabel>
|
||||
<FormDescription>
|
||||
Select the items you want to display in the sidebar.
|
||||
</FormDescription>
|
||||
</div>
|
||||
{items.map((item) => (
|
||||
<FormField
|
||||
key={item.id}
|
||||
control={form.control}
|
||||
name="items"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem
|
||||
key={item.id}
|
||||
className="flex flex-row items-start space-x-3 space-y-0"
|
||||
>
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value?.includes(item.id)}
|
||||
onCheckedChange={(checked) => {
|
||||
return checked
|
||||
? field.onChange([...field.value, item.id])
|
||||
: field.onChange(field.value?.filter((value) => value !== item.id))
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">{item.label}</FormLabel>
|
||||
</FormItem>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit">Submit</Button>
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
CheckboxWithText.storyName = 'Checkbox with text'
|
||||
CheckboxDisabled.storyName = 'Checkbox disabled'
|
||||
CheckboxReactHookFormSingle.storyName = 'Checkbox react hook form single'
|
||||
CheckboxReactHookFormMultiple.storyName = ' Checkbox react hook form multiple'
|
||||
export default meta
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Meta } from '@storybook/react'
|
||||
import { Button } from '@ui/components/shadcn/ui/button'
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from '@ui/components/shadcn/ui/collapsible'
|
||||
import { ChevronsUpDown } from 'lucide-react'
|
||||
import * as React from 'react'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/Collapsible',
|
||||
component: Collapsible,
|
||||
}
|
||||
|
||||
export const Default = () => {
|
||||
const [isOpen, setIsOpen] = React.useState(false)
|
||||
|
||||
return (
|
||||
<Collapsible open={isOpen} onOpenChange={setIsOpen} className="w-[350px] space-y-2">
|
||||
<div className="flex items-center justify-between space-x-4 px-4">
|
||||
<h4 className="text-sm font-semibold">@peduarte starred 3 repositories</h4>
|
||||
<CollapsibleTrigger asChild>
|
||||
<Button variant="ghost" size="sm" className="w-9 p-0">
|
||||
<ChevronsUpDown className="h-4 w-4" />
|
||||
<span className="sr-only">Toggle</span>
|
||||
</Button>
|
||||
</CollapsibleTrigger>
|
||||
</div>
|
||||
<div className="rounded-md border px-4 py-3 font-mono text-sm">@radix-ui/primitives</div>
|
||||
<CollapsibleContent className="space-y-2">
|
||||
<div className="rounded-md border px-4 py-3 font-mono text-sm">@radix-ui/colors</div>
|
||||
<div className="rounded-md border px-4 py-3 font-mono text-sm">@stitches/react</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
)
|
||||
}
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,130 @@
|
||||
import { Meta } from '@storybook/react'
|
||||
import {
|
||||
Command,
|
||||
CommandDialog,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
CommandSeparator,
|
||||
CommandShortcut,
|
||||
} from '@ui/components/shadcn/ui/command'
|
||||
import { Calculator, Calendar, CreditCard, Settings, Smile, User } from 'lucide-react'
|
||||
import * as React from 'react'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/Command',
|
||||
component: Command,
|
||||
}
|
||||
|
||||
export function Default() {
|
||||
return (
|
||||
<Command className="rounded-lg border shadow-md">
|
||||
<CommandInput placeholder="Type a command or search..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
<CommandGroup heading="Suggestions">
|
||||
<CommandItem>
|
||||
<Calendar className="mr-2 h-4 w-4" />
|
||||
<span>Calendar</span>
|
||||
</CommandItem>
|
||||
<CommandItem>
|
||||
<Smile className="mr-2 h-4 w-4" />
|
||||
<span>Search Emoji</span>
|
||||
</CommandItem>
|
||||
<CommandItem>
|
||||
<Calculator className="mr-2 h-4 w-4" />
|
||||
<span>Calculator</span>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
<CommandSeparator />
|
||||
<CommandGroup heading="Settings">
|
||||
<CommandItem>
|
||||
<User className="mr-2 h-4 w-4" />
|
||||
<span>Profile</span>
|
||||
<CommandShortcut>⌘P</CommandShortcut>
|
||||
</CommandItem>
|
||||
<CommandItem>
|
||||
<CreditCard className="mr-2 h-4 w-4" />
|
||||
<span>Billing</span>
|
||||
<CommandShortcut>⌘B</CommandShortcut>
|
||||
</CommandItem>
|
||||
<CommandItem>
|
||||
<Settings className="mr-2 h-4 w-4" />
|
||||
<span>Settings</span>
|
||||
<CommandShortcut>⌘S</CommandShortcut>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
)
|
||||
}
|
||||
|
||||
export function CommandDialogDemo() {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
const down = (e: KeyboardEvent) => {
|
||||
if (e.key === 'k' && e.metaKey) {
|
||||
setOpen((open) => !open)
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', down)
|
||||
return () => document.removeEventListener('keydown', down)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Press{' '}
|
||||
<kbd className="pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground opacity-100">
|
||||
<span className="text-xs">⌘</span>K
|
||||
</kbd>
|
||||
</p>
|
||||
<CommandDialog open={open} onOpenChange={setOpen}>
|
||||
<CommandInput placeholder="Type a command or search..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
<CommandGroup heading="Suggestions">
|
||||
<CommandItem>
|
||||
<Calendar className="mr-2 h-4 w-4" />
|
||||
<span>Calendar</span>
|
||||
</CommandItem>
|
||||
<CommandItem>
|
||||
<Smile className="mr-2 h-4 w-4" />
|
||||
<span>Search Emoji</span>
|
||||
</CommandItem>
|
||||
<CommandItem>
|
||||
<Calculator className="mr-2 h-4 w-4" />
|
||||
<span>Calculator</span>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
<CommandSeparator />
|
||||
<CommandGroup heading="Settings">
|
||||
<CommandItem>
|
||||
<User className="mr-2 h-4 w-4" />
|
||||
<span>Profile</span>
|
||||
<CommandShortcut>⌘P</CommandShortcut>
|
||||
</CommandItem>
|
||||
<CommandItem>
|
||||
<CreditCard className="mr-2 h-4 w-4" />
|
||||
<span>Billing</span>
|
||||
<CommandShortcut>⌘B</CommandShortcut>
|
||||
</CommandItem>
|
||||
<CommandItem>
|
||||
<Settings className="mr-2 h-4 w-4" />
|
||||
<span>Settings</span>
|
||||
<CommandShortcut>⌘S</CommandShortcut>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</CommandDialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
CommandDialogDemo.storyName = 'Command in dialog'
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,73 @@
|
||||
import { Meta } from '@storybook/react'
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuCheckboxItem,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuLabel,
|
||||
ContextMenuRadioGroup,
|
||||
ContextMenuRadioItem,
|
||||
ContextMenuSeparator,
|
||||
ContextMenuShortcut,
|
||||
ContextMenuSub,
|
||||
ContextMenuSubContent,
|
||||
ContextMenuSubTrigger,
|
||||
ContextMenuTrigger,
|
||||
} from '@ui/components/shadcn/ui/context-menu'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/ContextMenu',
|
||||
component: ContextMenu,
|
||||
}
|
||||
|
||||
export const Default = () => {
|
||||
return (
|
||||
<ContextMenu>
|
||||
<ContextMenuTrigger className="flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed text-sm">
|
||||
Right click here
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent className="w-64">
|
||||
<ContextMenuItem inset>
|
||||
Back
|
||||
<ContextMenuShortcut>⌘[</ContextMenuShortcut>
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem inset disabled>
|
||||
Forward
|
||||
<ContextMenuShortcut>⌘]</ContextMenuShortcut>
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem inset>
|
||||
Reload
|
||||
<ContextMenuShortcut>⌘R</ContextMenuShortcut>
|
||||
</ContextMenuItem>
|
||||
<ContextMenuSub>
|
||||
<ContextMenuSubTrigger inset>More Tools</ContextMenuSubTrigger>
|
||||
<ContextMenuSubContent className="w-48">
|
||||
<ContextMenuItem>
|
||||
Save Page As...
|
||||
<ContextMenuShortcut>⇧⌘S</ContextMenuShortcut>
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem>Create Shortcut...</ContextMenuItem>
|
||||
<ContextMenuItem>Name Window...</ContextMenuItem>
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuItem>Developer Tools</ContextMenuItem>
|
||||
</ContextMenuSubContent>
|
||||
</ContextMenuSub>
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuCheckboxItem checked>
|
||||
Show Bookmarks Bar
|
||||
<ContextMenuShortcut>⌘⇧B</ContextMenuShortcut>
|
||||
</ContextMenuCheckboxItem>
|
||||
<ContextMenuCheckboxItem>Show Full URLs</ContextMenuCheckboxItem>
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuRadioGroup value="pedro">
|
||||
<ContextMenuLabel inset>People</ContextMenuLabel>
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuRadioItem value="pedro">Pedro Duarte</ContextMenuRadioItem>
|
||||
<ContextMenuRadioItem value="colm">Colm Tuite</ContextMenuRadioItem>
|
||||
</ContextMenuRadioGroup>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
)
|
||||
}
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,311 @@
|
||||
import { Meta } from '@storybook/react'
|
||||
import { Card } from '@ui/components/shadcn/ui/card'
|
||||
;('use client')
|
||||
import * as React from 'react'
|
||||
import {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
SortingState,
|
||||
VisibilityState,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFilteredRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
useReactTable,
|
||||
} from '@tanstack/react-table'
|
||||
import { ArrowUpDown, ChevronDown, MoreHorizontal } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table'
|
||||
|
||||
const data: Payment[] = [
|
||||
{
|
||||
id: 'm5gr84i9',
|
||||
amount: 316,
|
||||
status: 'success',
|
||||
email: 'ken99@yahoo.com',
|
||||
},
|
||||
{
|
||||
id: '3u1reuv4',
|
||||
amount: 242,
|
||||
status: 'success',
|
||||
email: 'Abe45@gmail.com',
|
||||
},
|
||||
{
|
||||
id: 'derv1ws0',
|
||||
amount: 837,
|
||||
status: 'processing',
|
||||
email: 'Monserrat44@gmail.com',
|
||||
},
|
||||
{
|
||||
id: '5kma53ae',
|
||||
amount: 874,
|
||||
status: 'success',
|
||||
email: 'Silas22@gmail.com',
|
||||
},
|
||||
{
|
||||
id: 'bhqecj4p',
|
||||
amount: 721,
|
||||
status: 'failed',
|
||||
email: 'carmella@hotmail.com',
|
||||
},
|
||||
]
|
||||
|
||||
export type Payment = {
|
||||
id: string
|
||||
amount: number
|
||||
status: 'pending' | 'processing' | 'success' | 'failed'
|
||||
email: string
|
||||
}
|
||||
|
||||
export const columns: ColumnDef<Payment>[] = [
|
||||
{
|
||||
id: 'select',
|
||||
header: ({ table }) => (
|
||||
<Checkbox
|
||||
checked={table.getIsAllPageRowsSelected()}
|
||||
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||
aria-label="Select all"
|
||||
/>
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||
aria-label="Select row"
|
||||
/>
|
||||
),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
},
|
||||
{
|
||||
accessorKey: 'status',
|
||||
header: 'Status',
|
||||
cell: ({ row }) => <div className="capitalize">{row.getValue('status')}</div>,
|
||||
},
|
||||
{
|
||||
accessorKey: 'email',
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||
>
|
||||
Email
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
)
|
||||
},
|
||||
cell: ({ row }) => <div className="lowercase">{row.getValue('email')}</div>,
|
||||
},
|
||||
{
|
||||
accessorKey: 'amount',
|
||||
header: () => <div className="text-right">Amount</div>,
|
||||
cell: ({ row }) => {
|
||||
const amount = parseFloat(row.getValue('amount'))
|
||||
|
||||
// Format the amount as a dollar amount
|
||||
const formatted = new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
}).format(amount)
|
||||
|
||||
return <div className="text-right font-medium">{formatted}</div>
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const payment = row.original
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="h-8 w-8 p-0">
|
||||
<span className="sr-only">Open menu</span>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<DropdownMenuItem onClick={() => navigator.clipboard.writeText(payment.id)}>
|
||||
Copy payment ID
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>View customer</DropdownMenuItem>
|
||||
<DropdownMenuItem>View payment details</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export function DataTableDemo() {
|
||||
const [sorting, setSorting] = React.useState<SortingState>([])
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([])
|
||||
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({})
|
||||
const [rowSelection, setRowSelection] = React.useState({})
|
||||
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns,
|
||||
onSortingChange: setSorting,
|
||||
onColumnFiltersChange: setColumnFilters,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
onColumnVisibilityChange: setColumnVisibility,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
state: {
|
||||
sorting,
|
||||
columnFilters,
|
||||
columnVisibility,
|
||||
rowSelection,
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="flex items-center py-4">
|
||||
<Input
|
||||
placeholder="Filter emails..."
|
||||
value={(table.getColumn('email')?.getFilterValue() as string) ?? ''}
|
||||
onChange={(event) => table.getColumn('email')?.setFilterValue(event.target.value)}
|
||||
className="max-w-sm"
|
||||
/>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="ml-auto">
|
||||
Columns <ChevronDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{table
|
||||
.getAllColumns()
|
||||
.filter((column) => column.getCanHide())
|
||||
.map((column) => {
|
||||
return (
|
||||
<DropdownMenuCheckboxItem
|
||||
key={column.id}
|
||||
className="capitalize"
|
||||
checked={column.getIsVisible()}
|
||||
onCheckedChange={(value) => column.toggleVisibility(!!value)}
|
||||
>
|
||||
{column.id}
|
||||
</DropdownMenuCheckboxItem>
|
||||
)
|
||||
})}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => {
|
||||
return (
|
||||
<TableHead key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(header.column.columnDef.header, header.getContext())}
|
||||
</TableHead>
|
||||
)
|
||||
})}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<div className="flex items-center justify-end space-x-2 py-4">
|
||||
<div className="flex-1 text-sm text-muted-foreground">
|
||||
{table.getFilteredSelectedRowModel().rows.length} of{' '}
|
||||
{table.getFilteredRowModel().rows.length} row(s) selected.
|
||||
</div>
|
||||
<div className="space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => table.previousPage()}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
Previous
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => table.nextPage()}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/Card',
|
||||
component: Card,
|
||||
}
|
||||
|
||||
export const Default = () => {
|
||||
return (
|
||||
<div>
|
||||
<div></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const CardWithForm = () => {
|
||||
return (
|
||||
<div>
|
||||
<div></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
CardWithForm.storyName = 'Card with form'
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,235 @@
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@ui/components/shadcn/ui/form'
|
||||
import { toast } from '@ui/components/shadcn/ui/use-toast'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { Meta } from '@storybook/react'
|
||||
import { Button } from '@ui/components/shadcn/ui/button'
|
||||
import { Calendar } from '@ui/components/shadcn/ui/calendar'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@ui/components/shadcn/ui/popover'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@ui/components/shadcn/ui/select'
|
||||
import { cn } from '@ui/lib/utils'
|
||||
import { addDays, format } from 'date-fns'
|
||||
import { Calendar as CalendarIcon } from 'lucide-react'
|
||||
import * as React from 'react'
|
||||
import { DateRange } from 'react-day-picker'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import * as z from 'zod'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/DatePicker',
|
||||
// component: Card,
|
||||
}
|
||||
|
||||
export function Default() {
|
||||
const [date, setDate] = React.useState<Date>()
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant={'outline'}
|
||||
className={cn(
|
||||
'w-[280px] justify-start text-left font-normal',
|
||||
!date && 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
<CalendarIcon className="mr-2 h-4 w-4" />
|
||||
{date ? format(date, 'PPP') : <span>Pick a date</span>}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0">
|
||||
<Calendar mode="single" selected={date} onSelect={setDate} initialFocus />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
export function DatePickerWithRange({ className }: React.HTMLAttributes<HTMLDivElement>) {
|
||||
const [date, setDate] = React.useState<DateRange | undefined>({
|
||||
from: new Date(2022, 0, 20),
|
||||
to: addDays(new Date(2022, 0, 20), 20),
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={cn('grid gap-2', className)}>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
id="date"
|
||||
variant={'outline'}
|
||||
className={cn(
|
||||
'w-[300px] justify-start text-left font-normal',
|
||||
!date && 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
<CalendarIcon className="mr-2 h-4 w-4" />
|
||||
{date?.from ? (
|
||||
date.to ? (
|
||||
<>
|
||||
{format(date.from, 'LLL dd, y')} - {format(date.to, 'LLL dd, y')}
|
||||
</>
|
||||
) : (
|
||||
format(date.from, 'LLL dd, y')
|
||||
)
|
||||
) : (
|
||||
<span>Pick a date</span>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
initialFocus
|
||||
mode="range"
|
||||
defaultMonth={date?.from}
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
numberOfMonths={2}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 'use client'
|
||||
|
||||
// import * as React from 'react'
|
||||
// import { addDays, format } from 'date-fns'
|
||||
// import { Calendar as CalendarIcon } from 'lucide-react'
|
||||
|
||||
// import { cn } from '@/lib/utils'
|
||||
// import { Button } from '@/components/ui/button'
|
||||
// import { Calendar } from '@/components/ui/calendar'
|
||||
// import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
// import {
|
||||
// Select,
|
||||
// SelectContent,
|
||||
// SelectItem,
|
||||
// SelectTrigger,
|
||||
// SelectValue,
|
||||
// } from '@/components/ui/select'
|
||||
|
||||
export function DatePickerWithPresets() {
|
||||
const [date, setDate] = React.useState<Date>()
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant={'outline'}
|
||||
className={cn(
|
||||
'w-[280px] justify-start text-left font-normal',
|
||||
!date && 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
<CalendarIcon className="mr-2 h-4 w-4" />
|
||||
{date ? format(date, 'PPP') : <span>Pick a date</span>}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="flex w-auto flex-col space-y-2 p-2">
|
||||
<Select onValueChange={(value) => setDate(addDays(new Date(), parseInt(value)))}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select" />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="popper">
|
||||
<SelectItem value="0">Today</SelectItem>
|
||||
<SelectItem value="1">Tomorrow</SelectItem>
|
||||
<SelectItem value="3">In 3 days</SelectItem>
|
||||
<SelectItem value="7">In a week</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="rounded-md border">
|
||||
<Calendar mode="single" selected={date} onSelect={setDate} />
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
const FormSchema = z.object({
|
||||
dob: z.date({
|
||||
required_error: 'A date of birth is required.',
|
||||
}),
|
||||
})
|
||||
|
||||
export function DatePickerForm() {
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
})
|
||||
|
||||
function onSubmit(data: z.infer<typeof FormSchema>) {
|
||||
toast({
|
||||
title: 'You submitted the following values:',
|
||||
description: (
|
||||
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
|
||||
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
|
||||
</pre>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="dob"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col">
|
||||
<FormLabel>Date of birth</FormLabel>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
variant={'outline'}
|
||||
className={cn(
|
||||
'w-[240px] pl-3 text-left font-normal',
|
||||
!field.value && 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
{field.value ? format(field.value, 'PPP') : <span>Pick a date</span>}
|
||||
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={field.value}
|
||||
onSelect={(e) => {
|
||||
if (e) {
|
||||
field.onChange(e)
|
||||
}
|
||||
}}
|
||||
disabled={(date) => date > new Date() || date < new Date('1900-01-01')}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<FormDescription>Your date of birth is used to calculate your age.</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit">Submit</Button>
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
// CardWithForm.storyName = 'Card with form'
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,55 @@
|
||||
import { Meta } from '@storybook/react'
|
||||
import { Button } from '@ui/components/shadcn/ui/button'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@ui/components/shadcn/ui/dialog'
|
||||
import { Input } from '@ui/components/shadcn/ui/input'
|
||||
import { Label } from '@ui/components/shadcn/ui/label'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/Dialog',
|
||||
component: Dialog,
|
||||
}
|
||||
|
||||
export function Default() {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline">Edit Profile</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit profile</DialogTitle>
|
||||
<DialogDescription>
|
||||
Make changes to your profile here. Click save when you're done.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="name" className="text-right">
|
||||
Name
|
||||
</Label>
|
||||
<Input id="name" value="Pedro Duarte" className="col-span-3" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="username" className="text-right">
|
||||
Username
|
||||
</Label>
|
||||
<Input id="username" value="@peduarte" className="col-span-3" />
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="submit">Save changes</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
export default meta
|
||||
@@ -29,7 +29,7 @@ const meta: Meta<typeof Form> = {
|
||||
* See https://storybook.js.org/docs/react/configure/overview#configure-story-loading
|
||||
* to learn how to generate automatic titles
|
||||
*/
|
||||
title: 'Shadcn/Form',
|
||||
title: 'shadcn/Form',
|
||||
component: Form,
|
||||
}
|
||||
|
||||
@@ -173,7 +173,12 @@ export const CalendarExample = {
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={field.value}
|
||||
onSelect={field.onChange}
|
||||
onSelect={(e) => {
|
||||
if (e) {
|
||||
// @mildtomato: guard in case returns undefined
|
||||
field.onChange
|
||||
}
|
||||
}}
|
||||
disabled={(date) => date > new Date() || date < new Date('1900-01-01')}
|
||||
initialFocus
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Meta } from '@storybook/react'
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@ui/components/shadcn/ui/avatar'
|
||||
import { Button } from '@ui/components/shadcn/ui/button'
|
||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@ui/components/shadcn/ui/hover-card'
|
||||
import { CalendarDays } from 'lucide-react'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/HoverCard',
|
||||
component: HoverCard,
|
||||
}
|
||||
|
||||
export function Default() {
|
||||
return (
|
||||
<HoverCard>
|
||||
<HoverCardTrigger asChild>
|
||||
<Button variant="link">@nextjs</Button>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent className="w-80">
|
||||
<div className="flex justify-between space-x-4">
|
||||
<Avatar>
|
||||
<AvatarImage src="https://github.com/vercel.png" />
|
||||
<AvatarFallback>VC</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="space-y-1">
|
||||
<h4 className="text-sm font-semibold">@nextjs</h4>
|
||||
<p className="text-sm">The React Framework – created and maintained by @vercel.</p>
|
||||
<div className="flex items-center pt-2">
|
||||
<CalendarDays className="mr-2 h-4 w-4 opacity-70" />{' '}
|
||||
<span className="text-xs text-muted-foreground">Joined December 2021</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
)
|
||||
}
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,103 @@
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@ui/components/shadcn/ui/form'
|
||||
import { toast } from '@ui/components/shadcn/ui/use-toast'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { Meta } from '@storybook/react'
|
||||
import { Button } from '@ui/components/shadcn/ui/button'
|
||||
import { Input } from '@ui/components/shadcn/ui/input'
|
||||
import { Label } from '@ui/components/shadcn/ui/label'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import * as z from 'zod'
|
||||
|
||||
const FormSchema = z.object({
|
||||
username: z.string().min(2, {
|
||||
message: 'Username must be at least 2 characters.',
|
||||
}),
|
||||
})
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/Input',
|
||||
component: Input,
|
||||
}
|
||||
|
||||
export function Default() {
|
||||
return <Input type="email" placeholder="Email" />
|
||||
}
|
||||
|
||||
export function InputFile() {
|
||||
return (
|
||||
<div className="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Label htmlFor="picture">Picture</Label>
|
||||
<Input id="picture" type="file" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function InputDisabled() {
|
||||
return <Input disabled type="email" placeholder="Email" />
|
||||
}
|
||||
|
||||
export function InputWithLabel() {
|
||||
return (
|
||||
<div className="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input type="email" id="email" placeholder="Email" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function InputWithButton() {
|
||||
return (
|
||||
<div className="flex w-full max-w-sm items-center space-x-2">
|
||||
<Input type="email" placeholder="Email" />
|
||||
<Button type="submit">Subscribe</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function InputForm() {
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
})
|
||||
|
||||
function onSubmit(data: z.infer<typeof FormSchema>) {
|
||||
toast({
|
||||
title: 'You submitted the following values:',
|
||||
description: (
|
||||
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
|
||||
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
|
||||
</pre>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="w-2/3 space-y-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="username"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="shadcn" {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>This is your public display name.</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit">Submit</Button>
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
export default meta
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Meta } from '@storybook/react'
|
||||
|
||||
import { Checkbox } from '@ui/components/shadcn/ui/checkbox'
|
||||
import { Label } from '@ui/components/shadcn/ui/label'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/Label',
|
||||
component: Label,
|
||||
}
|
||||
|
||||
export function Default() {
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox id="terms" />
|
||||
<Label htmlFor="terms">Accept terms and conditions</Label>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,114 @@
|
||||
import { Meta } from '@storybook/react'
|
||||
import { Card } from '@ui/components/shadcn/ui/card'
|
||||
import {
|
||||
Menubar,
|
||||
MenubarCheckboxItem,
|
||||
MenubarContent,
|
||||
MenubarItem,
|
||||
MenubarMenu,
|
||||
MenubarRadioGroup,
|
||||
MenubarRadioItem,
|
||||
MenubarSeparator,
|
||||
MenubarShortcut,
|
||||
MenubarSub,
|
||||
MenubarSubContent,
|
||||
MenubarSubTrigger,
|
||||
MenubarTrigger,
|
||||
} from '@ui/components/shadcn/ui/menubar'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/Menubar',
|
||||
component: Menubar,
|
||||
}
|
||||
|
||||
export function Default() {
|
||||
return (
|
||||
<Menubar>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>File</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarItem>
|
||||
New Tab <MenubarShortcut>⌘T</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarItem>
|
||||
New Window <MenubarShortcut>⌘N</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarItem disabled>New Incognito Window</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarSub>
|
||||
<MenubarSubTrigger>Share</MenubarSubTrigger>
|
||||
<MenubarSubContent>
|
||||
<MenubarItem>Email link</MenubarItem>
|
||||
<MenubarItem>Messages</MenubarItem>
|
||||
<MenubarItem>Notes</MenubarItem>
|
||||
</MenubarSubContent>
|
||||
</MenubarSub>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem>
|
||||
Print... <MenubarShortcut>⌘P</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>Edit</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarItem>
|
||||
Undo <MenubarShortcut>⌘Z</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarItem>
|
||||
Redo <MenubarShortcut>⇧⌘Z</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarSub>
|
||||
<MenubarSubTrigger>Find</MenubarSubTrigger>
|
||||
<MenubarSubContent>
|
||||
<MenubarItem>Search the web</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem>Find...</MenubarItem>
|
||||
<MenubarItem>Find Next</MenubarItem>
|
||||
<MenubarItem>Find Previous</MenubarItem>
|
||||
</MenubarSubContent>
|
||||
</MenubarSub>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem>Cut</MenubarItem>
|
||||
<MenubarItem>Copy</MenubarItem>
|
||||
<MenubarItem>Paste</MenubarItem>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>View</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarCheckboxItem>Always Show Bookmarks Bar</MenubarCheckboxItem>
|
||||
<MenubarCheckboxItem checked>Always Show Full URLs</MenubarCheckboxItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>
|
||||
Reload <MenubarShortcut>⌘R</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarItem disabled inset>
|
||||
Force Reload <MenubarShortcut>⇧⌘R</MenubarShortcut>
|
||||
</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>Toggle Fullscreen</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>Hide Sidebar</MenubarItem>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>Profiles</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarRadioGroup value="benoit">
|
||||
<MenubarRadioItem value="andy">Andy</MenubarRadioItem>
|
||||
<MenubarRadioItem value="benoit">Benoit</MenubarRadioItem>
|
||||
<MenubarRadioItem value="Luis">Luis</MenubarRadioItem>
|
||||
</MenubarRadioGroup>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>Edit...</MenubarItem>
|
||||
<MenubarSeparator />
|
||||
<MenubarItem inset>Add Profile...</MenubarItem>
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
</Menubar>
|
||||
)
|
||||
}
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,155 @@
|
||||
import { Meta } from '@storybook/react'
|
||||
import { Card } from '@ui/components/shadcn/ui/card'
|
||||
import * as React from 'react'
|
||||
import Link from 'next/link'
|
||||
import { cn } from '@ui/lib/utils'
|
||||
import {
|
||||
NavigationMenu,
|
||||
NavigationMenuContent,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuList,
|
||||
NavigationMenuTrigger,
|
||||
navigationMenuTriggerStyle,
|
||||
} from '@ui/components/shadcn/ui/navigation-menu'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/NavigationMenu',
|
||||
component: NavigationMenu,
|
||||
}
|
||||
|
||||
export function Default() {
|
||||
const components: { title: string; href: string; description: string }[] = [
|
||||
{
|
||||
title: 'Alert Dialog',
|
||||
href: '/docs/primitives/alert-dialog',
|
||||
description:
|
||||
'A modal dialog that interrupts the user with important content and expects a response.',
|
||||
},
|
||||
{
|
||||
title: 'Hover Card',
|
||||
href: '/docs/primitives/hover-card',
|
||||
description: 'For sighted users to preview content available behind a link.',
|
||||
},
|
||||
{
|
||||
title: 'Progress',
|
||||
href: '/docs/primitives/progress',
|
||||
description:
|
||||
'Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.',
|
||||
},
|
||||
{
|
||||
title: 'Scroll-area',
|
||||
href: '/docs/primitives/scroll-area',
|
||||
description: 'Visually or semantically separates content.',
|
||||
},
|
||||
{
|
||||
title: 'Tabs',
|
||||
href: '/docs/primitives/tabs',
|
||||
description:
|
||||
'A set of layered sections of content—known as tab panels—that are displayed one at a time.',
|
||||
},
|
||||
{
|
||||
title: 'Tooltip',
|
||||
href: '/docs/primitives/tooltip',
|
||||
description:
|
||||
'A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.',
|
||||
},
|
||||
]
|
||||
|
||||
const ListItem = React.forwardRef<React.ElementRef<'a'>, React.ComponentPropsWithoutRef<'a'>>(
|
||||
({ className, title, children, ...props }, ref) => {
|
||||
return (
|
||||
<li>
|
||||
<NavigationMenuLink asChild>
|
||||
<a
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="text-sm font-medium leading-none">{title}</div>
|
||||
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">{children}</p>
|
||||
</a>
|
||||
</NavigationMenuLink>
|
||||
</li>
|
||||
|
||||
/**
|
||||
* if using Next.js
|
||||
*
|
||||
* import { navigationMenuTriggerStyle } from "@/components/ui/navigation-menu"
|
||||
*
|
||||
* <NavigationMenuItem>
|
||||
* <Link href="/docs" legacyBehavior passHref>
|
||||
* <NavigationMenuLink className={navigationMenuTriggerStyle()}>
|
||||
* Documentation
|
||||
* </NavigationMenuLink>
|
||||
* </Link>
|
||||
* </NavigationMenuItem>
|
||||
*/
|
||||
)
|
||||
}
|
||||
)
|
||||
ListItem.displayName = 'ListItem'
|
||||
|
||||
return (
|
||||
<div className="preview flex min-h-[350px] w-full justify-center p-10 items-center">
|
||||
<NavigationMenu>
|
||||
<NavigationMenuList>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuTrigger>Getting started</NavigationMenuTrigger>
|
||||
<NavigationMenuContent>
|
||||
<ul className="grid gap-3 p-6 md:w-[400px] lg:w-[500px] lg:grid-cols-[.75fr_1fr]">
|
||||
<li className="row-span-3">
|
||||
<NavigationMenuLink asChild>
|
||||
<a
|
||||
className="flex h-full w-full select-none flex-col justify-end rounded-md bg-gradient-to-b from-muted/50 to-muted p-6 no-underline outline-none focus:shadow-md"
|
||||
href="/"
|
||||
>
|
||||
<div className="w-7 h-7 rounded-lg bg-foreground"></div>
|
||||
<div className="mb-2 mt-4 text-lg font-medium">shadcn/ui</div>
|
||||
<p className="text-sm leading-tight text-muted-foreground">
|
||||
Beautifully designed components built with Radix UI and Tailwind CSS.
|
||||
</p>
|
||||
</a>
|
||||
</NavigationMenuLink>
|
||||
</li>
|
||||
<ListItem href="/docs" title="Introduction">
|
||||
Re-usable components built using Radix UI and Tailwind CSS.
|
||||
</ListItem>
|
||||
<ListItem href="/docs/installation" title="Installation">
|
||||
How to install dependencies and structure your app.
|
||||
</ListItem>
|
||||
<ListItem href="/docs/primitives/typography" title="Typography">
|
||||
Styles for headings, paragraphs, lists...etc
|
||||
</ListItem>
|
||||
</ul>
|
||||
</NavigationMenuContent>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuTrigger>Components</NavigationMenuTrigger>
|
||||
<NavigationMenuContent>
|
||||
<ul className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px] ">
|
||||
{components.map((component) => (
|
||||
<ListItem key={component.title} title={component.title} href={component.href}>
|
||||
{component.description}
|
||||
</ListItem>
|
||||
))}
|
||||
</ul>
|
||||
</NavigationMenuContent>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<Link href="/docs" legacyBehavior passHref>
|
||||
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
|
||||
Documentation
|
||||
</NavigationMenuLink>
|
||||
</Link>
|
||||
</NavigationMenuItem>
|
||||
</NavigationMenuList>
|
||||
</NavigationMenu>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Meta } from '@storybook/react'
|
||||
import { Card } from '@ui/components/shadcn/ui/card'
|
||||
import { Button } from '@ui/components/shadcn/ui/button'
|
||||
import { Input } from '@ui/components/shadcn/ui/input'
|
||||
import { Label } from '@ui/components/shadcn/ui/label'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@ui/components/shadcn/ui/popover'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/Popover',
|
||||
component: Popover,
|
||||
}
|
||||
|
||||
export function PopoverDemo() {
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline">Open popover</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80">
|
||||
<div className="grid gap-4">
|
||||
<div className="space-y-2">
|
||||
<h4 className="font-medium leading-none">Dimensions</h4>
|
||||
<p className="text-sm text-muted-foreground">Set the dimensions for the layer.</p>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<div className="grid grid-cols-3 items-center gap-4">
|
||||
<Label htmlFor="width">Width</Label>
|
||||
<Input id="width" defaultValue="100%" className="col-span-2 h-8" />
|
||||
</div>
|
||||
<div className="grid grid-cols-3 items-center gap-4">
|
||||
<Label htmlFor="maxWidth">Max. width</Label>
|
||||
<Input id="maxWidth" defaultValue="300px" className="col-span-2 h-8" />
|
||||
</div>
|
||||
<div className="grid grid-cols-3 items-center gap-4">
|
||||
<Label htmlFor="height">Height</Label>
|
||||
<Input id="height" defaultValue="25px" className="col-span-2 h-8" />
|
||||
</div>
|
||||
<div className="grid grid-cols-3 items-center gap-4">
|
||||
<Label htmlFor="maxHeight">Max. height</Label>
|
||||
<Input id="maxHeight" defaultValue="none" className="col-span-2 h-8" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Progress } from '@ui/components/shadcn/ui/progress'
|
||||
import { Meta } from '@storybook/react'
|
||||
import * as React from 'react'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/Progress',
|
||||
component: Progress,
|
||||
}
|
||||
|
||||
export function Default() {
|
||||
const [progress, setProgress] = React.useState(13)
|
||||
|
||||
React.useEffect(() => {
|
||||
const timer = setTimeout(() => setProgress(66), 500)
|
||||
return () => clearTimeout(timer)
|
||||
}, [])
|
||||
|
||||
return <Progress value={progress} className="w-[60%]" />
|
||||
}
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,109 @@
|
||||
import { Button } from '@ui/components/shadcn/ui/button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@ui/components/shadcn/ui/form'
|
||||
import { Label } from '@ui/components/shadcn/ui/label'
|
||||
import { RadioGroup, RadioGroupItem } from '@ui/components/shadcn/ui/radio-group'
|
||||
import { toast } from '@ui/components/shadcn/ui/use-toast'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { Meta } from '@storybook/react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import * as z from 'zod'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/RadioGroup',
|
||||
component: RadioGroup,
|
||||
}
|
||||
|
||||
export function Default() {
|
||||
return (
|
||||
<RadioGroup defaultValue="comfortable">
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="default" id="r1" />
|
||||
<Label htmlFor="r1">Default</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="comfortable" id="r2" />
|
||||
<Label htmlFor="r2">Comfortable</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="compact" id="r3" />
|
||||
<Label htmlFor="r3">Compact</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
)
|
||||
}
|
||||
|
||||
export function RadioGroupForm() {
|
||||
const FormSchema = z.object({
|
||||
type: z.enum(['all', 'mentions', 'none'], {
|
||||
required_error: 'You need to select a notification type.',
|
||||
}),
|
||||
})
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
})
|
||||
|
||||
function onSubmit(data: z.infer<typeof FormSchema>) {
|
||||
toast({
|
||||
title: 'You submitted the following values:',
|
||||
description: (
|
||||
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
|
||||
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
|
||||
</pre>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="w-2/3 space-y-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="type"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-3">
|
||||
<FormLabel>Notify me about...</FormLabel>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
// @ts-expect-error
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
className="flex flex-col space-y-1"
|
||||
>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="all" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">All new messages</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="mentions" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">Direct messages and mentions</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="none" />
|
||||
</FormControl>
|
||||
<FormLabel className="font-normal">Nothing</FormLabel>
|
||||
</FormItem>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit">Submit</Button>
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Meta } from '@storybook/react'
|
||||
import { ScrollArea } from '@ui/components/shadcn/ui/scroll-area'
|
||||
import { Separator } from '@ui/components/shadcn/ui/separator'
|
||||
import * as React from 'react'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/ScrollArea',
|
||||
component: ScrollArea,
|
||||
}
|
||||
|
||||
export function Default() {
|
||||
const tags = Array.from({ length: 50 }).map((_, i, a) => `v1.2.0-beta.${a.length - i}`)
|
||||
return (
|
||||
<ScrollArea className="h-72 w-48 rounded-md border">
|
||||
<div className="p-4">
|
||||
<h4 className="mb-4 text-sm font-medium leading-none">Tags</h4>
|
||||
{tags.map((tag) => (
|
||||
<React.Fragment>
|
||||
<div className="text-sm" key={tag}>
|
||||
{tag}
|
||||
</div>
|
||||
<Separator className="my-2" />
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
)
|
||||
}
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,138 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { Meta } from '@storybook/react'
|
||||
import { Button } from '@ui/components/shadcn/ui/button'
|
||||
import { Card } from '@ui/components/shadcn/ui/card'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@ui/components/shadcn/ui/form'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@ui/components/shadcn/ui/select'
|
||||
import { toast } from '@ui/components/shadcn/ui/use-toast'
|
||||
import Link from 'next/link'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import * as z from 'zod'
|
||||
import { SelectSeparator } from '../select'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/Select',
|
||||
component: Select,
|
||||
}
|
||||
|
||||
export function Default() {
|
||||
return (
|
||||
<Select>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder="Select a fruit" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>Fruits</SelectLabel>
|
||||
<SelectItem value="apple">Apple</SelectItem>
|
||||
<SelectItem value="banana">Banana</SelectItem>
|
||||
<SelectItem value="blueberry">Blueberry</SelectItem>
|
||||
<SelectItem value="grapes">Grapes</SelectItem>
|
||||
<SelectItem value="pineapple">Pineapple</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)
|
||||
}
|
||||
|
||||
export function withSeperators() {
|
||||
return (
|
||||
<Select>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder="Select a fruit" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>Fruits</SelectLabel>
|
||||
<SelectItem value="apple">Apple</SelectItem>
|
||||
<SelectItem value="banana">Banana</SelectItem>
|
||||
<SelectItem value="blueberry">Blueberry</SelectItem>
|
||||
<SelectItem value="grapes">Grapes</SelectItem>
|
||||
<SelectItem value="pineapple">Pineapple</SelectItem>
|
||||
<SelectSeparator />
|
||||
<SelectLabel>Vegetables</SelectLabel>
|
||||
<SelectItem value="cabbage">Cabbage</SelectItem>
|
||||
<SelectItem value="potato">Potato</SelectItem>
|
||||
<SelectItem value="carrot">Carrot</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)
|
||||
}
|
||||
|
||||
export function SelectForm() {
|
||||
const FormSchema = z.object({
|
||||
email: z
|
||||
.string({
|
||||
required_error: 'Please select an email to display.',
|
||||
})
|
||||
.email(),
|
||||
})
|
||||
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
})
|
||||
|
||||
function onSubmit(data: z.infer<typeof FormSchema>) {
|
||||
toast({
|
||||
title: 'You submitted the following values:',
|
||||
description: (
|
||||
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
|
||||
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
|
||||
</pre>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="w-2/3 space-y-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a verified email to display" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="m@example.com">m@example.com</SelectItem>
|
||||
<SelectItem value="m@google.com">m@google.com</SelectItem>
|
||||
<SelectItem value="m@support.com">m@support.com</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormDescription>
|
||||
You can manage email addresses in your{' '}
|
||||
<Link href="/examples/forms">email settings</Link>.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit">Submit</Button>
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Meta } from '@storybook/react'
|
||||
import { Separator } from '@ui/components/shadcn/ui/separator'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/Separator',
|
||||
component: Separator,
|
||||
}
|
||||
|
||||
export function Default() {
|
||||
return (
|
||||
<div>
|
||||
<div className="space-y-1">
|
||||
<h4 className="text-sm font-medium leading-none">Radix Primitives</h4>
|
||||
<p className="text-sm text-muted-foreground">An open-source UI component library.</p>
|
||||
</div>
|
||||
<Separator className="my-4" />
|
||||
<div className="flex h-5 items-center space-x-4 text-sm">
|
||||
<div>Blog</div>
|
||||
<Separator orientation="vertical" />
|
||||
<div>Docs</div>
|
||||
<Separator orientation="vertical" />
|
||||
<div>Source</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,103 @@
|
||||
import { Meta } from '@storybook/react'
|
||||
import { Button } from '@ui/components/shadcn/ui/button'
|
||||
import { Input } from '@ui/components/shadcn/ui/input'
|
||||
import { Label } from '@ui/components/shadcn/ui/label'
|
||||
import {
|
||||
Sheet,
|
||||
SheetClose,
|
||||
SheetContent,
|
||||
SheetDescription,
|
||||
SheetFooter,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from '@ui/components/shadcn/ui/sheet'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/Sheet',
|
||||
component: Sheet,
|
||||
}
|
||||
|
||||
export function Default() {
|
||||
return (
|
||||
<Sheet>
|
||||
<SheetTrigger asChild>
|
||||
<Button variant="outline">Open</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent>
|
||||
<SheetHeader>
|
||||
<SheetTitle>Edit profile</SheetTitle>
|
||||
<SheetDescription>
|
||||
Make changes to your profile here. Click save when you're done.
|
||||
</SheetDescription>
|
||||
</SheetHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="name" className="text-right">
|
||||
Name
|
||||
</Label>
|
||||
<Input id="name" value="Pedro Duarte" className="col-span-3" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="username" className="text-right">
|
||||
Username
|
||||
</Label>
|
||||
<Input id="username" value="@peduarte" className="col-span-3" />
|
||||
</div>
|
||||
</div>
|
||||
<SheetFooter>
|
||||
<SheetClose asChild>
|
||||
<Button type="submit">Save changes</Button>
|
||||
</SheetClose>
|
||||
</SheetFooter>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
)
|
||||
}
|
||||
|
||||
export function SheetSide() {
|
||||
const SHEET_SIDES = ['top', 'right', 'bottom', 'left'] as const
|
||||
|
||||
type SheetSide = (typeof SHEET_SIDES)[number]
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{SHEET_SIDES.map((side) => (
|
||||
<Sheet key={side}>
|
||||
<SheetTrigger asChild>
|
||||
<Button variant="outline">{side}</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent position={side}>
|
||||
<SheetHeader>
|
||||
<SheetTitle>Edit profile</SheetTitle>
|
||||
<SheetDescription>
|
||||
Make changes to your profile here. Click save when you're done.
|
||||
</SheetDescription>
|
||||
</SheetHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="name" className="text-right">
|
||||
Name
|
||||
</Label>
|
||||
<Input id="name" value="Pedro Duarte" className="col-span-3" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="username" className="text-right">
|
||||
Username
|
||||
</Label>
|
||||
<Input id="username" value="@peduarte" className="col-span-3" />
|
||||
</div>
|
||||
</div>
|
||||
<SheetFooter>
|
||||
<SheetClose asChild>
|
||||
<Button type="submit">Save changes</Button>
|
||||
</SheetClose>
|
||||
</SheetFooter>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Meta } from '@storybook/react'
|
||||
import { Skeleton } from '@ui/components/shadcn/ui/skeleton'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/Skeleton',
|
||||
component: Skeleton,
|
||||
}
|
||||
|
||||
export function Default() {
|
||||
return (
|
||||
<div className="flex items-center space-x-4">
|
||||
<Skeleton className="h-12 w-12 rounded-full" />
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-4 w-[250px]" />
|
||||
<Skeleton className="h-4 w-[200px]" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Meta } from '@storybook/react'
|
||||
import { Slider } from '@ui/components/shadcn/ui/slider'
|
||||
import { cn } from '@ui/lib/utils'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/Slider',
|
||||
component: Slider,
|
||||
}
|
||||
|
||||
type SliderProps = React.ComponentProps<typeof Slider>
|
||||
|
||||
export function SliderDemo({ className, ...props }: SliderProps) {
|
||||
return (
|
||||
<Slider
|
||||
defaultValue={[50]}
|
||||
max={100}
|
||||
step={1}
|
||||
className={cn('w-[60%]', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,107 @@
|
||||
import { Button } from '@ui/components/shadcn/ui/button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
} from '@ui/components/shadcn/ui/form'
|
||||
import { toast } from '@ui/components/shadcn/ui/use-toast'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { Meta } from '@storybook/react'
|
||||
import { Label } from '@ui/components/shadcn/ui/label'
|
||||
import { Switch } from '@ui/components/shadcn/ui/switch'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import * as z from 'zod'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/Switch',
|
||||
component: Switch,
|
||||
}
|
||||
|
||||
export function Default() {
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch id="airplane-mode" />
|
||||
<Label htmlFor="airplane-mode">Airplane Mode</Label>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function SwitchForm() {
|
||||
const FormSchema = z.object({
|
||||
marketing_emails: z.boolean().default(false).optional(),
|
||||
security_emails: z.boolean(),
|
||||
})
|
||||
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: {
|
||||
security_emails: true,
|
||||
},
|
||||
})
|
||||
|
||||
function onSubmit(data: z.infer<typeof FormSchema>) {
|
||||
toast({
|
||||
title: 'You submitted the following values:',
|
||||
description: (
|
||||
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
|
||||
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
|
||||
</pre>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="w-full space-y-6">
|
||||
<div>
|
||||
<h3 className="mb-4 text-lg font-medium">Email Notifications</h3>
|
||||
<div className="space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="marketing_emails"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>Marketing emails</FormLabel>
|
||||
<FormDescription>
|
||||
Receive emails about new products, features, and more.
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch checked={field.value} onCheckedChange={field.onChange} />
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="security_emails"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>Security emails</FormLabel>
|
||||
<FormDescription>Receive emails about your account security.</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
disabled
|
||||
aria-readonly
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Button type="submit">Submit</Button>
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,88 @@
|
||||
import { Meta } from '@storybook/react'
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCaption,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@ui/components/shadcn/ui/table'
|
||||
|
||||
const invoices = [
|
||||
{
|
||||
invoice: 'INV001',
|
||||
paymentStatus: 'Paid',
|
||||
totalAmount: '$250.00',
|
||||
paymentMethod: 'Credit Card',
|
||||
},
|
||||
{
|
||||
invoice: 'INV002',
|
||||
paymentStatus: 'Pending',
|
||||
totalAmount: '$150.00',
|
||||
paymentMethod: 'PayPal',
|
||||
},
|
||||
{
|
||||
invoice: 'INV003',
|
||||
paymentStatus: 'Unpaid',
|
||||
totalAmount: '$350.00',
|
||||
paymentMethod: 'Bank Transfer',
|
||||
},
|
||||
{
|
||||
invoice: 'INV004',
|
||||
paymentStatus: 'Paid',
|
||||
totalAmount: '$450.00',
|
||||
paymentMethod: 'Credit Card',
|
||||
},
|
||||
{
|
||||
invoice: 'INV005',
|
||||
paymentStatus: 'Paid',
|
||||
totalAmount: '$550.00',
|
||||
paymentMethod: 'PayPal',
|
||||
},
|
||||
{
|
||||
invoice: 'INV006',
|
||||
paymentStatus: 'Pending',
|
||||
totalAmount: '$200.00',
|
||||
paymentMethod: 'Bank Transfer',
|
||||
},
|
||||
{
|
||||
invoice: 'INV007',
|
||||
paymentStatus: 'Unpaid',
|
||||
totalAmount: '$300.00',
|
||||
paymentMethod: 'Credit Card',
|
||||
},
|
||||
]
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/Table',
|
||||
component: Table,
|
||||
}
|
||||
|
||||
export function TableDemo() {
|
||||
return (
|
||||
<Table>
|
||||
<TableCaption>A list of your recent invoices.</TableCaption>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[100px]">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.paymentStatus}</TableCell>
|
||||
<TableCell>{invoice.paymentMethod}</TableCell>
|
||||
<TableCell className="text-right">{invoice.totalAmount}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)
|
||||
}
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,77 @@
|
||||
import { Meta } from '@storybook/react'
|
||||
import { Button } from '@ui/components/shadcn/ui/button'
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@ui/components/shadcn/ui/card'
|
||||
import { Input } from '@ui/components/shadcn/ui/input'
|
||||
import { Label } from '@ui/components/shadcn/ui/label'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@ui/components/shadcn/ui/tabs'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/Tabs',
|
||||
component: Tabs,
|
||||
}
|
||||
|
||||
export function TabsDemo() {
|
||||
return (
|
||||
<Tabs defaultValue="account" className="w-[400px]">
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="account">Account</TabsTrigger>
|
||||
<TabsTrigger value="password">Password</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="account">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Account</CardTitle>
|
||||
<CardDescription>
|
||||
Make changes to your account here. Click save when you're done.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="name">Name</Label>
|
||||
<Input id="name" defaultValue="Pedro Duarte" />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="username">Username</Label>
|
||||
<Input id="username" defaultValue="@peduarte" />
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button>Save changes</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
<TabsContent value="password">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Password</CardTitle>
|
||||
<CardDescription>
|
||||
Change your password here. After saving, you'll be logged out.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="current">Current password</Label>
|
||||
<Input id="current" type="password" />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="new">New password</Label>
|
||||
<Input id="new" type="password" />
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button>Save password</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,117 @@
|
||||
import { Button } from '@ui/components/shadcn/ui/button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@ui/components/shadcn/ui/form'
|
||||
import { toast } from '@ui/components/shadcn/ui/use-toast'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { Meta } from '@storybook/react'
|
||||
import { Textarea } from '@ui/components/shadcn/ui/textarea'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import * as z from 'zod'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/Textarea',
|
||||
component: Textarea,
|
||||
}
|
||||
|
||||
export function Default() {
|
||||
return <Textarea placeholder="Type your message here." />
|
||||
}
|
||||
|
||||
export function TextareaDisabled() {
|
||||
return <Textarea placeholder="Type your message here." disabled />
|
||||
}
|
||||
|
||||
export function TextareaWithLabel() {
|
||||
return (
|
||||
<div className="grid w-full gap-1.5">
|
||||
<Label htmlFor="message">Your message</Label>
|
||||
<Textarea placeholder="Type your message here." id="message" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function TextareaWithText() {
|
||||
return (
|
||||
<div className="grid w-full gap-1.5">
|
||||
<Label htmlFor="message-2">Your Message</Label>
|
||||
<Textarea placeholder="Type your message here." id="message-2" />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Your message will be copied to the support team.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function TextareaWithButton() {
|
||||
return (
|
||||
<div className="grid w-full gap-2">
|
||||
<Textarea placeholder="Type your message here." />
|
||||
<Button>Send message</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function TextareaForm() {
|
||||
const FormSchema = z.object({
|
||||
bio: z
|
||||
.string()
|
||||
.min(10, {
|
||||
message: 'Bio must be at least 10 characters.',
|
||||
})
|
||||
.max(160, {
|
||||
message: 'Bio must not be longer than 30 characters.',
|
||||
}),
|
||||
})
|
||||
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
})
|
||||
|
||||
function onSubmit(data: z.infer<typeof FormSchema>) {
|
||||
toast({
|
||||
title: 'You submitted the following values:',
|
||||
description: (
|
||||
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
|
||||
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
|
||||
</pre>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="w-2/3 space-y-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="bio"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Bio</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder="Tell us a little bit about yourself"
|
||||
className="resize-none"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
You can <span>@mention</span> other users and organizations.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit">Submit</Button>
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,104 @@
|
||||
import { Meta } from '@storybook/react'
|
||||
import { Card } from '@ui/components/shadcn/ui/card'
|
||||
import { Button } from '@ui/components/shadcn/ui/button'
|
||||
import { ToastAction } from '@ui/components/shadcn/ui/toast'
|
||||
import { useToast } from '@ui/components/shadcn/ui/use-toast'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/Toast',
|
||||
}
|
||||
|
||||
export function Default() {
|
||||
const { toast } = useToast()
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
toast({
|
||||
description: 'Your message has been sent.',
|
||||
})
|
||||
}}
|
||||
>
|
||||
Show Toast
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export function MoreDescription() {
|
||||
const { toast } = useToast()
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
toast({
|
||||
title: 'Scheduled: Catch up ',
|
||||
description: 'Friday, February 10, 2023 at 5:57 PM',
|
||||
action: <ToastAction altText="Goto schedule to undo">Undo</ToastAction>,
|
||||
})
|
||||
}}
|
||||
>
|
||||
Add to calendar
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export function ToastWithTitle() {
|
||||
const { toast } = useToast()
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
toast({
|
||||
title: 'Uh oh! Something went wrong.',
|
||||
description: 'There was a problem with your request.',
|
||||
})
|
||||
}}
|
||||
>
|
||||
Show Toast
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export function ToastWithAction() {
|
||||
const { toast } = useToast()
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
toast({
|
||||
title: 'Uh oh! Something went wrong.',
|
||||
description: 'There was a problem with your request.',
|
||||
action: <ToastAction altText="Try again">Try again</ToastAction>,
|
||||
})
|
||||
}}
|
||||
>
|
||||
Show Toast
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export function ToastDestructive() {
|
||||
const { toast } = useToast()
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
toast({
|
||||
variant: 'destructive',
|
||||
title: 'Uh oh! Something went wrong.',
|
||||
description: 'There was a problem with your request.',
|
||||
action: <ToastAction altText="Try again">Try again</ToastAction>,
|
||||
})
|
||||
}}
|
||||
>
|
||||
Show Toast
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Meta } from '@storybook/react'
|
||||
import { Toggle } from '@ui/components/shadcn/ui/toggle'
|
||||
import { Bold, Italic } from 'lucide-react'
|
||||
import { Underline } from 'lucide-react'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/Toggle',
|
||||
component: Toggle,
|
||||
}
|
||||
|
||||
export function Default() {
|
||||
return (
|
||||
<Toggle aria-label="Toggle italic">
|
||||
<Bold className="h-4 w-4" />
|
||||
</Toggle>
|
||||
)
|
||||
}
|
||||
|
||||
export function ToggleOutline() {
|
||||
return (
|
||||
<Toggle variant="outline" aria-label="Toggle italic">
|
||||
<Italic className="h-4 w-4" />
|
||||
</Toggle>
|
||||
)
|
||||
}
|
||||
|
||||
export function ToggleWithText() {
|
||||
return (
|
||||
<Toggle aria-label="Toggle italic">
|
||||
<Italic className="mr-2 h-4 w-4" />
|
||||
Italic
|
||||
</Toggle>
|
||||
)
|
||||
}
|
||||
|
||||
export function ToggleSm() {
|
||||
return (
|
||||
<Toggle size="sm" aria-label="Toggle italic">
|
||||
<Italic className="h-4 w-4" />
|
||||
</Toggle>
|
||||
)
|
||||
}
|
||||
|
||||
export function ToggleLg() {
|
||||
return (
|
||||
<Toggle size="lg" aria-label="Toggle italic">
|
||||
<Italic className="h-4 w-4" />
|
||||
</Toggle>
|
||||
)
|
||||
}
|
||||
|
||||
export function ToggleDisabled() {
|
||||
return (
|
||||
<Toggle aria-label="Toggle italic" disabled>
|
||||
<Underline className="h-4 w-4" />
|
||||
</Toggle>
|
||||
)
|
||||
}
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Meta } from '@storybook/react'
|
||||
import { Button } from '@ui/components/shadcn/ui/button'
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from '@ui/components/shadcn/ui/tooltip'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/Tooltip',
|
||||
component: Tooltip,
|
||||
}
|
||||
|
||||
export function Default() {
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="outline">Hover</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Add to library</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default meta
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Meta } from '@storybook/react'
|
||||
import { Card } from '@ui/components/shadcn/ui/card'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'shadcn/Card',
|
||||
component: Card,
|
||||
}
|
||||
|
||||
export const Default = () => {
|
||||
return (
|
||||
<div>
|
||||
<div></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const CardWithForm = () => {
|
||||
return (
|
||||
<div>
|
||||
<div></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
CardWithForm.storyName = 'Card with form custom name'
|
||||
|
||||
export default meta
|
||||
Reference in new issue
Block a user