{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "fullscreen-viewer", "dependencies": [ "react-pdf", "@wojtekmaj/react-hooks", "lucide-react" ], "registryDependencies": [ "button" ], "files": [ { "path": "registry/reusables/pdf-viewers/fullscreen-viewer.tsx", "content": "\"use client\"\n\nimport { useEffect, useRef, useState } from \"react\"\nimport {\n ChevronLeft,\n ChevronRight,\n Download,\n X,\n ZoomIn,\n ZoomOut,\n} from \"lucide-react\"\nimport { Document, Page, pdfjs } from \"react-pdf\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\n\nimport { LoadingSpinner } from \"./loading-spinner\"\n\npdfjs.GlobalWorkerOptions.workerSrc = new URL(\n \"pdfjs-dist/build/pdf.worker.min.mjs\",\n import.meta.url\n).toString()\n\ninterface FullscreenViewerProps {\n url: string\n onClose?: () => void\n}\nconst maxWidth = 800\n\nexport const FullscreenViewer = ({ url, onClose }: FullscreenViewerProps) => {\n const [numPages, setNumPages] = useState(null)\n const [pageNumber, setPageNumber] = useState(1)\n const [scale, setScale] = useState(1)\n const [pageHeight, setPageHeight] = useState(0)\n const containerRef = useRef(null)\n const [containerWidth, setContainerWidth] = useState(maxWidth)\n\n const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) => {\n setNumPages(numPages)\n }\n\n useEffect(() => {\n const el = containerRef.current\n if (!el) return\n // Initialize width\n setContainerWidth(el.clientWidth)\n // Observe size changes\n const ro = new ResizeObserver((entries) => {\n for (const entry of entries) {\n if (entry.contentRect) {\n setContainerWidth(Math.floor(entry.contentRect.width))\n }\n }\n })\n ro.observe(el)\n // Fallback on window resize (older browsers)\n const handleResize = () => setContainerWidth(el.clientWidth)\n window.addEventListener(\"resize\", handleResize)\n return () => {\n ro.disconnect()\n window.removeEventListener(\"resize\", handleResize)\n }\n }, [])\n\n const pageWidth = Math.min(containerWidth, maxWidth)\n\n return (\n
\n
\n
\n \n {/* Navigation Controls */}\n
\n setPageNumber((p) => Math.max(1, p - 1))}\n disabled={pageNumber <= 1}\n className=\"rounded-full dark:border-white/10 dark:bg-white/10 dark:backdrop-blur-sm dark:hover:bg-white/20\"\n >\n \n \n\n \n \n {pageNumber} / {numPages || \"-\"}\n \n \n Page {pageNumber} of {numPages || \"-\"}\n \n \n\n \n setPageNumber((p) => Math.min(numPages || p, p + 1))\n }\n disabled={pageNumber >= (numPages || 1)}\n className=\"rounded-full dark:border-white/10 dark:bg-white/10 dark:backdrop-blur-sm dark:hover:bg-white/20\"\n >\n \n \n
\n\n
\n setScale((s) => Math.max(0.5, s - 0.1))}\n className=\"rounded-full text-sm hover:opacity-90 dark:border-white/10 dark:bg-white/10 dark:text-white dark:backdrop-blur-sm dark:hover:bg-white/20\"\n disabled={scale <= 0.5}\n aria-label=\"Zoom Out\"\n >\n \n \n \n Zoom Out\n \n \n {Math.round(scale * 100)}%\n \n setScale((s) => Math.min(2, s + 0.1))}\n className=\"rounded-full text-sm hover:opacity-90 dark:border-white/10 dark:bg-white/10 dark:text-white dark:backdrop-blur-sm dark:hover:bg-white/20\"\n disabled={scale >= 2}\n aria-label=\"Zoom In\"\n >\n \n \n \n Zoom In\n \n\n {/* Download Button */}\n window.open(url, \"_blank\")}\n className={cn(\n \"ml-2 h-8 w-8 rounded-full\",\n \"hover:opacity-90 dark:bg-white/10 dark:text-white\",\n \"dark:border-white/10 dark:backdrop-blur-sm dark:hover:bg-white/20\",\n \"transition-all duration-200 ease-in-out\"\n )}\n title=\"Download PDF\"\n >\n \n \n\n {/* Close Button */}\n {onClose && (\n \n \n \n )}\n
\n
\n
\n
\n\n
\n
\n \n }\n className={cn(\n \"flex flex-col items-center\",\n \"[&_.react-pdf__Page]:my-0\",\n \"[&_.react-pdf__Page__canvas]:max-w-full\",\n \"[&_.react-pdf__Page__canvas]:h-auto\"\n )}\n >\n \n }\n onLoadSuccess={(page) => {\n setPageHeight(page.height * (pageWidth / page.width) * scale)\n }}\n />\n \n
\n
\n \n )\n}\n", "type": "registry:component", "target": "components/pdf-viewers/fullscreen-viewer.tsx" }, { "path": "registry/reusables/pdf-viewers/loading-spinner.tsx", "content": "import { Loader2 } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface LoadingSpinnerProps {\n minHeight?: string\n minWidth?: string\n className?: string\n spinnerClassName?: string\n}\n\nexport const LoadingSpinner = ({\n minHeight = \"600px\",\n minWidth = \"800px\",\n className,\n spinnerClassName,\n}: LoadingSpinnerProps) => {\n return (\n \n \n \n )\n}\n", "type": "registry:component", "target": "components/pdf-viewers/loading-spinner.tsx" } ], "type": "registry:component" }