{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "drawer", "type": "registry:ui", "title": "Drawer", "description": "A drawer component that can be used to display a modal window.", "registryDependencies": [ "@tra-kit/button" ], "files": [ { "path": "registry/tra-kit/components/drawer.tsx", "content": "/* eslint-disable tailwindcss/no-custom-classname */\r\nimport React, {\r\n Fragment,\r\n createContext,\r\n useCallback,\r\n useContext,\r\n useEffect,\r\n useState,\r\n useRef,\r\n} from 'react';\r\nimport { createPortal } from 'react-dom';\r\nimport { cva } from 'class-variance-authority';\r\nimport { XIcon } from '@phosphor-icons/react';\r\nimport type { VariantProps } from 'class-variance-authority';\r\nimport { cn, preventScrollShift } from '@/lib/utils';\r\nimport Button from '@/components/ui/button';\r\n\r\nexport type DrawerPosition = 'left' | 'right' | 'top' | 'bottom';\r\n\r\ninterface DrawerContextProps {\r\n open: boolean;\r\n setOpen: (open: boolean) => void;\r\n position?: DrawerPosition;\r\n disableBackdropClick?: boolean;\r\n disableEscapeKeyDown?: boolean;\r\n showCloseButton?: boolean;\r\n}\r\n\r\nconst DrawerContext = createContext(undefined);\r\n\r\nconst useDrawer = () => {\r\n const context = useContext(DrawerContext);\r\n if (!context) {\r\n throw new Error('useDrawer must be used within a component.');\r\n }\r\n return context;\r\n};\r\n\r\ninterface DrawerProps {\r\n children: React.ReactNode;\r\n open?: boolean;\r\n defaultOpen?: boolean;\r\n onOpenChange?: (open: boolean) => void;\r\n position?: DrawerPosition;\r\n disableBackdropClick?: boolean;\r\n disableEscapeKeyDown?: boolean;\r\n showCloseButton?: boolean;\r\n}\r\n\r\nconst Drawer: React.FC = ({\r\n children,\r\n open: controlledOpen,\r\n defaultOpen = false,\r\n onOpenChange,\r\n position = 'left',\r\n disableBackdropClick = false,\r\n disableEscapeKeyDown = false,\r\n showCloseButton = true,\r\n}) => {\r\n const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);\r\n const isControlled = controlledOpen !== undefined;\r\n const open = isControlled ? controlledOpen : uncontrolledOpen;\r\n\r\n const setOpen = useCallback(\r\n (value: boolean) => {\r\n if (!isControlled) {\r\n setUncontrolledOpen(value);\r\n }\r\n onOpenChange?.(value);\r\n },\r\n [isControlled, onOpenChange],\r\n );\r\n\r\n return (\r\n \r\n {children}\r\n \r\n );\r\n};\r\n\r\ninterface DrawerTriggerProps {\r\n children?: React.ReactNode;\r\n asChild?: boolean;\r\n className?: string;\r\n}\r\n\r\nconst DrawerTrigger: React.FC = ({ children, asChild = false, className }) => {\r\n const { setOpen } = useDrawer();\r\n\r\n if (asChild) {\r\n if (!React.isValidElement(children)) {\r\n throw new Error('DrawerTrigger: when using asChild, children must be a single React element');\r\n }\r\n\r\n return React.cloneElement(children as React.ReactElement, {\r\n onClick: (e: React.MouseEvent) => {\r\n (children as React.ReactElement).props.onClick?.(e);\r\n setOpen(true);\r\n },\r\n });\r\n }\r\n\r\n return (\r\n setOpen(true)}\r\n >\r\n {children}\r\n \r\n );\r\n};\r\n\r\nconst overlayVariants = cva(\r\n 'fixed inset-0 z-50 bg-black/50 duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',\r\n);\r\n\r\nconst contentVariants = cva(\r\n 'fixed z-50 bg-background shadow-lg duration-300 data-[state=open]:animate-in data-[state=closed]:animate-out',\r\n {\r\n variants: {\r\n position: {\r\n left: 'inset-y-0 left-0 h-full w-full sm:max-w-sm data-[state=open]:slide-in-from-left data-[state=closed]:slide-out-to-left',\r\n right:\r\n 'inset-y-0 right-0 h-full w-full sm:max-w-sm data-[state=open]:slide-in-from-right data-[state=closed]:slide-out-to-right',\r\n top: 'inset-x-0 top-0 w-full max-h-[85vh] data-[state=open]:slide-in-from-top data-[state=closed]:slide-out-to-top',\r\n bottom:\r\n 'inset-x-0 bottom-0 w-full max-h-[85vh] data-[state=open]:slide-in-from-bottom data-[state=closed]:slide-out-to-bottom',\r\n },\r\n },\r\n defaultVariants: {\r\n position: 'left',\r\n },\r\n },\r\n);\r\n\r\ninterface DrawerContentProps\r\n extends React.HTMLAttributes, VariantProps {\r\n className?: string;\r\n children: React.ReactNode;\r\n}\r\n\r\nconst DrawerContent: React.FC = ({ className, children, ...props }) => {\r\n const { open, setOpen, position, disableBackdropClick, disableEscapeKeyDown, showCloseButton } =\r\n useDrawer();\r\n const drawerRef = useRef(null);\r\n\r\n useEffect(() => {\r\n if (open) {\r\n preventScrollShift.lock();\r\n }\r\n\r\n return () => {\r\n preventScrollShift.unlock();\r\n };\r\n }, [open]);\r\n\r\n useEffect(() => {\r\n if (!open) return;\r\n\r\n const handleEscape = (e: KeyboardEvent) => {\r\n if (e.key === 'Escape' && !disableEscapeKeyDown) {\r\n setOpen(false);\r\n }\r\n };\r\n\r\n document.addEventListener('keydown', handleEscape);\r\n return () => document.removeEventListener('keydown', handleEscape);\r\n }, [open, disableEscapeKeyDown, setOpen]);\r\n\r\n const handleBackdropClick = useCallback(\r\n (e: React.MouseEvent) => {\r\n e.stopPropagation();\r\n if (!disableBackdropClick) {\r\n setOpen(false);\r\n }\r\n },\r\n [disableBackdropClick, setOpen],\r\n );\r\n\r\n const handleContentClick = useCallback((e: React.MouseEvent) => {\r\n e.stopPropagation();\r\n }, []);\r\n\r\n if (!open) return null;\r\n\r\n return createPortal(\r\n \r\n \r\n\r\n \r\n {children}\r\n\r\n {showCloseButton && (\r\n setOpen(false)}\r\n className=\"absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground\"\r\n >\r\n \r\n Close\r\n \r\n )}\r\n \r\n ,\r\n document.body,\r\n );\r\n};\r\n\r\nconst DrawerHeader = ({ className, ...props }: React.HTMLAttributes) => (\r\n \r\n);\r\n\r\nconst DrawerFooter = ({ className, ...props }: React.HTMLAttributes) => (\r\n \r\n);\r\n\r\nconst DrawerTitle = ({ className, ...props }: React.HTMLAttributes) => (\r\n \r\n);\r\n\r\nconst DrawerDescription = ({ className, ...props }: React.HTMLAttributes) => (\r\n \r\n);\r\n\r\nconst DrawerBody = ({ className, ...props }: React.HTMLAttributes) => (\r\n \r\n);\r\n\r\nexport {\r\n Drawer,\r\n DrawerTrigger,\r\n DrawerContent,\r\n DrawerHeader,\r\n DrawerFooter,\r\n DrawerTitle,\r\n DrawerDescription,\r\n DrawerBody,\r\n};\r\n", "type": "registry:ui" } ] }