{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"sheet","type":"registry:ui","title":"Sheet","description":"A side panel that slides in from any edge of the screen over a light overlay.","dependencies":["@radix-ui/react-dialog@^1.1.23","@untitledui/icons@^0.0.23","motion@^14.0.0"],"files":[{"path":"components/ui/sheet.tsx","type":"registry:ui","content":"\"use client\";\n\nimport * as SheetPrimitive from \"@radix-ui/react-dialog\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport * as React from \"react\";\nimport { CloseButton } from \"@/components/ui/close-button\";\nimport { DialogOverlay } from \"@/components/ui/dialog\";\nimport { createAnimatedRoot, panelMotion } from \"@/lib/motion\";\nimport { cn } from \"@/lib/utils\";\n\nconst [Sheet, useSheetOpenState] = createAnimatedRoot(SheetPrimitive.Root, \"sheet\");\n\nfunction SheetTrigger({ ...props }: React.ComponentProps<typeof SheetPrimitive.Trigger>) {\n  return <SheetPrimitive.Trigger data-slot=\"sheet-trigger\" {...props} />;\n}\n\nfunction SheetClose({ ...props }: React.ComponentProps<typeof SheetPrimitive.Close>) {\n  return <SheetPrimitive.Close data-slot=\"sheet-close\" {...props} />;\n}\n\nfunction SheetPortal({ ...props }: React.ComponentProps<typeof SheetPrimitive.Portal>) {\n  return <SheetPrimitive.Portal data-slot=\"sheet-portal\" {...props} />;\n}\n\nfunction SheetContent({\n  className,\n  containerClassName,\n  children,\n  side = \"right\",\n  ...props\n}: React.ComponentProps<typeof SheetPrimitive.Content> & {\n  side?: \"top\" | \"right\" | \"bottom\" | \"left\";\n  containerClassName?: string;\n}) {\n  const { open, onExitComplete } = useSheetOpenState();\n\n  return (\n    <AnimatePresence onExitComplete={onExitComplete}>\n      {open && (\n        <SheetPortal forceMount>\n          <DialogOverlay />\n          <SheetPrimitive.Content\n            data-slot=\"sheet-content\"\n            forceMount\n            className={cn(\n              \"fixed z-50 outline-none\",\n              side === \"right\" && \"inset-y-0 right-0 w-3/4 sm:max-w-sm\",\n              side === \"left\" && \"inset-y-0 left-0 w-3/4 sm:max-w-sm\",\n              side === \"top\" && \"inset-x-0 top-0\",\n              side === \"bottom\" && \"inset-x-0 bottom-0\",\n              containerClassName,\n            )}\n            {...props}\n          >\n            <motion.div\n              {...panelMotion(side)}\n              className={cn(\n                \"frosted relative flex h-full w-full flex-col gap-4 shadow-float\",\n                side === \"right\" && \"border-l\",\n                side === \"left\" && \"border-r\",\n                side === \"top\" && \"border-b\",\n                side === \"bottom\" && \"border-t\",\n                className,\n              )}\n            >\n              {children}\n              <SheetPrimitive.Close asChild>\n                <CloseButton />\n              </SheetPrimitive.Close>\n            </motion.div>\n          </SheetPrimitive.Content>\n        </SheetPortal>\n      )}\n    </AnimatePresence>\n  );\n}\n\nfunction SheetHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return <div data-slot=\"sheet-header\" className={cn(\"flex flex-col gap-1.5 p-4\", className)} {...props} />;\n}\n\nfunction SheetFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return <div data-slot=\"sheet-footer\" className={cn(\"mt-auto flex flex-col gap-2 p-4\", className)} {...props} />;\n}\n\nfunction SheetTitle({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Title>) {\n  return (\n    <SheetPrimitive.Title\n      data-slot=\"sheet-title\"\n      className={cn(\"text-foreground font-semibold\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SheetDescription({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Description>) {\n  return (\n    <SheetPrimitive.Description\n      data-slot=\"sheet-description\"\n      className={cn(\"text-muted-foreground text-sm\", className)}\n      {...props}\n    />\n  );\n}\n\nexport { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger };\n"},{"path":"components/ui/close-button.tsx","type":"registry:ui","content":"\"use client\";\n\nimport { XClose } from \"@untitledui/icons\";\nimport { motion } from \"motion/react\";\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nfunction CloseButton({ className, ...props }: React.ComponentProps<typeof motion.button>) {\n  return (\n    <motion.button\n      type=\"button\"\n      data-slot=\"close-button\"\n      whileTap={{ scale: 0.9 }}\n      className={cn(\n        \"text-muted-foreground hover:bg-accent hover:text-foreground absolute top-3 right-3 flex size-8 items-center justify-center rounded-full transition-colors outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid focus-visible:outline-foreground disabled:pointer-events-none [&_svg]:size-4\",\n        className,\n      )}\n      {...props}\n    >\n      <XClose />\n      <span className=\"sr-only\">Close</span>\n    </motion.button>\n  );\n}\n\nexport { CloseButton };\n"},{"path":"components/ui/dialog.tsx","type":"registry:ui","content":"\"use client\";\n\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport * as React from \"react\";\nimport { CloseButton } from \"@/components/ui/close-button\";\nimport { createAnimatedRoot, dialogMotion, overlayMotion } from \"@/lib/motion\";\nimport { cn } from \"@/lib/utils\";\n\nconst [Dialog, useDialogOpenState] = createAnimatedRoot(DialogPrimitive.Root, \"dialog\");\n\nfunction DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {\n  return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {\n  return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {\n  return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {\n  return (\n    <DialogPrimitive.Overlay data-slot=\"dialog-overlay\" forceMount className=\"fixed inset-0 z-50\" {...props}>\n      <motion.div {...overlayMotion} className={cn(\"absolute inset-0 bg-zinc-500/20 dark:bg-black/40\", className)} />\n    </DialogPrimitive.Overlay>\n  );\n}\n\nfunction DialogContent({\n  className,\n  containerClassName,\n  children,\n  showCloseButton = true,\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Content> & {\n  showCloseButton?: boolean;\n  containerClassName?: string;\n}) {\n  const { open, onExitComplete } = useDialogOpenState();\n\n  return (\n    <AnimatePresence onExitComplete={onExitComplete}>\n      {open && (\n        <DialogPortal data-slot=\"dialog-portal\" forceMount>\n          <DialogOverlay />\n          <DialogPrimitive.Content\n            data-slot=\"dialog-content\"\n            forceMount\n            className={cn(\n              \"fixed top-[50%] left-[50%] z-50 w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] outline-none sm:max-w-lg\",\n              containerClassName,\n            )}\n            {...props}\n          >\n            <motion.div\n              {...dialogMotion}\n              className={cn(\"frosted relative grid gap-4 rounded-lg border p-6 shadow-float\", className)}\n            >\n              {children}\n              {showCloseButton && (\n                <DialogPrimitive.Close asChild>\n                  <CloseButton />\n                </DialogPrimitive.Close>\n              )}\n            </motion.div>\n          </DialogPrimitive.Content>\n        </DialogPortal>\n      )}\n    </AnimatePresence>\n  );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"dialog-header\"\n      className={cn(\"flex flex-col gap-2 text-center sm:text-left\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction DialogFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"dialog-footer\"\n      className={cn(\"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {\n  return (\n    <DialogPrimitive.Title\n      data-slot=\"dialog-title\"\n      className={cn(\"text-lg leading-none font-semibold\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction DialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>) {\n  return (\n    <DialogPrimitive.Description\n      data-slot=\"dialog-description\"\n      className={cn(\"text-muted-foreground text-sm\", className)}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogOverlay,\n  DialogPortal,\n  DialogTitle,\n  DialogTrigger,\n};\n"},{"path":"lib/motion.ts","type":"registry:lib","content":"\"use client\";\n\nimport * as React from \"react\";\nimport { useControllableState } from \"@/hooks/use-controllable-state\";\nimport type { MotionProps, Transition } from \"motion/react\";\n\nexport const chewyEase = [0.32, 0.72, 0, 1] as const;\n\nexport const growTransition = { duration: 0.6, ease: chewyEase };\n\nconst springIn: Transition = { type: \"spring\", bounce: 0.2, duration: 0.3 };\nconst quickOut: Transition = { duration: 0.12, ease: \"easeIn\" };\n\nexport const popMotion = {\n  initial: { opacity: 0, scale: 0.96 },\n  animate: { opacity: 1, scale: 1, transition: springIn },\n  exit: { opacity: 0, scale: 0.96, transition: quickOut },\n};\n\nexport const tooltipMotion = {\n  initial: { opacity: 0, scale: 0.94 },\n  animate: { opacity: 1, scale: 1, transition: { ...springIn, duration: 0.2 } },\n  exit: { opacity: 0, scale: 0.94, transition: { duration: 0.08 } },\n};\n\nexport const dialogMotion = {\n  initial: { opacity: 0, scale: 0.95, y: 8 },\n  animate: { opacity: 1, scale: 1, y: 0, transition: { type: \"spring\", bounce: 0.25, duration: 0.4 } as Transition },\n  exit: { opacity: 0, scale: 0.97, y: 4, transition: { duration: 0.15, ease: \"easeIn\" } as Transition },\n};\n\nexport const overlayMotion = {\n  initial: { opacity: 0 },\n  animate: { opacity: 1, transition: { duration: 0.2 } },\n  exit: { opacity: 0, transition: { duration: 0.15 } },\n};\n\nexport type PanelSide = \"top\" | \"right\" | \"bottom\" | \"left\";\n\nexport function panelMotion(side: PanelSide): Pick<MotionProps, \"initial\" | \"animate\" | \"exit\"> {\n  const offscreen = side === \"left\" || side === \"top\" ? \"calc(-100% - 1rem)\" : \"calc(100% + 1rem)\";\n  const horizontal = side === \"left\" || side === \"right\";\n  const hidden = horizontal ? { x: offscreen } : { y: offscreen };\n  const shown = horizontal ? { x: 0 } : { y: 0 };\n\n  return {\n    initial: hidden,\n    animate: { ...shown, transition: { duration: 0.45, ease: chewyEase } },\n    exit: { ...hidden, transition: { duration: 0.3, ease: chewyEase } },\n  };\n}\n\nexport function useAnimatedOpen(\n  openProp: boolean | undefined,\n  defaultOpen: boolean | undefined,\n  onOpenChange: ((open: boolean) => void) | undefined,\n) {\n  const [open, setOpen] = useControllableState(openProp, defaultOpen ?? false, onOpenChange);\n  const [exiting, setExiting] = React.useState(false);\n  const [wasOpen, setWasOpen] = React.useState(open);\n\n  if (open !== wasOpen) {\n    setWasOpen(open);\n    setExiting(!open);\n  }\n\n  const onExitComplete = React.useCallback(() => setExiting(false), []);\n\n  return { open, setOpen, radixOpen: open || exiting, onExitComplete };\n}\n\ntype AnimatedOpenState = { open: boolean; onExitComplete: () => void };\n\ntype OpenProps = { open?: boolean; defaultOpen?: boolean; onOpenChange?: (open: boolean) => void };\n\nexport function createAnimatedRoot<P extends OpenProps>(Primitive: React.ComponentType<P>, slot: string) {\n  const Context = React.createContext<AnimatedOpenState>({ open: false, onExitComplete: () => {} });\n\n  function AnimatedRoot({ open: openProp, defaultOpen, onOpenChange, ...props }: P) {\n    const { open, setOpen, radixOpen, onExitComplete } = useAnimatedOpen(openProp, defaultOpen, onOpenChange);\n    const value = React.useMemo(() => ({ open, onExitComplete }), [open, onExitComplete]);\n    const primitiveProps = { \"data-slot\": slot, ...props, open: radixOpen, onOpenChange: setOpen } as unknown as P;\n\n    return React.createElement(Context.Provider, { value }, React.createElement(Primitive, primitiveProps));\n  }\n\n  return [AnimatedRoot, () => React.useContext(Context)] as const;\n}\n"},{"path":"hooks/use-controllable-state.ts","type":"registry:hook","content":"import * as React from \"react\";\n\nexport function useControllableState<T>(prop: T | undefined, defaultProp: T, onChange?: (value: T) => void) {\n  const [uncontrolled, setUncontrolled] = React.useState(defaultProp);\n  const isControlled = prop !== undefined;\n  const value = isControlled ? prop : uncontrolled;\n\n  const setValue = React.useCallback(\n    (next: T) => {\n      if (!isControlled) setUncontrolled(next);\n      onChange?.(next);\n    },\n    [isControlled, onChange],\n  );\n\n  return [value, setValue] as const;\n}\n"}],"cssVars":{"theme":{"shadow-float":"0 16px 36px -12px oklch(0 0 0 / 0.2), 0 2px 6px -2px oklch(0 0 0 / 0.08)"}},"css":{"@utility frosted":{"background-color":"color-mix(in oklab, var(--popover) 65%, transparent)","backdrop-filter":"blur(20px) saturate(1.6)"}}}