bigbullui logobigbullui
Componenteditors

Diff Editor

Editable side-by-side diff view.

Preview

Live Preview
ÖnceSonra

Installation

Tokens guide →

Add to your project via CLI (zero dependencies, code you own):

npx bigbullui add diff-editor

Or install the full npm package:

npm install bigbullui

Usage

import { DiffEditor } from "@/components/ui/diff-editor";

<DiffEditor />

Source code

diff-editor.tsx

Zero dependencies (only React + utils). Copy and paste directly into your project:

"use client";

import * as React from "react";
import { cn } from "./lib/utils";

export interface DiffEditorProps extends React.HTMLAttributes<HTMLDivElement> {
  before: string;
  after: string;
  readOnly?: boolean;
  onBeforeChange?: (v: string) => void;
  onAfterChange?: (v: string) => void;
}

/** Yan yana düzenlenebilir diff görünümü. */
export function DiffEditor({ before, after, readOnly, onBeforeChange, onAfterChange, className, ...props }: DiffEditorProps) {
  const beforeLines = before.split("\n");
  const afterLines = after.split("\n");
  const maxLines = Math.max(beforeLines.length, afterLines.length);
  const beforeSet = new Set(beforeLines);
  const afterSet = new Set(afterLines);

  const rowTone = (idx: number): "same" | "added" | "removed" | "changed" => {
    const b = beforeLines[idx];
    const a = afterLines[idx];
    if (b === a) return "same";
    if (b === undefined) return "added";
    if (a === undefined) return "removed";
    if (!afterSet.has(b) || !beforeSet.has(a)) return "changed";
    return "same";
  };

  return (
    <div className={cn("w-full overflow-hidden rounded-lg border border-border bg-card", className)} {...props}>
      <style>{`@keyframes deIn { from { opacity: 0; } to { opacity: 1; } }`}</style>
      <div className="grid grid-cols-2 divide-x divide-border border-b border-border bg-secondary/60 font-mono text-[10px] uppercase tracking-wider text-muted-foreground">
        <span className="px-3 py-1.5">Önce</span>
        <span className="px-3 py-1.5">Sonra</span>
      </div>
      <div className="grid max-h-64 grid-cols-2 divide-x divide-border overflow-auto">
        <textarea
          value={before}
          onChange={(e) => onBeforeChange?.(e.target.value)}
          readOnly={readOnly}
          aria-label="Önceki sürüm"
          spellCheck={false}
          className="min-h-40 resize-none bg-transparent p-3 font-mono text-xs leading-5 text-foreground focus-visible:outline-none"
        />
        <textarea
          value={after}
          onChange={(e) => onAfterChange?.(e.target.value)}
          readOnly={readOnly}
          aria-label="Sonraki sürüm"
          spellCheck={false}
          className={cn(
            "min-h-40 resize-none bg-transparent p-3 font-mono text-xs leading-5 text-foreground focus-visible:outline-none animate-[deIn_0.3s_ease-out] motion-reduce:animate-none",
            maxLines > 0 && afterLines.some((l, i) => rowTone(i) !== "same") && "bg-emerald-500/5"
          )}
        />
      </div>
    </div>
  );
}

Props

PropTypeDescription
beforestring
afterstring
readOnlyboolean
onBeforeChange(v: string) => void
onAfterChange(v: string) => void