diff --git a/src/components/PrimaryButton.module.css b/src/components/PrimaryButton.module.css index f4cc75b..67b6e59 100644 --- a/src/components/PrimaryButton.module.css +++ b/src/components/PrimaryButton.module.css @@ -11,7 +11,13 @@ transition: transform 80ms ease, box-shadow 80ms ease; } -.button:active { +.button:active:not(:disabled) { transform: translateY(2px); box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); } + +.button:disabled { + opacity: 0.45; + box-shadow: none; + cursor: not-allowed; +} diff --git a/src/components/PrimaryButton.tsx b/src/components/PrimaryButton.tsx index 26e34f2..7abc014 100644 --- a/src/components/PrimaryButton.tsx +++ b/src/components/PrimaryButton.tsx @@ -3,11 +3,12 @@ import styles from './PrimaryButton.module.css' interface Props { onClick: () => void children: React.ReactNode + disabled?: boolean } -export default function PrimaryButton({ onClick, children }: Props) { +export default function PrimaryButton({ onClick, children, disabled }: Props) { return ( - ) diff --git a/src/pages/PlayerSetup.module.css b/src/pages/PlayerSetup.module.css new file mode 100644 index 0000000..b39c2aa --- /dev/null +++ b/src/pages/PlayerSetup.module.css @@ -0,0 +1,71 @@ +.row { + display: flex; + align-items: center; + gap: 0.75rem; + margin-bottom: 0.75rem; +} + +.nameInput { + flex: 1; + padding: 0.85rem 1rem; + font-size: 1.1rem; + font-family: inherit; + border: 1.5px solid #c8b99a; + border-radius: 0.75rem; + background: #fdfaf4; +} + +.nameInputError { + border-color: #c0392b; + background: #fff5f5; +} + +.removeBtn { + flex-shrink: 0; + width: 2.5rem; + height: 2.5rem; + border-radius: 50%; + border: none; + background: #d94f4f; + color: white; + font-size: 1.25rem; + line-height: 1; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25); + transition: transform 80ms ease, box-shadow 80ms ease; +} + +.removeBtn:active { + transform: translateY(2px); + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); +} + +.addRow { + display: flex; + justify-content: center; + margin-top: 0.5rem; +} + +.addBtn { + width: 3rem; + height: 3rem; + border-radius: 50%; + border: none; + background: #a8d5e8; + font-size: 1.75rem; + line-height: 1; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25); + transition: transform 80ms ease, box-shadow 80ms ease; +} + +.addBtn:active { + transform: translateY(2px); + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); +} diff --git a/src/pages/PlayerSetup.tsx b/src/pages/PlayerSetup.tsx index 41e257f..256d6e5 100644 --- a/src/pages/PlayerSetup.tsx +++ b/src/pages/PlayerSetup.tsx @@ -1,14 +1,67 @@ +import { useState } from 'react' import PageFrame from '../components/PageFrame' import PrimaryButton from '../components/PrimaryButton' +import styles from './PlayerSetup.module.css' interface Props { - onStart: () => void + onStart: (players: string[]) => void +} + +function normalize(s: string) { + return s.trim().replace(/\s+/g, ' ') } export default function PlayerSetup({ onStart }: Props) { + const [names, setNames] = useState(['', '', '']) + + function updateName(i: number, value: string) { + setNames(n => n.map((v, idx) => (idx === i ? value : v))) + } + + function addName() { + setNames(n => [...n, '']) + } + + function removeName(i: number) { + setNames(n => n.filter((_, idx) => idx !== i)) + } + + const normalized = names.map(normalize) + const counts = new Map() + normalized.forEach(n => counts.set(n, (counts.get(n) ?? 0) + 1)) + const fieldErrors = normalized.map(n => n === '' || (counts.get(n) ?? 0) > 1) + const isValid = fieldErrors.every(e => !e) + return ( - Start}> - {null} + onStart(names.map(n => n.trim()))} disabled={!isValid}> + Start + + } + > + {names.map((name, i) => ( +
+ updateName(i, e.target.value)} + /> + {i >= 3 && ( + + )} +
+ ))} + {names.length < 6 && ( +
+ +
+ )}
) }