feat: optimize player selection for touch input

- Increases the number of quick-pick buttons from 4 to 10.
- Adds a '...' button that appears when more than 10 players exist in history.
- Clicking '...' opens a scrollable modal listing all past players for easy selection.
- This provides a much faster player selection flow on touch devices.

Closes #4
This commit is contained in:
Frank Schwenk
2025-06-20 11:03:37 +02:00
parent b466dd2a0a
commit dbc173f57b
2 changed files with 116 additions and 3 deletions

View File

@@ -1,6 +1,25 @@
import { h } from 'preact';
import { useState, useEffect, useRef } from 'preact/hooks';
import styles from './NewGame.module.css';
import modalStyles from './PlayerSelectModal.module.css';
const PlayerSelectModal = ({ players, onSelect, onClose }) => (
<div className={modalStyles.modalOverlay} onClick={onClose}>
<div className={modalStyles.modalContent} onClick={e => e.stopPropagation()}>
<div className={modalStyles.modalHeader}>
<h3>Alle Spieler</h3>
<button className={modalStyles.closeButton} onClick={onClose}>×</button>
</div>
<div className={modalStyles.playerList}>
{players.map(player => (
<button key={player} className={modalStyles.playerItem} onClick={() => onSelect(player)}>
{player}
</button>
))}
</div>
</div>
</div>
);
/**
* Player 1 input step for multi-step game creation wizard.
@@ -15,6 +34,7 @@ const Player1Step = ({ playerNameHistory, onNext, onCancel, initialValue = '' })
const [player1, setPlayer1] = useState(initialValue);
const [error, setError] = useState(null);
const [filteredNames, setFilteredNames] = useState(playerNameHistory);
const [isModalOpen, setIsModalOpen] = useState(false);
const inputRef = useRef(null);
useEffect(() => {
@@ -44,6 +64,11 @@ const Player1Step = ({ playerNameHistory, onNext, onCancel, initialValue = '' })
onNext(name);
};
const handleModalSelect = (name) => {
setIsModalOpen(false);
handleQuickPick(name);
};
const handleClear = () => {
setPlayer1('');
setError(null);
@@ -103,7 +128,7 @@ const Player1Step = ({ playerNameHistory, onNext, onCancel, initialValue = '' })
</div>
{filteredNames.length > 0 && (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 12, marginTop: 8 }}>
{filteredNames.slice(0, 4).map((name, idx) => (
{filteredNames.slice(0, 10).map((name, idx) => (
<button
type="button"
key={name + idx}
@@ -115,10 +140,28 @@ const Player1Step = ({ playerNameHistory, onNext, onCancel, initialValue = '' })
{name}
</button>
))}
{playerNameHistory.length > 10 && (
<button
type="button"
className={styles['quick-pick-btn']}
style={{ fontSize: '1.1rem', padding: '12px 20px', borderRadius: 8, background: '#333', color: '#fff', border: 'none', cursor: 'pointer' }}
onClick={() => setIsModalOpen(true)}
aria-label="Weitere Spieler anzeigen"
>
...
</button>
)}
</div>
)}
</div>
{error && <div className={styles['validation-error']} style={{ marginBottom: 16 }}>{error}</div>}
{isModalOpen && (
<PlayerSelectModal
players={playerNameHistory}
onSelect={handleModalSelect}
onClose={() => setIsModalOpen(false)}
/>
)}
<div className={styles['arrow-nav']} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 48 }}>
<button
type="button"
@@ -244,7 +287,7 @@ const Player2Step = ({ playerNameHistory, onNext, onCancel, initialValue = '' })
</div>
{filteredNames.length > 0 && (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 12, marginTop: 8 }}>
{filteredNames.slice(0, 4).map((name, idx) => (
{filteredNames.slice(0, 10).map((name, idx) => (
<button
type="button"
key={name + idx}
@@ -381,7 +424,7 @@ const Player3Step = ({ playerNameHistory, onNext, onCancel, initialValue = '' })
</div>
{filteredNames.length > 0 && (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 12, marginTop: 8 }}>
{filteredNames.slice(0, 4).map((name, idx) => (
{filteredNames.slice(0, 10).map((name, idx) => (
<button
type="button"
key={name + idx}

View File

@@ -0,0 +1,70 @@
.modalOverlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modalContent {
background: #2c2c2c;
padding: 24px;
border-radius: 12px;
width: 90%;
max-width: 400px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
display: flex;
flex-direction: column;
}
.modalHeader {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.modalHeader h3 {
margin: 0;
font-size: 1.5rem;
color: #fff;
}
.closeButton {
background: none;
border: none;
font-size: 2rem;
color: #aaa;
cursor: pointer;
padding: 0;
line-height: 1;
}
.playerList {
max-height: 60vh;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 12px;
}
.playerItem {
background: #444;
color: #fff;
border: none;
padding: 16px;
border-radius: 8px;
text-align: left;
font-size: 1.2rem;
cursor: pointer;
transition: background-color 0.2s;
}
.playerItem:hover {
background: #555;
}