{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ai-web-preview",
  "title": "AI: Web Preview",
  "description": "An embedded iframe live preview with a URL bar, navigation and console panel.",
  "dependencies": [
    "@untitledui/icons@^0.0.22"
  ],
  "registryDependencies": [
    "@nx-ui/utils",
    "@nx-ui/button",
    "@nx-ui/collapsible",
    "@nx-ui/input",
    "@nx-ui/tooltip"
  ],
  "files": [
    {
      "path": "components/ai/web-preview.tsx",
      "content": "\"use client\"\n\nimport type { ComponentProps, ReactNode } from \"react\"\nimport { createContext, useContext, useEffect, useState } from \"react\"\nimport { ChevronDown } from \"@untitledui/icons\"\n\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Collapsible,\n  CollapsibleContent,\n  CollapsibleTrigger,\n} from \"@/components/ui/collapsible\"\nimport { Input } from \"@/components/ui/input\"\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\"\nimport { cn } from \"@/lib/utils\"\n\nexport type WebPreviewContextValue = {\n  url: string\n  setUrl: (url: string) => void\n  consoleOpen: boolean\n  setConsoleOpen: (open: boolean) => void\n}\n\nconst WebPreviewContext = createContext<WebPreviewContextValue | null>(null)\n\nconst useWebPreview = () => {\n  const context = useContext(WebPreviewContext)\n  if (!context) {\n    throw new Error(\"WebPreview components must be used within a WebPreview\")\n  }\n  return context\n}\n\nexport type WebPreviewProps = ComponentProps<\"div\"> & {\n  defaultUrl?: string\n  onUrlChange?: (url: string) => void\n}\n\nexport const WebPreview = ({\n  className,\n  children,\n  defaultUrl = \"\",\n  onUrlChange,\n  ...props\n}: WebPreviewProps) => {\n  const [url, setUrl] = useState(defaultUrl)\n  const [consoleOpen, setConsoleOpen] = useState(false)\n\n  const handleUrlChange = (newUrl: string) => {\n    setUrl(newUrl)\n    onUrlChange?.(newUrl)\n  }\n\n  const contextValue: WebPreviewContextValue = {\n    url,\n    setUrl: handleUrlChange,\n    consoleOpen,\n    setConsoleOpen,\n  }\n\n  return (\n    <WebPreviewContext.Provider value={contextValue}>\n      <div\n        className={cn(\n          \"flex size-full flex-col rounded-lg border bg-card\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n      </div>\n    </WebPreviewContext.Provider>\n  )\n}\n\nexport type WebPreviewNavigationProps = ComponentProps<\"div\">\n\nexport const WebPreviewNavigation = ({\n  className,\n  children,\n  ...props\n}: WebPreviewNavigationProps) => (\n  <div\n    className={cn(\"flex items-center gap-1 border-b p-2\", className)}\n    {...props}\n  >\n    {children}\n  </div>\n)\n\nexport type WebPreviewNavigationButtonProps = ComponentProps<typeof Button> & {\n  tooltip?: string\n}\n\nexport const WebPreviewNavigationButton = ({\n  onClick,\n  disabled,\n  tooltip,\n  children,\n  ...props\n}: WebPreviewNavigationButtonProps) => (\n  <TooltipProvider>\n    <Tooltip>\n      <TooltipTrigger\n        render={\n          <Button\n            aria-label={tooltip}\n            className=\"hover:text-foreground\"\n            disabled={disabled}\n            onClick={onClick}\n            size=\"icon-sm\"\n            variant=\"ghost\"\n            {...props}\n          >\n            {children}\n          </Button>\n        }\n      />\n      <TooltipContent>\n        <p>{tooltip}</p>\n      </TooltipContent>\n    </Tooltip>\n  </TooltipProvider>\n)\n\nexport type WebPreviewUrlProps = ComponentProps<typeof Input>\n\nexport const WebPreviewUrl = ({\n  value,\n  onChange,\n  onKeyDown,\n  ...props\n}: WebPreviewUrlProps) => {\n  const { url, setUrl } = useWebPreview()\n  const [inputValue, setInputValue] = useState(url)\n\n  // Sync input value with context URL when it changes externally\n  useEffect(() => {\n    setInputValue(url)\n  }, [url])\n\n  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n    setInputValue(event.target.value)\n    onChange?.(event)\n  }\n\n  const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {\n    if (event.key === \"Enter\") {\n      const target = event.target as HTMLInputElement\n      setUrl(target.value)\n    }\n    onKeyDown?.(event)\n  }\n\n  return (\n    <Input\n      className=\"h-8 flex-1 text-sm\"\n      onChange={onChange ?? handleChange}\n      onKeyDown={handleKeyDown}\n      placeholder=\"Enter URL...\"\n      value={value ?? inputValue}\n      {...props}\n    />\n  )\n}\n\nexport type WebPreviewBodyProps = ComponentProps<\"iframe\"> & {\n  loading?: ReactNode\n}\n\nexport const WebPreviewBody = ({\n  className,\n  loading,\n  src,\n  ...props\n}: WebPreviewBodyProps) => {\n  const { url } = useWebPreview()\n\n  return (\n    <div className=\"flex-1\">\n      <iframe\n        className={cn(\"size-full\", className)}\n        sandbox=\"allow-scripts allow-same-origin allow-forms allow-popups allow-presentation\"\n        src={(src ?? url) || undefined}\n        title=\"Preview\"\n        {...props}\n      />\n      {loading}\n    </div>\n  )\n}\n\nexport type WebPreviewConsoleProps = ComponentProps<\"div\"> & {\n  logs?: Array<{\n    level: \"log\" | \"warn\" | \"error\"\n    message: string\n    timestamp: Date\n  }>\n}\n\nexport const WebPreviewConsole = ({\n  className,\n  logs = [],\n  children,\n  ...props\n}: WebPreviewConsoleProps) => {\n  const { consoleOpen, setConsoleOpen } = useWebPreview()\n\n  return (\n    <Collapsible\n      className={cn(\"border-t bg-muted/50 font-mono text-sm\", className)}\n      onOpenChange={setConsoleOpen}\n      open={consoleOpen}\n      {...props}\n    >\n      <CollapsibleTrigger\n        render={\n          <Button\n            className=\"flex w-full items-center justify-between p-4 text-left font-medium hover:bg-muted/50\"\n            variant=\"ghost\"\n          >\n            Console\n            <ChevronDown\n              className={cn(\n                \"h-4 w-4 transition-transform duration-200\",\n                consoleOpen && \"rotate-180\"\n              )}\n            />\n          </Button>\n        }\n      />\n      <CollapsibleContent\n        className={cn(\n          \"px-4 pb-4\",\n          \"data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 outline-none data-closed:animate-out data-open:animate-in\"\n        )}\n      >\n        <div className=\"max-h-48 space-y-1 overflow-y-auto\">\n          {logs.length === 0 ? (\n            <p className=\"text-muted-foreground\">No console output</p>\n          ) : (\n            logs.map((log, index) => (\n              <div\n                className={cn(\n                  \"text-xs\",\n                  log.level === \"error\" && \"text-destructive\",\n                  log.level === \"warn\" && \"text-utility-yellow-600\",\n                  log.level === \"log\" && \"text-foreground\"\n                )}\n                key={`${log.timestamp.getTime()}-${index}`}\n              >\n                <span className=\"text-muted-foreground\">\n                  {log.timestamp.toLocaleTimeString()}\n                </span>{\" \"}\n                {log.message}\n              </div>\n            ))\n          )}\n          {children}\n        </div>\n      </CollapsibleContent>\n    </Collapsible>\n  )\n}\n",
      "type": "registry:ui",
      "target": "components/ai/web-preview.tsx"
    }
  ],
  "type": "registry:ui"
}