{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "drawer",
  "title": "Drawer",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "A swipe-to-dismiss panel from any edge on Vaul, with grab handle, header, scrollable body and footer, drag-safe inner content and reduced-motion handling.",
  "dependencies": [
    "vaul@^1"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/drawer.tsx",
      "content": "// Ballmac UI: Drawer. https://ui.ballmac.com/components/drawer\n// Based on shadcn/ui Drawer (MIT, Copyright (c) 2023 shadcn) on Vaul (MIT, Copyright (c) 2023 Emil Kowalski), restyled with tokens, a grab handle, all four directions, and a scrollable body.\n\"use client\";\n\nimport * as React from \"react\";\nimport { Drawer as Primitive } from \"vaul\";\nimport { cn } from \"@/lib/utils\";\n\ntype DrawerProps = React.ComponentProps<typeof Primitive.Root>;\n/** Root. Props come from Vaul: `direction`, `snapPoints`, `activeSnapPoint`, `dismissible`, `shouldScaleBackground`, `modal`. */\nfunction Drawer({ shouldScaleBackground = false, ...props }: DrawerProps) {\n  return <Primitive.Root data-slot=\"drawer\" shouldScaleBackground={shouldScaleBackground} {...props} />;\n}\n\ntype DrawerTriggerProps = React.ComponentProps<typeof Primitive.Trigger>;\nfunction DrawerTrigger(props: DrawerTriggerProps) {\n  return <Primitive.Trigger data-slot=\"drawer-trigger\" {...props} />;\n}\n\ntype DrawerCloseProps = React.ComponentProps<typeof Primitive.Close>;\nfunction DrawerClose(props: DrawerCloseProps) {\n  return <Primitive.Close data-slot=\"drawer-close\" {...props} />;\n}\n\ntype DrawerContentProps = React.ComponentProps<typeof Primitive.Content> & {\n  /** Show the grab handle. On by default for bottom drawers. */\n  handle?: boolean;\n};\nfunction DrawerContent({ className, children, handle, ...props }: DrawerContentProps) {\n  return (\n    <Primitive.Portal>\n      <Primitive.Overlay\n        data-slot=\"drawer-overlay\"\n        className=\"fixed inset-0 z-50 bg-black/50 backdrop-blur-[2px]\"\n      />\n      <Primitive.Content\n        data-slot=\"drawer-content\"\n        className={cn(\n          \"group/drawer-content fixed z-50 flex h-auto flex-col bg-card text-card-foreground shadow-[0_24px_48px_-12px_rgb(0_0_0/0.3)] outline-none\",\n          \"data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[90dvh] data-[vaul-drawer-direction=bottom]:rounded-t-2xl data-[vaul-drawer-direction=bottom]:border-t\",\n          \"data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[90dvh] data-[vaul-drawer-direction=top]:rounded-b-2xl data-[vaul-drawer-direction=top]:border-b\",\n          \"data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:end-0 data-[vaul-drawer-direction=right]:w-[min(24rem,calc(100vw-2.5rem))] data-[vaul-drawer-direction=right]:border-s\",\n          \"data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:start-0 data-[vaul-drawer-direction=left]:w-[min(24rem,calc(100vw-2.5rem))] data-[vaul-drawer-direction=left]:border-e\",\n          \"motion-reduce:!transition-none\",\n          className,\n        )}\n        {...props}\n      >\n        <span\n          aria-hidden=\"true\"\n          className={cn(\n            \"mx-auto mt-3 h-1.5 w-12 shrink-0 rounded-full bg-muted-foreground/30\",\n            handle === false ? \"hidden\" : handle ? \"block\" : \"hidden group-data-[vaul-drawer-direction=bottom]/drawer-content:block\",\n          )}\n        />\n        {children}\n      </Primitive.Content>\n    </Primitive.Portal>\n  );\n}\n\ntype DrawerHeaderProps = React.ComponentProps<\"div\">;\nfunction DrawerHeader({ className, ...props }: DrawerHeaderProps) {\n  return (\n    <div\n      data-slot=\"drawer-header\"\n      className={cn(\"grid gap-1.5 px-5 pt-4 text-center sm:text-start group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center\", className)}\n      {...props}\n    />\n  );\n}\n\ntype DrawerBodyProps = React.ComponentProps<\"div\">;\n/** Scrollable middle region. Focusable so keyboard users can scroll long content. */\nfunction DrawerBody({ className, ...props }: DrawerBodyProps) {\n  return (\n    <div\n      data-slot=\"drawer-body\"\n      tabIndex={0}\n      data-vaul-no-drag=\"\"\n      className={cn(\n        \"min-h-0 flex-1 overflow-y-auto px-5 py-3 outline-none focus-visible:ring-[3px] focus-visible:ring-inset focus-visible:ring-ring/50\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\ntype DrawerFooterProps = React.ComponentProps<\"div\">;\nfunction DrawerFooter({ className, ...props }: DrawerFooterProps) {\n  return (\n    <div\n      data-slot=\"drawer-footer\"\n      className={cn(\"mt-auto flex flex-col gap-2 px-5 pt-3 pb-[max(1.25rem,env(safe-area-inset-bottom))]\", className)}\n      {...props}\n    />\n  );\n}\n\ntype DrawerTitleProps = React.ComponentProps<typeof Primitive.Title>;\nfunction DrawerTitle({ className, ...props }: DrawerTitleProps) {\n  return (\n    <Primitive.Title\n      data-slot=\"drawer-title\"\n      className={cn(\"text-lg leading-tight font-semibold tracking-tight\", className)}\n      {...props}\n    />\n  );\n}\n\ntype DrawerDescriptionProps = React.ComponentProps<typeof Primitive.Description>;\nfunction DrawerDescription({ className, ...props }: DrawerDescriptionProps) {\n  return (\n    <Primitive.Description\n      data-slot=\"drawer-description\"\n      className={cn(\"text-sm text-muted-foreground\", className)}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Drawer,\n  DrawerTrigger,\n  DrawerClose,\n  DrawerContent,\n  DrawerHeader,\n  DrawerBody,\n  DrawerFooter,\n  DrawerTitle,\n  DrawerDescription,\n  type DrawerProps,\n  type DrawerTriggerProps,\n  type DrawerCloseProps,\n  type DrawerContentProps,\n  type DrawerHeaderProps,\n  type DrawerBodyProps,\n  type DrawerFooterProps,\n  type DrawerTitleProps,\n  type DrawerDescriptionProps,\n};\n",
      "type": "registry:ui",
      "target": "@components/ballmac/drawer.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "overlay",
      "mobile",
      "bottom sheet",
      "vaul"
    ],
    "version": "1.0.0",
    "updated": "2026-09-30",
    "ai": {
      "summary": "Bottom drawers suit mobile actions; use direction left or right for side panels. Compose DrawerHeader, DrawerBody and DrawerFooter.",
      "whenToUse": [
        "Mobile-first confirmations and pickers",
        "Panels users may want to flick away"
      ],
      "whenNotToUse": [
        "Desktop settings panels; use sheet",
        "Critical confirmations; use alert-dialog"
      ],
      "composesWith": [
        "button",
        "sheet"
      ],
      "a11y": [
        {
          "keys": "Tab / Shift+Tab",
          "action": "Cycles focus inside the drawer"
        },
        {
          "keys": "Escape",
          "action": "Closes and returns focus to the trigger"
        },
        {
          "keys": "Drag / swipe",
          "action": "Dismisses by gesture"
        }
      ],
      "customization": [
        "direction",
        "snapPoints",
        "dismissible",
        "handle visibility"
      ]
    },
    "source": {
      "name": "shadcn/ui Drawer",
      "url": "https://github.com/shadcn-ui/ui",
      "license": "MIT",
      "copyright": "Copyright (c) 2023 shadcn",
      "modified": true
    },
    "examples": [
      "drawer-demo",
      "drawer-states"
    ],
    "url": "https://ui.ballmac.com/components/drawer"
  },
  "categories": [
    "primitives"
  ],
  "type": "registry:ui"
}