Skip to content

Commit c4af85b

Browse files
authored
Sync textarea size of source and target panel (#104)
1 parent 26dbfa7 commit c4af85b

3 files changed

Lines changed: 77 additions & 19 deletions

File tree

src/layouts/bible/DraftingPage.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const DraftingUI: React.FC<DraftingUIProps> = ({
4444
const [verses, setVerses] = useState<TargetVerse[]>(targetVerses);
4545
const [activeVerseId, setActiveVerseId] = useState(1);
4646
const [previousActiveVerseId, setPreviousActiveVerseId] = useState<number | null>(null);
47+
const [textareaHeights, setTextareaHeights] = useState<Record<number, number>>({});
4748

4849
const sourceScrollRef = useRef<HTMLDivElement>(null);
4950
const targetScrollRef = useRef<HTMLDivElement>(null);
@@ -75,6 +76,17 @@ const DraftingUI: React.FC<DraftingUIProps> = ({
7576
}
7677
);
7778

79+
const handleHeightChange = (verseId: number, height: number) => {
80+
setTextareaHeights(prev => {
81+
const currentHeight = prev[verseId] || 0;
82+
const newHeight = Math.max(currentHeight, height);
83+
if (newHeight !== currentHeight) {
84+
return { ...prev, [verseId]: newHeight };
85+
}
86+
return prev;
87+
});
88+
};
89+
7890
useEffect(() => {
7991
if (targetVerses.length > 0) {
8092
targetVerses.forEach(verse => {
@@ -253,7 +265,9 @@ const DraftingUI: React.FC<DraftingUIProps> = ({
253265
activeVerseId={activeVerseId}
254266
bibleName={projectItem.bibleName}
255267
scrollRef={sourceScrollRef}
268+
textareaHeights={textareaHeights}
256269
verses={sourceVerses}
270+
onHeightChange={handleHeightChange}
257271
onScroll={scrollTop => handleScroll('source', scrollTop)}
258272
/>
259273
</div>
@@ -265,9 +279,11 @@ const DraftingUI: React.FC<DraftingUIProps> = ({
265279
setActiveVerseId={handleActiveVerseChange}
266280
setVerses={setVerses}
267281
targetLanguage={projectItem.targetLanguage}
282+
textareaHeights={textareaHeights}
268283
totalSourceVerses={totalSourceVerses}
269284
updateVerse={updateTargetVerse}
270285
verses={verses}
286+
onHeightChange={handleHeightChange}
271287
onScroll={scrollTop => handleScroll('target', scrollTop)}
272288
/>
273289
</div>

src/layouts/bible/SourcePanel.tsx

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type React from 'react';
2-
import { useEffect, useRef } from 'react';
2+
import { useCallback, useEffect, useRef } from 'react';
33

44
import type { Source } from './DraftingPage';
55

@@ -9,6 +9,8 @@ interface SourcePanelProps {
99
activeVerseId: number;
1010
scrollRef: React.RefObject<HTMLDivElement>;
1111
onScroll: (scrollTop: number) => void;
12+
textareaHeights: Record<number, number>;
13+
onHeightChange: (verseId: number, height: number) => void;
1214
}
1315

1416
export const SourcePanel: React.FC<SourcePanelProps> = ({
@@ -17,14 +19,21 @@ export const SourcePanel: React.FC<SourcePanelProps> = ({
1719
activeVerseId,
1820
scrollRef,
1921
onScroll,
22+
textareaHeights,
23+
onHeightChange,
2024
}) => {
2125
const textareaRefs = useRef<Record<number, HTMLTextAreaElement | null>>({});
2226
const verseRefs = useRef<Record<number, HTMLDivElement | null>>({});
2327

24-
const autoResizeTextarea = (textarea: HTMLTextAreaElement) => {
25-
textarea.style.height = 'auto';
26-
textarea.style.height = Math.max(20, textarea.scrollHeight) + 'px';
27-
};
28+
const autoResizeTextarea = useCallback(
29+
(textarea: HTMLTextAreaElement, verseId: number) => {
30+
textarea.style.height = 'auto';
31+
const newHeight = Math.max(20, textarea.scrollHeight);
32+
textarea.style.height = newHeight + 'px';
33+
onHeightChange(verseId, newHeight);
34+
},
35+
[onHeightChange]
36+
);
2837

2938
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
3039
onScroll(e.currentTarget.scrollTop);
@@ -38,7 +47,6 @@ export const SourcePanel: React.FC<SourcePanelProps> = ({
3847
const verseHeight = activeVerseElement.offsetHeight;
3948
const containerHeight = container.clientHeight;
4049
const currentScroll = container.scrollTop;
41-
4250
if (verseTop < currentScroll || verseTop + verseHeight > currentScroll + containerHeight) {
4351
container.scrollTo({
4452
top: verseTop - containerHeight / 2 + verseHeight / 2,
@@ -52,10 +60,15 @@ export const SourcePanel: React.FC<SourcePanelProps> = ({
5260
verses.forEach(verse => {
5361
const textarea = textareaRefs.current[verse.verseNumber];
5462
if (textarea) {
55-
autoResizeTextarea(textarea);
63+
const syncHeight = textareaHeights[verse.verseNumber];
64+
if (syncHeight) {
65+
textarea.style.height = syncHeight + 'px';
66+
} else {
67+
autoResizeTextarea(textarea, verse.verseNumber);
68+
}
5669
}
5770
});
58-
}, [verses]);
71+
}, [verses, textareaHeights, autoResizeTextarea]);
5972

6073
return (
6174
<div className='flex h-full flex-col'>
@@ -90,6 +103,11 @@ export const SourcePanel: React.FC<SourcePanelProps> = ({
90103
ref={el => (textareaRefs.current[verse.verseNumber] = el)}
91104
readOnly
92105
className='h-auto min-h-[20px] w-full resize-none content-center overflow-hidden border-none bg-transparent text-base leading-relaxed leading-snug text-gray-800 outline-none'
106+
style={{
107+
height: textareaHeights[verse.verseNumber]
108+
? `${textareaHeights[verse.verseNumber]}px`
109+
: 'auto',
110+
}}
93111
value={verse.text}
94112
/>
95113
</div>

src/layouts/bible/TargetPanel.tsx

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type React from 'react';
2-
import { useEffect, useRef } from 'react';
2+
import { useCallback, useEffect, useRef } from 'react';
33

44
import { Button } from '@/components/ui/button';
55

@@ -16,6 +16,8 @@ interface TargetPanelProps {
1616
moveToNextVerse: () => void;
1717
scrollRef: React.RefObject<HTMLDivElement>;
1818
onScroll: (scrollTop: number) => void;
19+
textareaHeights: Record<number, number>;
20+
onHeightChange: (verseId: number, height: number) => void;
1921
}
2022

2123
export const TargetPanel: React.FC<TargetPanelProps> = ({
@@ -29,14 +31,21 @@ export const TargetPanel: React.FC<TargetPanelProps> = ({
2931
moveToNextVerse,
3032
scrollRef,
3133
onScroll,
34+
textareaHeights,
35+
onHeightChange,
3236
}) => {
3337
const textareaRefs = useRef<Record<number, HTMLTextAreaElement | null>>({});
3438
const verseRefs = useRef<Record<number, HTMLDivElement | null>>({});
3539

36-
const autoResizeTextarea = (textarea: HTMLTextAreaElement) => {
37-
textarea.style.height = 'auto';
38-
textarea.style.height = Math.max(20, textarea.scrollHeight) + 'px';
39-
};
40+
const autoResizeTextarea = useCallback(
41+
(textarea: HTMLTextAreaElement, verseId: number) => {
42+
textarea.style.height = 'auto';
43+
const newHeight = Math.max(20, textarea.scrollHeight);
44+
textarea.style.height = newHeight + 'px';
45+
onHeightChange(verseId, newHeight);
46+
},
47+
[onHeightChange]
48+
);
4049

4150
const handleVerseClick = async (verseId: number) => {
4251
const verse = verses.find(v => v.verseNumber === verseId);
@@ -63,7 +72,7 @@ export const TargetPanel: React.FC<TargetPanelProps> = ({
6372

6473
const textarea = textareaRefs.current[verseId];
6574
if (textarea) {
66-
autoResizeTextarea(textarea);
75+
autoResizeTextarea(textarea, verseId);
6776
}
6877
};
6978

@@ -96,9 +105,14 @@ export const TargetPanel: React.FC<TargetPanelProps> = ({
96105

97106
if (textarea) {
98107
textarea.focus();
99-
autoResizeTextarea(textarea);
108+
const syncHeight = textareaHeights[activeVerseId];
109+
if (syncHeight) {
110+
textarea.style.height = syncHeight + 'px';
111+
} else {
112+
autoResizeTextarea(textarea, activeVerseId);
113+
}
100114
}
101-
}, [activeVerseId, scrollRef]);
115+
}, [activeVerseId, scrollRef, textareaHeights, autoResizeTextarea]);
102116

103117
useEffect(() => {
104118
if (verses.length === 0) {
@@ -111,12 +125,17 @@ export const TargetPanel: React.FC<TargetPanelProps> = ({
111125
} else {
112126
verses.forEach(verse => {
113127
const textarea = textareaRefs.current[verse.verseNumber];
114-
if (textarea && verse.content) {
115-
autoResizeTextarea(textarea);
128+
if (textarea) {
129+
const syncHeight = textareaHeights[verse.verseNumber];
130+
if (syncHeight) {
131+
textarea.style.height = syncHeight + 'px';
132+
} else if (verse.content) {
133+
autoResizeTextarea(textarea, verse.verseNumber);
134+
}
116135
}
117136
});
118137
}
119-
}, [setVerses, verses]);
138+
}, [setVerses, verses, textareaHeights, autoResizeTextarea]);
120139

121140
return (
122141
<div className='flex h-full flex-col'>
@@ -155,6 +174,11 @@ export const TargetPanel: React.FC<TargetPanelProps> = ({
155174
ref={el => (textareaRefs.current[verseId] = el)}
156175
className='h-auto min-h-[20px] w-full resize-none content-center overflow-hidden border-none bg-transparent text-base leading-relaxed leading-snug text-gray-800 outline-none'
157176
placeholder='Enter translation...'
177+
style={{
178+
height: textareaHeights[verseId]
179+
? `${textareaHeights[verseId]}px`
180+
: 'auto',
181+
}}
158182
value={TargetVerse.content}
159183
onChange={e => handleTextChange(verseId, e.target.value)}
160184
onFocus={() => handleFocus(verseId)}

0 commit comments

Comments
 (0)