{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "document-splits",
  "title": "Document Splits",
  "description": "Mark where one scanned batch becomes several documents.",
  "dependencies": [
    "lucide-react@^1.31.0"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/default/document-splits/document-splits.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { FileText, Scissors, Undo2 } from \"lucide-react\"\nimport { cn } from \"@/lib/utils\"\n\nexport type SplitPage = {\n  number: number\n  src?: string\n  label?: string\n}\n\nexport type DocumentSegment = {\n  index: number\n  pages: SplitPage[]\n}\n\nexport type DocumentSplitsProps = Omit<\n  React.HTMLAttributes<HTMLElement>,\n  \"children\" | \"onChange\"\n> & {\n  pages: SplitPage[]\n  splitAfter?: number[]\n  defaultSplitAfter?: number[]\n  onSplitChange?: (splitAfter: number[]) => void\n  segmentLabel?: (segment: DocumentSegment) => React.ReactNode\n  label?: string\n  renderImage?: (props: {\n    src: string\n    alt: string\n    className: string\n  }) => React.ReactNode\n}\n\nfunction toSegments(pages: SplitPage[], splitAfter: Set<number>) {\n  const segments: SplitPage[][] = [[]]\n\n  for (const page of pages) {\n    segments[segments.length - 1]?.push(page)\n    if (splitAfter.has(page.number) && page !== pages[pages.length - 1]) {\n      segments.push([])\n    }\n  }\n\n  return segments.filter((segment) => segment.length > 0)\n}\n\nexport function DocumentSplits({\n  pages,\n  splitAfter,\n  defaultSplitAfter = [],\n  onSplitChange,\n  segmentLabel,\n  label = \"Document splits\",\n  renderImage,\n  className,\n  ...rootProps\n}: DocumentSplitsProps) {\n  const [uncontrolled, setUncontrolled] =\n    React.useState<number[]>(defaultSplitAfter)\n  const splits = splitAfter ?? uncontrolled\n  const splitSet = React.useMemo(() => new Set(splits), [splits])\n\n  const setSplits = (next: number[]) => {\n    const sorted = [...next].sort((a, b) => a - b)\n    if (splitAfter === undefined) setUncontrolled(sorted)\n    onSplitChange?.(sorted)\n  }\n\n  const toggle = (pageNumber: number) => {\n    setSplits(\n      splitSet.has(pageNumber)\n        ? splits.filter((entry) => entry !== pageNumber)\n        : [...splits, pageNumber]\n    )\n  }\n\n  const segments = React.useMemo(\n    () => toSegments(pages, splitSet),\n    [pages, splitSet]\n  )\n\n  return (\n    <section\n      data-slot=\"document-splits\"\n      aria-label={label}\n      className={cn(\n        \"border-border bg-card text-card-foreground rounded-[var(--radius)] border\",\n        className\n      )}\n      {...rootProps}\n    >\n      <div className=\"border-border flex items-center gap-3 border-b px-3.5 py-2.5\">\n        <p className=\"text-sm font-semibold\">\n          {segments.length} {segments.length === 1 ? \"document\" : \"documents\"}\n        </p>\n\n        {splits.length > 0 ? (\n          <button\n            type=\"button\"\n            data-slot=\"document-splits-reset\"\n            className=\"text-muted-foreground hover:text-foreground focus-visible:ring-ring ml-auto inline-flex min-h-11 items-center gap-1.5 rounded-full px-2 text-xs font-semibold transition-colors duration-150 focus-visible:ring-2 focus-visible:outline-none motion-reduce:transition-none\"\n            onClick={() => setSplits([])}\n          >\n            <Undo2 aria-hidden=\"true\" size={13} />\n            Clear splits\n          </button>\n        ) : null}\n      </div>\n\n      <div className=\"grid gap-4 p-3.5\">\n        {segments.map((segment, index) => (\n          <div key={segment[0]?.number ?? index} data-slot=\"document-segment\">\n            <p className=\"text-muted-foreground mb-2 text-xs font-bold tracking-[0.08em] uppercase\">\n              {segmentLabel?.({ index, pages: segment }) ??\n                `Document ${index + 1} · ${segment.length} ${segment.length === 1 ? \"page\" : \"pages\"}`}\n            </p>\n\n            <div className=\"flex flex-wrap items-start gap-1\">\n              {segment.map((page) => {\n                const isLastOverall =\n                  page.number === pages[pages.length - 1]?.number\n                const isSplit = splitSet.has(page.number)\n\n                return (\n                  <React.Fragment key={page.number}>\n                    <span\n                      data-slot=\"document-splits-page\"\n                      className=\"grid justify-items-center gap-1\"\n                    >\n                      <span className=\"border-border bg-background flex aspect-[3/4] w-16 items-center justify-center overflow-hidden rounded-[3px] border\">\n                        {page.src ? (\n                          renderImage ? (\n                            renderImage({\n                              src: page.src,\n                              alt: \"\",\n                              className: \"size-full object-cover\",\n                            })\n                          ) : (\n                            // eslint-disable-next-line @next/next/no-img-element\n                            <img\n                              alt=\"\"\n                              className=\"size-full object-cover\"\n                              src={page.src}\n                            />\n                          )\n                        ) : (\n                          <FileText\n                            aria-hidden=\"true\"\n                            className=\"text-muted-foreground\"\n                            size={16}\n                          />\n                        )}\n                      </span>\n                      <span className=\"text-muted-foreground font-[family-name:var(--font-mono),monospace] text-[0.65rem] tabular-nums\">\n                        {page.label ?? page.number}\n                      </span>\n                    </span>\n\n                    {isLastOverall ? null : (\n                      <button\n                        type=\"button\"\n                        data-slot=\"document-splits-divider\"\n                        data-split={isSplit || undefined}\n                        aria-pressed={isSplit}\n                        className={cn(\n                          \"focus-visible:ring-ring group mt-4 flex h-16 w-6 shrink-0 items-center justify-center rounded-full focus-visible:ring-2 focus-visible:outline-none\",\n                          isSplit\n                            ? \"text-primary\"\n                            : \"text-muted-foreground/40 hover:text-foreground transition-colors duration-150 motion-reduce:transition-none\"\n                        )}\n                        onClick={() => toggle(page.number)}\n                      >\n                        <span className=\"sr-only\">\n                          {isSplit ? \"Remove split after\" : \"Split after\"} page{\" \"}\n                          {page.number}\n                        </span>\n                        <Scissors aria-hidden=\"true\" size={14} />\n                      </button>\n                    )}\n                  </React.Fragment>\n                )\n              })}\n            </div>\n          </div>\n        ))}\n      </div>\n    </section>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "document",
    "split",
    "pages"
  ],
  "type": "registry:ui"
}