| 12 | } |
| 13 | |
| 14 | export function CodeDiff({ oldSource, newSource, oldLabel, newLabel }: CodeDiffProps) { |
| 15 | const [viewMode, setViewMode] = useState<"unified" | "split">("unified"); |
| 16 | |
| 17 | const changes = useMemo(() => diffLines(oldSource, newSource), [oldSource, newSource]); |
| 18 | |
| 19 | return ( |
| 20 | <div> |
| 21 | <div className="mb-4 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between"> |
| 22 | <div className="min-w-0 truncate text-sm text-zinc-500 dark:text-zinc-400"> |
| 23 | <span className="font-medium text-zinc-700 dark:text-zinc-300">{oldLabel}</span> |
| 24 | {" -> "} |
| 25 | <span className="font-medium text-zinc-700 dark:text-zinc-300">{newLabel}</span> |
| 26 | </div> |
| 27 | <div className="flex shrink-0 rounded-lg border border-zinc-200 dark:border-zinc-700"> |
| 28 | <button |
| 29 | onClick={() => setViewMode("unified")} |
| 30 | className={cn( |
| 31 | "min-h-[36px] px-3 text-xs font-medium transition-colors", |
| 32 | viewMode === "unified" |
| 33 | ? "bg-zinc-900 text-white dark:bg-white dark:text-zinc-900" |
| 34 | : "text-zinc-500 hover:text-zinc-700 dark:text-zinc-400" |
| 35 | )} |
| 36 | > |
| 37 | Unified |
| 38 | </button> |
| 39 | <button |
| 40 | onClick={() => setViewMode("split")} |
| 41 | className={cn( |
| 42 | "min-h-[36px] px-3 text-xs font-medium transition-colors sm:inline-flex hidden", |
| 43 | viewMode === "split" |
| 44 | ? "bg-zinc-900 text-white dark:bg-white dark:text-zinc-900" |
| 45 | : "text-zinc-500 hover:text-zinc-700 dark:text-zinc-400" |
| 46 | )} |
| 47 | > |
| 48 | Split |
| 49 | </button> |
| 50 | </div> |
| 51 | </div> |
| 52 | |
| 53 | {viewMode === "unified" ? ( |
| 54 | <UnifiedView changes={changes} /> |
| 55 | ) : ( |
| 56 | <SplitView changes={changes} /> |
| 57 | )} |
| 58 | </div> |
| 59 | ); |
| 60 | } |
| 61 | |
| 62 | function UnifiedView({ changes }: { changes: Change[] }) { |
| 63 | let oldLine = 1; |