Files
ltbxd-actorle/assets/react/controllers/GameRow.jsx
thibaud-leclere f6d180474a feat: responsive grid with scrollable area and fixed hint column
- Add CSS variables (--cell, --cell-font, --trigger-h) for responsive cell sizing
- Shrink grid cells at 600px and 420px breakpoints
- Add .page-body wrapper with 16px horizontal padding to prevent edge collisions
- Separate hint column from scrollable grid: hints rendered outside the table in a fixed flex column, only the letter grid scrolls horizontally

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 14:17:22 +02:00

62 lines
1.9 KiB
JavaScript

import React, { useRef, useCallback, useMemo } from 'react';
import LetterInput from './LetterInput';
function isLetter(ch) {
return /[a-zA-Z]/.test(ch);
}
export default function GameRow({ actorName, pos, colStart, totalWidth }) {
const inputRefs = useRef([]);
const letters = actorName.split('');
const letterIndices = useMemo(
() => letters.reduce((acc, ch, i) => { if (isLetter(ch)) acc.push(i); return acc; }, []),
[actorName]
);
const setInputRef = useCallback((index) => (el) => {
inputRefs.current[index] = el;
}, []);
const focusNextInput = useCallback((charIndex, direction) => {
const currentPos = letterIndices.indexOf(charIndex);
const nextPos = currentPos + direction;
if (nextPos >= 0 && nextPos < letterIndices.length) {
inputRefs.current[letterIndices[nextPos]]?.focus();
}
}, [letterIndices]);
return (
<tr>
{Array.from({ length: totalWidth + 1 }, (_, colIndex) => {
const charIndex = colIndex - colStart;
const isInRange = charIndex >= 0 && charIndex < letters.length;
if (!isInRange) {
return <td key={colIndex} />;
}
const ch = letters[charIndex];
if (!isLetter(ch)) {
return (
<td key={colIndex} className="letter-static">
{ch}
</td>
);
}
return (
<LetterInput
key={colIndex}
highlighted={charIndex === pos}
inputRef={setInputRef(charIndex)}
onNext={() => focusNextInput(charIndex, 1)}
onPrev={() => focusNextInput(charIndex, -1)}
/>
);
})}
</tr>
);
}