Kraft102's picture
fix: sql.js Docker/Alpine compatibility layer for PatternMemory and FailureMemory
5a81b95
/**
* ╔═══════════════════════════════════════════════════════════════════════════╗
* β•‘ CODE DIFF VIEWER β•‘
* ║═══════════════════════════════════════════════════════════════════════════║
* β•‘ Side-by-side or unified diff viewer for code changes β•‘
* β•‘ Part of the Liquid UI Arsenal β•‘
* β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
*/
import ReactDiffViewer, { DiffMethod } from 'react-diff-viewer-continued';
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { cn } from '@/lib/utils';
import { SplitSquareHorizontal, AlignJustify, Copy, Check } from 'lucide-react';
export interface CodeDiffViewerProps {
oldCode: string;
newCode: string;
oldTitle?: string;
newTitle?: string;
language?: string;
splitView?: boolean;
showLineNumbers?: boolean;
highlightLines?: number[];
summary?: {
additions: number;
deletions: number;
changes: number;
};
}
// Dark mode styles matching WidgeTDC aesthetic
const darkModeStyles = {
variables: {
dark: {
diffViewerBackground: 'transparent',
diffViewerColor: '#e5e5e5',
addedBackground: 'rgba(34, 197, 94, 0.15)',
addedColor: '#4ade80',
removedBackground: 'rgba(239, 68, 68, 0.15)',
removedColor: '#f87171',
wordAddedBackground: 'rgba(34, 197, 94, 0.3)',
wordRemovedBackground: 'rgba(239, 68, 68, 0.3)',
addedGutterBackground: 'rgba(34, 197, 94, 0.1)',
removedGutterBackground: 'rgba(239, 68, 68, 0.1)',
gutterBackground: 'rgba(0, 0, 0, 0.2)',
gutterBackgroundDark: 'rgba(0, 0, 0, 0.3)',
highlightBackground: 'rgba(59, 130, 246, 0.2)',
highlightGutterBackground: 'rgba(59, 130, 246, 0.1)',
codeFoldGutterBackground: 'rgba(0, 0, 0, 0.2)',
codeFoldBackground: 'rgba(0, 0, 0, 0.1)',
emptyLineBackground: 'transparent',
gutterColor: '#6b7280',
addedGutterColor: '#4ade80',
removedGutterColor: '#f87171',
codeFoldContentColor: '#9ca3af',
diffViewerTitleBackground: 'rgba(0, 0, 0, 0.3)',
diffViewerTitleColor: '#e5e5e5',
diffViewerTitleBorderColor: 'rgba(255, 255, 255, 0.1)',
},
},
line: {
padding: '4px 8px',
fontSize: '12px',
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
},
gutter: {
padding: '0 8px',
minWidth: '40px',
},
marker: {
padding: '0 4px',
},
contentText: {
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
fontSize: '12px',
lineHeight: '1.5',
},
};
export function CodeDiffViewer({
oldCode,
newCode,
oldTitle = 'Original',
newTitle = 'Modified',
language = 'typescript',
splitView = true,
showLineNumbers = true,
highlightLines = [],
summary,
}: CodeDiffViewerProps) {
const [isSplit, setIsSplit] = useState(splitView);
const [copied, setCopied] = useState(false);
const copyNewCode = async () => {
await navigator.clipboard.writeText(newCode);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
// Calculate summary if not provided
const diffSummary = summary || (() => {
const oldLines = oldCode.split('\n');
const newLines = newCode.split('\n');
let additions = 0;
let deletions = 0;
// Simple diff calculation
const oldSet = new Set(oldLines);
const newSet = new Set(newLines);
newLines.forEach(line => {
if (!oldSet.has(line)) additions++;
});
oldLines.forEach(line => {
if (!newSet.has(line)) deletions++;
});
return { additions, deletions, changes: additions + deletions };
})();
return (
<div className="rounded-lg border border-border/30 bg-background/50 overflow-hidden">
{/* Header */}
<div className="flex items-center justify-between px-4 py-2 bg-muted/30 border-b border-border/30">
<div className="flex items-center gap-3">
<Badge variant="outline" className="text-[10px] font-mono">
{language}
</Badge>
<div className="flex items-center gap-2 text-xs">
<span className="text-green-500 font-mono">+{diffSummary.additions}</span>
<span className="text-red-500 font-mono">-{diffSummary.deletions}</span>
</div>
</div>
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="sm"
onClick={() => setIsSplit(!isSplit)}
className="h-7 px-2"
>
{isSplit ? (
<AlignJustify className="w-3 h-3 mr-1" />
) : (
<SplitSquareHorizontal className="w-3 h-3 mr-1" />
)}
<span className="text-[10px]">{isSplit ? 'Unified' : 'Split'}</span>
</Button>
<Button
variant="ghost"
size="sm"
onClick={copyNewCode}
className="h-7 px-2"
>
{copied ? (
<Check className="w-3 h-3 text-green-500" />
) : (
<Copy className="w-3 h-3" />
)}
</Button>
</div>
</div>
{/* File titles */}
<div className={cn(
'grid border-b border-border/30 bg-muted/20',
isSplit ? 'grid-cols-2' : 'grid-cols-1'
)}>
<div className="px-4 py-1.5 text-xs font-mono text-muted-foreground border-r border-border/30">
{oldTitle}
</div>
{isSplit && (
<div className="px-4 py-1.5 text-xs font-mono text-muted-foreground">
{newTitle}
</div>
)}
</div>
{/* Diff viewer */}
<div className="max-h-[400px] overflow-auto">
<ReactDiffViewer
oldValue={oldCode}
newValue={newCode}
splitView={isSplit}
showDiffOnly={false}
useDarkTheme={true}
compareMethod={DiffMethod.WORDS}
styles={darkModeStyles}
hideLineNumbers={!showLineNumbers}
leftTitle={undefined}
rightTitle={undefined}
extraLinesSurroundingDiff={3}
/>
</div>
{/* Footer with stats */}
<div className="px-4 py-2 bg-muted/20 border-t border-border/30 flex items-center justify-between text-[10px] text-muted-foreground">
<span>
{oldCode.split('\n').length} β†’ {newCode.split('\n').length} lines
</span>
<span className="font-mono">
{diffSummary.changes} change{diffSummary.changes !== 1 ? 's' : ''} detected
</span>
</div>
</div>
);
}
export default CodeDiffViewer;