{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "macbook-mockup",
  "title": "MacBook Mockup",
  "author": "STARCK",
  "description": "A responsive CSS MacBook frame with an accessible fullscreen mode.",
  "files": [
    {
      "path": "registry/default/macbook-mockup.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { createPortal } from \"react-dom\"\n\ntype MacbookMockupProps = {\n  children: React.ReactNode\n  className?: string\n  expandable?: boolean\n  frameSrc?: string\n  label?: string\n}\n\nfunction CloseIcon() {\n  return (\n    <svg viewBox=\"0 0 20 20\" fill=\"none\" className=\"size-4\" aria-hidden>\n      <path\n        d=\"m5 5 10 10M15 5 5 15\"\n        stroke=\"currentColor\"\n        strokeWidth=\"1.6\"\n        strokeLinecap=\"round\"\n      />\n    </svg>\n  )\n}\n\nfunction Frame({\n  children,\n  frameSrc,\n}: {\n  children: React.ReactNode\n  frameSrc: string\n}) {\n  return (\n    <div className=\"relative mx-auto aspect-[1792/1165] w-full max-w-4xl select-none\">\n      <div className=\"absolute [inset:11.7%_11.6%_11.6%] z-0 overflow-hidden bg-zinc-100\">\n        {children}\n      </div>\n      {/* This is the same photographed silver frame used by SessionWatcher. */}\n      {/* eslint-disable-next-line @next/next/no-img-element */}\n      <img\n        src={frameSrc}\n        alt=\"\"\n        width={1792}\n        height={1165}\n        draggable={false}\n        className=\"pointer-events-none relative z-10 block size-full object-contain\"\n      />\n    </div>\n  )\n}\n\nfunction MacbookMockup({\n  children,\n  className = \"\",\n  expandable = true,\n  frameSrc = \"https://ui.starck.studio/preview/macbook-frame.webp\",\n  label = \"Open fullscreen preview\",\n}: MacbookMockupProps) {\n  const [open, setOpen] = React.useState(false)\n  const triggerRef = React.useRef<HTMLButtonElement>(null)\n  const dialogRef = React.useRef<HTMLDivElement>(null)\n  const closeRef = React.useRef<HTMLButtonElement>(null)\n\n  React.useEffect(() => {\n    if (!open) return\n    const handleKey = (event: KeyboardEvent) => {\n      if (event.key === \"Escape\") setOpen(false)\n      if (event.key !== \"Tab\") return\n      const focusable = dialogRef.current?.querySelectorAll<HTMLElement>(\n        'button:not([disabled]), [tabindex]:not([tabindex=\"-1\"])'\n      )\n      if (!focusable?.length) return\n      const first = focusable[0]\n      const last = focusable[focusable.length - 1]\n      if (event.shiftKey && document.activeElement === first) {\n        event.preventDefault()\n        last.focus()\n      } else if (!event.shiftKey && document.activeElement === last) {\n        event.preventDefault()\n        first.focus()\n      }\n    }\n    const previous = document.body.style.overflow\n    const returnFocus = triggerRef.current\n    document.body.style.overflow = \"hidden\"\n    const frame = requestAnimationFrame(() => closeRef.current?.focus())\n    window.addEventListener(\"keydown\", handleKey)\n    return () => {\n      cancelAnimationFrame(frame)\n      document.body.style.overflow = previous\n      window.removeEventListener(\"keydown\", handleKey)\n      returnFocus?.focus()\n    }\n  }, [open])\n\n  const frame = <Frame frameSrc={frameSrc}>{children}</Frame>\n\n  return (\n    <>\n      <div className={`relative ${className}`}>\n        {frame}\n        {expandable ? (\n          <button\n            ref={triggerRef}\n            type=\"button\"\n            aria-label={label}\n            onClick={() => setOpen(true)}\n            className=\"group absolute [inset:11.7%_11.6%_11.6%] z-20 cursor-zoom-in rounded-lg focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:outline-none\"\n          >\n            <span className=\"absolute inset-0 grid place-items-center bg-black/0 transition-colors group-hover:bg-black/35 group-focus-visible:bg-black/35\">\n              <span className=\"translate-y-1 rounded-full bg-white/95 px-3 py-1.5 text-xs font-medium text-zinc-950 opacity-0 shadow-lg transition-[opacity,transform] group-hover:translate-y-0 group-hover:opacity-100 group-focus-visible:translate-y-0 group-focus-visible:opacity-100\">\n                Fullscreen\n              </span>\n            </span>\n          </button>\n        ) : null}\n      </div>\n\n      {open && typeof document !== \"undefined\"\n        ? createPortal(\n            <div\n              ref={dialogRef}\n              role=\"dialog\"\n              aria-modal=\"true\"\n              aria-label={label}\n              className=\"fixed inset-0 z-[100] grid place-items-center bg-white/96 p-5 backdrop-blur-xl\"\n              onClick={() => setOpen(false)}\n            >\n              <button\n                ref={closeRef}\n                type=\"button\"\n                aria-label=\"Close preview\"\n                onClick={() => setOpen(false)}\n                className=\"absolute top-5 right-5 grid size-10 cursor-pointer place-items-center rounded-full bg-zinc-950/6 text-zinc-700 hover:bg-zinc-950/10\"\n              >\n                <CloseIcon />\n              </button>\n              <div\n                className=\"w-full max-w-6xl\"\n                onClick={(event) => event.stopPropagation()}\n              >\n                {frame}\n              </div>\n            </div>,\n            document.body\n          )\n        : null}\n    </>\n  )\n}\n\nexport { MacbookMockup }\nexport type { MacbookMockupProps }\n",
      "type": "registry:component",
      "target": "components/macbook-mockup.tsx"
    }
  ],
  "type": "registry:component"
}