shadimage

Sheet

A sheet component that slides in from different sides with blur effects

source cached 2026-10-19

registry/preskok/ui/preskok-ui/sheet.tsx

"use client"

import { twJoin } from "cn"
import { composeRenderProps } from "react-aria-components/composeRenderProps"
import type {
  DialogProps,
  DialogTriggerProps,
  ModalOverlayProps,
} from "react-aria-components/Modal"
import {
  DialogTrigger as DialogTriggerPrimitive,
  Modal,
  ModalOverlay,
} from "react-aria-components/Modal"
import { tv } from "tailwind-variants"

import {
  Dialog,
  DialogBody,
  DialogClose,
  DialogCloseIcon,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "./dialog"

type Sides = "top" | "bottom" | "left" | "right"
const generateCompoundVariants = (sides: Array<Sides>) => {
  return sides.map((side) => ({
    side,
    isFloat: true,
    className:
      side === "top"
        ? "top-2 inset-x-2 rounded-lg ring-1 border-b-0"
        : side === "bottom"
          ? "bottom-2 inset-x-2 rounded-lg ring-1 border-t-0"
          : side === "left"
            ? "left-2 inset-y-2 rounded-lg ring-1 border-r-0"
            : "right-2 inset-y-2 rounded-lg ring-1 border-l-0",
  }))
}

const sheetContentStyles = tv({
  base: [
    "fixed z-50 grid gap-4 border-muted-foreground/20 bg-overlay text-overlay-foreground shadow-lg dark:border-border",
    "transform-gpu transition ease-in-out will-change-transform",
  ],
  variants: {
    isEntering: {
      true: "animate-in duration-500 fade-in",
    },
    isExiting: {
      true: "animate-out duration-300 fade-in",
    },
    side: {
      top: "inset-x-0 top-0 rounded-b-2xl border-b entering:slide-in-from-top exiting:slide-out-to-top",
      bottom:
        "inset-x-0 bottom-0 rounded-t-2xl border-t entering:slide-in-from-bottom exiting:slide-out-to-bottom",
      left: "inset-y-0 left-0 h-auto w-3/4 overflow-y-auto border-r sm:max-w-80 entering:slide-in-from-left exiting:slide-out-to-left-80",
      right:
        "inset-y-0 right-0 h-auto w-3/4 overflow-y-auto border-l sm:max-w-80 entering:slide-in-from-right exiting:slide-out-to-right-80",
    },
    isFloat: {
      false: "border-foreground/20 dark:border-border",
      true: "ring-foreground/5 dark:ring-border",
    },
  },
  compoundVariants: generateCompoundVariants([
    "top",
    "bottom",
    "left",
    "right",
  ]),
})

type SheetProps = DialogTriggerProps
const Sheet = (props: SheetProps) => {
  return <DialogTriggerPrimitive {...props} />
}

interface SheetContentProps
  extends
    Omit<ModalOverlayProps, "children">,
    Pick<DialogProps, "aria-label" | "role" | "aria-labelledby" | "children"> {
  closeButton?: boolean
  isBlurred?: boolean
  isFloat?: boolean
  side?: Sides
  overlay?: Omit<ModalOverlayProps, "children">
}

const SheetContent = ({
  className,
  isBlurred = false,
  isDismissable: isDismissableInternal,
  side = "right",
  role = "dialog",
  closeButton = true,
  isFloat = true,
  overlay,
  children,
  ...props
}: SheetContentProps) => {
  const isDismissable = isDismissableInternal ?? role !== "alertdialog"
  return (
    <ModalOverlay
      {...overlay}
      isDismissable={isDismissable}
      className={composeRenderProps(
        overlay?.className,
        (className, { isExiting, isEntering }) =>
          twJoin(
            "fixed inset-0 z-50 h-(--visual-viewport-height,100vh) w-screen overflow-hidden bg-black/15",
            isEntering && "fade-in animate-in duration-500",
            isExiting && "fade-out animate-out duration-300",
            isBlurred && "backdrop-blur-[1px] backdrop-filter",
            className
          )
      )}
      {...props}
    >
      <Modal
        className={composeRenderProps(className, (className, renderProps) =>
          sheetContentStyles({
            ...renderProps,
            side,
            isFloat,
            className,
          })
        )}
      >
        <Dialog aria-label={props["aria-label"]} role={role}>
          {(values) => (
            <>
              {typeof children === "function" ? children(values) : children}
              {closeButton && (
                <DialogCloseIcon
                  className="top-2.5 right-2.5"
                  isDismissable={isDismissable}
                />
              )}
            </>
          )}
        </Dialog>
      </Modal>
    </ModalOverlay>
  )
}

const SheetTrigger = DialogTrigger
const SheetFooter = DialogFooter
const SheetHeader = DialogHeader
const SheetTitle = DialogTitle
const SheetDescription = DialogDescription
const SheetBody = DialogBody
const SheetClose = DialogClose

export {
  Sheet,
  SheetBody,
  SheetClose,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
}
export type { SheetContentProps, SheetProps, Sides }
Visual style
glass-blur

DependenciesBundle weight · light

react-aria-componentscn@^0.2.4tailwind-variants
Registry dependencies
https://ui-three-mu.vercel.app/r/dialog.json