UI
Drawer
Variant of the Dialog component. Display content as an overlay area at the right side of the screen.
import { Button } from '@/components/Button';import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger,} from "@/components/Dialog";
export const DrawerHero = () => ( <div className="flex justify-center"> <Drawer> <DrawerTrigger asChild> <Button variant="secondary">Open Drawer</Button> </DrawerTrigger> <DrawerContent className="sm:max-w-lg"> <DrawerHeader> <DrawerTitle>Account Created Successfully</DrawerTitle> <DrawerDescription className="mt-1 text-sm"> Your account has been created successfully. You can now login to your account. For more information, please contact us. </DrawerDescription> </DrawerHeader> <DrawerBody> This is they body of the drawer, content goes here. </DrawerBody> <DrawerFooter className="mt-6"> <DrawerClose asChild> <Button className="mt-2 w-full sm:mt-0 sm:w-fit" variant="secondary" > Go back </Button> </DrawerClose> <DrawerClose asChild> <Button className="w-full sm:w-fit">Ok, got it!</Button> </DrawerClose> </DrawerFooter> </DrawerContent> </Drawer> </div>);
Installation
- Please add the Button component to use the Drawer.
npm install @radix-ui/react-dialog
- Copy and paste the code into your project’s component directory. Do not forget to update the import paths.
// Tremor Drawer [v0.0.1] import * as React from "react"import * as DrawerPrimitives from "@radix-ui/react-dialog"import { RiCloseLine } from "@remixicon/react" import { cx, focusRing } from "@/lib/utils" import { Button } from "./Button" const Drawer = ( props: React.ComponentPropsWithoutRef<typeof DrawerPrimitives.Root>,) => { return <DrawerPrimitives.Root tremor-id="tremor-raw" {...props} />}Drawer.displayName = "Drawer" const DrawerTrigger = React.forwardRef< React.ElementRef<typeof DrawerPrimitives.Trigger>, React.ComponentPropsWithoutRef<typeof DrawerPrimitives.Trigger>>(({ className, ...props }, ref) => { return ( <DrawerPrimitives.Trigger ref={ref} className={cx(className)} {...props} /> )})DrawerTrigger.displayName = "Drawer.Trigger" const DrawerClose = React.forwardRef< React.ElementRef<typeof DrawerPrimitives.Close>, React.ComponentPropsWithoutRef<typeof DrawerPrimitives.Close>>(({ className, ...props }, ref) => { return ( <DrawerPrimitives.Close ref={ref} className={cx(className)} {...props} /> )})DrawerClose.displayName = "Drawer.Close" const DrawerPortal = DrawerPrimitives.Portal DrawerPortal.displayName = "DrawerPortal" const DrawerOverlay = React.forwardRef< React.ElementRef<typeof DrawerPrimitives.Overlay>, React.ComponentPropsWithoutRef<typeof DrawerPrimitives.Overlay>>(({ className, ...props }, forwardedRef) => { return ( <DrawerPrimitives.Overlay ref={forwardedRef} className={cx( // base "fixed inset-0 z-50 overflow-y-auto", // background color "bg-black/30", // transition "data-[state=closed]:animate-hide data-[state=open]:animate-dialogOverlayShow", className, )} {...props} style={{ animationDuration: "400ms", animationFillMode: "backwards", }} /> )}) DrawerOverlay.displayName = "DrawerOverlay" const DrawerContent = React.forwardRef< React.ElementRef<typeof DrawerPrimitives.Content>, React.ComponentPropsWithoutRef<typeof DrawerPrimitives.Content>>(({ className, ...props }, forwardedRef) => { return ( <DrawerPortal> <DrawerOverlay> <DrawerPrimitives.Content ref={forwardedRef} className={cx( // base "fixed inset-y-2 mx-auto flex w-[95vw] flex-1 flex-col overflow-y-auto rounded-md border p-4 shadow-lg focus:outline-none max-sm:inset-x-2 sm:inset-y-2 sm:right-2 sm:max-w-lg sm:p-6", // border color "border-gray-200 dark:border-gray-900", // background color "bg-white dark:bg-[#090E1A]", // transition "data-[state=closed]:animate-drawerSlideRightAndFade data-[state=open]:animate-drawerSlideLeftAndFade", focusRing, className, )} {...props} /> </DrawerOverlay> </DrawerPortal> )}) DrawerContent.displayName = "DrawerContent" const DrawerHeader = React.forwardRef< HTMLDivElement, React.ComponentPropsWithoutRef<"div">>(({ children, className, ...props }, ref) => { return ( <div ref={ref} className="flex items-start justify-between gap-x-4 border-b border-gray-200 pb-4 dark:border-gray-900" {...props} > <div className={cx("mt-1 flex flex-col gap-y-1", className)}> {children} </div> <DrawerPrimitives.Close asChild> <Button variant="ghost" className="aspect-square p-1 hover:bg-gray-100 hover:dark:bg-gray-400/10" > <RiCloseLine className="size-6" aria-hidden="true" /> </Button> </DrawerPrimitives.Close> </div> )}) DrawerHeader.displayName = "Drawer.Header" const DrawerTitle = React.forwardRef< React.ElementRef<typeof DrawerPrimitives.Title>, React.ComponentPropsWithoutRef<typeof DrawerPrimitives.Title>>(({ className, ...props }, forwardedRef) => ( <DrawerPrimitives.Title ref={forwardedRef} className={cx( // base "text-base font-semibold", // text color "text-gray-900 dark:text-gray-50", className, )} {...props} />)) DrawerTitle.displayName = "DrawerTitle" const DrawerBody = React.forwardRef< HTMLDivElement, React.ComponentPropsWithoutRef<"div">>(({ className, ...props }, ref) => { return <div ref={ref} className={cx("flex-1 py-4", className)} {...props} />})DrawerBody.displayName = "Drawer.Body" const DrawerDescription = React.forwardRef< React.ElementRef<typeof DrawerPrimitives.Description>, React.ComponentPropsWithoutRef<typeof DrawerPrimitives.Description>>(({ className, ...props }, forwardedRef) => { return ( <DrawerPrimitives.Description ref={forwardedRef} className={cx("text-gray-500 dark:text-gray-500", className)} {...props} /> )}) DrawerDescription.displayName = "DrawerDescription" const DrawerFooter = ({ className, ...props}: React.HTMLAttributes<HTMLDivElement>) => { return ( <div className={cx( "flex flex-col-reverse border-t border-gray-200 pt-4 sm:flex-row sm:justify-end sm:space-x-2 dark:border-gray-900", className, )} {...props} /> )} DrawerFooter.displayName = "DrawerFooter" export { Drawer, DrawerBody, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger,}
import type { Config } from 'tailwindcss';export default { // ... theme: { extend: { keyframes: { drawerSlideLeftAndFade: { from: { opacity: "0", transform: "translateX(100%)" }, to: { opacity: "1", transform: "translateX(0)" }, }, drawerSlideRightAndFade: { from: { opacity: "1", transform: "translateX(0)" }, to: { opacity: "0", transform: "translateX(100%)" }, }, }, animation: { // Drawer drawerSlideLeftAndFade: "drawerSlideLeftAndFade 150ms cubic-bezier(0.16, 1, 0.3, 1)", drawerSlideRightAndFade: "drawerSlideRightAndFade 150ms ease-in", }, }, }, // ...} satisfies Config;
Example
Title | Author | Year | Actions |
---|---|---|---|
Twenty Thousand Leagues Under the Sea | Jules Verne | 1870 | |
Journey to the Center of the Earth | Jules Verne | 1864 | |
Around the World in Eighty Days | Jules Verne | 1873 | |
The Mysterious Island | Jules Verne | 1874 |
"use client"
import React from "react"
import { Button } from "@/components/Button"import { Drawer, DrawerBody, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle,} from "@/components/Drawer"import { Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow,} from "@/components/Table"
type StateType = [boolean, () => void, () => void, () => void] & { state: boolean open: () => void close: () => void toggle: () => void}
const useToggleState = (initial = false) => { const [state, setState] = React.useState<boolean>(initial)
const close = () => { setState(false) }
const open = () => { setState(true) }
const toggle = () => { setState((state) => !state) }
const hookData = [state, open, close, toggle] as StateType hookData.state = state hookData.open = open hookData.close = close hookData.toggle = toggle return hookData}
interface Book { title: string author: string year: number genre: string summary: string}
const books: Book[] = [ { title: "Twenty Thousand Leagues Under the Sea", author: "Jules Verne", year: 1870, genre: "Science Fiction", summary: "The novel chronicles the adventures of Captain Nemo and his submarine, the Nautilus, as seen by Professor Pierre Aronnax. After being captured by the mysterious captain, Aronnax, his servant Conseil, and a Canadian whaler named Ned Land, experience the wonders of the underwater world. Throughout their journey, they encounter various marine life, explore underwater forests, and discover the lost city of Atlantis. They also face dangers, such as giant squids and treacherous underwater tunnels. Ultimately, they must decide whether to remain with Captain Nemo or attempt to escape his obsessive quest for revenge against the surface world.", }, { title: "Journey to the Center of the Earth", author: "Jules Verne", year: 1864, genre: "Science Fiction", summary: "Professor Otto Lidenbrock discovers an ancient manuscript that points to a passage to the center of the Earth. Accompanied by his nephew Axel and their guide, Hans Belker, they embark on an expedition to Iceland to find the entrance to the subterranean world. They descend through volcanic tubes and encounter a series of remarkable underground environments, including vast caverns, subterranean seas, and prehistoric creatures. Along the way, they face numerous challenges and near-death experiences. Their journey is not only a physical adventure but also a scientific quest to understand the Earth's inner layers, ultimately returning to the surface with a wealth of knowledge and a new perspective on the natural world.", }, { title: "Around the World in Eighty Days", author: "Jules Verne", year: 1873, genre: "Adventure", summary: "Phileas Fogg, an English gentleman, wagers that he can circumnavigate the globe in just eighty days. Accompanied by his loyal French valet, Passepartout, Fogg sets off on a whirlwind adventure across various countries and continents. Their journey is filled with obstacles, including delays, natural disasters, and relentless pursuit by a detective named Fix, who mistakenly believes Fogg is a bank robber. Despite these challenges, Fogg remains calm and determined to win the bet. In the end, Fogg's meticulous planning and resourcefulness, along with some unexpected help, allow him to complete the journey just in time, proving that with determination and ingenuity, anything is possible.", }, { title: "The Mysterious Island", author: "Jules Verne", year: 1874, genre: "Adventure", summary: "During the American Civil War, five prisoners escape in a hot air balloon, only to crash on an uncharted island in the Pacific. They must use their ingenuity and knowledge to survive and explore their new home, which they name Lincoln Island. The castaways discover that the island is rich in natural resources, enabling them to build a new society from scratch. They face various challenges, including wild animals, pirates, and mysterious occurrences that suggest they are not alone. Throughout their adventures, they uncover the island's secrets, including the presence of Captain Nemo, who has been living in seclusion. The story highlights themes of survival, cooperation, and the triumph of human spirit and intelligence.", },]
export function DrawerExample() { const [editOpen, showEdit, closeEdit] = useToggleState() const [bookToEdit, setBookToEdit] = React.useState<Book | null>(null)
const editBook = (book: Book) => { setBookToEdit(book) showEdit() }
const onSave = () => { // update closeEdit() }
return ( <> <Table className="overflow-scroll"> <TableHead> <TableRow> <TableHeaderCell>Title</TableHeaderCell> <TableHeaderCell>Author</TableHeaderCell> <TableHeaderCell>Year</TableHeaderCell> <TableHeaderCell className="text-right">Actions</TableHeaderCell> </TableRow> </TableHead> <TableBody> {books.map((book, index) => ( <TableRow key={index}> <TableCell>{book.title}</TableCell> <TableCell className="whitespace-nowrap">{book.author}</TableCell> <TableCell>{book.year}</TableCell> <TableCell className="text-right"> <Button variant="secondary" onClick={() => editBook(book)}> Edit </Button> </TableCell> </TableRow> ))} </TableBody> </Table> <div className="flex justify-center"> <Drawer open={editOpen} onOpenChange={(modalOpened) => { if (!modalOpened) { closeEdit() } }} > <DrawerContent className="sm:max-w-lg"> <DrawerHeader> <DrawerTitle>{bookToEdit?.title}</DrawerTitle> <DrawerDescription className="mt-1 text-sm"> {bookToEdit?.author} - {bookToEdit?.year} </DrawerDescription> </DrawerHeader> <DrawerBody> <p>{bookToEdit?.summary}</p> </DrawerBody> <DrawerFooter className="mt-6"> <DrawerClose asChild> <Button className="mt-2 w-full sm:mt-0 sm:w-fit" variant="secondary" > Go back </Button> </DrawerClose> <Button className="w-full sm:w-fit" onClick={() => onSave()}> Save </Button> </DrawerFooter> </DrawerContent> </Drawer> </div> </> )}
API Reference: Drawer
This component uses the Radix UI API.
API Reference: DrawerTrigger
This component uses the Radix UI API.
API Reference: DrawerContent
This component uses the Radix UI API.
API Reference: DrawerHeader
This component is based on the div element and supports all of its props.
API Reference: DrawerTitle
This component uses the Radix UI API.
API Reference: DrawerBody
This component is based on the div element and supports all of its props.
API Reference: DrawerDescription
This component uses the Radix UI API.
API Reference: DrawerFooter
This component is based on the div element and supports all of its props.
API Reference: DrawerClose
This component uses the Radix UI API.