{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "docx-editor",
  "title": "DOCX Editor",
  "description": "An experimental DOCX editor with a Word-style toolbar, page thumbnails, import, export, and live document mutation.",
  "dependencies": [
    "@extend-ai/react-docx@^0.9.1",
    "@base-ui/react@^1.4.1"
  ],
  "registryDependencies": [
    "@extend/document-viewer-sidebar",
    "@extend/file-thumbnail",
    "utils",
    "button",
    "dropdown-menu",
    "input",
    "scroll-area",
    "select",
    "separator",
    "toggle-group",
    "tooltip",
    "@extend/document-color-picker",
    "@extend/document-preview-card",
    "badge",
    "card"
  ],
  "files": [
    {
      "path": "components/extend/docx-editor.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ScrollArea as ScrollAreaPrimitive } from \"@base-ui/react/scroll-area\"\nimport {\n  DocxEditorViewer,\n  paragraphLetterheadFloatSideAtNodeIndex,\n  useDocxBorders,\n  useDocxComments,\n  useDocxDocumentTheme,\n  useDocxEditor,\n  useDocxLineSpacing,\n  useDocxPageLayout,\n  useDocxParagraphStyles,\n  useDocxTrackChanges,\n  useDocxViewerThumbnails,\n  type DocxBorderContext,\n  type DocxBorderPreset,\n  type DocxDocumentTheme,\n  type DocxEditorController,\n  type ParagraphStyleDefinition,\n} from \"@extend-ai/react-docx\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  DropdownMenu,\n  DropdownMenuCheckboxItem,\n  DropdownMenuContent,\n  DropdownMenuSeparator,\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 { ToggleGroup, ToggleGroupItem } from \"@/components/ui/toggle-group\"\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\"\nimport { ColorPicker } from \"@/components/extend/document-color-picker\"\nimport {\n  PreviewCard,\n  PreviewCardContent,\n  PreviewCardTrigger,\n} from \"@/components/extend/document-preview-card\"\nimport {\n  DocumentViewerThumbnailSidebar,\n  useElementWidth,\n  useInlineThumbnailSidebar,\n} from \"@/components/extend/document-viewer-sidebar\"\nimport {\n  createDocxCommentCardRenderer,\n  createDocxTrackedChangeCardRenderer,\n} from \"@/components/extend/docx-annotation-card\"\nimport { FileThumbnail } from \"@/components/extend/file-thumbnail\"\nimport { IconPlaceholder } from \"@/components/icon-placeholder\"\n\nfunction ArrowExpandDiagonal01Glyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"ArrowDownRight\"\n      tabler=\"IconArrowDownRight\"\n      hugeicons=\"ArrowExpandDiagonal01Icon\"\n      phosphor=\"ArrowDownRightIcon\"\n      remixicon=\"RiArrowRightDownLine\"\n      {...props}\n    />\n  )\n}\nfunction ArrowExpandDiagonal02Glyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"ArrowUpRight\"\n      tabler=\"IconArrowUpRight\"\n      hugeicons=\"ArrowExpandDiagonal02Icon\"\n      phosphor=\"ArrowUpRightIcon\"\n      remixicon=\"RiArrowRightUpLine\"\n      {...props}\n    />\n  )\n}\nfunction BorderAll01Glyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"Grid2x2\"\n      tabler=\"IconBorderAll\"\n      hugeicons=\"BorderAll01Icon\"\n      phosphor=\"GridFourIcon\"\n      remixicon=\"RiGridLine\"\n      {...props}\n    />\n  )\n}\nfunction BorderBottom01Glyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"PanelBottom\"\n      tabler=\"IconBorderBottom\"\n      hugeicons=\"BorderBottom01Icon\"\n      phosphor=\"SquareHalfBottomIcon\"\n      remixicon=\"RiLayoutBottomLine\"\n      {...props}\n    />\n  )\n}\nfunction BorderHorizontalGlyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"SeparatorHorizontal\"\n      tabler=\"IconBorderHorizontal\"\n      hugeicons=\"BorderHorizontalIcon\"\n      phosphor=\"MinusIcon\"\n      remixicon=\"RiSeparator\"\n      {...props}\n    />\n  )\n}\nfunction BorderInnerGlyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"SquarePlus\"\n      tabler=\"IconBorderInner\"\n      hugeicons=\"BorderInnerIcon\"\n      phosphor=\"PlusSquareIcon\"\n      remixicon=\"RiAddBoxLine\"\n      {...props}\n    />\n  )\n}\nfunction BorderLeft01Glyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"PanelLeft\"\n      tabler=\"IconBorderLeft\"\n      hugeicons=\"BorderLeft01Icon\"\n      phosphor=\"SquareHalfIcon\"\n      remixicon=\"RiLayoutLeftLine\"\n      {...props}\n    />\n  )\n}\nfunction BorderNone01Glyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"SquareDashed\"\n      tabler=\"IconBorderNone\"\n      hugeicons=\"BorderNone01Icon\"\n      phosphor=\"SelectionIcon\"\n      remixicon=\"RiCheckboxBlankLine\"\n      {...props}\n    />\n  )\n}\nfunction BorderRight01Glyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"PanelRight\"\n      tabler=\"IconBorderRight\"\n      hugeicons=\"BorderRight01Icon\"\n      phosphor=\"SquareHalfIcon\"\n      remixicon=\"RiLayoutRightLine\"\n      {...props}\n    />\n  )\n}\nfunction BorderTop01Glyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"PanelTop\"\n      tabler=\"IconBorderTop\"\n      hugeicons=\"BorderTop01Icon\"\n      phosphor=\"AlignTopSimpleIcon\"\n      remixicon=\"RiLayoutTopLine\"\n      {...props}\n    />\n  )\n}\nfunction BorderVerticalGlyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"SeparatorVertical\"\n      tabler=\"IconBorderVertical\"\n      hugeicons=\"BorderVerticalIcon\"\n      phosphor=\"LineVerticalIcon\"\n      remixicon=\"RiLayoutColumnLine\"\n      {...props}\n    />\n  )\n}\nfunction LineGlyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"Minus\"\n      tabler=\"IconMinus\"\n      hugeicons=\"LineIcon\"\n      phosphor=\"MinusIcon\"\n      remixicon=\"RiSubtractLine\"\n      {...props}\n    />\n  )\n}\nfunction Moon02Glyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"Moon\"\n      tabler=\"IconMoon\"\n      hugeicons=\"Moon02Icon\"\n      phosphor=\"MoonIcon\"\n      remixicon=\"RiMoonLine\"\n      {...props}\n    />\n  )\n}\nfunction Sun03Glyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"Sun\"\n      tabler=\"IconSun\"\n      hugeicons=\"Sun03Icon\"\n      phosphor=\"SunIcon\"\n      remixicon=\"RiSunLine\"\n      {...props}\n    />\n  )\n}\nfunction TextColorGlyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"Baseline\"\n      tabler=\"IconTextColor\"\n      hugeicons=\"TextColorIcon\"\n      phosphor=\"TextAaIcon\"\n      remixicon=\"RiFontColor\"\n      {...props}\n    />\n  )\n}\nconst DOCX_MIME_TYPE =\n  \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\"\nconst DOCX_LOADING_INDICATOR_DELAY_MS = 300\nconst DOCX_THUMBNAIL_WIDTH = 92\nconst DOCX_EDITOR_DEFAULT_ZOOM_SCALE = 100\nconst ZOOM_OPTIONS = [50, 75, 90, 100, 110, 125, 150, 175, 200] as const\nconst FONT_FAMILIES = [\n  \"Calibri\",\n  \"Arial\",\n  \"Times New Roman\",\n  \"Georgia\",\n  \"Helvetica\",\n  \"Courier New\",\n] as const\nconst FONT_SIZE_OPTIONS = [\n  8, 9, 10, 11, 12, 14, 16, 18, 20, 24, 28, 32, 36, 48,\n] as const\nconst LINE_SPACING_OPTIONS = [1, 1.15, 1.2, 1.5, 2, 2.5, 3] as const\nconst FALLBACK_PARAGRAPH_STYLE_OPTIONS = [\n  { id: \"Normal\", name: \"Body\" },\n  { id: \"Heading1\", name: \"Heading 1\" },\n  { id: \"Heading2\", name: \"Heading 2\" },\n  { id: \"Heading3\", name: \"Heading 3\" },\n] as const\nconst HIGHLIGHT_COLORS = [\n  { label: \"Yellow\", value: \"yellow\", color: \"#fff59d\" },\n  { label: \"Green\", value: \"green\", color: \"#bbf7d0\" },\n  { label: \"Cyan\", value: \"cyan\", color: \"#a5f3fc\" },\n  { label: \"Magenta\", value: \"magenta\", color: \"#f5d0fe\" },\n  { label: \"Red\", value: \"red\", color: \"#fecaca\" },\n  { label: \"Blue\", value: \"blue\", color: \"#bfdbfe\" },\n] as const\nconst HIGHLIGHT_PREVIEW_COLORS: Record<string, string> = {\n  black: \"#111827\",\n  white: \"#ffffff\",\n  ...Object.fromEntries(\n    HIGHLIGHT_COLORS.map((option) => [option.value, option.color])\n  ),\n}\nconst DOCX_PADDING_WARNING_TEXT = \"a style property during rerender\"\nconst DEFAULT_HEADING_PREVIEW_RUN_STYLES: Record<\n  1 | 2 | 3 | 4 | 5 | 6,\n  NonNullable<ParagraphStyleDefinition[\"runStyle\"]>\n> = {\n  1: {\n    fontFamily: \"Calibri Light\",\n    fontSizePt: 16,\n    bold: true,\n    color: \"#2f5496\",\n  },\n  2: {\n    fontFamily: \"Calibri Light\",\n    fontSizePt: 13,\n    bold: true,\n    color: \"#2f5496\",\n  },\n  3: {\n    fontFamily: \"Calibri\",\n    fontSizePt: 12,\n    bold: true,\n    color: \"#1f3763\",\n  },\n  4: {\n    fontFamily: \"Calibri\",\n    fontSizePt: 11,\n    bold: true,\n    color: \"#1f3763\",\n  },\n  5: {\n    fontFamily: \"Calibri\",\n    fontSizePt: 11,\n    bold: true,\n    color: \"#1f3763\",\n  },\n  6: {\n    fontFamily: \"Calibri\",\n    fontSizePt: 11,\n    bold: true,\n    color: \"#1f3763\",\n  },\n}\ntype UploadedDocxFile = {\n  file: File\n  identity: string\n}\ntype BorderControlOption = {\n  id: DocxBorderPreset\n  label: string\n  contexts?: DocxBorderContext[]\n  separatorBefore?: boolean\n}\ntype DocxFontToggleValue =\n  | \"bold\"\n  | \"italic\"\n  | \"underline\"\n  | \"strike\"\n  | \"superscript\"\n  | \"subscript\"\nconst BORDER_CONTROL_OPTIONS: BorderControlOption[] = [\n  { id: \"bottom\", label: \"Bottom Border\" },\n  { id: \"top\", label: \"Top Border\" },\n  { id: \"left\", label: \"Left Border\" },\n  { id: \"right\", label: \"Right Border\" },\n  { id: \"none\", label: \"No Border\", separatorBefore: true },\n  { id: \"all\", label: \"All Borders\" },\n  { id: \"outside\", label: \"Outside Borders\" },\n  { id: \"inside\", label: \"Inside Borders\", contexts: [\"table\"] },\n  {\n    id: \"inside-horizontal\",\n    label: \"Inside Horizontal Border\",\n    contexts: [\"table\"],\n  },\n  {\n    id: \"inside-vertical\",\n    label: \"Inside Vertical Border\",\n    contexts: [\"table\"],\n  },\n  {\n    id: \"diagonal-down\",\n    label: \"Diagonal Down Border\",\n    contexts: [\"table\"],\n    separatorBefore: true,\n  },\n  { id: \"diagonal-up\", label: \"Diagonal Up Border\", contexts: [\"table\"] },\n  { id: \"horizontal-line\", label: \"Horizontal Line\", separatorBefore: true },\n]\nfunction borderControlOptionIcon(optionId: DocxBorderPreset) {\n  switch (optionId) {\n    case \"bottom\":\n      return BorderBottom01Glyph\n    case \"top\":\n      return BorderTop01Glyph\n    case \"left\":\n      return BorderLeft01Glyph\n    case \"right\":\n      return BorderRight01Glyph\n    case \"none\":\n      return BorderNone01Glyph\n    case \"inside\":\n      return BorderInnerGlyph\n    case \"inside-horizontal\":\n      return BorderHorizontalGlyph\n    case \"inside-vertical\":\n      return BorderVerticalGlyph\n    case \"diagonal-down\":\n      return ArrowExpandDiagonal01Glyph\n    case \"diagonal-up\":\n      return ArrowExpandDiagonal02Glyph\n    case \"horizontal-line\":\n      return LineGlyph\n    default:\n      return BorderAll01Glyph\n  }\n}\nasync function loadDocxFile(\n  url: string,\n  displayFileName: string\n): Promise<File> {\n  const response = await fetch(url)\n  if (!response.ok) {\n    throw new Error(`Failed to fetch DOCX (${response.status})`)\n  }\n  const blob = await response.blob()\n  return new File([blob], displayFileName, {\n    type: blob.type || DOCX_MIME_TYPE,\n  })\n}\nfunction formatDocumentName(fileName: string | undefined, url: string) {\n  if (fileName?.trim()) return fileName\n  const pathname = url.split(\"?\")[0] ?? \"\"\n  const rawName = pathname.split(\"/\").pop() ?? \"document.docx\"\n  try {\n    return decodeURIComponent(rawName)\n  } catch {\n    return rawName\n  }\n}\nfunction getNextZoomScale(currentZoomScale: number, direction: 1 | -1) {\n  const currentIndex = ZOOM_OPTIONS.indexOf(\n    currentZoomScale as (typeof ZOOM_OPTIONS)[number]\n  )\n  let fallbackIndex = -1\n  if (direction > 0) {\n    fallbackIndex = ZOOM_OPTIONS.findIndex((value) => value > currentZoomScale)\n  } else {\n    for (let index = ZOOM_OPTIONS.length - 1; index >= 0; index -= 1) {\n      if (ZOOM_OPTIONS[index] < currentZoomScale) {\n        fallbackIndex = index\n        break\n      }\n    }\n  }\n  const resolvedIndex = currentIndex >= 0 ? currentIndex : fallbackIndex\n  if (resolvedIndex < 0) return currentZoomScale\n  const nextIndex = Math.min(\n    Math.max(resolvedIndex + direction, 0),\n    ZOOM_OPTIONS.length - 1\n  )\n  return ZOOM_OPTIONS[nextIndex] ?? currentZoomScale\n}\nfunction normalizeDocxZoomScale(value: number | undefined): number {\n  return typeof value === \"number\" &&\n    ZOOM_OPTIONS.includes(value as (typeof ZOOM_OPTIONS)[number])\n    ? value\n    : DOCX_EDITOR_DEFAULT_ZOOM_SCALE\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 isDocxPaddingWarning(args: unknown[]) {\n  return (\n    typeof args[0] === \"string\" &&\n    args[0].includes(DOCX_PADDING_WARNING_TEXT) &&\n    args.some((arg) => String(arg).includes(\"padding\"))\n  )\n}\nfunction useSuppressDocxPaddingWarning(enabled: boolean) {\n  React.useEffect(() => {\n    if (!enabled) return\n    const originalConsoleError = console.error\n    console.error = (...args: unknown[]) => {\n      if (isDocxPaddingWarning(args)) return\n      originalConsoleError(...args)\n    }\n    return () => {\n      console.error = originalConsoleError\n    }\n  }, [enabled])\n}\nfunction normalizeHexColor(value?: string, fallback = \"#111827\") {\n  if (!value) return fallback\n  const trimmed = value.trim()\n  const threeDigit = /^#([0-9a-f]{3})$/i.exec(trimmed)\n  if (threeDigit?.[1]) {\n    const [red, green, blue] = threeDigit[1].split(\"\")\n    return `#${red}${red}${green}${green}${blue}${blue}`.toLowerCase()\n  }\n  const sixDigit = /^#([0-9a-f]{6})$/i.exec(trimmed)\n  if (sixDigit?.[1]) {\n    return `#${sixDigit[1].toLowerCase()}`\n  }\n  return fallback\n}\nfunction inferParagraphStyleHeadingLevel(value?: string) {\n  if (!value) return undefined\n  const match = value.match(/(?:^|[\\s_-])(?:heading|h)\\s*([1-6])(?:$|[\\s_-])/i)\n  const level = match?.[1] ? Number(match[1]) : NaN\n  return level >= 1 && level <= 6 ? (level as 1 | 2 | 3 | 4 | 5 | 6) : undefined\n}\nfunction resolveParagraphStyleHeadingLevel(option?: ParagraphStyleDefinition) {\n  if (\n    Number.isFinite(option?.headingLevel) &&\n    option?.headingLevel &&\n    option.headingLevel >= 1 &&\n    option.headingLevel <= 6\n  ) {\n    return option.headingLevel as 1 | 2 | 3 | 4 | 5 | 6\n  }\n  return (\n    inferParagraphStyleHeadingLevel(option?.id) ??\n    inferParagraphStyleHeadingLevel(option?.name)\n  )\n}\nfunction resolveParagraphStyleRunPreview(option?: ParagraphStyleDefinition) {\n  if (!option) return undefined\n  const headingLevel = resolveParagraphStyleHeadingLevel(option)\n  const headingRunStyle = headingLevel\n    ? DEFAULT_HEADING_PREVIEW_RUN_STYLES[headingLevel]\n    : undefined\n  return headingRunStyle\n    ? {\n        ...headingRunStyle,\n        ...(option.runStyle ?? {}),\n      }\n    : option.runStyle\n}\nfunction resolveHighlightPreview(value?: string) {\n  if (!value) return undefined\n  const normalized = value.trim().toLowerCase()\n  if (!normalized) return undefined\n  const hex = normalizeHexColor(normalized, \"\")\n  if (hex) return hex\n  return HIGHLIGHT_PREVIEW_COLORS[normalized]\n}\nfunction themedPreviewColor(\n  color: string | undefined,\n  documentTheme: DocxDocumentTheme\n) {\n  if (documentTheme !== \"dark\") return color\n  if (!color) return \"#f3f4f6\"\n  const normalized = color.trim().toLowerCase()\n  if (\n    normalized === \"#000\" ||\n    normalized === \"#000000\" ||\n    normalized === \"#111111\" ||\n    normalized === \"#111827\" ||\n    normalized === \"black\" ||\n    normalized === \"rgb(0,0,0)\" ||\n    normalized === \"rgb(0, 0, 0)\"\n  ) {\n    return \"#f3f4f6\"\n  }\n  return color\n}\nfunction paragraphStylePreviewStyle(\n  option: ParagraphStyleDefinition,\n  documentTheme: DocxDocumentTheme\n): React.CSSProperties {\n  const runStyle = resolveParagraphStyleRunPreview(option)\n  const textDecoration = [\n    runStyle?.underline ? \"underline\" : \"\",\n    runStyle?.strike ? \"line-through\" : \"\",\n  ]\n    .filter(Boolean)\n    .join(\" \")\n  return {\n    textAlign: option.align ?? \"left\",\n    fontFamily: runStyle?.fontFamily,\n    fontSize: runStyle?.fontSizePt ? `${runStyle.fontSizePt}pt` : \"11pt\",\n    fontWeight:\n      runStyle?.bold !== undefined ? (runStyle.bold ? 700 : 400) : undefined,\n    fontStyle: runStyle?.italic ? \"italic\" : undefined,\n    textDecoration: textDecoration || undefined,\n    color:\n      runStyle?.color !== undefined\n        ? themedPreviewColor(normalizeHexColor(runStyle.color), documentTheme)\n        : undefined,\n    backgroundColor: resolveHighlightPreview(runStyle?.highlight),\n    lineHeight: 1,\n    whiteSpace: \"pre-wrap\",\n  }\n}\nfunction parseToolbarSectionColumns(sectionPropertiesXml?: string):\n  | {\n      count: number\n      gapPx: number\n    }\n  | undefined {\n  if (!sectionPropertiesXml) return undefined\n  const columnsTag = sectionPropertiesXml.match(/<w:cols\\b[^>]*\\/?>/i)?.[0]\n  if (!columnsTag) return undefined\n  const countRaw = columnsTag.match(/\\bw:num=\"(\\d+)\"/i)?.[1]\n  const count = countRaw ? Number(countRaw) : 1\n  if (!Number.isFinite(count) || count <= 1) return undefined\n  const gapRaw = columnsTag.match(/\\bw:space=\"(\\d+)\"/i)?.[1]\n  const gapTwips = gapRaw ? Number(gapRaw) : 720\n  const gapPx = Math.max(0, Math.round((gapTwips * 96) / 1440))\n  return {\n    count: Math.max(2, Math.round(count)),\n    gapPx,\n  }\n}\nfunction ToolbarTooltip({\n  label,\n  children,\n}: {\n  label: string\n  children: React.ReactNode\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 EditorLoadingSurface({\n  showSpinner = true,\n}: {\n  showSpinner?: boolean\n}) {\n  return (\n    <div className=\"grid h-full min-h-52 place-items-center bg-transparent\">\n      {showSpinner ? <InlineSpinner className=\"size-4\" /> : null}\n    </div>\n  )\n}\nfunction ToolbarIconButton({\n  active,\n  children,\n  label,\n  ...props\n}: React.ComponentProps<typeof Button> & {\n  active?: boolean\n  label: string\n}) {\n  return (\n    <ToolbarTooltip label={label}>\n      <Button\n        type=\"button\"\n        variant={active ? \"secondary\" : \"ghost\"}\n        size=\"icon-sm\"\n        aria-label={label}\n        data-pressed={active ? \"\" : undefined}\n        {...props}\n      >\n        {children}\n      </Button>\n    </ToolbarTooltip>\n  )\n}\nfunction ToolbarSeparator() {\n  return <Separator orientation=\"vertical\" className=\"mx-1 h-4 self-center\" />\n}\nfunction DocxSidebarThumbnail({\n  canvasRef,\n  displayFileName,\n  hasError,\n  isActive,\n  isLoading,\n  pageNumber,\n  pixelHeightPx,\n  pixelWidthPx,\n  previewAspectRatio,\n}: {\n  canvasRef: React.RefCallback<HTMLCanvasElement>\n  displayFileName: string\n  hasError: boolean\n  isActive: boolean\n  isLoading: boolean\n  pageNumber: number\n  pixelHeightPx: number\n  pixelWidthPx: number\n  previewAspectRatio: number\n}) {\n  return (\n    <FileThumbnail\n      file={{\n        name: `${displayFileName} page ${pageNumber}`,\n        type: DOCX_MIME_TYPE,\n      }}\n      previewAspectRatio={previewAspectRatio}\n      previewClassName=\"rounded-md bg-white\"\n      previewContent={\n        <canvas\n          ref={canvasRef}\n          width={pixelWidthPx}\n          height={pixelHeightPx}\n          className=\"!size-full bg-white object-cover object-top\"\n        />\n      }\n      isLoading={isLoading}\n      hasError={hasError}\n      className={cn(\n        \"w-[92px] rounded-md border-0 shadow-xs ring-0 transition-shadow duration-150\",\n        isActive && \"shadow-sm\"\n      )}\n    />\n  )\n}\nfunction ParagraphStylePreviewCard({\n  documentTheme,\n  option,\n}: {\n  documentTheme: DocxDocumentTheme\n  option: ParagraphStyleDefinition\n}) {\n  const surfaceStyle: React.CSSProperties =\n    documentTheme === \"dark\"\n      ? {\n          backgroundColor: \"#111827\",\n          borderColor: \"#374151\",\n          color: \"#f3f4f6\",\n        }\n      : {\n          backgroundColor: \"#ffffff\",\n          borderColor: \"#d4d4d8\",\n          color: \"#111827\",\n        }\n  const secondaryTextColor = documentTheme === \"dark\" ? \"#9ca3af\" : \"#6b7280\"\n  return (\n    <div className=\"w-[260px] p-3\">\n      <p className=\"text-[10px] font-medium tracking-wide text-muted-foreground uppercase\">\n        Style Preview\n      </p>\n      <div className=\"mt-2 rounded-sm border p-2.5\" style={surfaceStyle}>\n        <p style={paragraphStylePreviewStyle(option, documentTheme)}>\n          {option.name}\n        </p>\n        <p\n          className=\"mt-1 text-[11px]\"\n          style={{\n            color: secondaryTextColor,\n            textAlign: option.align ?? \"left\",\n          }}\n        >\n          The quick brown fox jumps over the lazy dog.\n        </p>\n      </div>\n    </div>\n  )\n}\nfunction DocxEditorToolbar({\n  activePage,\n  controlsDisabled,\n  editor,\n  isReadOnly,\n  onImageUploadClick,\n  onIsDarkChange,\n  onIsReadOnlyChange,\n  onOpenLinkEditor,\n  onToggleSidebar,\n  onUploadClick,\n  pageCount,\n  setZoomScale,\n  showNightRenderToggle,\n  zoomScale,\n}: {\n  activePage: number\n  controlsDisabled: boolean\n  editor: DocxEditorController\n  isReadOnly: boolean\n  onImageUploadClick: () => void\n  onIsDarkChange: (checked: boolean) => void\n  onIsReadOnlyChange: (checked: boolean) => void\n  onOpenLinkEditor: () => void\n  onToggleSidebar: () => void\n  onUploadClick: () => void\n  pageCount: number\n  setZoomScale: React.Dispatch<React.SetStateAction<number>>\n  showNightRenderToggle: boolean\n  zoomScale: number\n}) {\n  const { documentTheme, setDocumentTheme } = useDocxDocumentTheme(editor)\n  const { layout: pageLayout } = useDocxPageLayout(editor)\n  const { paragraphStyles, selectedParagraphStyleId, setParagraphStyle } =\n    useDocxParagraphStyles(editor)\n  const { lineSpacing, setLineSpacing } = useDocxLineSpacing(editor)\n  const { borderContext, activeBorderPresets, applyBorderPreset } =\n    useDocxBorders(editor)\n  const { showTrackedChanges, setShowTrackedChanges } =\n    useDocxTrackChanges(editor)\n  const { showComments, setShowComments } = useDocxComments(editor)\n  const selectedRunStyle = editor.selectedRunStyle\n  const selectedParagraph = editor.selectedParagraph\n  const paragraphStyleOptions: ParagraphStyleDefinition[] =\n    paragraphStyles.length > 0\n      ? paragraphStyles\n      : [...FALLBACK_PARAGRAPH_STYLE_OPTIONS]\n  const selectedParagraphStyleValue =\n    selectedParagraphStyleId ??\n    paragraphStyleOptions.find((option) => \"isDefault\" in option)?.id ??\n    paragraphStyleOptions[0]?.id ??\n    \"Normal\"\n  const [paragraphStylePreviewId, setParagraphStylePreviewId] = React.useState<\n    string | null\n  >(null)\n  const selectedLineSpacingValue = React.useMemo(() => {\n    const current = Number.isFinite(lineSpacing.multiple)\n      ? lineSpacing.multiple\n      : 1\n    const nearest = LINE_SPACING_OPTIONS.reduce((closest, candidate) => {\n      return Math.abs(candidate - current) < Math.abs(closest - current)\n        ? candidate\n        : closest\n    }, LINE_SPACING_OPTIONS[0])\n    return String(nearest)\n  }, [lineSpacing.multiple])\n  const textColorValue = normalizeHexColor(selectedRunStyle?.color)\n  const highlightColor =\n    HIGHLIGHT_COLORS.find(\n      (option) => option.value === selectedRunStyle?.highlight\n    )?.color ?? \"#fff59d\"\n  const activeNodeIndex =\n    editor.selection.kind === \"paragraph\"\n      ? editor.selection.nodeIndex\n      : editor.selection.tableIndex\n  const letterheadColumns =\n    editor.selection.kind === \"paragraph\" &&\n    paragraphLetterheadFloatSideAtNodeIndex(\n      editor.model.nodes,\n      editor.selection.nodeIndex\n    )\n      ? { count: 2, gapPx: 28 }\n      : undefined\n  const sections = editor.model.metadata.sections ?? []\n  const activeSection = sections\n    .filter((section) => section.startNodeIndex <= activeNodeIndex)\n    .at(-1)\n  const activeColumns =\n    letterheadColumns ??\n    parseToolbarSectionColumns(activeSection?.sectionPropertiesXml) ??\n    parseToolbarSectionColumns(editor.model.metadata.sectionPropertiesXml) ??\n    pageLayout.columns\n  const activeBorderControlOptions = BORDER_CONTROL_OPTIONS.filter(\n    (option) => activeBorderPresets[option.id]\n  )\n  const borderTriggerLabel =\n    activeBorderControlOptions.length === 1\n      ? activeBorderControlOptions[0].label\n      : \"Borders\"\n  const borderTriggerIcon = React.createElement(\n    borderControlOptionIcon(\n      activeBorderControlOptions.length === 1\n        ? activeBorderControlOptions[0].id\n        : \"all\"\n    ),\n    { className: \"size-4\" }\n  )\n  const canEdit = !controlsDisabled && !isReadOnly\n  const canZoomIn = zoomScale < ZOOM_OPTIONS[ZOOM_OPTIONS.length - 1]\n  const canZoomOut = zoomScale > ZOOM_OPTIONS[0]\n  const fontToggleValues = React.useMemo<DocxFontToggleValue[]>(() => {\n    const values: DocxFontToggleValue[] = []\n    if (selectedRunStyle?.bold) values.push(\"bold\")\n    if (selectedRunStyle?.italic) values.push(\"italic\")\n    if (selectedRunStyle?.underline) values.push(\"underline\")\n    if (selectedRunStyle?.strike) values.push(\"strike\")\n    if (selectedRunStyle?.verticalAlign === \"superscript\") {\n      values.push(\"superscript\")\n    }\n    if (selectedRunStyle?.verticalAlign === \"subscript\") {\n      values.push(\"subscript\")\n    }\n    return values\n  }, [selectedRunStyle])\n  const preserveTextSelection = React.useCallback(\n    (event: React.MouseEvent<HTMLElement> | React.PointerEvent<HTMLElement>) =>\n      event.preventDefault(),\n    []\n  )\n  const applyFontToggleValues = React.useCallback(\n    (nextValues: DocxFontToggleValue[]) => {\n      const changedValue =\n        nextValues.find((value) => !fontToggleValues.includes(value)) ??\n        fontToggleValues.find((value) => !nextValues.includes(value))\n      if (changedValue === \"bold\") {\n        editor.toggleBold()\n      } else if (changedValue === \"italic\") {\n        editor.toggleItalic()\n      } else if (changedValue === \"underline\") {\n        editor.toggleUnderline()\n      } else if (changedValue === \"strike\") {\n        editor.toggleStrike()\n      } else if (changedValue === \"superscript\") {\n        editor.toggleSuperscript()\n      } else if (changedValue === \"subscript\") {\n        editor.toggleSubscript()\n      }\n    },\n    [editor, fontToggleValues]\n  )\n  return (\n    <div className=\"bg-background\">\n      <TooltipProvider>\n        <div className=\"flex min-h-11 items-center gap-2 overflow-x-auto overflow-y-hidden border-b px-3\">\n          <ToolbarIconButton\n            label=\"Toggle pages\"\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          </ToolbarIconButton>\n          <div className=\"min-w-28 text-sm whitespace-nowrap text-primary\">\n            Page {pageCount ? activePage : 1} of {pageCount || \"-\"}\n          </div>\n          <div className=\"min-w-0 flex-1\" />\n          {showNightRenderToggle ? (\n            <>\n              <ToolbarIconButton\n                label={\n                  documentTheme === \"dark\"\n                    ? \"Use light document\"\n                    : \"Use dark document\"\n                }\n                disabled={controlsDisabled}\n                onClick={() => {\n                  const nextIsDark = documentTheme !== \"dark\"\n                  setDocumentTheme(nextIsDark ? \"dark\" : \"light\")\n                  onIsDarkChange(nextIsDark)\n                }}\n              >\n                {documentTheme === \"dark\" ? (\n                  <Sun03Glyph className=\"size-4\" />\n                ) : (\n                  <Moon02Glyph className=\"size-4\" />\n                )}\n              </ToolbarIconButton>\n              <Separator\n                orientation=\"vertical\"\n                className=\"mx-1 h-4 self-center\"\n              />\n            </>\n          ) : null}\n          <ToolbarIconButton\n            label=\"Show tracked changes\"\n            active={showTrackedChanges}\n            disabled={controlsDisabled}\n            onClick={() => setShowTrackedChanges(!showTrackedChanges)}\n          >\n            <IconPlaceholder\n              lucide=\"FileDiff\"\n              tabler=\"IconFileDiff\"\n              hugeicons=\"FileDiffIcon\"\n              phosphor=\"GitDiffIcon\"\n              remixicon=\"RiFileHistoryLine\"\n              className=\"size-4\"\n            />\n          </ToolbarIconButton>\n          <ToolbarIconButton\n            label=\"Show comments\"\n            active={showComments}\n            disabled={controlsDisabled}\n            onClick={() => setShowComments(!showComments)}\n          >\n            <IconPlaceholder\n              lucide=\"MessageSquare\"\n              tabler=\"IconMessage\"\n              hugeicons=\"Comment01Icon\"\n              phosphor=\"ChatIcon\"\n              remixicon=\"RiChat1Line\"\n              className=\"size-4\"\n            />\n          </ToolbarIconButton>\n          <ToolbarIconButton\n            label={isReadOnly ? \"Switch to edit mode\" : \"Switch to read-only\"}\n            active={isReadOnly}\n            disabled={controlsDisabled}\n            onClick={() => onIsReadOnlyChange(!isReadOnly)}\n          >\n            <IconPlaceholder\n              lucide=\"PencilOff\"\n              tabler=\"IconPencilOff\"\n              hugeicons=\"EditOffIcon\"\n              phosphor=\"PencilSlashIcon\"\n              remixicon=\"RiLockLine\"\n              className=\"size-4\"\n            />\n          </ToolbarIconButton>\n          <Separator orientation=\"vertical\" className=\"mx-1 h-4 self-center\" />\n          <ToolbarIconButton\n            label=\"Import DOCX\"\n            disabled={editor.isImporting}\n            onClick={onUploadClick}\n          >\n            {editor.isImporting ? (\n              <InlineSpinner className=\"size-4\" />\n            ) : (\n              <IconPlaceholder\n                lucide=\"Upload\"\n                tabler=\"IconUpload\"\n                hugeicons=\"Upload01Icon\"\n                phosphor=\"UploadSimpleIcon\"\n                remixicon=\"RiUpload2Line\"\n                className=\"size-4\"\n              />\n            )}\n          </ToolbarIconButton>\n          <ToolbarIconButton\n            label=\"Download DOCX\"\n            disabled={controlsDisabled || editor.isImporting}\n            onClick={editor.exportDocx}\n          >\n            <IconPlaceholder\n              lucide=\"Download\"\n              tabler=\"IconDownload\"\n              hugeicons=\"Download01Icon\"\n              phosphor=\"DownloadSimpleIcon\"\n              remixicon=\"RiDownload2Line\"\n              className=\"size-4\"\n            />\n          </ToolbarIconButton>\n        </div>\n\n        <div className=\"flex min-h-12 flex-wrap items-center gap-2 border-b px-3 py-2\">\n          <div className=\"flex shrink-0 items-center gap-1\">\n            <ToolbarIconButton\n              label=\"Undo\"\n              disabled={!editor.canUndo || isReadOnly}\n              onClick={editor.undo}\n            >\n              <IconPlaceholder\n                lucide=\"Undo2\"\n                tabler=\"IconArrowBackUp\"\n                hugeicons=\"Undo02Icon\"\n                phosphor=\"ArrowUUpLeftIcon\"\n                remixicon=\"RiArrowGoBackLine\"\n                className=\"size-4\"\n              />\n            </ToolbarIconButton>\n            <ToolbarIconButton\n              label=\"Redo\"\n              disabled={!editor.canRedo || isReadOnly}\n              onClick={editor.redo}\n            >\n              <IconPlaceholder\n                lucide=\"Redo2\"\n                tabler=\"IconArrowForwardUp\"\n                hugeicons=\"Redo02Icon\"\n                phosphor=\"ArrowUUpRightIcon\"\n                remixicon=\"RiArrowGoForwardLine\"\n                className=\"size-4\"\n              />\n            </ToolbarIconButton>\n          </div>\n\n          <ToolbarSeparator />\n\n          <Select\n            value={selectedParagraphStyleValue}\n            onOpenChange={() => setParagraphStylePreviewId(null)}\n            onValueChange={(value) => {\n              if (value) {\n                setParagraphStyle(value)\n              }\n            }}\n            disabled={!canEdit}\n            modal={false}\n          >\n            <SelectTrigger\n              size=\"sm\"\n              className=\"w-[136px] min-w-[136px]\"\n              aria-label=\"Paragraph style\"\n            >\n              <SelectValue placeholder=\"Style\" />\n            </SelectTrigger>\n            <SelectContent\n              align=\"start\"\n              alignItemWithTrigger={false}\n              className=\"z-40 min-w-[210px]\"\n            >\n              {paragraphStyleOptions.map((option) => (\n                <PreviewCard\n                  key={option.id}\n                  open={paragraphStylePreviewId === option.id}\n                  onOpenChange={(open) => {\n                    setParagraphStylePreviewId((current) =>\n                      open ? option.id : current === option.id ? null : current\n                    )\n                  }}\n                  openDelay={0}\n                  closeDelay={120}\n                >\n                  <SelectItem\n                    value={option.id}\n                    label={option.name}\n                    className=\"relative min-w-[190px]\"\n                    onFocus={() => setParagraphStylePreviewId(option.id)}\n                    onPointerEnter={() => setParagraphStylePreviewId(option.id)}\n                  >\n                    <PreviewCardTrigger\n                      render={\n                        <span className=\"block w-full truncate\">\n                          {option.name}\n                        </span>\n                      }\n                    ></PreviewCardTrigger>\n                  </SelectItem>\n                  <PreviewCardContent\n                    side=\"right\"\n                    align=\"start\"\n                    sideOffset={10}\n                    alignOffset={-4}\n                    data-slot=\"paragraph-style-preview-card\"\n                    className=\"origin-(--transform-origin) rounded-lg border bg-popover text-popover-foreground shadow-lg/5 transition-opacity duration-100 outline-none not-dark:bg-clip-padding before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] data-ending-style:opacity-0 data-starting-style:opacity-0 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]\"\n                  >\n                    <ParagraphStylePreviewCard\n                      documentTheme={documentTheme}\n                      option={option}\n                    />\n                  </PreviewCardContent>\n                </PreviewCard>\n              ))}\n            </SelectContent>\n          </Select>\n\n          <Select\n            value={selectedRunStyle?.fontFamily ?? \"Calibri\"}\n            onValueChange={(value) => {\n              if (value) {\n                editor.setFontFamily(value)\n              }\n            }}\n            disabled={!canEdit}\n            modal={false}\n          >\n            <SelectTrigger\n              size=\"sm\"\n              className=\"w-[156px] min-w-[156px]\"\n              aria-label=\"Font family\"\n            >\n              <SelectValue placeholder=\"Font\" />\n            </SelectTrigger>\n            <SelectContent\n              align=\"start\"\n              alignItemWithTrigger={false}\n              className=\"z-40\"\n            >\n              {FONT_FAMILIES.map((fontFamily) => (\n                <SelectItem key={fontFamily} value={fontFamily}>\n                  <span style={{ fontFamily }}>{fontFamily}</span>\n                </SelectItem>\n              ))}\n            </SelectContent>\n          </Select>\n\n          <Select\n            value={String(Math.round(selectedRunStyle?.fontSizePt ?? 12))}\n            onValueChange={(value) => {\n              const nextSize = Number(value)\n              if (Number.isFinite(nextSize)) {\n                editor.setFontSize(nextSize)\n              }\n            }}\n            disabled={!canEdit}\n            modal={false}\n          >\n            <SelectTrigger\n              size=\"sm\"\n              className=\"w-[86px] min-w-[86px]\"\n              aria-label=\"Font size\"\n            >\n              <SelectValue placeholder=\"Size\" />\n            </SelectTrigger>\n            <SelectContent\n              align=\"start\"\n              alignItemWithTrigger={false}\n              className=\"z-40\"\n            >\n              {FONT_SIZE_OPTIONS.map((size) => (\n                <SelectItem key={size} value={String(size)}>\n                  {size} pt\n                </SelectItem>\n              ))}\n            </SelectContent>\n          </Select>\n\n          <Select\n            value={selectedLineSpacingValue}\n            onValueChange={(value) => {\n              const nextSpacing = Number(value)\n              if (Number.isFinite(nextSpacing)) {\n                setLineSpacing(nextSpacing)\n              }\n            }}\n            disabled={!canEdit}\n            modal={false}\n          >\n            <SelectTrigger\n              size=\"sm\"\n              className=\"w-[94px] min-w-[94px]\"\n              aria-label=\"Line spacing\"\n            >\n              <SelectValue placeholder=\"Spacing\" />\n            </SelectTrigger>\n            <SelectContent\n              align=\"start\"\n              alignItemWithTrigger={false}\n              className=\"z-40\"\n            >\n              {LINE_SPACING_OPTIONS.map((spacing) => (\n                <SelectItem key={spacing} value={String(spacing)}>\n                  {spacing}x\n                </SelectItem>\n              ))}\n            </SelectContent>\n          </Select>\n\n          <ToolbarSeparator />\n\n          <ToggleGroup\n            className=\"shrink-0\"\n            disabled={!canEdit}\n            multiple\n            value={fontToggleValues}\n            onMouseDown={preserveTextSelection}\n            onPointerDown={preserveTextSelection}\n            onValueChange={(value) =>\n              applyFontToggleValues(value as DocxFontToggleValue[])\n            }\n            spacing={1}\n          >\n            <ToolbarTooltip label=\"Bold\">\n              <ToggleGroupItem aria-label=\"Bold\" size=\"sm\" value=\"bold\">\n                <IconPlaceholder\n                  lucide=\"Bold\"\n                  tabler=\"IconBold\"\n                  hugeicons=\"TextBoldIcon\"\n                  phosphor=\"TextBIcon\"\n                  remixicon=\"RiBold\"\n                  className=\"size-4\"\n                />\n              </ToggleGroupItem>\n            </ToolbarTooltip>\n            <ToolbarTooltip label=\"Italic\">\n              <ToggleGroupItem aria-label=\"Italic\" size=\"sm\" value=\"italic\">\n                <IconPlaceholder\n                  lucide=\"Italic\"\n                  tabler=\"IconItalic\"\n                  hugeicons=\"TextItalicIcon\"\n                  phosphor=\"TextItalicIcon\"\n                  remixicon=\"RiItalic\"\n                  className=\"size-4\"\n                />\n              </ToggleGroupItem>\n            </ToolbarTooltip>\n            <ToolbarTooltip label=\"Underline\">\n              <ToggleGroupItem\n                aria-label=\"Underline\"\n                size=\"sm\"\n                value=\"underline\"\n              >\n                <IconPlaceholder\n                  lucide=\"Underline\"\n                  tabler=\"IconUnderline\"\n                  hugeicons=\"TextUnderlineIcon\"\n                  phosphor=\"TextUnderlineIcon\"\n                  remixicon=\"RiUnderline\"\n                  className=\"size-4\"\n                />\n              </ToggleGroupItem>\n            </ToolbarTooltip>\n            <ToolbarTooltip label=\"Strikethrough\">\n              <ToggleGroupItem\n                aria-label=\"Strikethrough\"\n                size=\"sm\"\n                value=\"strike\"\n              >\n                <IconPlaceholder\n                  lucide=\"Strikethrough\"\n                  tabler=\"IconStrikethrough\"\n                  hugeicons=\"TextStrikethroughIcon\"\n                  phosphor=\"TextStrikethroughIcon\"\n                  remixicon=\"RiStrikethrough\"\n                  className=\"size-4\"\n                />\n              </ToggleGroupItem>\n            </ToolbarTooltip>\n            <ToolbarTooltip label=\"Superscript\">\n              <ToggleGroupItem\n                aria-label=\"Superscript\"\n                size=\"sm\"\n                value=\"superscript\"\n              >\n                <IconPlaceholder\n                  lucide=\"Superscript\"\n                  tabler=\"IconSuperscript\"\n                  hugeicons=\"TextSuperscriptIcon\"\n                  phosphor=\"TextSuperscriptIcon\"\n                  remixicon=\"RiSuperscript\"\n                  className=\"size-4\"\n                />\n              </ToggleGroupItem>\n            </ToolbarTooltip>\n            <ToolbarTooltip label=\"Subscript\">\n              <ToggleGroupItem\n                aria-label=\"Subscript\"\n                size=\"sm\"\n                value=\"subscript\"\n              >\n                <IconPlaceholder\n                  lucide=\"Subscript\"\n                  tabler=\"IconSubscript\"\n                  hugeicons=\"TextSubscriptIcon\"\n                  phosphor=\"TextSubscriptIcon\"\n                  remixicon=\"RiSubscript\"\n                  className=\"size-4\"\n                />\n              </ToggleGroupItem>\n            </ToolbarTooltip>\n          </ToggleGroup>\n\n          <ToolbarSeparator />\n\n          <div className=\"flex shrink-0 items-center gap-1\">\n            <ColorPicker\n              label=\"Text color\"\n              icon={TextColorGlyph}\n              color={textColorValue}\n              disabled={!canEdit}\n              onTriggerMouseDown={preserveTextSelection}\n              onTriggerPointerDown={preserveTextSelection}\n              onChange={(color) =>\n                editor.setTextColor(normalizeHexColor(color))\n              }\n            />\n            <DropdownMenu>\n              <DropdownMenuTrigger\n                render={\n                  <Button\n                    type=\"button\"\n                    variant=\"ghost\"\n                    size=\"icon-sm\"\n                    disabled={!canEdit}\n                    aria-label=\"Highlight color\"\n                    className=\"relative\"\n                  >\n                    <IconPlaceholder\n                      lucide=\"Highlighter\"\n                      tabler=\"IconHighlight\"\n                      hugeicons=\"HighlighterIcon\"\n                      phosphor=\"HighlighterIcon\"\n                      remixicon=\"RiMarkPenLine\"\n                      className=\"size-4\"\n                    />\n                    <span\n                      className=\"absolute right-1 bottom-1 h-1 w-4 rounded-full border border-background\"\n                      style={{ backgroundColor: highlightColor }}\n                    />\n                  </Button>\n                }\n              ></DropdownMenuTrigger>\n              <DropdownMenuContent align=\"start\" className=\"z-40 w-44\">\n                {HIGHLIGHT_COLORS.map((option) => (\n                  <DropdownMenuCheckboxItem\n                    key={option.value}\n                    checked={selectedRunStyle?.highlight === option.value}\n                    onCheckedChange={() => editor.setHighlight(option.value)}\n                  >\n                    <span\n                      className=\"size-3 rounded-full border\"\n                      style={{ backgroundColor: option.color }}\n                    />\n                    {option.label}\n                  </DropdownMenuCheckboxItem>\n                ))}\n                <DropdownMenuSeparator />\n                <DropdownMenuCheckboxItem\n                  checked={!selectedRunStyle?.highlight}\n                  onCheckedChange={() => editor.setHighlight(undefined)}\n                >\n                  No highlight\n                </DropdownMenuCheckboxItem>\n              </DropdownMenuContent>\n            </DropdownMenu>\n            <ToolbarIconButton\n              label=\"Link\"\n              disabled={!canEdit}\n              onMouseDown={preserveTextSelection}\n              onPointerDown={preserveTextSelection}\n              onClick={onOpenLinkEditor}\n            >\n              <IconPlaceholder\n                lucide=\"Link\"\n                tabler=\"IconLink\"\n                hugeicons=\"Link02Icon\"\n                phosphor=\"LinkIcon\"\n                remixicon=\"RiLink\"\n                className=\"size-4\"\n              />\n            </ToolbarIconButton>\n          </div>\n\n          <ToolbarSeparator />\n\n          <div className=\"flex shrink-0 items-center gap-1\">\n            <ToolbarIconButton\n              label=\"Align left\"\n              active={\n                selectedParagraph?.style?.align === \"left\" ||\n                !selectedParagraph?.style?.align\n              }\n              disabled={!canEdit}\n              onMouseDown={preserveTextSelection}\n              onPointerDown={preserveTextSelection}\n              onClick={() => editor.setAlignment(\"left\")}\n            >\n              <IconPlaceholder\n                lucide=\"AlignLeft\"\n                tabler=\"IconAlignLeft\"\n                hugeicons=\"TextAlignLeft01Icon\"\n                phosphor=\"TextAlignLeftIcon\"\n                remixicon=\"RiAlignLeft\"\n                className=\"size-4\"\n              />\n            </ToolbarIconButton>\n            <ToolbarIconButton\n              label=\"Align center\"\n              active={selectedParagraph?.style?.align === \"center\"}\n              disabled={!canEdit}\n              onMouseDown={preserveTextSelection}\n              onPointerDown={preserveTextSelection}\n              onClick={() => editor.setAlignment(\"center\")}\n            >\n              <IconPlaceholder\n                lucide=\"AlignCenter\"\n                tabler=\"IconAlignCenter\"\n                hugeicons=\"TextAlignCenterIcon\"\n                phosphor=\"TextAlignCenterIcon\"\n                remixicon=\"RiAlignCenter\"\n                className=\"size-4\"\n              />\n            </ToolbarIconButton>\n            <ToolbarIconButton\n              label=\"Align right\"\n              active={selectedParagraph?.style?.align === \"right\"}\n              disabled={!canEdit}\n              onMouseDown={preserveTextSelection}\n              onPointerDown={preserveTextSelection}\n              onClick={() => editor.setAlignment(\"right\")}\n            >\n              <IconPlaceholder\n                lucide=\"AlignRight\"\n                tabler=\"IconAlignRight\"\n                hugeicons=\"TextAlignRight01Icon\"\n                phosphor=\"TextAlignRightIcon\"\n                remixicon=\"RiAlignRight\"\n                className=\"size-4\"\n              />\n            </ToolbarIconButton>\n            <ToolbarIconButton\n              label=\"Justify\"\n              active={selectedParagraph?.style?.align === \"justify\"}\n              disabled={!canEdit}\n              onMouseDown={preserveTextSelection}\n              onPointerDown={preserveTextSelection}\n              onClick={() => editor.setAlignment(\"justify\")}\n            >\n              <IconPlaceholder\n                lucide=\"AlignJustify\"\n                tabler=\"IconAlignJustified\"\n                hugeicons=\"TextAlignJustifyLeftIcon\"\n                phosphor=\"TextAlignJustifyIcon\"\n                remixicon=\"RiAlignJustify\"\n                className=\"size-4\"\n              />\n            </ToolbarIconButton>\n          </div>\n\n          <ToolbarSeparator />\n\n          <div className=\"flex shrink-0 items-center gap-1\">\n            <ToolbarIconButton\n              label=\"Bulleted list\"\n              active={editor.hasUnorderedList}\n              disabled={!canEdit}\n              onMouseDown={preserveTextSelection}\n              onPointerDown={preserveTextSelection}\n              onClick={() => editor.toggleList(\"unordered\")}\n            >\n              <IconPlaceholder\n                lucide=\"Columns3Icon\"\n                tabler=\"IconLayoutColumns\"\n                hugeicons=\"LeftToRightListBulletIcon\"\n                phosphor=\"ColumnsIcon\"\n                remixicon=\"RiLayoutColumnLine\"\n                className=\"size-4\"\n              />\n            </ToolbarIconButton>\n            <ToolbarIconButton\n              label=\"Numbered list\"\n              active={editor.hasOrderedList}\n              disabled={!canEdit}\n              onMouseDown={preserveTextSelection}\n              onPointerDown={preserveTextSelection}\n              onClick={() => editor.toggleList(\"ordered\")}\n            >\n              <IconPlaceholder\n                lucide=\"ListOrdered\"\n                tabler=\"IconListNumbers\"\n                hugeicons=\"LeftToRightListNumberIcon\"\n                phosphor=\"ListNumbersIcon\"\n                remixicon=\"RiListOrdered\"\n                className=\"size-4\"\n              />\n            </ToolbarIconButton>\n          </div>\n\n          <ToolbarSeparator />\n\n          <DropdownMenu>\n            <DropdownMenuTrigger\n              render={\n                <Button\n                  type=\"button\"\n                  variant=\"ghost\"\n                  size=\"sm\"\n                  disabled={!canEdit}\n                  className=\"h-7 gap-1 px-2 shadow-none\"\n                >\n                  {borderTriggerIcon}\n                  {borderTriggerLabel}\n                </Button>\n              }\n            ></DropdownMenuTrigger>\n            <DropdownMenuContent align=\"start\" className=\"z-40 w-56\">\n              {BORDER_CONTROL_OPTIONS.map((option) => {\n                const enabledForContext =\n                  !option.contexts || option.contexts.includes(borderContext)\n                const BorderOptionIcon = borderControlOptionIcon(option.id)\n                return (\n                  <React.Fragment key={option.id}>\n                    {option.separatorBefore ? <DropdownMenuSeparator /> : null}\n                    <DropdownMenuCheckboxItem\n                      checked={activeBorderPresets[option.id]}\n                      disabled={!enabledForContext}\n                      onCheckedChange={() => {\n                        if (enabledForContext) {\n                          applyBorderPreset(option.id)\n                        }\n                      }}\n                    >\n                      <BorderOptionIcon className=\"size-4 text-muted-foreground\" />\n                      {option.label}\n                    </DropdownMenuCheckboxItem>\n                  </React.Fragment>\n                )\n              })}\n            </DropdownMenuContent>\n          </DropdownMenu>\n\n          <ToolbarSeparator />\n\n          <div className=\"flex shrink-0 items-center gap-1\">\n            <ToolbarIconButton\n              label=\"Insert image\"\n              disabled={!canEdit}\n              onClick={onImageUploadClick}\n            >\n              <IconPlaceholder\n                lucide=\"ImagePlus\"\n                tabler=\"IconPhotoPlus\"\n                hugeicons=\"ImageAdd01Icon\"\n                phosphor=\"ImageIcon\"\n                remixicon=\"RiImageAddLine\"\n                className=\"size-4\"\n              />\n            </ToolbarIconButton>\n            <ToolbarIconButton\n              label=\"Insert table\"\n              disabled={!canEdit}\n              onClick={editor.insertTable}\n            >\n              <IconPlaceholder\n                lucide=\"TableIcon\"\n                tabler=\"IconTable\"\n                hugeicons=\"TableIcon\"\n                phosphor=\"TableIcon\"\n                remixicon=\"RiTableLine\"\n                className=\"size-4\"\n              />\n            </ToolbarIconButton>\n            <ToolbarTooltip label=\"Section columns\">\n              <div className=\"flex h-7 shrink-0 items-center gap-1 rounded-lg px-2 text-xs text-muted-foreground\">\n                <IconPlaceholder\n                  lucide=\"Columns3Cog\"\n                  tabler=\"IconColumns3\"\n                  hugeicons=\"ColumnsThreeCogIcon\"\n                  phosphor=\"ColumnsIcon\"\n                  remixicon=\"RiLayoutColumnLine\"\n                  className=\"size-4\"\n                />\n                {activeColumns ? `${activeColumns.count} cols` : \"1 col\"}\n              </div>\n            </ToolbarTooltip>\n          </div>\n\n          <ToolbarSeparator />\n\n          <div className=\"ml-auto flex flex-none items-center gap-1\">\n            <ToolbarIconButton\n              label=\"Zoom out\"\n              disabled={controlsDisabled || !canZoomOut}\n              onClick={() =>\n                setZoomScale((currentZoomScale) =>\n                  getNextZoomScale(currentZoomScale, -1)\n                )\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            </ToolbarIconButton>\n            <Select\n              value={zoomScale.toString()}\n              onValueChange={(value) => setZoomScale(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(zoomScale)}%</SelectValue>\n              </SelectTrigger>\n              <SelectContent\n                align=\"end\"\n                alignItemWithTrigger={false}\n                className=\"z-40\"\n              >\n                {ZOOM_OPTIONS.map((value) => (\n                  <SelectItem key={value} value={value.toString()}>\n                    {value}%\n                  </SelectItem>\n                ))}\n              </SelectContent>\n            </Select>\n            <ToolbarIconButton\n              label=\"Zoom in\"\n              disabled={controlsDisabled || !canZoomIn}\n              onClick={() =>\n                setZoomScale((currentZoomScale) =>\n                  getNextZoomScale(currentZoomScale, 1)\n                )\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            </ToolbarIconButton>\n          </div>\n        </div>\n      </TooltipProvider>\n    </div>\n  )\n}\nexport function DocxEditorPreview({\n  className,\n  defaultZoomScale = DOCX_EDITOR_DEFAULT_ZOOM_SCALE,\n  fileName,\n  isDark,\n  onIsDarkChange,\n  src,\n}: {\n  className?: string\n  defaultZoomScale?: number\n  fileName?: string\n  isDark: boolean\n  onIsDarkChange: (isDark: boolean) => void\n  src?: string\n}) {\n  return (\n    <DocxEditorContent\n      key={src}\n      className={className}\n      defaultZoomScale={defaultZoomScale}\n      effectiveIsDark={isDark}\n      fileName={fileName}\n      setIsDark={onIsDarkChange}\n      shouldRenderNightMode\n      url={src}\n    />\n  )\n}\nfunction DocxEditorContent({\n  className,\n  defaultZoomScale,\n  effectiveIsDark,\n  fileName,\n  setIsDark,\n  shouldRenderNightMode,\n  url,\n}: {\n  className?: string\n  defaultZoomScale?: number\n  effectiveIsDark: boolean\n  fileName?: string\n  setIsDark: (checked: boolean) => void\n  shouldRenderNightMode: boolean\n  url?: string\n}) {\n  const fileInputRef = React.useRef<HTMLInputElement>(null)\n  const imageInputRef = React.useRef<HTMLInputElement>(null)\n  const viewportRef = React.useRef<HTMLDivElement | null>(null)\n  const [viewportElement, setViewportElement] =\n    React.useState<HTMLDivElement | null>(null)\n  const [viewerShellRef, viewerShellWidth] = useElementWidth<HTMLDivElement>()\n  const [uploadedDocxFile, setUploadedDocxFile] =\n    React.useState<UploadedDocxFile | null>(null)\n  const [activePage, setActivePage] = React.useState(1)\n  const [sidebarOpen, setSidebarOpen] = React.useState(false)\n  const resolvedDefaultZoomScale = normalizeDocxZoomScale(defaultZoomScale)\n  const [zoomScale, setZoomScale] = React.useState<number>(\n    resolvedDefaultZoomScale\n  )\n  const [loadError, setLoadError] = React.useState<string>()\n  const [isLoadingDocument, setIsLoadingDocument] = React.useState(Boolean(url))\n  const [isReadOnly, setIsReadOnly] = React.useState(false)\n  const [linkEditorOpen, setLinkEditorOpen] = React.useState(false)\n  const [linkDraft, setLinkDraft] = React.useState(\"\")\n  const viewerBackgroundColor =\n    \"color-mix(in oklab, var(--muted) 40%, transparent)\"\n  const displayFileName = React.useMemo(\n    () =>\n      uploadedDocxFile?.file.name ??\n      (url ? formatDocumentName(fileName, url) : (fileName ?? \"document.docx\")),\n    [fileName, uploadedDocxFile?.file.name, url]\n  )\n  const [initialDocumentTheme] = React.useState<DocxDocumentTheme>(() =>\n    effectiveIsDark ? \"dark\" : \"light\"\n  )\n  const editorOptions = React.useMemo(\n    () => ({\n      initialDocumentTheme,\n      initialFileName: displayFileName,\n    }),\n    [displayFileName, initialDocumentTheme]\n  )\n  const editor = useDocxEditor(editorOptions)\n  const { layout: pageLayout } = useDocxPageLayout(editor)\n  const { importDocxFile, setDocumentTheme, status } = editor\n  const [reportedPageCount, setReportedPageCount] = React.useState(0)\n  const thumbnailEditor = React.useMemo<DocxEditorController>(\n    () => ({\n      ...editor,\n      totalPages: Math.max(editor.totalPages, reportedPageCount),\n    }),\n    [editor, reportedPageCount]\n  )\n  const thumbnailOptions = React.useMemo(\n    () => ({\n      pixelRatio: 2,\n      resolution: {\n        maxHeight: DOCX_THUMBNAIL_WIDTH * 1.35,\n        maxWidth: DOCX_THUMBNAIL_WIDTH,\n      },\n    }),\n    []\n  )\n  const { thumbnails } = useDocxViewerThumbnails(\n    thumbnailEditor,\n    thumbnailOptions\n  )\n  const shouldShowDocumentSpinner = useDelayedLoadingIndicator(\n    isLoadingDocument,\n    DOCX_LOADING_INDICATOR_DELAY_MS\n  )\n  const loadingState = (\n    <EditorLoadingSurface showSpinner={shouldShowDocumentSpinner} />\n  )\n  const renderTrackedChangeCard = React.useMemo(\n    () => createDocxTrackedChangeCardRenderer(editor.documentTheme),\n    [editor.documentTheme]\n  )\n  const renderCommentCard = React.useMemo(\n    () => createDocxCommentCardRenderer(editor.documentTheme),\n    [editor.documentTheme]\n  )\n  const hasDocument = Boolean(url || uploadedDocxFile)\n  const sidebarInline = useInlineThumbnailSidebar(viewerShellWidth)\n  const pageCount =\n    hasDocument && !isLoadingDocument && !loadError\n      ? Math.max(1, reportedPageCount || editor.totalPages)\n      : 0\n  const controlsDisabled =\n    !hasDocument || isLoadingDocument || Boolean(loadError)\n  const thumbnailSidebarOpen = Boolean(\n    sidebarOpen && (pageCount || isLoadingDocument)\n  )\n  const handlePageCountChange = React.useCallback((nextPageCount: number) => {\n    setReportedPageCount(Math.max(1, Math.round(nextPageCount || 1)))\n  }, [])\n  const setViewportRef = React.useCallback((element: HTMLDivElement | null) => {\n    viewportRef.current = element\n    setViewportElement(element)\n  }, [])\n  const pageVirtualization = React.useMemo(\n    () => ({\n      enabled: true,\n      overscan: 1,\n      scrollElement: viewportElement,\n      zoomScale: zoomScale / 100,\n    }),\n    [viewportElement, zoomScale]\n  )\n  useSuppressDocxPaddingWarning(!isLoadingDocument && !loadError)\n  const [previousDefaultZoom, setPreviousDefaultZoom] = React.useState(\n    resolvedDefaultZoomScale\n  )\n  if (previousDefaultZoom !== resolvedDefaultZoomScale) {\n    setPreviousDefaultZoom(resolvedDefaultZoomScale)\n    setZoomScale(resolvedDefaultZoomScale)\n    setActivePage(1)\n  }\n  React.useEffect(() => {\n    viewportRef.current?.scrollTo({ top: 0, left: 0 })\n  }, [resolvedDefaultZoomScale, url])\n  React.useEffect(() => {\n    setDocumentTheme(effectiveIsDark ? \"dark\" : \"light\")\n  }, [effectiveIsDark, setDocumentTheme])\n  const [previousStatus, setPreviousStatus] = React.useState(status)\n  if (previousStatus !== status) {\n    setPreviousStatus(status)\n    if (\n      status.startsWith(\"Failed to load file\") ||\n      status === \"Only .docx files are supported\"\n    ) {\n      setLoadError(status)\n      setIsLoadingDocument(false)\n    }\n  }\n  React.useEffect(() => {\n    let isCurrent = true\n    async function load() {\n      if (!uploadedDocxFile && !url) return\n      try {\n        const docxFile =\n          uploadedDocxFile?.file ??\n          (url ? await loadDocxFile(url, displayFileName) : null)\n        if (!docxFile) return\n        await importDocxFile(docxFile)\n        if (isCurrent) {\n          setIsLoadingDocument(false)\n          setActivePage(1)\n          viewportRef.current?.scrollTo({ top: 0, left: 0 })\n        }\n      } catch (error) {\n        if (isCurrent) {\n          setLoadError(\n            error instanceof Error ? error.message : \"Unknown DOCX load error\"\n          )\n          setIsLoadingDocument(false)\n        }\n      }\n    }\n    void load()\n    return () => {\n      isCurrent = false\n    }\n  }, [displayFileName, importDocxFile, uploadedDocxFile, url])\n  const updateActivePageFromViewport = React.useCallback(() => {\n    const viewport = viewportRef.current\n    if (!viewport || !pageCount) return\n    const viewportRect = viewport.getBoundingClientRect()\n    const viewportCenter = viewportRect.top + viewportRect.height / 2\n    let closestPage = 1\n    let closestDistance = Number.POSITIVE_INFINITY\n    viewport\n      .querySelectorAll<HTMLElement>(\n        '[data-docx-page-wrapper=\"true\"][data-index]'\n      )\n      .forEach((page) => {\n        const pageIndex = Number(page.dataset.index)\n        if (!Number.isFinite(pageIndex)) return\n        const pageRect = page.getBoundingClientRect()\n        const pageCenter = pageRect.top + pageRect.height / 2\n        const distance = Math.abs(pageCenter - viewportCenter)\n        if (distance < closestDistance) {\n          closestDistance = distance\n          closestPage = pageIndex + 1\n        }\n      })\n    setActivePage((currentPage) =>\n      currentPage === closestPage ? currentPage : closestPage\n    )\n  }, [pageCount])\n  React.useEffect(() => {\n    const viewport = viewportRef.current\n    if (!viewport || !pageCount) return\n    let frameId = 0\n    const handleScroll = () => {\n      window.cancelAnimationFrame(frameId)\n      frameId = window.requestAnimationFrame(updateActivePageFromViewport)\n    }\n    frameId = window.requestAnimationFrame(updateActivePageFromViewport)\n    viewport.addEventListener(\"scroll\", handleScroll, { passive: true })\n    return () => {\n      window.cancelAnimationFrame(frameId)\n      viewport.removeEventListener(\"scroll\", handleScroll)\n    }\n  }, [pageCount, updateActivePageFromViewport])\n  const scrollToPage = React.useCallback(\n    (pageNumber: number) => {\n      const viewport = viewportRef.current\n      const targetPageIndex = pageNumber - 1\n      const page = viewport?.querySelector<HTMLElement>(\n        `[data-docx-page-wrapper=\"true\"][data-index=\"${targetPageIndex}\"]`\n      )\n      setActivePage(pageNumber)\n      if (!viewport) return\n      if (!page) {\n        const pageStridePx =\n          (pageLayout.pageHeightPx + pageLayout.viewportDefaults.pageGapPx) *\n          (zoomScale / 100)\n        viewport.scrollTo({\n          top: Math.max(0, targetPageIndex * pageStridePx - 24),\n          behavior: \"auto\",\n        })\n        return\n      }\n      viewport.scrollTo({\n        top:\n          page.getBoundingClientRect().top -\n          viewport.getBoundingClientRect().top +\n          viewport.scrollTop -\n          24,\n        behavior: \"auto\",\n      })\n    },\n    [pageLayout.pageHeightPx, pageLayout.viewportDefaults.pageGapPx, zoomScale]\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    setZoomScale(resolvedDefaultZoomScale)\n    setActivePage(1)\n    setReportedPageCount(0)\n    setIsLoadingDocument(true)\n    setLoadError(undefined)\n    setUploadedDocxFile({\n      file,\n      identity: `${file.name}-${file.size}-${file.lastModified}`,\n    })\n  }\n  async function handleImageUpload(event: React.ChangeEvent<HTMLInputElement>) {\n    const file = event.target.files?.[0]\n    event.target.value = \"\"\n    if (!file) return\n    await editor.insertImageFile(file)\n  }\n  function openLinkEditor() {\n    setLinkDraft(editor.selectedLink ?? \"\")\n    setLinkEditorOpen(true)\n  }\n  function applyLink() {\n    editor.setLink(linkDraft.trim() || undefined)\n    setLinkEditorOpen(false)\n  }\n  return (\n    <div\n      className={cn(\n        \"flex h-[720px] min-h-0 flex-col overflow-hidden bg-background\",\n        className\n      )}\n    >\n      <input\n        ref={fileInputRef}\n        type=\"file\"\n        accept=\".docx,application/vnd.openxmlformats-officedocument.wordprocessingml.document\"\n        className=\"hidden\"\n        onChange={handleUpload}\n      />\n      <input\n        ref={imageInputRef}\n        type=\"file\"\n        accept=\"image/*\"\n        className=\"hidden\"\n        onChange={handleImageUpload}\n      />\n      <DocxEditorToolbar\n        activePage={activePage}\n        controlsDisabled={controlsDisabled}\n        editor={editor}\n        isReadOnly={isReadOnly}\n        onImageUploadClick={() => imageInputRef.current?.click()}\n        onIsDarkChange={setIsDark}\n        onIsReadOnlyChange={setIsReadOnly}\n        onOpenLinkEditor={openLinkEditor}\n        onToggleSidebar={() => setSidebarOpen((open) => !open)}\n        onUploadClick={() => fileInputRef.current?.click()}\n        pageCount={pageCount}\n        setZoomScale={setZoomScale}\n        showNightRenderToggle={shouldRenderNightMode}\n        zoomScale={zoomScale}\n      />\n      <div\n        ref={viewerShellRef}\n        className=\"relative flex min-h-0 flex-1 overflow-hidden bg-muted/30\"\n      >\n        {linkEditorOpen ? (\n          <div className=\"absolute top-3 left-1/2 z-50 flex w-[min(420px,calc(100%-2rem))] -translate-x-1/2 items-center gap-2 rounded-lg border bg-background p-2 shadow-lg\">\n            <Input\n              value={linkDraft}\n              onChange={(event) => setLinkDraft(event.target.value)}\n              onKeyDown={(event) => {\n                if (event.key === \"Enter\") {\n                  event.preventDefault()\n                  applyLink()\n                }\n                if (event.key === \"Escape\") {\n                  setLinkEditorOpen(false)\n                }\n              }}\n              placeholder=\"https://example.com\"\n              aria-label=\"Link URL\"\n            />\n            <Button type=\"button\" size=\"sm\" onClick={applyLink}>\n              Apply\n            </Button>\n            <Button\n              type=\"button\"\n              size=\"sm\"\n              variant=\"ghost\"\n              onClick={() => {\n                editor.setLink(undefined)\n                setLinkEditorOpen(false)\n              }}\n            >\n              Clear\n            </Button>\n          </div>\n        ) : null}\n        <DocumentViewerThumbnailSidebar\n          inline={sidebarInline}\n          open={thumbnailSidebarOpen}\n        >\n          <InlineScrollArea2\n            className=\"h-full\"\n            scrollFade\n            viewportClassName=\"overscroll-contain\"\n          >\n            <div className=\"p-4\">\n              {isLoadingDocument ? (\n                <>\n                  <div className=\"mx-auto h-28 w-20 overflow-hidden rounded-md 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                </>\n              ) : (\n                <div className=\"flex flex-col items-center gap-3\">\n                  {thumbnails.slice(0, pageCount || 0).map((thumbnail) => (\n                    <Button\n                      key={thumbnail.pageIndex}\n                      type=\"button\"\n                      variant=\"ghost\"\n                      size=\"sm\"\n                      className={cn(\n                        \"!h-auto w-full flex-col items-center gap-2 p-2 text-xs shadow-none hover:bg-sidebar-accent\",\n                        thumbnail.pageNumber === activePage &&\n                          \"bg-sidebar-accent text-foreground\",\n                        thumbnail.pageNumber !== activePage &&\n                          \"text-muted-foreground\"\n                      )}\n                      onFocus={(event) => event.currentTarget.blur()}\n                      onClick={() => scrollToPage(thumbnail.pageNumber)}\n                    >\n                      <DocxSidebarThumbnail\n                        canvasRef={thumbnail.canvasRef}\n                        displayFileName={displayFileName}\n                        hasError={thumbnail.status === \"error\"}\n                        isActive={thumbnail.pageNumber === activePage}\n                        isLoading={\n                          !thumbnail.isMounted &&\n                          thumbnail.status !== \"ready\" &&\n                          thumbnail.status !== \"error\"\n                        }\n                        pageNumber={thumbnail.pageNumber}\n                        pixelHeightPx={thumbnail.pixelHeightPx}\n                        pixelWidthPx={thumbnail.pixelWidthPx}\n                        previewAspectRatio={thumbnail.aspectRatio}\n                      />\n                      {thumbnail.pageNumber}\n                    </Button>\n                  ))}\n                </div>\n              )}\n            </div>\n          </InlineScrollArea2>\n        </DocumentViewerThumbnailSidebar>\n        <InlineScrollArea2\n          className=\"min-h-0 flex-1\"\n          style={{ backgroundColor: viewerBackgroundColor }}\n          viewportClassName=\"overscroll-contain p-4\"\n          viewportRef={setViewportRef}\n        >\n          {!url && !uploadedDocxFile ? (\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\">Upload a DOCX to edit</div>\n                <div className=\"mt-1 text-muted-foreground\">\n                  Pass a DOCX URL with the <code>src</code> prop or upload a\n                  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 DOCX\n                </Button>\n              </div>\n            </div>\n          ) : loadError ? (\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\">Unable to edit DOCX</div>\n                <div className=\"mt-1 text-muted-foreground\">{loadError}</div>\n              </div>\n            </div>\n          ) : isLoadingDocument ? (\n            loadingState\n          ) : (\n            <div className=\"mx-auto flex min-h-full justify-center\">\n              <div\n                className={cn(\n                  \"origin-top\",\n                  editor.documentTheme === \"dark\" && \"docx-night-reader-shell\"\n                )}\n                style={{ zoom: zoomScale / 100 }}\n              >\n                <DocxEditorViewer\n                  editor={editor}\n                  mode={isReadOnly ? \"read-only\" : \"edit\"}\n                  showTrackedChanges={editor.showTrackedChanges}\n                  renderTrackedChangeCard={renderTrackedChangeCard}\n                  showComments={editor.showComments}\n                  renderCommentCard={renderCommentCard}\n                  loadingState={loadingState}\n                  pageBackgroundColor={\n                    editor.documentTheme === \"dark\" ? \"#0a0a0a\" : undefined\n                  }\n                  pageGapBackgroundColor={viewerBackgroundColor}\n                  pageVirtualization={pageVirtualization}\n                  deferInitialPaginationPaint={false}\n                  onPageCountChange={handlePageCountChange}\n                />\n              </div>\n            </div>\n          )}\n        </InlineScrollArea2>\n      </div>\n    </div>\n  )\n}\ntype InlineRegistryIconProps = Omit<\n  React.ComponentProps<\"svg\">,\n  \"children\" | \"strokeWidth\"\n> & { strokeWidth?: number }\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}\n",
      "type": "registry:ui",
      "target": "@components/extend/docx-editor.tsx"
    },
    {
      "path": "components/extend/docx-annotation-card.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport type {\n  DocxCommentCardRenderProps,\n  DocxDocumentTheme,\n  DocxTrackedChangeCardRenderProps,\n} from \"@extend-ai/react-docx\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Badge } from \"@/components/ui/badge\"\nimport { Card } from \"@/components/ui/card\"\n\ntype AnnotationBadgeVariant =\n  | \"outline\"\n  | \"secondary\"\n  | \"success\"\n  | \"error\"\n  | \"warning\"\n  | \"info\"\nfunction trackedChangeBadgeVariant(\n  kind: DocxTrackedChangeCardRenderProps[\"change\"][\"kind\"]\n): AnnotationBadgeVariant {\n  switch (kind) {\n    case \"insertion\":\n    case \"move-to\":\n      return \"success\"\n    case \"deletion\":\n    case \"move-from\":\n      return \"error\"\n    default:\n      return \"warning\"\n  }\n}\nfunction trackedChangeBadgeLabel({\n  change,\n  kindLabel,\n}: Pick<DocxTrackedChangeCardRenderProps, \"change\" | \"kindLabel\">) {\n  switch (change.kind) {\n    case \"insertion\":\n      return \"Inserted\"\n    case \"deletion\":\n      return \"Removed\"\n    case \"move-from\":\n      return \"Moved from\"\n    case \"move-to\":\n      return \"Moved to\"\n    default:\n      return kindLabel\n  }\n}\nfunction DocxAnnotationCard({\n  anchorText,\n  badge,\n  badgeVariant = \"outline\",\n  date,\n  meta,\n  documentTheme,\n  snippet,\n  style,\n}: {\n  anchorText?: string\n  badge: string\n  badgeVariant?: AnnotationBadgeVariant\n  date?: string\n  documentTheme: DocxDocumentTheme\n  meta: string\n  snippet: string\n  style: React.CSSProperties\n}) {\n  const isDarkDocument = documentTheme === \"dark\"\n  const cardStyle: React.CSSProperties = {\n    ...style,\n    backgroundColor: isDarkDocument\n      ? \"rgb(24 24 27 / 0.95)\"\n      : \"rgb(255 255 255 / 0.95)\",\n    color: isDarkDocument ? \"#f4f4f5\" : \"#18181b\",\n  }\n  const mutedTextColor = isDarkDocument ? \"#a1a1aa\" : \"#71717a\"\n  const anchorStyle: React.CSSProperties = {\n    backgroundColor: isDarkDocument\n      ? \"rgb(63 63 70 / 0.55)\"\n      : \"rgb(244 244 245 / 0.75)\",\n    color: mutedTextColor,\n  }\n  return (\n    <Card\n      style={cardStyle}\n      className=\"pointer-events-auto box-border gap-2 rounded-lg p-2 shadow-sm before:rounded-[7px]\"\n    >\n      <div className=\"flex min-w-0 items-start justify-between gap-2\">\n        <div\n          className=\"min-w-0 text-[11px] leading-tight font-medium\"\n          style={{ color: mutedTextColor }}\n        >\n          <div className=\"truncate\">{meta}</div>\n          {date ? <div className=\"mt-0.5 truncate\">{date}</div> : null}\n        </div>\n        <Badge\n          variant={badgeVariant === \"outline\" ? \"outline\" : \"secondary\"}\n          className={cn(\n            \"h-4 px-1 text-[10px]\",\n            cn(\n              \"max-w-[92px] truncate\",\n              badgeVariant === \"success\" &&\n                \"bg-green-500/10 text-green-700 dark:text-green-300\",\n              badgeVariant === \"error\" && \"bg-destructive/10 text-destructive\",\n              badgeVariant === \"warning\" &&\n                \"bg-amber-500/10 text-amber-700 dark:text-amber-300\",\n              badgeVariant === \"info\" &&\n                \"bg-blue-500/10 text-blue-700 dark:text-blue-300\"\n            )\n          )}\n        >\n          {badge}\n        </Badge>\n      </div>\n      {anchorText ? (\n        <div\n          className=\"rounded-md px-2 py-1 text-[11px] leading-snug italic\"\n          style={anchorStyle}\n        >\n          {anchorText}\n        </div>\n      ) : null}\n      <div className=\"text-xs leading-snug break-words\">{snippet}</div>\n    </Card>\n  )\n}\nexport function createDocxTrackedChangeCardRenderer(\n  documentTheme: DocxDocumentTheme\n) {\n  return function renderDocxTrackedChangeCard({\n    change,\n    formattedDate,\n    kindLabel,\n    snippet,\n    style,\n  }: DocxTrackedChangeCardRenderProps) {\n    return (\n      <DocxAnnotationCard\n        badge={trackedChangeBadgeLabel({ change, kindLabel })}\n        badgeVariant={trackedChangeBadgeVariant(change.kind)}\n        date={formattedDate}\n        documentTheme={documentTheme}\n        meta={change.author?.trim() || \"Unknown author\"}\n        snippet={snippet}\n        style={style}\n      />\n    )\n  }\n}\nexport function createDocxCommentCardRenderer(\n  documentTheme: DocxDocumentTheme\n) {\n  return function renderDocxCommentCard({\n    comment,\n    formattedDate,\n    snippet,\n    style,\n  }: DocxCommentCardRenderProps) {\n    const badge = comment.resolved\n      ? \"Resolved\"\n      : comment.parentId !== undefined\n        ? \"Reply\"\n        : \"Comment\"\n    return (\n      <DocxAnnotationCard\n        anchorText={comment.anchorText}\n        badge={badge}\n        badgeVariant={comment.resolved ? \"secondary\" : \"info\"}\n        date={formattedDate}\n        documentTheme={documentTheme}\n        meta={comment.author?.trim() || \"Unknown author\"}\n        snippet={snippet}\n        style={style}\n      />\n    )\n  }\n}\n",
      "type": "registry:ui",
      "target": "@components/extend/docx-annotation-card.tsx"
    }
  ],
  "categories": [
    "documents"
  ],
  "type": "registry:ui"
}
