Add help modal and back button to score entry pages

HelpModal component shows game setup and scoring rules in a scrollable
overlay, triggered by a ? button top-right on Player Setup and Enter
Scores. Enter Scores also gets a ← back button top-left that returns
to the scoresheet without saving. PageFrame gains a headerActionLeft
slot to support the left-side button.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-28 23:06:48 -05:00
parent aa121bbcf3
commit 79832ed818
8 changed files with 95 additions and 4 deletions
+1 -1
View File
@@ -59,5 +59,5 @@ export default function App() {
if (page === 'title') return <TitleScreen onNewGame={handleNewGame} />
if (page === 'setup') return <PlayerSetup onStart={handleStart} initialNames={previousPlayers} />
if (page === 'scoresheet') return <ScoreSheet game={game!} onScoreRound={() => setPage('enterscores')} onNewGame={handleNewGame} onEditRound={handleEditRound} />
if (page === 'enterscores') return <EnterScores game={game!} onSubmit={handleSubmitScores} initialValues={editingRound !== null ? game!.rounds[editingRound] : undefined} />
if (page === 'enterscores') return <EnterScores game={game!} onSubmit={handleSubmitScores} onBack={() => { setEditingRound(null); setPage('scoresheet') }} initialValues={editingRound !== null ? game!.rounds[editingRound] : undefined} />
}
+31
View File
@@ -0,0 +1,31 @@
.box {
display: flex;
flex-direction: column;
background: var(--color-parchment);
border: 2px solid black;
border-radius: 1.5rem;
width: calc(100% - 4rem);
max-width: 22rem;
max-height: 80vh;
overflow: hidden;
}
.body {
flex: 1;
overflow-y: auto;
padding: 1.5rem 1.5rem 1rem;
font-size: 0.95rem;
line-height: 1.5;
}
.dismiss {
margin: 0 1.5rem 1.5rem;
padding: 0.6rem 1rem;
background: var(--color-blue);
color: black;
border: none;
border-radius: 0.75rem;
font-size: 1rem;
font-weight: 600;
flex-shrink: 0;
}
+28
View File
@@ -0,0 +1,28 @@
import styles from './HelpModal.module.css'
interface Props {
onClose: () => void
}
export default function HelpModal({ onClose }: Props) {
return (
<div className={styles.box}>
<div className={styles.body}>
<h3>Setup</h3>
<ul>
<li>3 players, remove all 1s and 2s</li>
<li>4-6 players, use everything</li>
<li>7-8 players, remove non-yellow 2s</li>
</ul>
<h3>Scoring</h3>
<ul>
<li>Gold good! Each gold card is +5 points</li>
<li>Red bad! Each bad card -10 points</li>
<li>Black seven <em>very</em> bad: -50 points each</li>
<li>Last trick very good: +50 points</li>
</ul>
</div>
<button className={styles.dismiss} onClick={onClose}>Got it</button>
</div>
)
}
+7 -1
View File
@@ -11,10 +11,16 @@
.headerAction {
position: absolute;
top: 0.75rem;
top: 1rem;
right: 1rem;
}
.headerActionLeft {
position: absolute;
top: 1rem;
left: 1rem;
}
.title {
margin: 0;
font-size: 1.5rem;
+3 -1
View File
@@ -5,16 +5,18 @@ interface Props {
children: React.ReactNode
footer?: React.ReactNode
headerAction?: React.ReactNode
headerActionLeft?: React.ReactNode
overlay?: React.ReactNode
}
export default function PageFrame({ title, children, footer, headerAction, overlay }: Props) {
export default function PageFrame({ title, children, footer, headerAction, headerActionLeft, overlay }: Props) {
return (
<div className={styles.frame}>
<header className={styles.header}>
<h1 className={styles.title}>{title}</h1>
<div className={styles.rule} role="separator" />
{headerAction && <div className={styles.headerAction}>{headerAction}</div>}
{headerActionLeft && <div className={styles.headerActionLeft}>{headerActionLeft}</div>}
</header>
<div className={styles.content}>
{children}
+8 -1
View File
@@ -1,20 +1,24 @@
import { useState } from 'react'
import PageFrame from '../components/PageFrame'
import PrimaryButton from '../components/PrimaryButton'
import HelpModal from '../components/HelpModal'
import { isIndividualScoreValid, isRoundScoreValid } from '../game/scoring'
import { type GameState } from '../types'
import styles from './EnterScores.module.css'
import sharedStyles from '../styles/shared.module.css'
interface Props {
game: GameState
onSubmit: (scores: number[]) => void
onBack: () => void
initialValues?: number[]
}
export default function EnterScores({ game, onSubmit, initialValues }: Props) {
export default function EnterScores({ game, onSubmit, onBack, initialValues }: Props) {
const [values, setValues] = useState<string[]>(() =>
game.players.map((_, i) => initialValues ? String(initialValues[i]) : '')
)
const [showHelp, setShowHelp] = useState(false)
function updateValue(i: number, v: string) {
setValues(vs => vs.map((val, idx) => (idx === i ? v : val)))
@@ -38,6 +42,9 @@ export default function EnterScores({ game, onSubmit, initialValues }: Props) {
return (
<PageFrame
title="Enter Scores"
headerActionLeft={<button className={sharedStyles.helpBtn} onClick={onBack}></button>}
headerAction={<button className={sharedStyles.helpBtn} onClick={() => setShowHelp(true)}>?</button>}
overlay={showHelp ? <HelpModal onClose={() => setShowHelp(false)} /> : undefined}
footer={
<PrimaryButton onClick={() => onSubmit(parsed)} disabled={!canSubmit}>
Submit
+5
View File
@@ -1,7 +1,9 @@
import { useState } from 'react'
import PageFrame from '../components/PageFrame'
import PrimaryButton from '../components/PrimaryButton'
import HelpModal from '../components/HelpModal'
import styles from './PlayerSetup.module.css'
import sharedStyles from '../styles/shared.module.css'
interface Props {
onStart: (players: string[]) => void
@@ -14,6 +16,7 @@ function normalize(s: string) {
export default function PlayerSetup({ onStart, initialNames }: Props) {
const [names, setNames] = useState<string[]>(initialNames ?? ['', '', ''])
const [showHelp, setShowHelp] = useState(false)
function updateName(i: number, value: string) {
setNames(n => n.map((v, idx) => (idx === i ? value : v)))
@@ -36,6 +39,8 @@ export default function PlayerSetup({ onStart, initialNames }: Props) {
return (
<PageFrame
title="Player Setup"
headerAction={<button className={sharedStyles.helpBtn} onClick={() => setShowHelp(true)}>?</button>}
overlay={showHelp ? <HelpModal onClose={() => setShowHelp(false)} /> : undefined}
footer={
<PrimaryButton onClick={() => onStart(names.map(n => n.trim()))} disabled={!isValid}>
Start
+12
View File
@@ -1,3 +1,15 @@
.helpBtn {
background: none;
border: none;
padding: 0;
min-height: unset;
font-size: 1.25rem;
font-weight: 700;
color: #aaa;
line-height: 1;
box-shadow: none;
}
.card {
position: fixed;
inset: 1rem;