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:
@@ -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}
|
||||
|
||||
70
src/components/PlayerSelectModal.module.css
Normal file
70
src/components/PlayerSelectModal.module.css
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user