{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pptx-viewer",
  "title": "PowerPoint Viewer",
  "description": "A PowerPoint viewer with slide navigation, zoom, upload, download, custom scroll areas, and virtualized thumbnails.",
  "dependencies": [
    "@extend-ai/react-pptx@0.2.0",
    "@tanstack/react-virtual@^3.13.12",
    "@base-ui/react@^1.4.1"
  ],
  "registryDependencies": [
    "@extend/document-viewer-sidebar",
    "@extend/file-thumbnail",
    "utils",
    "button",
    "dropdown-menu",
    "input",
    "scroll-area",
    "select",
    "separator",
    "tooltip"
  ],
  "files": [
    {
      "path": "components/extend/pptx-viewer.tsx",
      "content": "\"use client\"\n\nimport { ScrollArea as ScrollAreaPrimitive } from \"@base-ui/react/scroll-area\"\nimport {\n  ReactPptxViewer,\n  usePptxViewerThumbnails,\n  type ParsedPresentation,\n  type PptxSlideThumbnailItem,\n  type PptxSlideThumbnailRenderWindow,\n  type PptxViewerController,\n  type PptxViewerError,\n  type PresentationSource,\n} from \"@extend-ai/react-pptx\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\"\nimport { Input } from \"@/components/ui/input\"\nimport {\n  ScrollArea as InlineScrollArea,\n  ScrollBar,\n} from \"@/components/ui/scroll-area\"\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\"\nimport { Separator } from \"@/components/ui/separator\"\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\"\nimport {\n  DocumentViewerThumbnailSidebar,\n  useElementWidth,\n  useInlineThumbnailSidebar,\n} from \"@/components/extend/document-viewer-sidebar\"\nimport { FileThumbnail } from \"@/components/extend/file-thumbnail\"\nimport { IconPlaceholder } from \"@/components/icon-placeholder\"\n\nimport \"@extend-ai/react-pptx/styles.css\"\n\nimport * as React from \"react\"\nimport { useVirtualizer } from \"@tanstack/react-virtual\"\n\nconst PPTX_MIME_TYPE =\n  \"application/vnd.openxmlformats-officedocument.presentationml.presentation\"\nconst PPT_MIME_TYPE = \"application/vnd.ms-powerpoint\"\nconst DEFAULT_ZOOM = 100\nconst PPTX_LOADING_INDICATOR_DELAY_MS = 300\nconst PPTX_THUMBNAIL_WIDTH = 112\nconst PPTX_THUMBNAIL_LIST_PADDING = 12\nconst PPTX_THUMBNAIL_ROW_ESTIMATE = 112\nconst PPTX_THUMBNAIL_PREFETCH_ROWS = 0\nconst PPTX_THUMBNAIL_FOLLOW_DELAY_MS = 250\nconst PPTX_SCROLL_TOP_EPSILON_PX = 24\nconst PPTX_INSTANT_NAVIGATION_TIMEOUT_MS = 250\nconst PPTX_SMOOTH_NAVIGATION_TIMEOUT_MS = 1000\nconst ZOOM_OPTIONS = [25, 50, 75, 100, 125, 150, 175, 200, 300, 400] as const\nconst PPTX_THUMBNAIL_FOCUS_RING_CLASS =\n  \"group-focus-visible/pptx-thumbnail-sidebar:ring-2 group-focus-visible/pptx-thumbnail-sidebar:ring-ring group-focus-visible/pptx-thumbnail-sidebar:ring-offset-1 group-focus-visible/pptx-thumbnail-sidebar:ring-offset-background\"\ntype UploadedPresentation = {\n  file: File\n  identity: string\n  sourceUrl: string | undefined\n}\nfunction areNumberArraysEqual(\n  left: readonly number[],\n  right: readonly number[]\n) {\n  return (\n    left.length === right.length &&\n    left.every((value, index) => value === right[index])\n  )\n}\nfunction formatPresentationName(fileName: string | undefined, url: string) {\n  if (fileName?.trim()) return fileName\n  const pathname = url.split(\"?\")[0] ?? \"\"\n  const rawName = pathname.split(\"/\").pop() ?? \"presentation.pptx\"\n  try {\n    return decodeURIComponent(rawName)\n  } catch {\n    return rawName\n  }\n}\nfunction ensurePresentationExtension(fileName: string) {\n  const lowerFileName = fileName.toLowerCase()\n  return lowerFileName.endsWith(\".pptx\") || lowerFileName.endsWith(\".ppt\")\n    ? fileName\n    : `${fileName}.pptx`\n}\nfunction downloadBlob(blob: Blob, fileName: string) {\n  const url = URL.createObjectURL(blob)\n  const anchor = document.createElement(\"a\")\n  anchor.href = url\n  anchor.download = fileName\n  anchor.rel = \"noopener\"\n  document.body.append(anchor)\n  anchor.click()\n  anchor.remove()\n  window.setTimeout(() => URL.revokeObjectURL(url), 0)\n}\nasync function downloadPresentation({\n  file,\n  fileName,\n  url,\n}: {\n  file?: File\n  fileName: string\n  url?: string\n}) {\n  if (file) {\n    downloadBlob(file, ensurePresentationExtension(fileName))\n    return\n  }\n  if (!url) return\n  const response = await fetch(url)\n  if (!response.ok) {\n    throw new Error(`Failed to download presentation (${response.status})`)\n  }\n  downloadBlob(await response.blob(), ensurePresentationExtension(fileName))\n}\nfunction getNextZoom(currentZoom: number, direction: 1 | -1) {\n  if (direction > 0) {\n    return ZOOM_OPTIONS.find((value) => value > currentZoom) ?? currentZoom\n  }\n  for (let index = ZOOM_OPTIONS.length - 1; index >= 0; index -= 1) {\n    const value = ZOOM_OPTIONS[index]\n    if (value < currentZoom) return value\n  }\n  return currentZoom\n}\nfunction useDelayedLoadingIndicator(isLoading: boolean, delayMs: number) {\n  const [showSpinner, setShowSpinner] = React.useState(false)\n  const [previousIsLoading, setPreviousIsLoading] = React.useState(isLoading)\n  if (previousIsLoading !== isLoading) {\n    setPreviousIsLoading(isLoading)\n    setShowSpinner(false)\n  }\n  React.useEffect(() => {\n    if (!isLoading) return\n    const timeoutId = window.setTimeout(() => {\n      setShowSpinner(true)\n    }, delayMs)\n    return () => window.clearTimeout(timeoutId)\n  }, [delayMs, isLoading])\n  return isLoading && showSpinner\n}\nfunction useDebouncedValue<TValue>(value: TValue, delayMs: number) {\n  const [debouncedValue, setDebouncedValue] = React.useState(value)\n  React.useEffect(() => {\n    const timeoutId = window.setTimeout(() => setDebouncedValue(value), delayMs)\n    return () => window.clearTimeout(timeoutId)\n  }, [delayMs, value])\n  return debouncedValue\n}\nfunction ViewerLoadingSurface({\n  showSpinner = true,\n}: {\n  showSpinner?: boolean\n}) {\n  return (\n    <div className=\"grid h-full min-h-96 w-full place-items-center bg-background\">\n      {showSpinner ? <InlineSpinner className=\"size-4\" /> : null}\n    </div>\n  )\n}\nfunction ToolbarTooltip({\n  children,\n  label,\n}: {\n  children: React.ReactNode\n  label: string\n}) {\n  return (\n    <Tooltip>\n      <TooltipTrigger\n        render={<span className=\"inline-flex\">{children}</span>}\n      ></TooltipTrigger>\n      <TooltipContent side=\"bottom\">{label}</TooltipContent>\n    </Tooltip>\n  )\n}\nfunction PptxFileActionsMenu({\n  controlsDisabled,\n  downloadDisabled,\n  isPreparingDownload,\n  onDownload,\n  onUploadClick,\n  showDownloadButton,\n  showUploadButton,\n}: {\n  controlsDisabled: boolean\n  downloadDisabled: boolean\n  isPreparingDownload: boolean\n  onDownload: () => void\n  onUploadClick: () => void\n  showDownloadButton: boolean\n  showUploadButton: boolean\n}) {\n  if (!showDownloadButton && !showUploadButton) return null\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger\n        render={\n          <Button\n            type=\"button\"\n            variant=\"ghost\"\n            size=\"icon-sm\"\n            aria-label=\"Open PowerPoint actions\"\n          >\n            <IconPlaceholder\n              lucide=\"Ellipsis\"\n              tabler=\"IconDots\"\n              hugeicons=\"MoreHorizontalIcon\"\n              phosphor=\"DotsThreeIcon\"\n              remixicon=\"RiMoreLine\"\n              className=\"size-4\"\n            />\n          </Button>\n        }\n      ></DropdownMenuTrigger>\n      <DropdownMenuContent align=\"end\" className=\"w-44\">\n        {showDownloadButton ? (\n          <DropdownMenuItem disabled={downloadDisabled} onClick={onDownload}>\n            {isPreparingDownload ? (\n              <InlineSpinner className=\"size-4\" />\n            ) : (\n              <IconPlaceholder\n                lucide=\"Download\"\n                tabler=\"IconDownload\"\n                hugeicons=\"Download01Icon\"\n                phosphor=\"DownloadSimpleIcon\"\n                remixicon=\"RiDownload2Line\"\n                className=\"size-4\"\n              />\n            )}\n            Download\n          </DropdownMenuItem>\n        ) : null}\n        {showUploadButton ? (\n          <DropdownMenuItem disabled={controlsDisabled} onClick={onUploadClick}>\n            <IconPlaceholder\n              lucide=\"Upload\"\n              tabler=\"IconUpload\"\n              hugeicons=\"Upload01Icon\"\n              phosphor=\"UploadSimpleIcon\"\n              remixicon=\"RiUpload2Line\"\n              className=\"size-4\"\n            />\n            Upload\n          </DropdownMenuItem>\n        ) : null}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\nfunction PptxSlideNumberControl({\n  activeSlideIndex,\n  controlsDisabled,\n  onSlideChange,\n  slideCount,\n}: {\n  activeSlideIndex: number\n  controlsDisabled: boolean\n  onSlideChange: (slideIndex: number) => void\n  slideCount: number\n}) {\n  const inputRef = React.useRef<HTMLInputElement>(null)\n  const displaySlide = slideCount ? activeSlideIndex + 1 : 1\n  const [isEditing, setIsEditing] = React.useState(false)\n  const [draftSlide, setDraftSlide] = React.useState(() => String(displaySlide))\n  React.useEffect(() => {\n    if (!isEditing) return\n    inputRef.current?.focus()\n    inputRef.current?.select()\n  }, [isEditing])\n  const applySlideDraft = React.useCallback(\n    (value: string) => {\n      const parsedSlide = Number(value.trim())\n      if (!Number.isInteger(parsedSlide)) return\n      const nextSlide = Math.min(\n        Math.max(parsedSlide, 1),\n        Math.max(slideCount, 1)\n      )\n      onSlideChange(nextSlide - 1)\n    },\n    [onSlideChange, slideCount]\n  )\n  return (\n    <div className=\"flex items-center text-sm whitespace-nowrap text-primary\">\n      <span>Slide</span>\n      {isEditing ? (\n        <Input\n          ref={inputRef}\n          aria-label=\"Slide number\"\n          inputMode=\"numeric\"\n          pattern=\"[0-9]*\"\n          value={draftSlide}\n          onBlur={() => setIsEditing(false)}\n          onChange={(event: React.ChangeEvent<HTMLInputElement>) => {\n            setDraftSlide(event.target.value)\n            applySlideDraft(event.target.value)\n          }}\n          onKeyDown={(event: React.KeyboardEvent<HTMLInputElement>) => {\n            if (event.key === \"Enter\" || event.key === \"Escape\") {\n              event.currentTarget.blur()\n            }\n          }}\n          className={cn(\n            \"h-8 px-2.5\",\n            \"mx-1 w-14 min-w-14 rounded-md [&_[data-slot=input]]:text-center\"\n          )}\n        />\n      ) : (\n        <Button\n          type=\"button\"\n          variant=\"ghost\"\n          size=\"sm\"\n          className=\"font-normal\"\n          aria-label={`Current slide ${displaySlide}. Edit slide number`}\n          disabled={controlsDisabled || !slideCount}\n          onClick={() => {\n            setDraftSlide(String(displaySlide))\n            setIsEditing(true)\n          }}\n        >\n          {displaySlide}\n        </Button>\n      )}\n      <span>of {slideCount || \"-\"}</span>\n    </div>\n  )\n}\nfunction PptxToolbar({\n  activeSlideIndex,\n  controlsDisabled,\n  isPreparingDownload,\n  onDownload,\n  onSlideChange,\n  onToggleSidebar,\n  onUploadClick,\n  setZoom,\n  showDownloadButton,\n  showUploadButton,\n  slideCount,\n  toolbarActions,\n  zoom,\n}: {\n  activeSlideIndex: number\n  controlsDisabled: boolean\n  isPreparingDownload: boolean\n  onDownload: () => void\n  onSlideChange: (slideIndex: number) => void\n  onToggleSidebar: () => void\n  onUploadClick: () => void\n  setZoom: React.Dispatch<React.SetStateAction<number>>\n  showDownloadButton: boolean\n  showUploadButton: boolean\n  slideCount: number\n  toolbarActions?: React.ReactNode\n  zoom: number\n}) {\n  const canGoPrevious = !controlsDisabled && activeSlideIndex > 0\n  const canGoNext =\n    !controlsDisabled && slideCount > 0 && activeSlideIndex < slideCount - 1\n  const canZoomOut = !controlsDisabled && zoom > ZOOM_OPTIONS[0]\n  const canZoomIn =\n    !controlsDisabled && zoom < ZOOM_OPTIONS[ZOOM_OPTIONS.length - 1]\n  return (\n    <div className=\"flex min-h-12 flex-wrap items-center justify-between gap-2 border-b bg-background px-3 py-2\">\n      <TooltipProvider>\n        <div className=\"flex min-w-0 flex-wrap items-center gap-1\">\n          <ToolbarTooltip label=\"Toggle thumbnails\">\n            <Button\n              type=\"button\"\n              variant=\"ghost\"\n              size=\"icon-sm\"\n              aria-label=\"Toggle thumbnails\"\n              disabled={controlsDisabled}\n              onClick={onToggleSidebar}\n            >\n              <IconPlaceholder\n                lucide=\"PanelLeft\"\n                tabler=\"IconLayoutSidebar\"\n                hugeicons=\"SidebarLeftIcon\"\n                phosphor=\"SidebarIcon\"\n                remixicon=\"RiLayoutLeftLine\"\n                className=\"size-4\"\n              />\n            </Button>\n          </ToolbarTooltip>\n          <Separator orientation=\"vertical\" className=\"mx-1 h-4 self-center\" />\n          <ToolbarTooltip label=\"Previous slide\">\n            <Button\n              type=\"button\"\n              variant=\"ghost\"\n              size=\"icon-sm\"\n              aria-label=\"Previous slide\"\n              disabled={!canGoPrevious}\n              onClick={() => onSlideChange(activeSlideIndex - 1)}\n            >\n              <IconPlaceholder\n                lucide=\"ChevronLeft\"\n                tabler=\"IconChevronLeft\"\n                hugeicons=\"ArrowLeft01Icon\"\n                phosphor=\"CaretLeftIcon\"\n                remixicon=\"RiArrowLeftSLine\"\n                className=\"size-4\"\n              />\n            </Button>\n          </ToolbarTooltip>\n          <PptxSlideNumberControl\n            activeSlideIndex={activeSlideIndex}\n            controlsDisabled={controlsDisabled}\n            onSlideChange={onSlideChange}\n            slideCount={slideCount}\n          />\n          <ToolbarTooltip label=\"Next slide\">\n            <Button\n              type=\"button\"\n              variant=\"ghost\"\n              size=\"icon-sm\"\n              aria-label=\"Next slide\"\n              disabled={!canGoNext}\n              onClick={() => onSlideChange(activeSlideIndex + 1)}\n            >\n              <IconPlaceholder\n                lucide=\"ArrowRight\"\n                tabler=\"IconArrowRight\"\n                hugeicons=\"ArrowRight01Icon\"\n                phosphor=\"ArrowRightIcon\"\n                remixicon=\"RiArrowRightLine\"\n                className=\"size-4\"\n              />\n            </Button>\n          </ToolbarTooltip>\n        </div>\n        <div className=\"ml-auto flex min-w-0 flex-wrap items-center justify-end gap-1\">\n          <div className=\"flex flex-none items-center gap-1\">\n            <ToolbarTooltip label=\"Zoom out\">\n              <Button\n                type=\"button\"\n                variant=\"ghost\"\n                size=\"icon-sm\"\n                aria-label=\"Zoom out\"\n                disabled={!canZoomOut}\n                onClick={() =>\n                  setZoom((currentZoom) => getNextZoom(currentZoom, -1))\n                }\n              >\n                <IconPlaceholder\n                  lucide=\"CircleMinus\"\n                  tabler=\"IconCircleMinus\"\n                  hugeicons=\"MinusSignCircleIcon\"\n                  phosphor=\"MinusCircleIcon\"\n                  remixicon=\"RiIndeterminateCircleLine\"\n                  className=\"size-4\"\n                />\n              </Button>\n            </ToolbarTooltip>\n            <Select\n              value={zoom.toString()}\n              onValueChange={(value) => setZoom(Number(value))}\n              disabled={controlsDisabled}\n              modal={false}\n            >\n              <SelectTrigger\n                size=\"sm\"\n                className=\"w-[84px] min-w-[84px]\"\n                aria-label=\"Zoom level\"\n              >\n                <SelectValue>{Math.round(zoom)}%</SelectValue>\n              </SelectTrigger>\n              <SelectContent align=\"end\" alignItemWithTrigger={false}>\n                {ZOOM_OPTIONS.map((value) => (\n                  <SelectItem key={value} value={value.toString()}>\n                    {value}%\n                  </SelectItem>\n                ))}\n              </SelectContent>\n            </Select>\n            <ToolbarTooltip label=\"Zoom in\">\n              <Button\n                type=\"button\"\n                variant=\"ghost\"\n                size=\"icon-sm\"\n                aria-label=\"Zoom in\"\n                disabled={!canZoomIn}\n                onClick={() =>\n                  setZoom((currentZoom) => getNextZoom(currentZoom, 1))\n                }\n              >\n                <IconPlaceholder\n                  lucide=\"CirclePlusIcon\"\n                  tabler=\"IconCirclePlusFilled\"\n                  hugeicons=\"PlusSignCircleIcon\"\n                  phosphor=\"PlusCircleIcon\"\n                  remixicon=\"RiAddCircleFill\"\n                  className=\"size-4\"\n                />\n              </Button>\n            </ToolbarTooltip>\n          </div>\n          {toolbarActions ? (\n            <>\n              <Separator\n                orientation=\"vertical\"\n                className=\"mx-1 h-4 self-center\"\n              />\n              {toolbarActions}\n            </>\n          ) : null}\n          {showDownloadButton || showUploadButton ? (\n            <Separator\n              orientation=\"vertical\"\n              className=\"mx-1 h-4 self-center\"\n            />\n          ) : null}\n          <PptxFileActionsMenu\n            controlsDisabled={false}\n            downloadDisabled={controlsDisabled || isPreparingDownload}\n            isPreparingDownload={isPreparingDownload}\n            onDownload={onDownload}\n            onUploadClick={onUploadClick}\n            showDownloadButton={showDownloadButton}\n            showUploadButton={showUploadButton}\n          />\n        </div>\n      </TooltipProvider>\n    </div>\n  )\n}\nfunction PptxSidebarThumbnail({\n  aspectRatio,\n  containerRef,\n  displayFileName,\n  isActive,\n  slideNumber,\n  status,\n}: {\n  aspectRatio: number\n  containerRef: PptxSlideThumbnailItem[\"containerRef\"]\n  displayFileName: string\n  isActive: boolean\n  slideNumber: number\n  status: PptxSlideThumbnailItem[\"status\"]\n}) {\n  return (\n    <FileThumbnail\n      file={{\n        name: `${displayFileName} slide ${slideNumber}`,\n        type: PPTX_MIME_TYPE,\n      }}\n      previewAspectRatio={aspectRatio}\n      previewClassName=\"rounded-sm bg-white\"\n      previewContent={\n        <div\n          ref={containerRef}\n          className=\"size-full overflow-hidden bg-white [&_[data-rpv-slide-wrapper]]:!m-0\"\n        />\n      }\n      isLoading={status !== \"ready\" && status !== \"error\"}\n      hasError={status === \"error\"}\n      className={cn(\n        \"w-full rounded-sm border-0 shadow-xs ring-0 transition-shadow duration-150\",\n        isActive && \"shadow-sm\"\n      )}\n    />\n  )\n}\nfunction PptxThumbnailSidebarList({\n  activeSlideIndex,\n  displayFileName,\n  isLoading,\n  onRenderWindowChange,\n  onSelectSlide,\n  sidebarOpen,\n  slideCount,\n  thumbnails,\n}: {\n  activeSlideIndex: number\n  displayFileName: string\n  isLoading: boolean\n  onRenderWindowChange: (window: PptxSlideThumbnailRenderWindow) => void\n  onSelectSlide: (slideIndex: number) => void\n  sidebarOpen: boolean\n  slideCount: number\n  thumbnails: PptxSlideThumbnailItem[]\n}) {\n  const viewportRef = React.useRef<HTMLDivElement | null>(null)\n  const thumbnailListboxId = React.useId()\n  const visibleThumbnails = React.useMemo(\n    () => thumbnails.slice(0, slideCount || 0),\n    [slideCount, thumbnails]\n  )\n  const activeDescendantId = visibleThumbnails.length\n    ? `${thumbnailListboxId}-slide-${activeSlideIndex + 1}`\n    : undefined\n  const virtualizer = useVirtualizer({\n    count: visibleThumbnails.length,\n    estimateSize: () => PPTX_THUMBNAIL_ROW_ESTIMATE,\n    getItemKey: (index) => visibleThumbnails[index]?.slide.id ?? index,\n    getScrollElement: () => viewportRef.current,\n    overscan: 0,\n  })\n  const virtualItems = virtualizer.getVirtualItems()\n  const renderWindowSignature = virtualItems\n    .map((virtualRow) => virtualRow.index)\n    .join(\",\")\n  const virtualSlideIndexes = React.useMemo(\n    () =>\n      renderWindowSignature\n        ? renderWindowSignature.split(\",\").map((index) => Number(index))\n        : [],\n    [renderWindowSignature]\n  )\n  React.useEffect(() => {\n    if (!sidebarOpen || isLoading || !visibleThumbnails.length) {\n      onRenderWindowChange({\n        prefetchSlideIndexes: [],\n        visibleSlideIndexes: [],\n      })\n      return\n    }\n    const visibleSlideIndexes = virtualSlideIndexes\n    const firstVirtualIndex = virtualSlideIndexes[0] ?? 0\n    const lastVirtualIndex =\n      virtualSlideIndexes[virtualSlideIndexes.length - 1] ?? firstVirtualIndex\n    const firstPrefetchIndex = Math.max(\n      0,\n      firstVirtualIndex - PPTX_THUMBNAIL_PREFETCH_ROWS\n    )\n    const lastPrefetchIndex = Math.min(\n      visibleThumbnails.length - 1,\n      lastVirtualIndex + PPTX_THUMBNAIL_PREFETCH_ROWS\n    )\n    const visibleSlideIndexSet = new Set(visibleSlideIndexes)\n    const prefetchSlideIndexes: number[] = []\n    for (\n      let index = firstPrefetchIndex;\n      index <= lastPrefetchIndex;\n      index += 1\n    ) {\n      if (!visibleSlideIndexSet.has(index)) {\n        prefetchSlideIndexes.push(index)\n      }\n    }\n    onRenderWindowChange({\n      prefetchSlideIndexes,\n      visibleSlideIndexes,\n    })\n  }, [\n    isLoading,\n    onRenderWindowChange,\n    sidebarOpen,\n    virtualSlideIndexes,\n    visibleThumbnails.length,\n  ])\n  React.useEffect(() => {\n    if (!sidebarOpen || !visibleThumbnails.length) return\n    const frameId = window.requestAnimationFrame(() => {\n      virtualizer.scrollToIndex(\n        Math.min(activeSlideIndex, visibleThumbnails.length - 1),\n        { align: \"auto\" }\n      )\n    })\n    return () => window.cancelAnimationFrame(frameId)\n  }, [activeSlideIndex, sidebarOpen, virtualizer, visibleThumbnails.length])\n  const handleKeyDown = React.useCallback(\n    (event: React.KeyboardEvent<HTMLDivElement>) => {\n      if (slideCount < 1) return\n      let nextSlideIndex: number | null = null\n      if (event.key === \"ArrowDown\") {\n        nextSlideIndex = Math.min(slideCount - 1, activeSlideIndex + 1)\n      } else if (event.key === \"ArrowUp\") {\n        nextSlideIndex = Math.max(0, activeSlideIndex - 1)\n      } else if (event.key === \"Home\") {\n        nextSlideIndex = 0\n      } else if (event.key === \"End\") {\n        nextSlideIndex = slideCount - 1\n      }\n      if (nextSlideIndex === null) return\n      event.preventDefault()\n      onSelectSlide(nextSlideIndex)\n    },\n    [activeSlideIndex, onSelectSlide, slideCount]\n  )\n  return (\n    <InlineScrollArea2\n      className=\"h-full\"\n      scrollFade\n      viewportClassName=\"group/pptx-thumbnail-sidebar focus-visible:ring-0 focus-visible:ring-offset-0\"\n      viewportProps={{\n        \"aria-activedescendant\": activeDescendantId,\n        \"aria-busy\": isLoading || undefined,\n        \"aria-label\": \"PowerPoint slides\",\n        onKeyDown: handleKeyDown,\n        onMouseDown: (event) => {\n          event.currentTarget.focus({ preventScroll: true })\n        },\n        role: \"listbox\",\n        tabIndex: 0,\n      }}\n      viewportRef={viewportRef}\n    >\n      {isLoading ? (\n        <div className=\"p-4\">\n          <div className=\"mx-auto aspect-video w-28 overflow-hidden rounded-sm bg-background shadow-xs\">\n            <div className=\"h-full animate-pulse bg-muted\" />\n          </div>\n          <div className=\"mx-auto mt-3 h-3 w-10 rounded-full bg-muted\" />\n        </div>\n      ) : visibleThumbnails.length ? (\n        <div\n          className=\"relative\"\n          style={{\n            height:\n              virtualizer.getTotalSize() + PPTX_THUMBNAIL_LIST_PADDING * 2,\n          }}\n        >\n          {virtualItems.map((virtualRow) => {\n            const thumbnail = visibleThumbnails[virtualRow.index]\n            if (!thumbnail) return null\n            const isActive = thumbnail.slideIndex === activeSlideIndex\n            return (\n              <div\n                key={virtualRow.key}\n                ref={virtualizer.measureElement}\n                data-index={virtualRow.index}\n                className={cn(\n                  \"absolute top-0 right-2 left-2 pb-2 [contain:layout_paint_style]\",\n                  isActive && \"z-10\"\n                )}\n                style={{\n                  transform: `translateY(${virtualRow.start + PPTX_THUMBNAIL_LIST_PADDING}px)`,\n                }}\n              >\n                <div\n                  id={`${thumbnailListboxId}-slide-${thumbnail.slideNumber}`}\n                  role=\"option\"\n                  aria-current={isActive ? \"page\" : undefined}\n                  aria-label={`Slide ${thumbnail.slideNumber}`}\n                  aria-posinset={thumbnail.slideNumber}\n                  aria-selected={isActive}\n                  aria-setsize={slideCount}\n                  className={cn(\n                    \"flex h-auto w-full cursor-default flex-col items-center gap-2 rounded-md p-2 text-xs transition-colors outline-none select-none hover:bg-sidebar-accent\",\n                    isActive\n                      ? \"bg-sidebar-accent text-foreground\"\n                      : \"text-muted-foreground\",\n                    isActive && PPTX_THUMBNAIL_FOCUS_RING_CLASS\n                  )}\n                  onClick={() => onSelectSlide(thumbnail.slideIndex)}\n                >\n                  <PptxSidebarThumbnail\n                    aspectRatio={thumbnail.aspectRatio}\n                    containerRef={thumbnail.containerRef}\n                    displayFileName={displayFileName}\n                    isActive={isActive}\n                    slideNumber={thumbnail.slideNumber}\n                    status={thumbnail.status}\n                  />\n                  {thumbnail.slideNumber}\n                </div>\n              </div>\n            )\n          })}\n        </div>\n      ) : null}\n    </InlineScrollArea2>\n  )\n}\nfunction PptxThumbnailSidebarContent({\n  activeSlideIndex,\n  controller,\n  displayFileName,\n  isLoading,\n  onSelectSlide,\n  sidebarOpen,\n  slideCount,\n}: {\n  activeSlideIndex: number\n  controller: PptxViewerController | null\n  displayFileName: string\n  isLoading: boolean\n  onSelectSlide: (slideIndex: number) => void\n  sidebarOpen: boolean\n  slideCount: number\n}) {\n  const [renderWindow, setRenderWindow] =\n    React.useState<PptxSlideThumbnailRenderWindow>({\n      prefetchSlideIndexes: [],\n      visibleSlideIndexes: [],\n    })\n  const thumbnailOptions = React.useMemo(\n    () => ({\n      renderWindow,\n      resolution: {\n        maxHeight: Math.round(PPTX_THUMBNAIL_WIDTH * 0.75),\n        maxWidth: PPTX_THUMBNAIL_WIDTH,\n      },\n    }),\n    [renderWindow]\n  )\n  const { thumbnails } = usePptxViewerThumbnails(controller, thumbnailOptions)\n  const handleRenderWindowChange = React.useCallback(\n    (nextWindow: PptxSlideThumbnailRenderWindow) => {\n      setRenderWindow((currentWindow) => {\n        const currentVisible = currentWindow.visibleSlideIndexes ?? []\n        const nextVisible = nextWindow.visibleSlideIndexes ?? []\n        const currentPrefetch = currentWindow.prefetchSlideIndexes ?? []\n        const nextPrefetch = nextWindow.prefetchSlideIndexes ?? []\n        if (\n          areNumberArraysEqual(currentVisible, nextVisible) &&\n          areNumberArraysEqual(currentPrefetch, nextPrefetch)\n        ) {\n          return currentWindow\n        }\n        return nextWindow\n      })\n    },\n    []\n  )\n  return (\n    <PptxThumbnailSidebarList\n      activeSlideIndex={activeSlideIndex}\n      displayFileName={displayFileName}\n      isLoading={isLoading}\n      onRenderWindowChange={handleRenderWindowChange}\n      onSelectSlide={onSelectSlide}\n      sidebarOpen={sidebarOpen}\n      slideCount={slideCount}\n      thumbnails={thumbnails}\n    />\n  )\n}\nexport type PptxViewerPreviewProps = {\n  className?: string\n  defaultThumbnailSidebarOpen?: boolean\n  defaultZoom?: number\n  fileName?: string\n  initialSlide?: number\n  showDownload?: boolean\n  showToolbar?: boolean\n  showUpload?: boolean\n  src?: string\n  toolbarActions?: React.ReactNode\n}\nexport function PptxViewerPreview({\n  className,\n  defaultThumbnailSidebarOpen = false,\n  defaultZoom = DEFAULT_ZOOM,\n  fileName,\n  initialSlide = 1,\n  showDownload = true,\n  showToolbar = true,\n  showUpload = true,\n  src,\n  toolbarActions,\n}: PptxViewerPreviewProps) {\n  const fileInputRef = React.useRef<HTMLInputElement>(null)\n  const [viewerShellRef, viewerShellWidth] = useElementWidth<HTMLDivElement>()\n  const requestedInitialSlideIndex = Math.max(0, Math.round(initialSlide) - 1)\n  const [viewportElement, setViewportElement] =\n    React.useState<HTMLDivElement | null>(null)\n  const [uploadedPresentation, setUploadedPresentation] =\n    React.useState<UploadedPresentation | null>(null)\n  const [sidebarOpen, setSidebarOpen] = React.useState(\n    defaultThumbnailSidebarOpen\n  )\n  const [thumbnailSidebarMounted, setThumbnailSidebarMounted] = React.useState(\n    defaultThumbnailSidebarOpen\n  )\n  const [activeSlideIndex, setActiveSlideIndex] = React.useState(\n    requestedInitialSlideIndex\n  )\n  const [zoom, setZoom] = React.useState(() =>\n    Math.min(400, Math.max(10, Math.round(defaultZoom)))\n  )\n  const [slideCount, setSlideCount] = React.useState(0)\n  const [isLoading, setIsLoading] = React.useState(Boolean(src))\n  const [loadError, setLoadError] = React.useState<string>()\n  const [isPreparingDownload, setIsPreparingDownload] = React.useState(false)\n  const [controller, setController] =\n    React.useState<PptxViewerController | null>(null)\n  const sidebarInline = useInlineThumbnailSidebar(viewerShellWidth)\n  const activeUploadedPresentation =\n    uploadedPresentation?.sourceUrl === src ? uploadedPresentation : null\n  const source: PresentationSource | undefined =\n    activeUploadedPresentation?.file ?? src\n  const sourceIdentity = activeUploadedPresentation?.identity ?? src ?? \"\"\n  const hasPresentation = Boolean(source)\n  const displayFileName = React.useMemo(\n    () =>\n      activeUploadedPresentation?.file.name ??\n      (src\n        ? formatPresentationName(fileName, src)\n        : (fileName ?? \"presentation.pptx\")),\n    [activeUploadedPresentation?.file.name, fileName, src]\n  )\n  const thumbnailSidebarVisible = Boolean(sidebarOpen && hasPresentation)\n  const sidebarActiveSlideIndex = useDebouncedValue(\n    activeSlideIndex,\n    PPTX_THUMBNAIL_FOLLOW_DELAY_MS\n  )\n  const controlsDisabled = !hasPresentation || isLoading || Boolean(loadError)\n  const shouldShowLoadingSpinner = useDelayedLoadingIndicator(\n    isLoading,\n    PPTX_LOADING_INDICATOR_DELAY_MS\n  )\n  const virtualization = React.useMemo(\n    () => ({\n      enabled: true,\n      overscanViewport: 0.75,\n      scrollElement: viewportElement,\n    }),\n    [viewportElement]\n  )\n  const pendingSlideNavigationRef = React.useRef<{\n    hasObservedIntermediateSlide: boolean\n    targetIndex: number\n    timeoutId: number\n  } | null>(null)\n  const cancelPendingSlideNavigation = React.useCallback(() => {\n    const pendingNavigation = pendingSlideNavigationRef.current\n    if (pendingNavigation) {\n      window.clearTimeout(pendingNavigation.timeoutId)\n      pendingSlideNavigationRef.current = null\n    }\n  }, [])\n  if (thumbnailSidebarVisible && !thumbnailSidebarMounted) {\n    setThumbnailSidebarMounted(true)\n  }\n  const [previousSource, setPreviousSource] = React.useState({\n    sourceIdentity,\n    defaultZoom,\n  })\n  if (\n    previousSource.sourceIdentity !== sourceIdentity ||\n    previousSource.defaultZoom !== defaultZoom\n  ) {\n    setPreviousSource({ sourceIdentity, defaultZoom })\n    setSlideCount(0)\n    setZoom(Math.min(400, Math.max(10, Math.round(defaultZoom))))\n    setLoadError(undefined)\n    setIsLoading(Boolean(sourceIdentity))\n  }\n  React.useEffect(() => {\n    cancelPendingSlideNavigation()\n    viewportElement?.scrollTo({ top: 0, left: 0 })\n  }, [\n    cancelPendingSlideNavigation,\n    defaultZoom,\n    sourceIdentity,\n    viewportElement,\n  ])\n  React.useEffect(\n    () => cancelPendingSlideNavigation,\n    [cancelPendingSlideNavigation]\n  )\n  const [initialSlideSource, setInitialSlideSource] = React.useState({\n    sourceIdentity,\n    requestedInitialSlideIndex,\n  })\n  if (\n    initialSlideSource.sourceIdentity !== sourceIdentity ||\n    initialSlideSource.requestedInitialSlideIndex !== requestedInitialSlideIndex\n  ) {\n    setInitialSlideSource({ sourceIdentity, requestedInitialSlideIndex })\n    setActiveSlideIndex(requestedInitialSlideIndex)\n  }\n  React.useEffect(() => {\n    if (controller?.isReady()) {\n      void controller.goToSlide(requestedInitialSlideIndex, {\n        behavior: \"instant\",\n        block: \"center\",\n      })\n    }\n  }, [requestedInitialSlideIndex, sourceIdentity, controller])\n  const navigateToSlide = React.useCallback(\n    (nextSlideIndex: number, behavior: ScrollBehavior) => {\n      const normalizedSlideIndex = Math.min(\n        Math.max(0, Math.round(nextSlideIndex)),\n        Math.max(0, slideCount - 1)\n      )\n      const resolvedBehavior =\n        behavior === \"smooth\" &&\n        window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches\n          ? \"instant\"\n          : behavior\n      cancelPendingSlideNavigation()\n      const timeoutId = window.setTimeout(\n        () => {\n          const pendingNavigation = pendingSlideNavigationRef.current\n          if (pendingNavigation?.targetIndex !== normalizedSlideIndex) return\n          pendingSlideNavigationRef.current = null\n          setActiveSlideIndex(normalizedSlideIndex)\n        },\n        resolvedBehavior === \"smooth\"\n          ? PPTX_SMOOTH_NAVIGATION_TIMEOUT_MS\n          : PPTX_INSTANT_NAVIGATION_TIMEOUT_MS\n      )\n      pendingSlideNavigationRef.current = {\n        hasObservedIntermediateSlide: false,\n        targetIndex: normalizedSlideIndex,\n        timeoutId,\n      }\n      setActiveSlideIndex(normalizedSlideIndex)\n      void controller?.goToSlide(normalizedSlideIndex, {\n        behavior: resolvedBehavior,\n        block: \"center\",\n      })\n    },\n    [cancelPendingSlideNavigation, slideCount, controller]\n  )\n  const handleSlideChange = React.useCallback(\n    (nextSlideIndex: number) => navigateToSlide(nextSlideIndex, \"smooth\"),\n    [navigateToSlide]\n  )\n  const handleThumbnailSlideChange = React.useCallback(\n    (nextSlideIndex: number) => navigateToSlide(nextSlideIndex, \"instant\"),\n    [navigateToSlide]\n  )\n  const handleViewerSlideChange = React.useCallback(\n    (nextSlideIndex: number) => {\n      const pendingNavigation = pendingSlideNavigationRef.current\n      if (pendingNavigation) {\n        if (nextSlideIndex !== pendingNavigation.targetIndex) {\n          pendingNavigation.hasObservedIntermediateSlide = true\n          return\n        }\n        // The controller reports the destination immediately, before the smooth\n        // scroll starts. Keep the destination authoritative until the scroll\n        // observer has crossed an intermediate slide and reports it again.\n        if (!pendingNavigation.hasObservedIntermediateSlide) return\n        window.clearTimeout(pendingNavigation.timeoutId)\n        pendingSlideNavigationRef.current = null\n      }\n      React.startTransition(() => {\n        setActiveSlideIndex((currentSlideIndex) =>\n          currentSlideIndex === nextSlideIndex\n            ? currentSlideIndex\n            : nextSlideIndex\n        )\n      })\n    },\n    []\n  )\n  const syncFirstSlideAtViewportTop = React.useCallback(\n    (viewport: HTMLDivElement) => {\n      if (viewport.scrollTop > PPTX_SCROLL_TOP_EPSILON_PX) return\n      setActiveSlideIndex((currentSlideIndex) =>\n        currentSlideIndex === 0 ? currentSlideIndex : 0\n      )\n      if (!isLoading && controller && controller.getSlideIndex() !== 0) {\n        void controller.goToSlide(0, {\n          behavior: \"instant\",\n          block: \"start\",\n        })\n      }\n    },\n    [isLoading, controller]\n  )\n  const handleViewerUserScrollIntent = React.useCallback(\n    (event: React.SyntheticEvent<HTMLDivElement>) => {\n      const viewport = event.currentTarget\n      // Stop an in-flight native smooth scroll before manual wheel, touch,\n      // pointer, or keyboard input takes ownership of the viewport.\n      viewport.scrollTo({\n        behavior: \"instant\",\n        left: viewport.scrollLeft,\n        top: viewport.scrollTop,\n      })\n      cancelPendingSlideNavigation()\n      syncFirstSlideAtViewportTop(viewport)\n    },\n    [cancelPendingSlideNavigation, syncFirstSlideAtViewportTop]\n  )\n  const handleViewerScroll = React.useCallback(\n    (event: React.UIEvent<HTMLDivElement>) => {\n      if (pendingSlideNavigationRef.current) return\n      syncFirstSlideAtViewportTop(event.currentTarget)\n    },\n    [syncFirstSlideAtViewportTop]\n  )\n  const handleViewerKeyDown = React.useCallback(\n    (event: React.KeyboardEvent<HTMLDivElement>) => {\n      if (\n        event.key === \"ArrowDown\" ||\n        event.key === \"ArrowUp\" ||\n        event.key === \"End\" ||\n        event.key === \"Home\" ||\n        event.key === \"PageDown\" ||\n        event.key === \"PageUp\" ||\n        event.key === \" \" ||\n        event.key === \"Spacebar\"\n      ) {\n        handleViewerUserScrollIntent(event)\n      }\n    },\n    [handleViewerUserScrollIntent]\n  )\n  const handleLoad = React.useCallback((presentation: ParsedPresentation) => {\n    const nextSlideCount = presentation.document.slides.length\n    setSlideCount(nextSlideCount)\n    setActiveSlideIndex((currentSlideIndex) =>\n      Math.min(currentSlideIndex, Math.max(0, nextSlideCount - 1))\n    )\n    setLoadError(undefined)\n  }, [])\n  const handleReady = React.useCallback(() => {\n    setIsLoading(false)\n  }, [])\n  const handleError = React.useCallback((error: PptxViewerError) => {\n    setLoadError(error.message)\n    setIsLoading(false)\n  }, [])\n  const handleDownload = React.useCallback(async () => {\n    if (isPreparingDownload || !source) return\n    setIsPreparingDownload(true)\n    try {\n      await downloadPresentation({\n        file: activeUploadedPresentation?.file,\n        fileName: displayFileName,\n        url: activeUploadedPresentation ? undefined : src,\n      })\n    } catch (error) {\n      console.error(error)\n    } finally {\n      setIsPreparingDownload(false)\n    }\n  }, [\n    activeUploadedPresentation,\n    displayFileName,\n    isPreparingDownload,\n    source,\n    src,\n  ])\n  async function handleUpload(event: React.ChangeEvent<HTMLInputElement>) {\n    const file = event.target.files?.[0]\n    event.target.value = \"\"\n    if (!file) return\n    setUploadedPresentation({\n      file,\n      identity: `${file.name}-${file.size}-${file.lastModified}`,\n      sourceUrl: src,\n    })\n  }\n  return (\n    <div\n      className={cn(\n        \"flex h-[640px] min-h-0 flex-col overflow-hidden bg-background\",\n        className\n      )}\n    >\n      <input\n        ref={fileInputRef}\n        type=\"file\"\n        accept={`.ppt,.pptx,${PPT_MIME_TYPE},${PPTX_MIME_TYPE}`}\n        className=\"hidden\"\n        onChange={handleUpload}\n      />\n      {showToolbar ? (\n        <PptxToolbar\n          activeSlideIndex={activeSlideIndex}\n          controlsDisabled={controlsDisabled}\n          isPreparingDownload={isPreparingDownload}\n          onDownload={handleDownload}\n          onSlideChange={handleSlideChange}\n          onToggleSidebar={() => setSidebarOpen((open) => !open)}\n          onUploadClick={() => fileInputRef.current?.click()}\n          setZoom={setZoom}\n          showDownloadButton={showDownload}\n          showUploadButton={showUpload}\n          slideCount={slideCount}\n          toolbarActions={toolbarActions}\n          zoom={zoom}\n        />\n      ) : null}\n      <div\n        ref={viewerShellRef}\n        className=\"relative flex min-h-0 flex-1 overflow-hidden bg-background\"\n      >\n        <DocumentViewerThumbnailSidebar\n          inline={sidebarInline}\n          open={thumbnailSidebarVisible}\n        >\n          {thumbnailSidebarMounted && hasPresentation ? (\n            <PptxThumbnailSidebarContent\n              activeSlideIndex={sidebarActiveSlideIndex}\n              controller={controller}\n              displayFileName={displayFileName}\n              isLoading={isLoading}\n              onSelectSlide={handleThumbnailSlideChange}\n              sidebarOpen={thumbnailSidebarVisible}\n              slideCount={slideCount}\n            />\n          ) : null}\n        </DocumentViewerThumbnailSidebar>\n        <InlineScrollArea2\n          className=\"min-h-0 flex-1 bg-background\"\n          viewportClassName=\"p-0\"\n          viewportProps={{\n            \"aria-label\": \"PowerPoint presentation\",\n            onKeyDown: handleViewerKeyDown,\n            onPointerDown: handleViewerUserScrollIntent,\n            onScroll: handleViewerScroll,\n            onTouchStart: handleViewerUserScrollIntent,\n            onWheel: handleViewerUserScrollIntent,\n            tabIndex: 0,\n          }}\n          viewportRef={setViewportElement}\n        >\n          {!source ? (\n            <div className=\"grid h-full min-h-96 place-items-center p-6 text-center\">\n              <div className=\"max-w-md rounded-lg border bg-background p-4 text-sm shadow-xs\">\n                <div className=\"font-medium\">\n                  Upload a PowerPoint presentation to preview\n                </div>\n                <div className=\"mt-1 text-muted-foreground\">\n                  Pass a PPTX URL with the <code>src</code> prop or upload a\n                  PPTX or legacy PPT file.\n                </div>\n                <Button\n                  type=\"button\"\n                  variant=\"outline\"\n                  size=\"sm\"\n                  className=\"mt-4\"\n                  onClick={() => fileInputRef.current?.click()}\n                >\n                  <IconPlaceholder\n                    lucide=\"Upload\"\n                    tabler=\"IconUpload\"\n                    hugeicons=\"Upload01Icon\"\n                    phosphor=\"UploadSimpleIcon\"\n                    remixicon=\"RiUpload2Line\"\n                    className=\"size-4\"\n                  />\n                  Upload PowerPoint\n                </Button>\n              </div>\n            </div>\n          ) : (\n            <ReactPptxViewer\n              key={sourceIdentity}\n              ref={setController}\n              source={source}\n              mode=\"continuous\"\n              initialSlide={requestedInitialSlideIndex}\n              zoom={zoom}\n              fitMode=\"contain\"\n              height=\"100%\"\n              showToolbar={false}\n              showThumbnails={false}\n              virtualization={virtualization}\n              className=\"min-h-full !border-0 !bg-background [&_.rpv-stage]:min-h-full [&_.rpv-stage]:!bg-background [&_.rpv-status]:!bg-background [&_.rpv-workspace]:min-h-full [&_[data-rpv-list-item]]:[contain:layout_paint_style]\"\n              viewportClassName=\"!min-h-full !px-4 !py-6\"\n              renderLoading={() => (\n                <ViewerLoadingSurface showSpinner={shouldShowLoadingSpinner} />\n              )}\n              renderError={(error) => (\n                <div className=\"grid h-full min-h-96 place-items-center p-6 text-center\">\n                  <div className=\"max-w-md rounded-lg border bg-background p-4 text-sm text-destructive shadow-xs\">\n                    <div className=\"font-medium\">\n                      Unable to display PowerPoint\n                    </div>\n                    <div className=\"mt-1 text-muted-foreground\">\n                      {error.message}\n                    </div>\n                  </div>\n                </div>\n              )}\n              emptyState={\n                <div className=\"text-sm text-muted-foreground\">\n                  This presentation has no slides.\n                </div>\n              }\n              onLoad={handleLoad}\n              onReady={handleReady}\n              onError={handleError}\n              onSlideChange={handleViewerSlideChange}\n            />\n          )}\n        </InlineScrollArea2>\n      </div>\n    </div>\n  )\n}\nfunction InlineScrollArea2({\n  className,\n  children,\n  orientation = \"both\",\n  scrollFade = false,\n  scrollbarGutter = false,\n  scrollbarOverflowOnly = false,\n  viewportClassName,\n  viewportProps,\n  viewportRef,\n  ...props\n}: InlineScrollAreaProps) {\n  const {\n    className: viewportPropsClassName,\n    ref: viewportPropsRef,\n    ...resolvedViewportProps\n  } = viewportProps ?? {}\n  const composedViewportRef = React.useMemo(\n    () => InlineComposeRefs(viewportPropsRef, viewportRef),\n    [viewportPropsRef, viewportRef]\n  )\n\n  if (\n    !viewportProps &&\n    !viewportRef &&\n    !viewportClassName &&\n    !scrollFade &&\n    !scrollbarGutter &&\n    !scrollbarOverflowOnly\n  ) {\n    return (\n      <InlineScrollArea\n        {...props}\n        className={cn(\n          \"size-full min-h-0\",\n          orientation === \"horizontal\" &&\n            \"[&>[data-orientation=vertical]]:hidden\",\n          className\n        )}\n      >\n        {children}\n        {orientation !== \"vertical\" ? (\n          <ScrollBar orientation=\"horizontal\" />\n        ) : null}\n      </InlineScrollArea>\n    )\n  }\n\n  return (\n    <ScrollAreaPrimitive.Root\n      className={cn(\n        \"size-full min-h-0\",\n        scrollbarOverflowOnly &&\n          \"[&:not(:has([data-slot=scroll-area-viewport][data-has-overflow-x]))_[data-orientation=horizontal]]:hidden [&:not(:has([data-slot=scroll-area-viewport][data-has-overflow-y]))_[data-orientation=vertical]]:hidden\",\n        className\n      )}\n      {...props}\n    >\n      <ScrollAreaPrimitive.Viewport\n        {...resolvedViewportProps}\n        ref={composedViewportRef}\n        className={cn(\n          \"h-full rounded-[inherit] outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n          scrollFade &&\n            \"mask-t-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-start)))] mask-r-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-end)))] mask-b-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-end)))] mask-l-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-start)))] [--fade-size:1.5rem]\",\n          scrollbarGutter && orientation !== \"vertical\" && \"pb-3.5\",\n          scrollbarGutter && orientation !== \"horizontal\" && \"pe-3.5\",\n          viewportPropsClassName,\n          viewportClassName\n        )}\n        data-slot=\"scroll-area-viewport\"\n      >\n        {children}\n      </ScrollAreaPrimitive.Viewport>\n      {orientation !== \"horizontal\" ? (\n        <ScrollBar orientation=\"vertical\" />\n      ) : null}\n      {orientation !== \"vertical\" ? (\n        <ScrollBar orientation=\"horizontal\" />\n      ) : null}\n      {orientation === \"both\" ? <ScrollAreaPrimitive.Corner /> : null}\n    </ScrollAreaPrimitive.Root>\n  )\n}\ntype InlineScrollAreaProps = ScrollAreaPrimitive.Root.Props & {\n  orientation?: \"vertical\" | \"horizontal\" | \"both\"\n  scrollFade?: boolean\n  scrollbarGutter?: boolean\n  scrollbarOverflowOnly?: boolean\n  viewportClassName?: string\n  viewportProps?: ScrollAreaPrimitive.Viewport.Props\n  viewportRef?: React.Ref<HTMLDivElement>\n}\nfunction InlineComposeRefs<T>(...refs: Array<React.Ref<T> | undefined>) {\n  return (node: T | null) => {\n    for (const ref of refs) {\n      if (!ref) continue\n      if (typeof ref === \"function\") ref(node)\n      else ref.current = node\n    }\n  }\n}\nfunction InlineSpinner({ className, ...props }: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"LoaderCircle\"\n      tabler=\"IconLoader2\"\n      hugeicons=\"Loading03Icon\"\n      phosphor=\"CircleNotchIcon\"\n      remixicon=\"RiLoader4Line\"\n      role=\"status\"\n      aria-label=\"Loading\"\n      className={cn(\"size-4 animate-spin\", className)}\n      {...props}\n    />\n  )\n}\ntype InlineRegistryIconProps = Omit<\n  React.ComponentProps<\"svg\">,\n  \"children\" | \"strokeWidth\"\n> & { strokeWidth?: number }\n",
      "type": "registry:ui",
      "target": "@components/extend/pptx-viewer.tsx"
    }
  ],
  "categories": [
    "documents"
  ],
  "type": "registry:ui"
}
