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 { h } from 'preact';
|
||||||
import { useState, useEffect, useRef } from 'preact/hooks';
|
import { useState, useEffect, useRef } from 'preact/hooks';
|
||||||
import styles from './NewGame.module.css';
|
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.
|
* 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 [player1, setPlayer1] = useState(initialValue);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const [filteredNames, setFilteredNames] = useState(playerNameHistory);
|
const [filteredNames, setFilteredNames] = useState(playerNameHistory);
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const inputRef = useRef(null);
|
const inputRef = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -44,6 +64,11 @@ const Player1Step = ({ playerNameHistory, onNext, onCancel, initialValue = '' })
|
|||||||
onNext(name);
|
onNext(name);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleModalSelect = (name) => {
|
||||||
|
setIsModalOpen(false);
|
||||||
|
handleQuickPick(name);
|
||||||
|
};
|
||||||
|
|
||||||
const handleClear = () => {
|
const handleClear = () => {
|
||||||
setPlayer1('');
|
setPlayer1('');
|
||||||
setError(null);
|
setError(null);
|
||||||
@@ -103,7 +128,7 @@ const Player1Step = ({ playerNameHistory, onNext, onCancel, initialValue = '' })
|
|||||||
</div>
|
</div>
|
||||||
{filteredNames.length > 0 && (
|
{filteredNames.length > 0 && (
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 12, marginTop: 8 }}>
|
<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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
key={name + idx}
|
key={name + idx}
|
||||||
@@ -115,10 +140,28 @@ const Player1Step = ({ playerNameHistory, onNext, onCancel, initialValue = '' })
|
|||||||
{name}
|
{name}
|
||||||
</button>
|
</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>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{error && <div className={styles['validation-error']} style={{ marginBottom: 16 }}>{error}</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 }}>
|
<div className={styles['arrow-nav']} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 48 }}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -244,7 +287,7 @@ const Player2Step = ({ playerNameHistory, onNext, onCancel, initialValue = '' })
|
|||||||
</div>
|
</div>
|
||||||
{filteredNames.length > 0 && (
|
{filteredNames.length > 0 && (
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 12, marginTop: 8 }}>
|
<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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
key={name + idx}
|
key={name + idx}
|
||||||
@@ -381,7 +424,7 @@ const Player3Step = ({ playerNameHistory, onNext, onCancel, initialValue = '' })
|
|||||||
</div>
|
</div>
|
||||||
{filteredNames.length > 0 && (
|
{filteredNames.length > 0 && (
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 12, marginTop: 8 }}>
|
<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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
key={name + idx}
|
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