{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"web-preview","type":"registry:component","title":"Web Preview","description":"AI-powered web preview component.","files":[{"path":"registry/default/ai-elements/web-preview.tsx","type":"registry:component","content":"\"use client\";\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\";\nimport { ChevronDownIcon } from \"lucide-react\";\nimport type { ComponentProps, ReactNode } from \"react\";\nimport { createContext, useContext, useEffect, useState } from \"react\";\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 asChild>\n        <Button\n          className=\"h-8 w-8 p-0 hover:text-foreground\"\n          disabled={disabled}\n          onClick={onClick}\n          size=\"sm\"\n          variant=\"ghost\"\n          {...props}\n        >\n          {children}\n        </Button>\n      </TooltipTrigger>\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 asChild>\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          <ChevronDownIcon\n            className={cn(\n              \"h-4 w-4 transition-transform duration-200\",\n              consoleOpen && \"rotate-180\"\n            )}\n          />\n        </Button>\n      </CollapsibleTrigger>\n      <CollapsibleContent\n        className={cn(\n          \"px-4 pb-4\",\n          \"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=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-[state=closed]:animate-out data-[state=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-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","target":"components/ai-elements/web-preview.tsx"}],"dependencies":["lucide-react"],"devDependencies":[],"registryDependencies":["button","collapsible","input","tooltip"]}