{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dialog",
  "title": "Dialog",
  "author": "AniBT <https://anibt.net>",
  "description": "AniBT 「贴纸手账」 dialog: the floating paper over the scrim, A's 48px stitched header bar with a quiet close square, a pinned header and footer and one scrolling DialogBody.",
  "dependencies": [
    "lucide-react",
    "radix-ui"
  ],
  "registryDependencies": [
    "@anibt/theme",
    "@anibt/button"
  ],
  "files": [
    {
      "path": "registry/anibt/ui/dialog.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { XIcon } from \"lucide-react\"\nimport { Dialog as DialogPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/anibt/ui/button\"\n\n/**\n * Dialog — AniBT 「贴纸手账」: the one container for \"one record\".\n *\n * The surface is the floating panel (`anibt-pop`): paper, a 1px edge and the\n * pop shadow on radius 14, over the theme's scrim. `DialogHeader` is A's 48px\n * bar across the top, parted from the body by the dashed stitch; the close\n * button is a quiet 32px icon square inside that bar.\n *\n * Scrolling has one convention: the content is a flex column capped at the\n * viewport, `DialogHeader` and `DialogFooter` stay put, and `DialogBody` is the\n * region that scrolls. A dialog without a body still reaches its footer,\n * because the content itself is the fallback scroll region.\n *\n * Export names and props are shadcn's, so this file can replace\n * `components/ui/dialog.tsx`. Triggers and closes also take Base UI's\n * `render` (shadcn's `base-*` styles) as a form of `asChild`.\n */\n\ntype RenderCompat = {\n  /** Base UI's `asChild`: render this element instead, with the part's props. */\n  render?: React.ReactElement\n  /** Base UI compatibility; ignored (Radix decides from the rendered element). */\n  nativeButton?: boolean\n}\n\n/** Maps Base UI's `render` onto Radix's `asChild`. */\nfunction renderAsChild<P extends { asChild?: boolean; children?: React.ReactNode }>({\n  render,\n  nativeButton: _nativeButton,\n  ...props\n}: P & RenderCompat): P {\n  void _nativeButton\n  if (!render) return props as P\n  const children = props.children === undefined ? render : React.cloneElement(render, undefined, props.children)\n  return { ...props, asChild: true, children } as P\n}\n\nfunction Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {\n  return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />\n}\n\nfunction DialogTrigger(props: React.ComponentProps<typeof DialogPrimitive.Trigger> & RenderCompat) {\n  return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...renderAsChild(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> & RenderCompat) {\n  return <DialogPrimitive.Close data-slot=\"dialog-close\" {...renderAsChild(props)} />\n}\n\n/** The scrim: the theme's `--anibt-scrim`, fading in and out. */\nfunction DialogOverlay({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {\n  return (\n    <DialogPrimitive.Overlay\n      data-slot=\"dialog-overlay\"\n      className={cn(\n        \"fixed inset-0 z-50 bg-anibt-scrim duration-200 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction DialogContent({\n  className,\n  children,\n  showCloseButton = true,\n  closeLabel = \"关闭\",\n  ...props\n}: React.ComponentProps<typeof DialogPrimitive.Content> & {\n  /** The quiet ✕ square in the header bar. */\n  showCloseButton?: boolean\n  /** The close button's accessible name. */\n  closeLabel?: string\n}) {\n  return (\n    <DialogPortal>\n      <DialogOverlay />\n      <DialogPrimitive.Content\n        data-slot=\"dialog-content\"\n        className={cn(\n          \"anibt-pop fixed top-1/2 left-1/2 z-50 flex max-h-[calc(100dvh-2rem)] w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 flex-col gap-3 overflow-y-auto overscroll-contain p-4 text-[14px] duration-200 outline-none sm:max-w-lg\",\n          \"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n          // A title never runs under the close button.\n          showCloseButton && \"[&>[data-slot=dialog-header]]:pe-12\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n        {showCloseButton ? (\n          <DialogPrimitive.Close\n            data-slot=\"dialog-close\"\n            className=\"anibt-icobtn anibt-icobtn-ghost absolute top-2 right-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:outline-solid disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\"\n          >\n            <XIcon />\n            <span className=\"sr-only\">{closeLabel}</span>\n          </DialogPrimitive.Close>\n        ) : null}\n      </DialogPrimitive.Content>\n    </DialogPortal>\n  )\n}\n\n/** A's 48px bar: title (and description) over the stitched bottom edge. */\nfunction DialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"dialog-header\"\n      className={cn(\n        \"anibt-stitch-under -mx-4 -mt-4 flex min-h-12 shrink-0 flex-col justify-center gap-1 px-4 py-3 text-left\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\n/** The one scrolling region of a dialog; the header and the footer stay reachable. */\nfunction DialogBody({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"dialog-body\"\n      className={cn(\"-m-1 min-h-0 flex-1 overflow-y-auto overscroll-contain p-1 text-anibt-ink2\", className)}\n      {...props}\n    />\n  )\n}\n\n/** Buttons at the foot, right-aligned: the quiet one first, the action last. */\nfunction DialogFooter({\n  className,\n  showCloseButton = false,\n  closeLabel = \"关闭\",\n  children,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  /** Adds a quiet 关闭 button before the footer's own buttons. */\n  showCloseButton?: boolean\n  closeLabel?: React.ReactNode\n}) {\n  return (\n    <div\n      data-slot=\"dialog-footer\"\n      className={cn(\"flex shrink-0 flex-col-reverse gap-2 pt-1 sm:flex-row sm:justify-end\", className)}\n      {...props}\n    >\n      {showCloseButton ? (\n        <DialogPrimitive.Close {...{ asChild: true }}>\n          <Button variant=\"ghost\">{closeLabel}</Button>\n        </DialogPrimitive.Close>\n      ) : null}\n      {children}\n    </div>\n  )\n}\n\n/** 14/700 ink, one line of the bar. */\nfunction DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {\n  return (\n    <DialogPrimitive.Title\n      data-slot=\"dialog-title\"\n      className={cn(\"flex items-center gap-2 text-[14px] leading-snug font-bold text-foreground\", className)}\n      {...props}\n    />\n  )\n}\n\n/** 13, the secondary ink. */\nfunction DialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>) {\n  return (\n    <DialogPrimitive.Description\n      data-slot=\"dialog-description\"\n      className={cn(\"text-[13px] leading-normal text-anibt-ink2\", className)}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Dialog,\n  DialogBody,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogOverlay,\n  DialogPortal,\n  DialogTitle,\n  DialogTrigger,\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "overlay"
  ],
  "type": "registry:ui"
}