Skip to content

Commit f2889cc

Browse files
Copilotasim
andauthored
Add transliteration tooltip on Arabic word click (#102)
* Initial plan * Add clickable Arabic word transliteration tooltip When clicking an Arabic word in the Quran view, a tooltip appears showing the transliteration (pronunciation guide) for that word. This works in both Arabic-only mode and Translation mode with word-by-word enabled, in both the chapter view and single verse view. Co-authored-by: asim <17530+asim@users.noreply.github.com> * Fix className concatenation in ClickableArabicWord to avoid extra whitespace Co-authored-by: asim <17530+asim@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: asim <17530+asim@users.noreply.github.com>
1 parent dc03568 commit f2889cc

File tree

4 files changed

+93
-12
lines changed

4 files changed

+93
-12
lines changed

web/app/app.css

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,33 @@
1818
color: #b45309;
1919
cursor: pointer;
2020
transition: color 0.3s ease;
21+
}
22+
23+
.transliteration-tooltip {
24+
position: absolute;
25+
bottom: 100%;
26+
left: 50%;
27+
transform: translateX(-50%);
28+
background-color: #1f2937;
29+
color: #fff;
30+
font-family: sans-serif;
31+
font-size: 0.8rem;
32+
padding: 4px 10px;
33+
border-radius: 6px;
34+
white-space: nowrap;
35+
z-index: 50;
36+
pointer-events: none;
37+
margin-bottom: 4px;
38+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
39+
}
40+
41+
.transliteration-tooltip::after {
42+
content: '';
43+
position: absolute;
44+
top: 100%;
45+
left: 50%;
46+
transform: translateX(-50%);
47+
border-width: 5px;
48+
border-style: solid;
49+
border-color: #1f2937 transparent transparent transparent;
2150
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React, { useCallback, useEffect, useRef, useState } from 'react';
2+
3+
type ClickableArabicWordProps = {
4+
arabic: string;
5+
transliteration?: string;
6+
children?: React.ReactNode;
7+
className?: string;
8+
};
9+
10+
export function ClickableArabicWord({
11+
arabic,
12+
transliteration,
13+
children,
14+
className = '',
15+
}: ClickableArabicWordProps) {
16+
const [showTooltip, setShowTooltip] = useState(false);
17+
const ref = useRef<HTMLSpanElement>(null);
18+
19+
const handleClick = useCallback(() => {
20+
if (!transliteration) return;
21+
setShowTooltip((prev) => !prev);
22+
}, [transliteration]);
23+
24+
useEffect(() => {
25+
if (!showTooltip) return;
26+
27+
const handleClickOutside = (e: MouseEvent) => {
28+
if (ref.current && !ref.current.contains(e.target as Node)) {
29+
setShowTooltip(false);
30+
}
31+
};
32+
33+
document.addEventListener('mousedown', handleClickOutside);
34+
return () => document.removeEventListener('mousedown', handleClickOutside);
35+
}, [showTooltip]);
36+
37+
return (
38+
<span
39+
ref={ref}
40+
className={`verse-arabic-word${className ? ` ${className}` : ''}`}
41+
onClick={handleClick}
42+
style={{ position: 'relative' }}
43+
>
44+
{children ?? arabic}
45+
{showTooltip && transliteration && (
46+
<span className='transliteration-tooltip'>{transliteration}</span>
47+
)}
48+
</span>
49+
);
50+
}

web/app/routes/_app.quran.$chapterNumber.$verseNumber.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { PageError } from '~/components/interface/page-error';
77
import { PrimaryButton } from '~/components/interface/primary-button';
88
import { AudioPlayer } from '~/components/quran/audio-player';
99
import { ChapterHeader } from '~/components/quran/chapter-header';
10+
import { ClickableArabicWord } from '~/components/quran/clickable-arabic-word';
1011
import { ViewMode } from '~/components/quran/view-mode';
1112
import { useQuranViewMode } from '~/hooks/use-quran-view-mode';
1213
import { getChapterOptions } from '~/queries/quran';
@@ -90,15 +91,15 @@ export default function QuranVerse(props: Route.ComponentProps) {
9091
>
9192
{verse.words && verse.words.length > 0
9293
? verse.words.map((word, idx, arr) => (
93-
<span key={idx} className='verse-arabic-word'>
94+
<ClickableArabicWord key={idx} arabic={word.arabic} transliteration={word.transliteration}>
9495
{word.arabic}
9596
{idx === arr.length - 1 && (
9697
<span className='mx-2 font-arabic'>
9798
{toArabicNumber(verse.number)}
9899
</span>
99100
)}
100101
&nbsp;
101-
</span>
102+
</ClickableArabicWord>
102103
))
103104
: verse.arabic.split(' ').map((word, idx, arr) => (
104105
<span key={idx} className='verse-arabic-word'>
@@ -130,20 +131,20 @@ export default function QuranVerse(props: Route.ComponentProps) {
130131
? verse.words.map((word, idx, arr) => {
131132
if (idx === arr.length - 1) {
132133
return (
133-
<span key={idx} className='verse-arabic-word text-3xl flex flex-col items-center mr-2 mb-2'>
134+
<ClickableArabicWord key={idx} arabic={word.arabic} transliteration={word.transliteration} className='text-3xl flex flex-col items-center mr-2 mb-2'>
134135
<span className='flex flex-row items-center' dir="rtl">
135136
<span>{word.arabic}</span>
136137
<span className='mx-2 font-arabic'>{toArabicNumber(verse.number)}</span>
137138
</span>
138139
<span className='text-xs sm:text-sm mt-1 px-1 rounded bg-gray-100 text-gray-700'>{word.english}</span>
139-
</span>
140+
</ClickableArabicWord>
140141
);
141142
} else {
142143
return (
143-
<span key={idx} className='verse-arabic-word text-3xl flex flex-col items-center mr-2 mb-2'>
144+
<ClickableArabicWord key={idx} arabic={word.arabic} transliteration={word.transliteration} className='text-3xl flex flex-col items-center mr-2 mb-2'>
144145
<span>{word.arabic}</span>
145146
<span className='text-xs sm:text-sm mt-1 px-1 rounded bg-gray-100 text-gray-700'>{word.english}</span>
146-
</span>
147+
</ClickableArabicWord>
147148
);
148149
}
149150
})

web/app/routes/_app.quran.$chapterNumber._index.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { PageError } from '~/components/interface/page-error';
99
import { PrimaryButton } from '~/components/interface/primary-button';
1010
import { AudioPlayer } from '~/components/quran/audio-player';
1111
import { ChapterHeader } from '~/components/quran/chapter-header';
12+
import { ClickableArabicWord } from '~/components/quran/clickable-arabic-word';
1213
import { ViewMode } from '~/components/quran/view-mode';
1314
import { useQuranViewMode } from '~/hooks/use-quran-view-mode';
1415
import { getChapterOptions } from '~/queries/quran';
@@ -160,15 +161,15 @@ export default function QuranChapter(props: Route.ComponentProps) {
160161
<Fragment key={verse.number}>
161162
{verse.words && verse.words.length > 0
162163
? verse.words.map((word, idx, arr) => (
163-
<span key={idx} className='verse-arabic-word'>
164+
<ClickableArabicWord key={idx} arabic={word.arabic} transliteration={word.transliteration}>
164165
{word.arabic}
165166
{idx === arr.length - 1 && (
166167
<span className='mx-2 font-arabic'>
167168
{toArabicNumber(verse.number)}
168169
</span>
169170
)}
170171
&nbsp;
171-
</span>
172+
</ClickableArabicWord>
172173
))
173174
: verse.arabic.split(' ').map((word, idx, arr) => (
174175
<span key={idx} className='verse-arabic-word'>
@@ -241,20 +242,20 @@ export default function QuranChapter(props: Route.ComponentProps) {
241242
? verse.words.map((word, idx, arr) => {
242243
if (idx === arr.length - 1) {
243244
return (
244-
<span key={idx} className='verse-arabic-word text-3xl flex flex-col items-center mr-2 mb-2'>
245+
<ClickableArabicWord key={idx} arabic={word.arabic} transliteration={word.transliteration} className='text-3xl flex flex-col items-center mr-2 mb-2'>
245246
<span className='flex flex-row items-center' dir="rtl">
246247
<span>{word.arabic}</span>
247248
<span className='mx-2 font-arabic'>{toArabicNumber(verse.number)}</span>
248249
</span>
249250
<span className='text-xs sm:text-sm mt-1 px-1 rounded bg-gray-100 text-gray-700'>{word.english}</span>
250-
</span>
251+
</ClickableArabicWord>
251252
);
252253
} else {
253254
return (
254-
<span key={idx} className='verse-arabic-word text-3xl flex flex-col items-center mr-2 mb-2'>
255+
<ClickableArabicWord key={idx} arabic={word.arabic} transliteration={word.transliteration} className='text-3xl flex flex-col items-center mr-2 mb-2'>
255256
<span>{word.arabic}</span>
256257
<span className='text-xs sm:text-sm mt-1 px-1 rounded bg-gray-100 text-gray-700'>{word.english}</span>
257-
</span>
258+
</ClickableArabicWord>
258259
);
259260
}
260261
})

0 commit comments

Comments
 (0)