{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sheet-dialog",
  "title": "Sheet Dialog",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "A macOS sheet: a frosted panel that drops from the top edge of its window and dims only that window, with a default accent button, on Radix Dialog for focus trapping and Escape to cancel.",
  "dependencies": [
    "motion@^12",
    "radix-ui"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/sheet-dialog.tsx",
      "content": "// Ballmac UI: Sheet Dialog. https://ui.ballmac.com/components/sheet-dialog\n\"use client\"\n\nimport * as React from \"react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\nimport { Dialog as DialogPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype Container = HTMLElement | null | React.RefObject<HTMLElement | null>\n\ntype SheetContextValue = { open: boolean; container?: Container }\nconst SheetContext = React.createContext<SheetContextValue>({ open: false })\n\ntype SheetDialogProps = {\n  children: React.ReactNode\n  /** Controlled open state. */\n  open?: boolean\n  /** Initial open state when uncontrolled. */\n  defaultOpen?: boolean\n  /** Called when the sheet opens or closes. */\n  onOpenChange?: (open: boolean) => void\n  /**\n   * The window the sheet attaches to: an element or a ref to one (it should be `relative` and `overflow-hidden`).\n   * Without it the sheet attaches to the whole page.\n   */\n  container?: Container\n}\n\n/** A macOS sheet: a panel that drops from the top of its window and dims only that window. */\nfunction SheetDialog({ children, open: openProp, defaultOpen = false, onOpenChange, container }: SheetDialogProps) {\n  const [openState, setOpenState] = React.useState(defaultOpen)\n  const open = openProp ?? openState\n  const value = React.useMemo(() => ({ open, container }), [open, container])\n  return (\n    <SheetContext.Provider value={value}>\n      <DialogPrimitive.Root\n        open={open}\n        onOpenChange={(next) => {\n          setOpenState(next)\n          onOpenChange?.(next)\n        }}\n      >\n        {children}\n      </DialogPrimitive.Root>\n    </SheetContext.Provider>\n  )\n}\n\nfunction SheetDialogTrigger({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {\n  return <DialogPrimitive.Trigger data-slot=\"sheet-dialog-trigger\" className={className} {...props} />\n}\n\nfunction SheetDialogClose({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {\n  return <DialogPrimitive.Close data-slot=\"sheet-dialog-close\" className={className} {...props} />\n}\n\ntype SheetDialogContentProps = Omit<React.ComponentProps<\"div\">, \"ref\" | \"title\">\n\nfunction SheetDialogContent({ className, children, ...props }: SheetDialogContentProps) {\n  const { open, container } = React.useContext(SheetContext)\n  const reduce = useReducedMotion()\n  const host = container && \"current\" in container ? container.current : container\n  return (\n    <DialogPrimitive.Portal forceMount container={host ?? undefined}>\n      <AnimatePresence>\n        {open && (\n          <>\n            {React.createElement(\n              DialogPrimitive.Overlay,\n              { forceMount: true, asChild: true },\n              <motion.div\n                data-slot=\"sheet-dialog-overlay\"\n                className={cn(\"z-50 bg-black/25 dark:bg-black/45\", host ? \"absolute inset-0\" : \"fixed inset-0\")}\n                initial={{ opacity: 0 }}\n                animate={{ opacity: 1 }}\n                exit={{ opacity: 0 }}\n                transition={{ duration: reduce ? 0 : 0.2 }}\n              />\n            )}\n            {React.createElement(\n              DialogPrimitive.Content,\n              { forceMount: true, asChild: true },\n              <motion.div\n                data-slot=\"sheet-dialog-content\"\n                className={cn(\n                  \"z-50 flex w-[min(28rem,calc(100%-2rem))] flex-col gap-4 rounded-b-[12px] border border-t-0 border-foreground/10 bg-popover/90 p-5 text-popover-foreground shadow-[0_18px_50px_-10px_rgb(0_0_0/0.45)] backdrop-blur-2xl backdrop-saturate-150 outline-none [--mac-accent:oklch(0.53_0.2_258)]\",\n                  host ? \"absolute top-0 left-1/2\" : \"fixed top-0 left-1/2\",\n                  className\n                )}\n                style={{ x: \"-50%\" }}\n                initial={reduce ? { opacity: 0 } : { y: \"-102%\" }}\n                animate={reduce ? { opacity: 1 } : { y: 0 }}\n                exit={reduce ? { opacity: 0 } : { y: \"-102%\" }}\n                transition={reduce ? { duration: 0 } : { type: \"spring\", stiffness: 420, damping: 38, mass: 0.9 }}\n                {...(props as object)}\n              >\n                {children}\n              </motion.div>\n            )}\n          </>\n        )}\n      </AnimatePresence>\n    </DialogPrimitive.Portal>\n  )\n}\n\nfunction SheetDialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return <div data-slot=\"sheet-dialog-header\" className={cn(\"flex items-start gap-4\", className)} {...props} />\n}\n\nfunction SheetDialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {\n  return <DialogPrimitive.Title data-slot=\"sheet-dialog-title\" className={cn(\"text-[15px] leading-5 font-semibold\", className)} {...props} />\n}\n\nfunction SheetDialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>) {\n  return <DialogPrimitive.Description data-slot=\"sheet-dialog-description\" className={cn(\"mt-1 text-[13px] leading-5 text-muted-foreground\", className)} {...props} />\n}\n\nfunction SheetDialogFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return <div data-slot=\"sheet-dialog-footer\" className={cn(\"flex items-center justify-end gap-2\", className)} {...props} />\n}\n\ntype SheetDialogButtonProps = React.ComponentProps<\"button\"> & {\n  /** The default button: filled with the accent color, and the one Return activates inside a form. */\n  primary?: boolean\n  /** Destructive default buttons use red. */\n  destructive?: boolean\n}\n\n/** A macOS push button for sheets. */\nfunction SheetDialogButton({ primary = false, destructive = false, className, type = \"button\", ...props }: SheetDialogButtonProps) {\n  return (\n    <button\n      type={type}\n      data-slot=\"sheet-dialog-button\"\n      className={cn(\n        \"inline-flex h-[22px] min-w-[4.25rem] items-center justify-center rounded-[6px] px-3 text-[13px] outline-none transition-[background-color,box-shadow] focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:opacity-50\",\n        primary\n          ? cn(\"text-white shadow-[0_0.5px_1px_rgb(0_0_0/0.3),inset_0_0.5px_0_rgb(255_255_255/0.3)] hover:brightness-110\", destructive ? \"bg-destructive\" : \"bg-(--mac-accent)\")\n          : \"border border-foreground/10 bg-background/80 shadow-[0_0.5px_1px_rgb(0_0_0/0.15)] hover:bg-background dark:bg-foreground/[0.12] dark:hover:bg-foreground/[0.18]\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  SheetDialog,\n  SheetDialogTrigger,\n  SheetDialogContent,\n  SheetDialogHeader,\n  SheetDialogTitle,\n  SheetDialogDescription,\n  SheetDialogFooter,\n  SheetDialogClose,\n  SheetDialogButton,\n  type SheetDialogProps,\n  type SheetDialogContentProps,\n  type SheetDialogButtonProps,\n}\n",
      "type": "registry:ui",
      "target": "@components/ballmac/sheet-dialog.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "sheet",
      "dialog",
      "macos",
      "modal",
      "window"
    ],
    "version": "1.0.0",
    "updated": "2026-10-01",
    "ai": {
      "summary": "<SheetDialog container={windowRef}> with SheetDialogTrigger and SheetDialogContent (Header, Title, Description, Footer, Button primary). Give it the window element so it attaches there; without it it drops from the page.",
      "whenToUse": [
        "Confirmations and short forms attached to a window",
        "Mac-style apps and demos"
      ],
      "whenNotToUse": [
        "General modals (dialog)",
        "Side panels (sheet)"
      ],
      "composesWith": [
        "mac-window",
        "dialog",
        "window-manager"
      ],
      "a11y": [
        {
          "keys": "Escape",
          "action": "Cancels"
        },
        {
          "keys": "Focus",
          "action": "Trapped in the sheet and returned to the trigger on close"
        },
        {
          "keys": "Screen readers",
          "action": "Named dialog with description"
        },
        {
          "keys": "Reduced motion",
          "action": "The sheet fades in place"
        }
      ],
      "customization": [
        "container",
        "primary and destructive buttons",
        "className on the content"
      ]
    },
    "examples": [
      "sheet-dialog-demo",
      "sheet-dialog-form"
    ],
    "url": "https://ui.ballmac.com/components/sheet-dialog"
  },
  "categories": [
    "macos"
  ],
  "type": "registry:ui"
}