11import { useState } from 'react' ;
22import { useTranslation } from 'react-i18next' ;
33import { diffJson , diffJsonLines } from '../utils/json' ;
4+ import { useFileDrop } from '../hooks/useFileDrop' ;
45
56type DiffMode = 'keys' | 'lines' ;
67
8+ function DiffTextarea ( {
9+ value,
10+ onChange,
11+ placeholder,
12+ ariaLabel,
13+ borderColor,
14+ } : {
15+ value : string ;
16+ onChange : ( v : string ) => void ;
17+ placeholder : string ;
18+ ariaLabel : string ;
19+ borderColor : 'indigo' | 'amber' ;
20+ } ) {
21+ const { t } = useTranslation ( ) ;
22+ const { isDragging, dropError, onDragOver, onDragLeave, onDrop } = useFileDrop ( onChange ) ;
23+
24+ const dragBorder = borderColor === 'indigo'
25+ ? 'border-indigo-500 dark:border-indigo-400 border-2'
26+ : 'border-amber-500 dark:border-amber-400 border-2' ;
27+
28+ const overlayBg = borderColor === 'indigo'
29+ ? 'bg-indigo-500/10 dark:bg-indigo-400/10'
30+ : 'bg-amber-500/10 dark:bg-amber-400/10' ;
31+
32+ const overlayText = borderColor === 'indigo'
33+ ? 'text-indigo-600 dark:text-indigo-400'
34+ : 'text-amber-600 dark:text-amber-400' ;
35+
36+ return (
37+ < div className = "flex-1 flex flex-col gap-1" >
38+ < div
39+ className = "relative flex-1"
40+ onDragOver = { onDragOver }
41+ onDragLeave = { onDragLeave }
42+ onDrop = { onDrop }
43+ >
44+ < textarea
45+ value = { value }
46+ onChange = { ( e ) => onChange ( e . target . value ) }
47+ placeholder = { placeholder }
48+ aria-label = { ariaLabel }
49+ className = { `flex-1 min-h-[200px] w-full h-full p-3 rounded-lg border bg-white dark:bg-gray-800 font-mono text-sm resize-none focus:outline-none focus:ring-2 focus:ring-amber-500 ${
50+ isDragging ? dragBorder : 'border-gray-300 dark:border-gray-600'
51+ } `}
52+ spellCheck = { false }
53+ />
54+ { isDragging && (
55+ < div
56+ className = { `absolute inset-0 flex items-center justify-center rounded-lg ${ overlayBg } pointer-events-none` }
57+ aria-label = { t ( 'dropFileHere' ) }
58+ >
59+ < span className = { `${ overlayText } font-medium text-sm` } >
60+ { t ( 'dropFileHere' ) }
61+ </ span >
62+ </ div >
63+ ) }
64+ </ div >
65+ { dropError && (
66+ < div className = "text-red-600 dark:text-red-400 text-xs bg-red-50 dark:bg-red-900/20 p-1.5 rounded" >
67+ { dropError }
68+ </ div >
69+ ) }
70+ </ div >
71+ ) ;
72+ }
73+
774export default function DiffView ( ) {
875 const { t } = useTranslation ( ) ;
976 const [ left , setLeft ] = useState ( '' ) ;
@@ -42,21 +109,19 @@ export default function DiffView() {
42109 return (
43110 < div className = "flex flex-col gap-4" >
44111 < div className = "flex flex-col lg:flex-row gap-4" >
45- < textarea
112+ < DiffTextarea
46113 value = { left }
47- onChange = { ( e ) => setLeft ( e . target . value ) }
114+ onChange = { setLeft }
48115 placeholder = { t ( 'diffLeftPlaceholder' ) }
49- aria-label = { t ( 'diffLeftPlaceholder' ) }
50- className = "flex-1 min-h-[200px] p-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-slate-800 font-mono text-sm resize-none focus:outline-none focus:ring-2 focus:ring-amber-500"
51- spellCheck = { false }
116+ ariaLabel = { t ( 'diffLeftPlaceholder' ) }
117+ borderColor = "indigo"
52118 />
53- < textarea
119+ < DiffTextarea
54120 value = { right }
55- onChange = { ( e ) => setRight ( e . target . value ) }
121+ onChange = { setRight }
56122 placeholder = { t ( 'diffRightPlaceholder' ) }
57- aria-label = { t ( 'diffRightPlaceholder' ) }
58- className = "flex-1 min-h-[200px] p-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-slate-800 font-mono text-sm resize-none focus:outline-none focus:ring-2 focus:ring-amber-500"
59- spellCheck = { false }
123+ ariaLabel = { t ( 'diffRightPlaceholder' ) }
124+ borderColor = "amber"
60125 />
61126 </ div >
62127
@@ -75,7 +140,7 @@ export default function DiffView() {
75140 className = { `px-3 py-1.5 transition-colors ${
76141 mode === 'keys'
77142 ? 'bg-amber-500 text-white'
78- : 'bg-white dark:bg-slate -800 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-slate -700'
143+ : 'bg-white dark:bg-gray -800 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray -700'
79144 } `}
80145 >
81146 { t ( 'diffByKeys' ) }
@@ -86,7 +151,7 @@ export default function DiffView() {
86151 className = { `px-3 py-1.5 transition-colors ${
87152 mode === 'lines'
88153 ? 'bg-amber-500 text-white'
89- : 'bg-white dark:bg-slate -800 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-slate -700'
154+ : 'bg-white dark:bg-gray -800 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray -700'
90155 } `}
91156 >
92157 { t ( 'diffByLines' ) }
@@ -96,7 +161,7 @@ export default function DiffView() {
96161
97162 { hasResults && (
98163 < div className = "rounded-lg border border-gray-300 dark:border-gray-600 overflow-hidden" >
99- < div className = "bg-gray-100 dark:bg-slate -700 px-3 py-2 text-sm font-medium flex items-center justify-between" >
164+ < div className = "bg-gray-100 dark:bg-gray -700 px-3 py-2 text-sm font-medium flex items-center justify-between" >
100165 < span > { t ( 'diffResult' ) } </ span >
101166 { mode === 'lines' && lineResult && ! lineResult . error && (
102167 < span className = "text-xs font-normal" >
0 commit comments