{
  "name": "drawer",
  "dependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "drawer.tsx",
      "content": "\"use client\";\r\n\r\nimport { createVariants, css, fadeIn, fadeOut, keyframes, themeVars as theme } from \"@yugnex/core\";\r\nimport { useClickOutside, useEscapeKey, useFocusTrap, usePortal, usePresence } from \"@yugnex/core/client\";\r\nimport { createContext, useContext, useEffect, type HTMLAttributes, type ReactNode } from \"react\";\r\nimport { createPortal } from \"react-dom\";\r\n\r\nexport type DrawerSide = \"left\" | \"right\" | \"top\" | \"bottom\";\r\n\r\ninterface DrawerContextValue {\r\n  open: boolean;\r\n  onOpenChange: (open: boolean) => void;\r\n}\r\n\r\nconst DrawerContext = createContext<DrawerContextValue | null>(null);\r\n\r\n/** Read the drawer's state from inside <DrawerContent> — e.g. for a close button. */\r\nexport function useDrawer(): DrawerContextValue {\r\n  const ctx = useContext(DrawerContext);\r\n  if (!ctx) throw new Error(\"useDrawer must be used within <Drawer>\");\r\n  return ctx;\r\n}\r\n\r\nconst slideInX = (from: string) =>\r\n  keyframes({ from: { transform: `translateX(${from})` }, to: { transform: \"translateX(0)\" } });\r\nconst slideOutX = (to: string) =>\r\n  keyframes({ from: { transform: \"translateX(0)\" }, to: { transform: `translateX(${to})` } });\r\nconst slideInY = (from: string) =>\r\n  keyframes({ from: { transform: `translateY(${from})` }, to: { transform: \"translateY(0)\" } });\r\nconst slideOutY = (to: string) =>\r\n  keyframes({ from: { transform: \"translateY(0)\" }, to: { transform: `translateY(${to})` } });\r\n\r\nconst inLeft = slideInX(\"-100%\");\r\nconst outLeft = slideOutX(\"-100%\");\r\nconst inRight = slideInX(\"100%\");\r\nconst outRight = slideOutX(\"100%\");\r\nconst inTop = slideInY(\"-100%\");\r\nconst outTop = slideOutY(\"-100%\");\r\nconst inBottom = slideInY(\"100%\");\r\nconst outBottom = slideOutY(\"100%\");\r\n\r\nconst overlayClass = css({\r\n  position: \"fixed\",\r\n  inset: 0,\r\n  zIndex: theme.zIndex.overlay,\r\n  backgroundColor: theme.color.overlay,\r\n  '&[data-state=\"open\"]': { animation: `${fadeIn} ${theme.duration.base} ${theme.easing.standard}` },\r\n  '&[data-state=\"closed\"]': { animation: `${fadeOut} ${theme.duration.base} ${theme.easing.standard}` },\r\n});\r\n\r\nconst contentVariants = createVariants({\r\n  base: {\r\n    position: \"fixed\",\r\n    zIndex: theme.zIndex.modal,\r\n    display: \"flex\",\r\n    flexDirection: \"column\",\r\n    backgroundColor: theme.color.card,\r\n    color: theme.color.cardForeground,\r\n    boxShadow: theme.shadow.overlay,\r\n    padding: theme.space[6],\r\n    overflowY: \"auto\",\r\n    \"&:focus-visible\": { outline: \"none\" },\r\n  },\r\n  variants: {\r\n    side: {\r\n      left: {\r\n        top: 0,\r\n        left: 0,\r\n        bottom: 0,\r\n        width: \"min(22rem, 90vw)\",\r\n        borderRight: `1px solid ${theme.color.border}`,\r\n        '&[data-state=\"open\"]': { animation: `${inLeft} ${theme.duration.base} ${theme.easing.decelerate}` },\r\n        '&[data-state=\"closed\"]': { animation: `${outLeft} ${theme.duration.base} ${theme.easing.accelerate}` },\r\n      },\r\n      right: {\r\n        top: 0,\r\n        right: 0,\r\n        bottom: 0,\r\n        width: \"min(22rem, 90vw)\",\r\n        borderLeft: `1px solid ${theme.color.border}`,\r\n        '&[data-state=\"open\"]': { animation: `${inRight} ${theme.duration.base} ${theme.easing.decelerate}` },\r\n        '&[data-state=\"closed\"]': { animation: `${outRight} ${theme.duration.base} ${theme.easing.accelerate}` },\r\n      },\r\n      top: {\r\n        top: 0,\r\n        left: 0,\r\n        right: 0,\r\n        maxHeight: \"80vh\",\r\n        borderBottom: `1px solid ${theme.color.border}`,\r\n        '&[data-state=\"open\"]': { animation: `${inTop} ${theme.duration.base} ${theme.easing.decelerate}` },\r\n        '&[data-state=\"closed\"]': { animation: `${outTop} ${theme.duration.base} ${theme.easing.accelerate}` },\r\n      },\r\n      bottom: {\r\n        bottom: 0,\r\n        left: 0,\r\n        right: 0,\r\n        maxHeight: \"80vh\",\r\n        borderTop: `1px solid ${theme.color.border}`,\r\n        borderTopLeftRadius: theme.radius.lg,\r\n        borderTopRightRadius: theme.radius.lg,\r\n        '&[data-state=\"open\"]': { animation: `${inBottom} ${theme.duration.base} ${theme.easing.decelerate}` },\r\n        '&[data-state=\"closed\"]': { animation: `${outBottom} ${theme.duration.base} ${theme.easing.accelerate}` },\r\n      },\r\n    },\r\n  },\r\n  defaultVariants: { side: \"right\" },\r\n});\r\n\r\nconst headerClass = css({\r\n  display: \"flex\",\r\n  flexDirection: \"column\",\r\n  gap: theme.space[1.5],\r\n  marginBottom: theme.space[4],\r\n});\r\nconst titleClass = css({ margin: 0, fontSize: theme.fontSize.lg, fontWeight: theme.fontWeight.semibold });\r\nconst descriptionClass = css({ margin: 0, fontSize: theme.fontSize.sm, color: theme.color.mutedForeground });\r\nconst footerClass = css({\r\n  display: \"flex\",\r\n  justifyContent: \"flex-end\",\r\n  gap: theme.space[2],\r\n  marginTop: \"auto\",\r\n  paddingTop: theme.space[6],\r\n});\r\n\r\nexport interface DrawerProps {\r\n  open: boolean;\r\n  onOpenChange: (open: boolean) => void;\r\n  children: ReactNode;\r\n}\r\n\r\n/** An edge-anchored panel — the sheet pattern for filters, details, and mobile navigation. */\r\nexport function Drawer({ open, onOpenChange, children }: DrawerProps) {\r\n  return <DrawerContext.Provider value={{ open, onOpenChange }}>{children}</DrawerContext.Provider>;\r\n}\r\n\r\nexport interface DrawerContentProps extends HTMLAttributes<HTMLDivElement> {\r\n  side?: DrawerSide;\r\n  children: ReactNode;\r\n}\r\n\r\nexport function DrawerContent({ side = \"right\", className, children, ...props }: DrawerContentProps) {\r\n  const { open, onOpenChange } = useDrawer();\r\n  const { mounted, dataState } = usePresence(open, { exitDuration: 200 });\r\n  const portalNode = usePortal();\r\n  // Keyed on `mounted`, not `open`: the node only exists a tick after open flips.\r\n  const contentRef = useFocusTrap<HTMLDivElement>(mounted);\r\n\r\n  useEscapeKey(() => onOpenChange(false), open);\r\n  useClickOutside(contentRef, () => onOpenChange(false), open);\r\n\r\n  useEffect(() => {\r\n    if (!mounted) return;\r\n    const original = document.body.style.overflow;\r\n    document.body.style.overflow = \"hidden\";\r\n    return () => {\r\n      document.body.style.overflow = original;\r\n    };\r\n  }, [mounted]);\r\n\r\n  if (!mounted || !portalNode) return null;\r\n\r\n  return createPortal(\r\n    <>\r\n      <div className={overlayClass} data-state={dataState} aria-hidden=\"true\" />\r\n      <div\r\n        ref={contentRef}\r\n        role=\"dialog\"\r\n        aria-modal=\"true\"\r\n        tabIndex={-1}\r\n        data-state={dataState}\r\n        className={contentVariants({ side, className })}\r\n        {...props}\r\n      >\r\n        {children}\r\n      </div>\r\n    </>,\r\n    portalNode,\r\n  );\r\n}\r\n\r\nexport function DrawerHeader({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\r\n  return <div className={className ? `${headerClass} ${className}` : headerClass} {...props} />;\r\n}\r\n\r\nexport function DrawerTitle({ className, ...props }: HTMLAttributes<HTMLHeadingElement>) {\r\n  return <h2 className={className ? `${titleClass} ${className}` : titleClass} {...props} />;\r\n}\r\n\r\nexport function DrawerDescription({ className, ...props }: HTMLAttributes<HTMLParagraphElement>) {\r\n  return <p className={className ? `${descriptionClass} ${className}` : descriptionClass} {...props} />;\r\n}\r\n\r\nexport function DrawerFooter({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\r\n  return <div className={className ? `${footerClass} ${className}` : footerClass} {...props} />;\r\n}\r\n",
      "type": "registry:component"
    }
  ]
}