mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-07-08 14:35:09 +00:00
feat(sheet): add sheet component (#50)
This commit is contained in:
@@ -39,6 +39,9 @@ import { RadioGroupDemo } from "@/components/examples/radio-group/demo"
|
||||
import { ScrollAreaDemo } from "@/components/examples/scroll-area/demo"
|
||||
import { SelectDemo } from "@/components/examples/select/demo"
|
||||
import { SeparatorDemo } from "@/components/examples/separator/demo"
|
||||
import { SheetDemo } from "@/components/examples/sheet/demo"
|
||||
import { SheetPosition } from "@/components/examples/sheet/position"
|
||||
import { SheetSize } from "@/components/examples/sheet/size"
|
||||
import { SliderDemo } from "@/components/examples/slider/demo"
|
||||
import { SwitchDemo } from "@/components/examples/switch/demo"
|
||||
import { TabsDemo } from "@/components/examples/tabs/demo"
|
||||
@@ -116,6 +119,9 @@ export const examples = {
|
||||
ScrollAreaDemo,
|
||||
SelectDemo,
|
||||
SeparatorDemo,
|
||||
SheetDemo,
|
||||
SheetSize,
|
||||
SheetPosition,
|
||||
SliderDemo,
|
||||
SwitchDemo,
|
||||
TabsDemo,
|
||||
|
||||
49
apps/www/components/examples/sheet/demo.tsx
Normal file
49
apps/www/components/examples/sheet/demo.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
"use client"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetDescription,
|
||||
SheetFooter,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from "@/components/ui/sheet"
|
||||
|
||||
export function SheetDemo() {
|
||||
return (
|
||||
<Sheet>
|
||||
<SheetTrigger asChild>
|
||||
<Button variant="outline">Open</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent position="right" size="sm">
|
||||
<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>
|
||||
<Button type="submit">Save changes</Button>
|
||||
</SheetFooter>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
)
|
||||
}
|
||||
75
apps/www/components/examples/sheet/position.tsx
Normal file
75
apps/www/components/examples/sheet/position.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetDescription,
|
||||
SheetFooter,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from "@/components/ui/sheet"
|
||||
|
||||
const SHEET_POSITIONS = ["top", "right", "bottom", "left"] as const
|
||||
|
||||
type SheetPosition = typeof SHEET_POSITIONS[number]
|
||||
|
||||
export function SheetPosition() {
|
||||
const [position, setPosition] = useState<SheetPosition>("right")
|
||||
return (
|
||||
<div className="flex flex-col space-y-8">
|
||||
<RadioGroup
|
||||
defaultValue={position}
|
||||
onValueChange={(value) => setPosition(value as SheetPosition)}
|
||||
>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{SHEET_POSITIONS.map((position, index) => (
|
||||
<div
|
||||
key={`${position}-${index}`}
|
||||
className="flex items-center space-x-2"
|
||||
>
|
||||
<RadioGroupItem value={position} id={position} />
|
||||
<Label htmlFor={position}>{position}</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<Sheet>
|
||||
<SheetTrigger asChild>
|
||||
<Button>Open {position} sheet</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent position={position} size="content">
|
||||
<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>
|
||||
<Button type="submit">Save changes</Button>
|
||||
</SheetFooter>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
75
apps/www/components/examples/sheet/size.tsx
Normal file
75
apps/www/components/examples/sheet/size.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetDescription,
|
||||
SheetFooter,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from "@/components/ui/sheet"
|
||||
|
||||
const SHEET_SIZES = ["sm", "default", "lg", "xl", "full", "content"] as const
|
||||
|
||||
type SheetSize = typeof SHEET_SIZES[number]
|
||||
|
||||
export function SheetSize() {
|
||||
const [size, setSize] = useState<SheetSize>("default")
|
||||
return (
|
||||
<div className="flex flex-col space-y-8">
|
||||
<RadioGroup
|
||||
defaultValue={size}
|
||||
onValueChange={(value) => setSize(value as SheetSize)}
|
||||
>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{SHEET_SIZES.map((size, index) => (
|
||||
<div
|
||||
key={`${size}-${index}`}
|
||||
className="flex items-center space-x-2"
|
||||
>
|
||||
<RadioGroupItem value={size} id={size} />
|
||||
<Label htmlFor={size}>{size}</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<Sheet>
|
||||
<SheetTrigger asChild>
|
||||
<Button>Open {size} sheet</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent position="right" size={size}>
|
||||
<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>
|
||||
<Button type="submit">Save changes</Button>
|
||||
</SheetFooter>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
234
apps/www/components/ui/sheet.tsx
Normal file
234
apps/www/components/ui/sheet.tsx
Normal file
@@ -0,0 +1,234 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
||||
import { VariantProps, cva } from "class-variance-authority"
|
||||
import { X } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Sheet = SheetPrimitive.Root
|
||||
|
||||
const SheetTrigger = SheetPrimitive.Trigger
|
||||
|
||||
const portalVariants = cva("fixed inset-0 z-50 flex", {
|
||||
variants: {
|
||||
position: {
|
||||
top: "items-start",
|
||||
bottom: "items-end",
|
||||
left: "justify-start",
|
||||
right: "justify-end",
|
||||
},
|
||||
},
|
||||
defaultVariants: { position: "right" },
|
||||
})
|
||||
|
||||
interface SheetPortalProps
|
||||
extends SheetPrimitive.DialogPortalProps,
|
||||
VariantProps<typeof portalVariants> {}
|
||||
|
||||
const SheetPortal = ({
|
||||
position,
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: SheetPortalProps) => (
|
||||
<SheetPrimitive.Portal className={cn(className)} {...props}>
|
||||
<div className={portalVariants({ position })}>{children}</div>
|
||||
</SheetPrimitive.Portal>
|
||||
)
|
||||
SheetPortal.displayName = SheetPrimitive.Portal.displayName
|
||||
|
||||
const SheetOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SheetPrimitive.Overlay
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-black/50 backdrop-blur-sm transition-all duration-100 data-[state=closed]:animate-out data-[state=open]:fade-in data-[state=closed]:fade-out",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
))
|
||||
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
|
||||
|
||||
const sheetVariants = cva(
|
||||
"fixed z-50 scale-100 gap-4 bg-white p-6 opacity-100 dark:bg-slate-900",
|
||||
{
|
||||
variants: {
|
||||
position: {
|
||||
top: "animate-in slide-in-from-top w-full duration-300",
|
||||
bottom: "animate-in slide-in-from-bottom w-full duration-300",
|
||||
left: "animate-in slide-in-from-left h-full duration-300",
|
||||
right: "animate-in slide-in-from-right h-full duration-300",
|
||||
},
|
||||
size: {
|
||||
content: "",
|
||||
default: "",
|
||||
sm: "",
|
||||
lg: "",
|
||||
xl: "",
|
||||
full: "",
|
||||
},
|
||||
},
|
||||
compoundVariants: [
|
||||
{
|
||||
position: ["top", "bottom"],
|
||||
size: "content",
|
||||
class: "max-h-screen",
|
||||
},
|
||||
{
|
||||
position: ["top", "bottom"],
|
||||
size: "default",
|
||||
class: "h-1/3",
|
||||
},
|
||||
{
|
||||
position: ["top", "bottom"],
|
||||
size: "sm",
|
||||
class: "h-1/4",
|
||||
},
|
||||
{
|
||||
position: ["top", "bottom"],
|
||||
size: "lg",
|
||||
class: "h-1/2",
|
||||
},
|
||||
{
|
||||
position: ["top", "bottom"],
|
||||
size: "xl",
|
||||
class: "h-5/6",
|
||||
},
|
||||
{
|
||||
position: ["top", "bottom"],
|
||||
size: "full",
|
||||
class: "h-screen",
|
||||
},
|
||||
{
|
||||
position: ["right", "left"],
|
||||
size: "content",
|
||||
class: "max-w-screen",
|
||||
},
|
||||
{
|
||||
position: ["right", "left"],
|
||||
size: "default",
|
||||
class: "w-1/3",
|
||||
},
|
||||
{
|
||||
position: ["right", "left"],
|
||||
size: "sm",
|
||||
class: "w-1/4",
|
||||
},
|
||||
{
|
||||
position: ["right", "left"],
|
||||
size: "lg",
|
||||
class: "w-1/2",
|
||||
},
|
||||
{
|
||||
position: ["right", "left"],
|
||||
size: "xl",
|
||||
class: "w-5/6",
|
||||
},
|
||||
{
|
||||
position: ["right", "left"],
|
||||
size: "full",
|
||||
class: "w-screen",
|
||||
},
|
||||
],
|
||||
defaultVariants: {
|
||||
position: "right",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export interface DialogContentProps
|
||||
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
|
||||
VariantProps<typeof sheetVariants> {}
|
||||
|
||||
const SheetContent = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Content>,
|
||||
DialogContentProps
|
||||
>(({ position, size, className, children, ...props }, ref) => (
|
||||
<SheetPortal position={position}>
|
||||
<SheetOverlay />
|
||||
<SheetPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(sheetVariants({ position, size }), className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SheetPrimitive.Close className="absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-slate-100 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 dark:data-[state=open]:bg-slate-800">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</SheetPrimitive.Close>
|
||||
</SheetPrimitive.Content>
|
||||
</SheetPortal>
|
||||
))
|
||||
SheetContent.displayName = SheetPrimitive.Content.displayName
|
||||
|
||||
const SheetHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col space-y-2 text-center sm:text-left",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
SheetHeader.displayName = "SheetHeader"
|
||||
|
||||
const SheetFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
SheetFooter.displayName = "SheetFooter"
|
||||
|
||||
const SheetTitle = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-lg font-semibold text-slate-900",
|
||||
"dark:text-slate-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SheetTitle.displayName = SheetPrimitive.Title.displayName
|
||||
|
||||
const SheetDescription = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-slate-500", "dark:text-slate-400", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SheetDescription.displayName = SheetPrimitive.Description.displayName
|
||||
|
||||
export {
|
||||
Sheet,
|
||||
SheetTrigger,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetFooter,
|
||||
SheetTitle,
|
||||
SheetDescription,
|
||||
}
|
||||
@@ -177,6 +177,11 @@ export const docsConfig: DocsConfig = {
|
||||
href: "/docs/primitives/separator",
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: "Sheet",
|
||||
href: "/docs/primitives/sheet",
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: "Slider",
|
||||
href: "/docs/primitives/slider",
|
||||
|
||||
77
apps/www/content/docs/primitives/sheet.mdx
Normal file
77
apps/www/content/docs/primitives/sheet.mdx
Normal file
@@ -0,0 +1,77 @@
|
||||
---
|
||||
title: Sheet
|
||||
description: Extension of Dialog component that displays content that complements the main content of the screen, or a list of actions that affect the main content of the screen.
|
||||
radix:
|
||||
link: https://www.radix-ui.com/docs/primitives/components/dialog
|
||||
api: https://www.radix-ui.com/docs/primitives/components/dialog#api-reference
|
||||
---
|
||||
|
||||
<ComponentExample src="/components/examples/sheet/demo.tsx">
|
||||
<SheetDemo />
|
||||
</ComponentExample>
|
||||
|
||||
## Installation
|
||||
|
||||
1. Install the `@radix-ui/react-dialog` component from radix-ui:
|
||||
|
||||
```bash
|
||||
npm install @radix-ui/react-dialog
|
||||
```
|
||||
|
||||
2. Copy and paste the following code into your project.
|
||||
|
||||
<ComponentSource src="/components/ui/sheet.tsx" />
|
||||
|
||||
<Callout>
|
||||
|
||||
This is the `<Sheet />` primitive. You can place it in a file at `components/ui/sheet.tsx`.
|
||||
|
||||
</Callout>
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetDescription,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from "@/components/ui/dialog"
|
||||
```
|
||||
|
||||
```tsx
|
||||
<Sheet>
|
||||
<SheetTrigger>Open</SheetTrigger>
|
||||
<SheetContent>
|
||||
<SheetHeader>
|
||||
<SheetTitle>Are you sure absolutely sure?</SheetTitle>
|
||||
<SheetDescription>
|
||||
This action cannot be undone. This will permanently delete your account
|
||||
and remove your data from our servers.
|
||||
</SheetDescription>
|
||||
</SheetHeader>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Position
|
||||
|
||||
Pass the `position` property to `<SheetContent />` to indicate the edge of the screen where the component will appear. The values can be `top`, `right`, `bottom` or `left`.
|
||||
|
||||
<ComponentExample src="/components/examples/sheet/position.tsx">
|
||||
<SheetPosition />
|
||||
</ComponentExample>
|
||||
|
||||
---
|
||||
|
||||
### Size
|
||||
|
||||
Pass the `size` property to `<SheetContent />` if you need to adjust the size of the sheet. The values can be `sm`, `default`, `lg`, `xl`, `full` or `content`.
|
||||
|
||||
<ComponentExample src="/components/examples/sheet/size.tsx">
|
||||
<SheetSize />
|
||||
</ComponentExample>
|
||||
Reference in New Issue
Block a user