{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "redline",
  "title": "Redline",
  "description": "Proposed edits in proofreader's red ink, each with a sign-here flag to accept or reject it. Nothing is applied until someone decides.",
  "dependencies": [
    "cn"
  ],
  "registryDependencies": [
    "https://bevel-ui.vercel.app/r/bevel.json"
  ],
  "files": [
    {
      "path": "registry/bevel/ui/redline.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"cn\"\n\ntype RedlineDecision = \"accepted\" | \"rejected\"\n\ntype RedlineContextValue = {\n  decisions: Record<string, RedlineDecision>\n  decide: (id: string, decision: RedlineDecision | undefined) => void\n  /** Decide several changes in one update. */\n  decideMany: (updates: Record<string, RedlineDecision>) => void\n  register: (id: string) => () => void\n  ids: string[]\n}\n\nconst RedlineContext = React.createContext<RedlineContextValue | null>(null)\n\nfunction useRedline() {\n  const context = React.useContext(RedlineContext)\n  if (!context) throw new Error(\"Redline parts must be inside <Redline>\")\n  return context\n}\n\ntype RedlineProps = React.ComponentProps<\"div\"> & {\n  decisions?: Record<string, RedlineDecision>\n  defaultDecisions?: Record<string, RedlineDecision>\n  onDecisionsChange?: (decisions: Record<string, RedlineDecision>) => void\n}\n\n/** A document with proposed edits. Nothing is applied until someone decides. */\nfunction Redline({\n  className,\n  decisions: decisionsProp,\n  defaultDecisions = {},\n  onDecisionsChange,\n  ...props\n}: RedlineProps) {\n  const [uncontrolled, setUncontrolled] = React.useState(defaultDecisions)\n  const decisions = decisionsProp ?? uncontrolled\n  const [ids, setIds] = React.useState<string[]>([])\n  // Stable, so a decision doesn't make every change re-register.\n  const register = React.useCallback((id: string) => {\n    setIds((list) => (list.includes(id) ? list : [...list, id]))\n    return () => setIds((list) => list.filter((x) => x !== id))\n  }, [])\n\n  const context = React.useMemo<RedlineContextValue>(\n    () => ({\n      decisions,\n      ids,\n      decide: (id, decision) => {\n        const next = { ...decisions }\n        if (decision) next[id] = decision\n        else delete next[id]\n        if (decisionsProp === undefined) setUncontrolled(next)\n        onDecisionsChange?.(next)\n      },\n      decideMany: (updates) => {\n        const next = { ...decisions, ...updates }\n        if (decisionsProp === undefined) setUncontrolled(next)\n        onDecisionsChange?.(next)\n      },\n      register,\n    }),\n    [decisions, ids, decisionsProp, onDecisionsChange, register]\n  )\n\n  return (\n    <RedlineContext value={context}>\n      <div\n        data-slot=\"redline\"\n        className={cn(\"bevel-paper flex w-full max-w-xl flex-col rounded-(--bevel-radius-xs)\", className)}\n        {...props}\n      />\n    </RedlineContext>\n  )\n}\n\n/** The text being edited. */\nfunction RedlineDocument({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"redline-document\"\n      className={cn(\"p-5 text-base leading-loose text-(--bevel-paper-foreground)\", className)}\n      {...props}\n    />\n  )\n}\n\ntype RedlineChangeProps = Omit<React.ComponentProps<\"span\">, \"children\"> & {\n  /** Stable id for this change. */\n  id: string\n  /** Text taken out. */\n  deletion?: string\n  /** Text put in. */\n  insertion?: string\n}\n\n/**\n * One proposed edit, marked in red ink with a flag to accept or reject it.\n * Accepted shows the new text; rejected keeps the original.\n */\nfunction RedlineChange({ id, deletion, insertion, className, ...props }: RedlineChangeProps) {\n  const { decisions, decide, register } = useRedline()\n  React.useEffect(() => register(id), [id, register])\n  const decision = decisions[id]\n\n  if (decision === \"accepted\") {\n    return (\n      <span data-slot=\"redline-change\" data-decision=\"accepted\" className={className} {...props}>\n        {insertion}\n      </span>\n    )\n  }\n  if (decision === \"rejected\") {\n    return (\n      <span data-slot=\"redline-change\" data-decision=\"rejected\" className={className} {...props}>\n        {deletion}\n      </span>\n    )\n  }\n\n  const summary = [deletion && `delete “${deletion}”`, insertion && `insert “${insertion}”`]\n    .filter(Boolean)\n    .join(\", \")\n\n  return (\n    <span\n      data-slot=\"redline-change\"\n      data-decision=\"pending\"\n      role=\"group\"\n      aria-label={`Proposed change: ${summary}`}\n      className={cn(\"text-(--bevel-pen-red)\", className)}\n      {...props}\n    >\n      {deletion && (\n        <del className=\"decoration-(--bevel-pen-red) decoration-(length:--redline-strike)\">\n          <span className=\"sr-only\">deleted: </span>\n          {deletion}\n        </del>\n      )}\n      {deletion && insertion && \" \"}\n      {insertion && (\n        <ins className=\"underline decoration-(--bevel-pen-red) decoration-double underline-offset-4\">\n          <span className=\"sr-only\">inserted: </span>\n          {insertion}\n        </ins>\n      )}\n      <span\n        data-slot=\"redline-flag\"\n        className=\"redline-flag-shape mx-1 inline-flex translate-y-0.5 items-center gap-0.5 bg-(--redline-flag) py-0.5 pr-1 pl-2.5 align-middle text-(--redline-flag-foreground) shadow-(--bevel-depth-sheet)\"\n      >\n        <FlagButton label=\"Accept change\" onClick={() => decide(id, \"accepted\")}>\n          <path d=\"M3 8.5l3 3 7-7.5\" />\n        </FlagButton>\n        <FlagButton label=\"Reject change\" onClick={() => decide(id, \"rejected\")}>\n          <path d=\"M4 4l8 8M12 4l-8 8\" />\n        </FlagButton>\n      </span>\n    </span>\n  )\n}\n\nfunction FlagButton({\n  label,\n  onClick,\n  children,\n}: {\n  label: string\n  onClick: () => void\n  children: React.ReactNode\n}) {\n  return (\n    <button\n      type=\"button\"\n      aria-label={label}\n      onClick={onClick}\n      className=\"inline-grid size-6 cursor-pointer place-items-center rounded-(--bevel-radius-xs) outline-none hover:bg-(--redline-flag-foreground)/10 focus-visible:ring-2 focus-visible:ring-(--bevel-pen-blue)/60\"\n    >\n      <svg viewBox=\"0 0 16 16\" aria-hidden className=\"size-3.5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n        {children}\n      </svg>\n    </button>\n  )\n}\n\n/** Count of open changes, with accept-all and reject-all. */\nfunction RedlineToolbar({ className, ...props }: React.ComponentProps<\"div\">) {\n  const { ids, decisions, decideMany } = useRedline()\n  const pending = ids.filter((id) => !decisions[id])\n  const applyAll = (decision: RedlineDecision) =>\n    decideMany(Object.fromEntries(pending.map((id) => [id, decision])))\n  return (\n    <div\n      role=\"toolbar\"\n      aria-label=\"Changes\"\n      data-slot=\"redline-toolbar\"\n      className={cn(\n        \"flex items-center justify-between gap-3 border-t border-(--bevel-paper-rule) px-5 py-3 text-sm\",\n        className\n      )}\n      {...props}\n    >\n      <span aria-live=\"polite\" className=\"text-(--bevel-paper-muted)\">\n        {pending.length === 0\n          ? \"All changes decided\"\n          : `${pending.length} change${pending.length === 1 ? \"\" : \"s\"} to review`}\n      </span>\n      <span className=\"flex gap-2\">\n        <ToolbarButton disabled={pending.length === 0} onClick={() => applyAll(\"rejected\")}>\n          Reject all\n        </ToolbarButton>\n        <ToolbarButton disabled={pending.length === 0} onClick={() => applyAll(\"accepted\")}>\n          Accept all\n        </ToolbarButton>\n      </span>\n    </div>\n  )\n}\n\nfunction ToolbarButton(props: React.ComponentProps<\"button\">) {\n  return (\n    <button\n      type=\"button\"\n      className=\"h-8 rounded-(--bevel-radius-sm) border border-(--bevel-paper-foreground)/30 px-3 font-medium text-(--bevel-paper-foreground) outline-none hover:bg-(--bevel-paper-foreground)/5 focus-visible:ring-2 focus-visible:ring-(--bevel-pen-blue)/50 disabled:opacity-50\"\n      {...props}\n    />\n  )\n}\n\nexport { Redline, RedlineDocument, RedlineChange, RedlineToolbar }\nexport type { RedlineDecision, RedlineProps, RedlineChangeProps }\n",
      "type": "registry:ui"
    }
  ],
  "css": {
    ":root": {
      "--redline-flag": "var(--bevel-sticker)",
      "--redline-flag-foreground": "var(--bevel-sticker-foreground)",
      "--redline-strike": "2px"
    },
    "@utility redline-flag-shape": {
      "clip-path": "polygon(0.5rem 0, 100% 0, 100% 100%, 0.5rem 100%, 0 50%)"
    }
  },
  "categories": [
    "documents"
  ],
  "type": "registry:ui"
}