From 76ef005cdaf6e09d80bc9bc4aad353fba88e19f4 Mon Sep 17 00:00:00 2001 From: Frank Schwenk Date: Wed, 18 Jun 2025 20:48:28 +0200 Subject: [PATCH] feat(wizard): Player 1 step UI overhaul MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Quick-pick now advances to next step - Added clear (×) icon to input field - Replaced navigation buttons with large left/right arrows - All controls are touch-friendly and visually prominent Closes #6 --- src/components/NewGame.jsx | 196 +++++++++++++++++------------- src/components/NewGame.module.css | 79 ++++++++++++ 2 files changed, 192 insertions(+), 83 deletions(-) diff --git a/src/components/NewGame.jsx b/src/components/NewGame.jsx index ca8102f..a3554e4 100644 --- a/src/components/NewGame.jsx +++ b/src/components/NewGame.jsx @@ -1,117 +1,147 @@ import { h } from 'preact'; -import { useState, useEffect } from 'preact/hooks'; +import { useState, useEffect, useRef } from 'preact/hooks'; import styles from './NewGame.module.css'; /** - * New game creation form. + * Player 1 input step for multi-step game creation wizard. * @param {object} props - * @param {Function} props.onCreateGame * @param {string[]} props.playerNameHistory + * @param {Function} props.onNext * @param {Function} props.onCancel - * @param {Function} props.onGameCreated - * @param {object} props.initialValues + * @param {string} [props.initialValue] * @returns {import('preact').VNode} */ -const NewGame = ({ onCreateGame, playerNameHistory, onCancel, onGameCreated, initialValues }) => { - const [player1, setPlayer1] = useState(initialValues?.player1 || ''); - const [player2, setPlayer2] = useState(initialValues?.player2 || ''); - const [player3, setPlayer3] = useState(initialValues?.player3 || ''); - const [gameType, setGameType] = useState(initialValues?.gameType || '8-Ball'); - const [raceTo, setRaceTo] = useState(initialValues?.raceTo ? String(initialValues.raceTo) : ''); +const Player1Step = ({ playerNameHistory, onNext, onCancel, initialValue = '' }) => { + const [player1, setPlayer1] = useState(initialValue); const [error, setError] = useState(null); + const [filteredNames, setFilteredNames] = useState(playerNameHistory); + const inputRef = useRef(null); useEffect(() => { - setPlayer1(initialValues?.player1 || ''); - setPlayer2(initialValues?.player2 || ''); - setPlayer3(initialValues?.player3 || ''); - setGameType(initialValues?.gameType || '8-Ball'); - setRaceTo(initialValues?.raceTo ? String(initialValues.raceTo) : ''); - setError(null); - }, [initialValues]); + if (!player1) { + setFilteredNames(playerNameHistory); + } else { + setFilteredNames( + playerNameHistory.filter(name => + name.toLowerCase().includes(player1.toLowerCase()) + ) + ); + } + }, [player1, playerNameHistory]); const handleSubmit = (e) => { e.preventDefault(); - if (!player1.trim() || !player2.trim()) { - setError('Bitte Namen für beide Spieler eingeben'); + if (!player1.trim()) { + setError('Bitte Namen für Spieler 1 eingeben'); return; } - const id = onCreateGame({ - player1: player1.trim(), - player2: player2.trim(), - player3: player3.trim() || null, - gameType, - raceTo: raceTo ? parseInt(raceTo) : null - }); - if (onGameCreated && id) { - onGameCreated(id); - } + setError(null); + onNext(player1.trim()); + }; + + const handleQuickPick = (name) => { + setError(null); + onNext(name); }; const handleClear = () => { setPlayer1(''); - setPlayer2(''); - setPlayer3(''); - setGameType('8-Ball'); - setRaceTo(''); setError(null); + if (inputRef.current) inputRef.current.focus(); }; return ( -
-
Neues Spiel
-
- + +
Neues Spiel – Schritt 1/5
+
+ + + + +
-
-
- -
- setPlayer1(e.target.value)} list="player1-history" aria-label="Name Spieler 1" /> - - {playerNameHistory.map((name, idx) => -
+
+ +
+ setPlayer1(e.target.value)} + autoFocus + autoComplete="off" + aria-label="Name Spieler 1" + style={{ fontSize: '1.2rem', minHeight: 48, marginTop: 12, marginBottom: 12, width: '100%', paddingRight: 44 }} + ref={inputRef} + /> + {player1 && ( + + )}
-
- -
- setPlayer2(e.target.value)} list="player2-history" aria-label="Name Spieler 2" /> - - {playerNameHistory.map((name, idx) => + {filteredNames.length > 0 && ( +
+ {filteredNames.slice(0, 4).map((name, idx) => ( + + ))}
-
-
- -
- setPlayer3(e.target.value)} list="player3-history" aria-label="Name Spieler 3" /> - - {playerNameHistory.map((name, idx) => -
-
+ )}
-
-
- - -
-
- - setRaceTo(e.target.value)} min="1" aria-label="Race to X" /> -
-
- {error &&
{error}
} -
- - + {error &&
{error}
} +
+ +
); }; -export default NewGame; \ No newline at end of file +export default Player1Step; \ No newline at end of file diff --git a/src/components/NewGame.module.css b/src/components/NewGame.module.css index ddb6d6b..4ff712b 100644 --- a/src/components/NewGame.module.css +++ b/src/components/NewGame.module.css @@ -109,4 +109,83 @@ display: flex; flex-direction: column; gap: 0; +} +.progress-indicator { + display: flex; + justify-content: center; + align-items: center; + gap: 10px; + margin-bottom: 16px; +} +.progress-dot { + width: 14px; + height: 14px; + border-radius: 50%; + background: #444; + opacity: 0.4; + transition: background 0.2s, opacity 0.2s; +} +.progress-dot.active { + background: #fff; + opacity: 1; +} +.quick-pick-btn { + min-width: 80px; + min-height: 44px; + font-size: 1.1rem; + border-radius: 8px; + background: #333; + color: #fff; + border: none; + cursor: pointer; + margin-bottom: 8px; + transition: background 0.2s; +} +.quick-pick-btn:active, .quick-pick-btn:focus { + background: #555; + outline: none; +} +.arrow-nav { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 48px; + width: 100%; +} +.arrow-btn { + font-size: 48px; + width: 80px; + height: 80px; + border-radius: 50%; + background: #222; + color: #fff; + border: none; + box-shadow: 0 2px 8px rgba(0,0,0,0.15); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: background 0.2s, color 0.2s; +} +.arrow-btn:active, .arrow-btn:focus { + background: #444; + color: #fff; + outline: none; +} +.clear-input-btn { + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + cursor: pointer; + font-size: 24px; + color: #aaa; + padding: 0; + z-index: 2; +} +.clear-input-btn:active, .clear-input-btn:focus { + color: #fff; + outline: none; } \ No newline at end of file