{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sheet",
  "title": "Sheet",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "A modal panel that slides from any edge, with header, scrollable body and footer parts, a grab handle on bottom sheets, and safe-area padding.",
  "dependencies": [
    "radix-ui",
    "lucide-react",
    "class-variance-authority"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/i18n.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/sheet.tsx",
      "content": "// Ballmac UI: Sheet. https://ui.ballmac.com/components/sheet\n// Based on shadcn/ui Sheet (MIT, Copyright (c) 2023 shadcn), adding a scrollable body, a grab handle for bottom sheets, and safe-area padding.\n\"use client\";\n\nimport * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { X } from \"lucide-react\";\nimport { Dialog as Primitive } from \"radix-ui\";\nimport { cn } from \"@/lib/utils\";\nimport { useMessages } from \"@/lib/ballmac/i18n\";\n\ntype SheetProps = React.ComponentProps<typeof Primitive.Root>;\nfunction Sheet(props: SheetProps) {\n  return <Primitive.Root data-slot=\"sheet\" {...props} />;\n}\n\ntype SheetTriggerProps = React.ComponentProps<typeof Primitive.Trigger>;\nfunction SheetTrigger(props: SheetTriggerProps) {\n  return <Primitive.Trigger data-slot=\"sheet-trigger\" {...props} />;\n}\n\ntype SheetCloseProps = React.ComponentProps<typeof Primitive.Close>;\nfunction SheetClose(props: SheetCloseProps) {\n  return <Primitive.Close data-slot=\"sheet-close\" {...props} />;\n}\n\nconst sheetVariants = cva(\n  \"fixed z-50 flex flex-col gap-4 bg-card text-card-foreground shadow-[0_24px_48px_-12px_rgb(0_0_0/0.3)] outline-none duration-300 data-[state=open]:animate-in data-[state=closed]:animate-out motion-reduce:animate-none motion-reduce:duration-0\",\n  {\n    variants: {\n      side: {\n        // `start` and `end` follow the reading direction; `left` and `right` are fixed edges.\n        end: \"inset-y-0 end-0 h-full w-[min(24rem,calc(100vw-2.5rem))] border-s data-[state=open]:slide-in-from-right data-[state=closed]:slide-out-to-right rtl:data-[state=open]:slide-in-from-left rtl:data-[state=closed]:slide-out-to-left sm:w-[min(28rem,calc(100vw-4rem))]\",\n        start:\n          \"inset-y-0 start-0 h-full w-[min(24rem,calc(100vw-2.5rem))] border-e data-[state=open]:slide-in-from-left data-[state=closed]:slide-out-to-left rtl:data-[state=open]:slide-in-from-right rtl:data-[state=closed]:slide-out-to-right sm:w-[min(28rem,calc(100vw-4rem))]\",\n        right:\n          \"inset-y-0 right-0 h-full w-[min(24rem,calc(100vw-2.5rem))] border-l data-[state=open]:slide-in-from-right data-[state=closed]:slide-out-to-right sm:w-[min(28rem,calc(100vw-4rem))]\", // rtl-fixed: fixed right edge\n        left: \"inset-y-0 left-0 h-full w-[min(24rem,calc(100vw-2.5rem))] border-r data-[state=open]:slide-in-from-left data-[state=closed]:slide-out-to-left sm:w-[min(28rem,calc(100vw-4rem))]\", // rtl-fixed: fixed left edge\n        top: \"inset-x-0 top-0 max-h-[85dvh] rounded-b-2xl border-b data-[state=open]:slide-in-from-top data-[state=closed]:slide-out-to-top\",\n        bottom:\n          \"inset-x-0 bottom-0 max-h-[85dvh] rounded-t-2xl border-t data-[state=open]:slide-in-from-bottom data-[state=closed]:slide-out-to-bottom\",\n      },\n    },\n    defaultVariants: { side: \"end\" },\n  },\n);\n\ntype SheetContentProps = React.ComponentProps<typeof Primitive.Content> &\n  VariantProps<typeof sheetVariants> & {\n    /** Edge the sheet slides in from. `end` (the default) and `start` follow the reading direction; `right` and `left` are fixed. */\n    side?: \"top\" | \"end\" | \"bottom\" | \"start\" | \"right\" | \"left\";\n    /** Show the close control in the corner. */\n    showCloseButton?: boolean;\n    /** Accessible name for the close control. */\n    closeLabel?: string;\n  };\nfunction SheetContent({\n  className,\n  children,\n  side = \"end\",\n  showCloseButton = true,\n  closeLabel,\n  ...props\n}: SheetContentProps) {\n  const msg = useMessages()\n  closeLabel ??= msg(\"sheet.closeLabel\", \"Close\")\n  return (\n    <Primitive.Portal>\n      <Primitive.Overlay\n        data-slot=\"sheet-overlay\"\n        className=\"fixed inset-0 z-50 bg-black/50 backdrop-blur-[2px] data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 motion-reduce:animate-none\"\n      />\n      <Primitive.Content\n        data-slot=\"sheet-content\"\n        data-side={side}\n        className={cn(sheetVariants({ side }), className)}\n        {...props}\n      >\n        {side === \"bottom\" && (\n          <span\n            aria-hidden=\"true\"\n            className=\"mx-auto mt-2.5 h-1.5 w-10 shrink-0 rounded-full bg-muted-foreground/30\"\n          />\n        )}\n        {children}\n        {showCloseButton && (\n          <Primitive.Close\n            data-slot=\"sheet-close\"\n            aria-label={closeLabel}\n            className=\"absolute top-3.5 end-3.5 inline-flex size-8 items-center justify-center rounded-md text-muted-foreground outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50\"\n          >\n            <X aria-hidden=\"true\" className=\"size-4\" />\n          </Primitive.Close>\n        )}\n      </Primitive.Content>\n    </Primitive.Portal>\n  );\n}\n\ntype SheetHeaderProps = React.ComponentProps<\"div\">;\nfunction SheetHeader({ className, ...props }: SheetHeaderProps) {\n  return (\n    <div\n      data-slot=\"sheet-header\"\n      className={cn(\"grid gap-1.5 px-5 pt-5 pe-14\", className)}\n      {...props}\n    />\n  );\n}\n\ntype SheetBodyProps = React.ComponentProps<\"div\">;\n/** Scrollable middle region. It is focusable so keyboard users can scroll long content. */\nfunction SheetBody({ className, ...props }: SheetBodyProps) {\n  return (\n    <div\n      data-slot=\"sheet-body\"\n      tabIndex={0}\n      className={cn(\n        \"min-h-0 flex-1 overflow-y-auto px-5 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 SheetFooterProps = React.ComponentProps<\"div\">;\nfunction SheetFooter({ className, ...props }: SheetFooterProps) {\n  return (\n    <div\n      data-slot=\"sheet-footer\"\n      className={cn(\n        \"mt-auto flex flex-col-reverse gap-2 px-5 pb-[max(1.25rem,env(safe-area-inset-bottom))] sm:flex-row sm:justify-end\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\ntype SheetTitleProps = React.ComponentProps<typeof Primitive.Title>;\nfunction SheetTitle({ className, ...props }: SheetTitleProps) {\n  return (\n    <Primitive.Title\n      data-slot=\"sheet-title\"\n      className={cn(\"text-lg leading-tight font-semibold tracking-tight\", className)}\n      {...props}\n    />\n  );\n}\n\ntype SheetDescriptionProps = React.ComponentProps<typeof Primitive.Description>;\nfunction SheetDescription({ className, ...props }: SheetDescriptionProps) {\n  return (\n    <Primitive.Description\n      data-slot=\"sheet-description\"\n      className={cn(\"text-sm text-muted-foreground\", className)}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Sheet,\n  SheetTrigger,\n  SheetClose,\n  SheetContent,\n  SheetHeader,\n  SheetBody,\n  SheetFooter,\n  SheetTitle,\n  SheetDescription,\n  sheetVariants,\n  type SheetProps,\n  type SheetTriggerProps,\n  type SheetCloseProps,\n  type SheetContentProps,\n  type SheetHeaderProps,\n  type SheetBodyProps,\n  type SheetFooterProps,\n  type SheetTitleProps,\n  type SheetDescriptionProps,\n};\n",
      "type": "registry:ui",
      "target": "@components/ballmac/sheet.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "overlay",
      "panel",
      "drawer",
      "dialog",
      "radix"
    ],
    "version": "1.0.0",
    "updated": "2026-09-30",
    "ai": {
      "summary": "A dialog attached to a screen edge. Use side right for settings panels, bottom for mobile actions, left for navigation.",
      "whenToUse": [
        "Edit forms that keep page context visible",
        "Filters and settings panels",
        "Mobile navigation drawers"
      ],
      "whenNotToUse": [
        "Short confirmations; use alert-dialog",
        "Small anchored panels; use popover"
      ],
      "composesWith": [
        "button",
        "input",
        "label"
      ],
      "a11y": [
        {
          "keys": "Enter / Space",
          "action": "Opens from the trigger"
        },
        {
          "keys": "Tab / Shift+Tab",
          "action": "Cycles focus inside the sheet"
        },
        {
          "keys": "Escape",
          "action": "Closes and returns focus to the trigger"
        }
      ],
      "customization": [
        "side: right | left | top | bottom",
        "showCloseButton",
        "SheetBody scrolls long content"
      ]
    },
    "source": {
      "name": "shadcn/ui Sheet",
      "url": "https://github.com/shadcn-ui/ui",
      "license": "MIT",
      "copyright": "Copyright (c) 2023 shadcn",
      "modified": true
    },
    "examples": [
      "sheet-demo",
      "sheet-states"
    ],
    "url": "https://ui.ballmac.com/components/sheet"
  },
  "categories": [
    "primitives"
  ],
  "type": "registry:ui"
}