disambiguation popup

This commit is contained in:
2026-03-10 23:59:21 +02:00
parent 90a822aeb4
commit 42d4b016fb
@@ -0,0 +1,80 @@
import { useEffect, useLayoutEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useStore } from '../store'
import Tooltip from './ui/Tooltip'
interface Props {
svgRef: React.RefObject<SVGSVGElement | null>
}
interface PopupGeom {
top: number
left: number
}
export default function DisambiguationPopup({ svgRef }: Props) {
const { t } = useTranslation()
const show = useStore((s) => s.showDisambiguation)
const result = useStore((s) => s.recognitionResult)
const puzzle = useStore((s) => s.puzzle)
const acceptCandidate = useStore((s) => s.acceptCandidate)
const dismissDisambiguation = useStore((s) => s.dismissDisambiguation)
const popupRef = useRef<HTMLDivElement>(null)
const [geom, setGeom] = useState<PopupGeom | null>(null)
useLayoutEffect(() => {
if (show && result && puzzle && svgRef.current) {
const rect = svgRef.current.getBoundingClientRect()
const cellW = rect.width / puzzle.width
const cellH = rect.height / puzzle.height
const cellX = result.cell.col * cellW
const cellY = result.cell.row * cellH
// position above the cell, or below if near the top
const above = result.cell.row > 1
setGeom({ top: above ? cellY - 40 : cellY + cellH + 4, left: Math.max(0, cellX - 20) })
} else {
setGeom(null)
}
}, [show, result, puzzle, svgRef])
useEffect(() => {
if (show && popupRef.current) {
const firstBtn = popupRef.current.querySelector<HTMLButtonElement>('.disambiguation-btn')
firstBtn?.focus()
}
}, [show])
if (!show || !result || !geom) return null
return (
<div
ref={popupRef}
className="disambiguation"
style={{
position: 'absolute',
top: geom.top,
left: geom.left,
}}
>
{result.candidates.slice(0, 3).map((c) => (
<Tooltip text={`${Math.round(c.confidence * 100)}%`} key={c.letter}>
<button
className="disambiguation-btn"
onClick={() => acceptCandidate(c.letter)}
>
{c.letter}
</button>
</Tooltip>
))}
<Tooltip text={t('common.dismiss')}>
<button
className="disambiguation-btn dismiss"
onClick={dismissDisambiguation}
>
x
</button>
</Tooltip>
</div>
)
}