UX: disable next arrow until input selected

- Disable right-arrow on Player1/2 until name entered
- Disable right-arrow on Player3 unless name entered (skip still available)
- Disable right-arrow on Race To until value present
- Break type: no preselect; disable next until rule chosen
- First break: no preselect; disable next until required choices

Purpose: Ensure consistent UX in new game wizard

Refs #28
This commit is contained in:
Frank Schwenk
2025-10-30 14:33:59 +01:00
parent 6da7a5f4e2
commit 26a97e7eaa

View File

@@ -291,7 +291,8 @@ const Player1Step = ({ playerNameHistory, onNext, onCancel, initialValue = '' }:
type="submit" type="submit"
className={styles['arrow-btn']} className={styles['arrow-btn']}
aria-label="Weiter" aria-label="Weiter"
style={{ fontSize: 48, width: 80, height: 80, borderRadius: '50%', background: '#222', color: '#fff', border: 'none', boxShadow: '0 2px 8px rgba(0,0,0,0.15)', cursor: 'pointer' }} disabled={!player1.trim()}
style={{ fontSize: 48, width: 80, height: 80, borderRadius: '50%', background: '#222', color: '#fff', border: 'none', boxShadow: '0 2px 8px rgba(0,0,0,0.15)', cursor: 'pointer', opacity: !player1.trim() ? 0.5 : 1 }}
> >
{/* Unicode right arrow */} {/* Unicode right arrow */}
→ →
@@ -440,7 +441,8 @@ const Player2Step = ({ playerNameHistory, onNext, onCancel, initialValue = '' }:
type="submit" type="submit"
className={styles['arrow-btn']} className={styles['arrow-btn']}
aria-label="Weiter" aria-label="Weiter"
style={{ fontSize: 48, width: 80, height: 80, borderRadius: '50%', background: '#222', color: '#fff', border: 'none', boxShadow: '0 2px 8px rgba(0,0,0,0.15)', cursor: 'pointer' }} disabled={!player2.trim()}
style={{ fontSize: 48, width: 80, height: 80, borderRadius: '50%', background: '#222', color: '#fff', border: 'none', boxShadow: '0 2px 8px rgba(0,0,0,0.15)', cursor: 'pointer', opacity: !player2.trim() ? 0.5 : 1 }}
> >
→ →
</button> </button>
@@ -594,7 +596,8 @@ const Player3Step = ({ playerNameHistory, onNext, onCancel, initialValue = '' }:
type="submit" type="submit"
className={styles['arrow-btn']} className={styles['arrow-btn']}
aria-label="Weiter" aria-label="Weiter"
style={{ fontSize: 48, width: 80, height: 80, borderRadius: '50%', background: '#222', color: '#fff', border: 'none', boxShadow: '0 2px 8px rgba(0,0,0,0.15)', cursor: 'pointer' }} disabled={!player3.trim()}
style={{ fontSize: 48, width: 80, height: 80, borderRadius: '50%', background: '#222', color: '#fff', border: 'none', boxShadow: '0 2px 8px rgba(0,0,0,0.15)', cursor: 'pointer', opacity: !player3.trim() ? 0.5 : 1 }}
> >
&#8594; &#8594;
</button> </button>
@@ -794,7 +797,8 @@ const RaceToStep = ({ onNext, onCancel, initialValue = '', gameType }: RaceToSte
type="submit" type="submit"
className={styles['arrow-btn']} className={styles['arrow-btn']}
aria-label="Weiter" aria-label="Weiter"
style={{ fontSize: 48, width: 80, height: 80, borderRadius: '50%', background: '#222', color: '#fff', border: 'none', boxShadow: '0 2px 8px rgba(0,0,0,0.15)', cursor: 'pointer' }} disabled={String(raceTo).trim() === ''}
style={{ fontSize: 48, width: 80, height: 80, borderRadius: '50%', background: '#222', color: '#fff', border: 'none', boxShadow: '0 2px 8px rgba(0,0,0,0.15)', cursor: 'pointer', opacity: String(raceTo).trim() === '' ? 0.5 : 1 }}
> >
{/* Unicode right arrow */} {/* Unicode right arrow */}
&#8594; &#8594;
@@ -810,8 +814,8 @@ interface BreakRuleStepProps {
initialValue?: BreakRule | ''; initialValue?: BreakRule | '';
} }
const BreakRuleStep = ({ onNext, onCancel, initialValue = 'winnerbreak' }: BreakRuleStepProps) => { const BreakRuleStep = ({ onNext, onCancel, initialValue = '' }: BreakRuleStepProps) => {
const [rule, setRule] = useState<BreakRule>(initialValue as BreakRule); const [rule, setRule] = useState<BreakRule | ''>(initialValue);
return ( return (
<form className={styles['new-game-form']} aria-label="Break-Regel wählen"> <form className={styles['new-game-form']} aria-label="Break-Regel wählen">
@@ -845,7 +849,7 @@ const BreakRuleStep = ({ onNext, onCancel, initialValue = 'winnerbreak' }: Break
<button type="button" className={styles['arrow-btn']} aria-label="Zurück" onClick={onCancel} style={{ fontSize: 48, width: 80, height: 80, borderRadius: '50%', background: '#222', color: '#fff', border: 'none', boxShadow: '0 2px 8px rgba(0,0,0,0.15)', cursor: 'pointer' }}> <button type="button" className={styles['arrow-btn']} aria-label="Zurück" onClick={onCancel} style={{ fontSize: 48, width: 80, height: 80, borderRadius: '50%', background: '#222', color: '#fff', border: 'none', boxShadow: '0 2px 8px rgba(0,0,0,0.15)', cursor: 'pointer' }}>
&#8592; &#8592;
</button> </button>
<button type="button" className={styles['arrow-btn']} aria-label="Weiter" onClick={() => onNext(rule)} style={{ fontSize: 48, width: 80, height: 80, borderRadius: '50%', background: '#222', color: '#fff', border: 'none', boxShadow: '0 2px 8px rgba(0,0,0,0.15)', cursor: 'pointer' }}> <button type="button" className={styles['arrow-btn']} aria-label="Weiter" onClick={() => rule && onNext(rule as BreakRule)} disabled={!rule} style={{ fontSize: 48, width: 80, height: 80, borderRadius: '50%', background: '#222', color: '#fff', border: 'none', boxShadow: '0 2px 8px rgba(0,0,0,0.15)', cursor: 'pointer', opacity: !rule ? 0.5 : 1 }}>
&#8594; &#8594;
</button> </button>
</div> </div>
@@ -862,10 +866,10 @@ interface BreakOrderStepProps {
initialSecond?: number; initialSecond?: number;
} }
const BreakOrderStep = ({ players, rule, onNext, onCancel, initialFirst = 1, initialSecond }: BreakOrderStepProps) => { const BreakOrderStep = ({ players, rule, onNext, onCancel, initialFirst = 0, initialSecond }: BreakOrderStepProps) => {
const playerCount = players.filter(Boolean).length; const playerCount = players.filter(Boolean).length;
const [first, setFirst] = useState<number>(initialFirst); const [first, setFirst] = useState<number>(initialFirst);
const [second, setSecond] = useState<number>(initialSecond ?? (playerCount >= 2 ? 2 : 1)); const [second, setSecond] = useState<number | undefined>(initialSecond);
const handleFirst = (idx: number) => { const handleFirst = (idx: number) => {
setFirst(idx); setFirst(idx);
@@ -918,12 +922,19 @@ const BreakOrderStep = ({ players, rule, onNext, onCancel, initialFirst = 1, ini
aria-label="Weiter" aria-label="Weiter"
onClick={() => { onClick={() => {
if (rule === 'wechselbreak' && playerCount === 3) { if (rule === 'wechselbreak' && playerCount === 3) {
handleSecond(second); if (first > 0 && (second ?? 0) > 0) {
handleSecond(second as number);
}
} else { } else {
onNext(first); if (first > 0) {
onNext(first);
}
} }
}} }}
style={{ fontSize: 48, width: 80, height: 80, borderRadius: '50%', background: '#222', color: '#fff', border: 'none', boxShadow: '0 2px 8px rgba(0,0,0,0.15)', cursor: 'pointer' }} disabled={
(rule === 'wechselbreak' && playerCount === 3) ? !(first > 0 && (second ?? 0) > 0) : !(first > 0)
}
style={{ fontSize: 48, width: 80, height: 80, borderRadius: '50%', background: '#222', color: '#fff', border: 'none', boxShadow: '0 2px 8px rgba(0,0,0,0.15)', cursor: 'pointer', opacity: ((rule === 'wechselbreak' && playerCount === 3) ? !(first > 0 && (second ?? 0) > 0) : !(first > 0)) ? 0.5 : 1 }}
> >
&#8594; &#8594;
</button> </button>