{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "browser-frame",
  "title": "Browser Frame",
  "description": "A Safari-style window with traffic lights, a padlocked address field and optional tabs. Children render as the page, at natural size or laid out at desktop width and scaled to fit.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "@tealmac/media"
  ],
  "files": [
    {
      "path": "registry/tealmac/browser-frame.tsx",
      "content": "// Adapted from Ballmac UI (https://github.com/vamsiy78/ballmac-ui), MIT License, Copyright (c) 2026 Ballmac.\n\"use client\"\n\nimport * as React from \"react\"\nimport {\n  ChevronLeft,\n  ChevronRight,\n  Copy,\n  Globe,\n  Lock,\n  PanelLeft,\n  Plus,\n  RotateCw,\n  Share,\n  X,\n} from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Media } from \"@/registry/tealmac/media\"\n\ntype BrowserFrameTab = {\n  /** Tab title. */\n  title: string\n  /** Optional favicon; defaults to a globe icon. */\n  icon?: React.ReactNode\n}\n\ntype BrowserFrameProps = Omit<React.ComponentProps<\"div\">, \"children\"> & {\n  /** Page content. Ignored when `src` is set. */\n  children?: React.ReactNode\n  /** Address shown in the URL field. Safari shows just the domain, so a short value reads best. */\n  url?: string\n  /** Show the padlock before the address. */\n  secure?: boolean\n  /** Tabs shown in a strip under the toolbar. Omit for a single-tab window. */\n  tabs?: (string | BrowserFrameTab)[]\n  /** Index of the selected tab. */\n  activeTab?: number\n  /** Screenshot shown as the page, cropped to fill it. */\n  src?: string\n  /** Alternative text for `src`. */\n  alt?: string\n  /**\n   * Width in CSS pixels that the page is laid out at; it is then scaled to fit, like a desktop\n   * window shown small. Needs a fixed height, so it implies `aspectRatio` 16/10 unless you set one.\n   */\n  screenWidth?: number\n  /** Aspect ratio of the page area, e.g. 16 / 10. Omit to let the content set the height. */\n  aspectRatio?: number\n  /** Class names for the page area. */\n  screenClassName?: string\n}\n\n/** Lays screen content out at `screenWidth` CSS pixels and scales it to fit, without re-rendering. */\nfunction useScreenScale(screenWidth: number | undefined) {\n  const ref = React.useRef<HTMLDivElement>(null)\n  React.useEffect(() => {\n    const screen = ref.current\n    if (!screen || !screenWidth) return\n    const measure = () =>\n      screen.style.setProperty(\n        \"--screen-scale\",\n        String(screen.clientWidth / screenWidth)\n      )\n    measure()\n    const observer = new ResizeObserver(measure)\n    observer.observe(screen)\n    return () => observer.disconnect()\n  }, [screenWidth])\n  return ref\n}\n\nconst toolbarIcon = \"size-4 shrink-0 text-muted-foreground\"\n\nfunction TrafficLights() {\n  return (\n    <div\n      aria-hidden=\"true\"\n      data-slot=\"browser-frame-traffic-lights\"\n      className=\"flex shrink-0 items-center gap-2\"\n    >\n      {[\n        \"bg-(--traffic-close)\",\n        \"bg-(--traffic-minimize)\",\n        \"bg-(--traffic-zoom)\",\n      ].map((color) => (\n        <span\n          key={color}\n          className={cn(\n            \"size-3 rounded-full shadow-[inset_0_0_0_0.5px_rgb(0_0_0/0.18)]\",\n            color\n          )}\n        />\n      ))}\n    </div>\n  )\n}\n\nfunction BrowserFrame({\n  url = \"acme.com\",\n  secure = true,\n  tabs,\n  activeTab = 0,\n  src,\n  alt = \"\",\n  screenWidth,\n  aspectRatio,\n  screenClassName,\n  className,\n  style,\n  children,\n  ...props\n}: BrowserFrameProps) {\n  const scaled = !src && !!screenWidth\n  const screenRef = useScreenScale(scaled ? screenWidth : undefined)\n  const ratio = aspectRatio ?? (scaled || src ? 16 / 10 : undefined)\n  const tabList = tabs?.map((tab) =>\n    typeof tab === \"string\" ? { title: tab } : tab\n  )\n\n  return (\n    <div\n      data-slot=\"browser-frame\"\n      className={cn(\n        \"bg-card text-card-foreground @container relative flex w-full min-w-0 flex-col overflow-hidden rounded-xl border shadow-[0_1px_2px_rgb(0_0_0/0.06),0_24px_60px_-24px_rgb(0_0_0/0.35)] dark:shadow-[0_0_0_1px_rgb(255_255_255/0.04),0_24px_60px_-24px_rgb(0_0_0/0.8)]\",\n        \"[--traffic-close:oklch(0.69_0.2_25)] [--traffic-minimize:oklch(0.83_0.16_82)] [--traffic-zoom:oklch(0.74_0.19_145)]\",\n        className\n      )}\n      style={style}\n      {...props}\n    >\n      {/* Toolbar */}\n      <div\n        data-slot=\"browser-frame-toolbar\"\n        className=\"bg-muted/50 flex h-11 shrink-0 items-center gap-3 border-b px-3.5 @md:gap-4\"\n      >\n        <TrafficLights />\n        <div aria-hidden=\"true\" className=\"hidden items-center gap-3 @lg:flex\">\n          <PanelLeft className={cn(toolbarIcon, \"rtl:-scale-x-100\")} />\n          <ChevronLeft className={cn(toolbarIcon, \"rtl:rotate-180\")} />\n          <ChevronRight\n            className={cn(toolbarIcon, \"opacity-40 rtl:rotate-180\")}\n          />\n        </div>\n        <div\n          data-slot=\"browser-frame-address\"\n          className=\"bg-background/80 text-foreground mx-auto flex h-7 w-full max-w-sm min-w-0 items-center justify-center gap-1.5 rounded-md px-2.5 text-xs shadow-[inset_0_0_0_1px_color-mix(in_oklch,var(--foreground)_8%,transparent)]\"\n        >\n          {secure && (\n            <Lock\n              aria-label=\"Secure connection\"\n              className=\"text-muted-foreground size-3 shrink-0\"\n            />\n          )}\n          <span className=\"truncate\">{url}</span>\n          <RotateCw\n            aria-hidden=\"true\"\n            className=\"text-muted-foreground ms-auto hidden size-3 shrink-0 @sm:block\"\n          />\n        </div>\n        <div aria-hidden=\"true\" className=\"hidden items-center gap-3 @lg:flex\">\n          <Share className={toolbarIcon} />\n          <Plus className={toolbarIcon} />\n          <Copy className={toolbarIcon} />\n        </div>\n      </div>\n\n      {/* Tab strip */}\n      {tabList && tabList.length > 0 && (\n        <div\n          data-slot=\"browser-frame-tabs\"\n          role=\"presentation\"\n          className=\"bg-muted/70 flex h-9 shrink-0 items-stretch gap-px border-b p-1\"\n        >\n          {tabList.map((tab, i) => {\n            const active = i === activeTab\n            return (\n              <div\n                key={i}\n                data-slot=\"browser-frame-tab\"\n                data-state={active ? \"active\" : \"inactive\"}\n                className={cn(\n                  \"relative flex min-w-0 flex-1 items-center justify-center gap-1.5 rounded-md px-2 text-[11px]\",\n                  active\n                    ? \"bg-background text-foreground font-medium shadow-[0_0_0_1px_color-mix(in_oklch,var(--foreground)_7%,transparent),0_1px_2px_rgb(0_0_0/0.06)]\"\n                    : \"text-muted-foreground\",\n                  // Hide extra tabs on narrow frames, but always keep the selected one.\n                  !active && i > 1 && \"hidden @md:flex\"\n                )}\n              >\n                {active && (\n                  <X\n                    aria-hidden=\"true\"\n                    className=\"text-muted-foreground absolute start-2 size-3\"\n                  />\n                )}\n                <span\n                  aria-hidden=\"true\"\n                  className=\"flex size-3.5 shrink-0 items-center justify-center [&_svg]:size-3.5\"\n                >\n                  {tab.icon ?? <Globe />}\n                </span>\n                <span className=\"truncate\">{tab.title}</span>\n              </div>\n            )\n          })}\n        </div>\n      )}\n\n      {/* Page */}\n      <div\n        ref={screenRef}\n        data-slot=\"browser-frame-screen\"\n        className={cn(\n          \"bg-background text-foreground relative min-h-0 flex-1 overflow-hidden\",\n          screenClassName\n        )}\n        style={ratio ? { aspectRatio: ratio } : undefined}\n      >\n        {src ? (\n          <Media\n            data-slot=\"browser-frame-image\"\n            media={{ src, alt: alt ?? \"\", position: \"top\" }}\n            fill\n            className=\"absolute inset-0\"\n          />\n        ) : scaled ? (\n          <div\n            data-slot=\"browser-frame-viewport\"\n            className=\"absolute top-0 left-0 origin-top-left\" // rtl-fixed: scaled from its top-left corner\n            style={{\n              width: screenWidth,\n              height: \"calc(100% / var(--screen-scale, 1))\",\n              transform: \"scale(var(--screen-scale, 1))\",\n            }}\n          >\n            {children}\n          </div>\n        ) : (\n          children\n        )}\n      </div>\n    </div>\n  )\n}\n\nexport { BrowserFrame, type BrowserFrameProps, type BrowserFrameTab }\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}