Phase 2: Build 9 presentational components with full test coverage
New components (all with .tsx + .test.tsx, 90%+ coverage): - CalendarDay: day cell with event badges - CalendarEvent: colored event chip with dismiss option - DaysBreakdown: table of days by type - ProgressChart: progress bar toward age-4 deadline - TipCard: optimization tip card with severity badges - VacationDaysInput: number input with min/max validation - ParentProfileInput: dual selects for salary type & employment status - PlanningGoalSelector: radio buttons for planning goal - BreakPeriodsInput: list manager with inline add/edit form All components follow existing patterns from Header/Footer: - Typed props interfaces (exported) - Semantic HTML with data-testid & ARIA attributes - No business logic, purely presentational - Comprehensive test coverage including edge cases npm test: 22 files, 167 tests passing npx tsc --noEmit: clean Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
export interface PlanningGoalSelectorProps {
|
||||
value?: string
|
||||
onChange: (goal: string) => void
|
||||
}
|
||||
|
||||
export function PlanningGoalSelector({ value, onChange }: PlanningGoalSelectorProps): ReactNode {
|
||||
return (
|
||||
<div data-testid="planning-goal-selector" className="planning-goal-selector">
|
||||
<fieldset className="planning-goal-selector__fieldset">
|
||||
<legend className="planning-goal-selector__legend">Planning Goal</legend>
|
||||
|
||||
<div className="planning-goal-selector__option">
|
||||
<input
|
||||
type="radio"
|
||||
id="goal-maximize-contiguous"
|
||||
name="planning-goal"
|
||||
value="maximize_contiguous"
|
||||
checked={value === 'maximize_contiguous'}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
data-testid="goal-maximize-contiguous"
|
||||
/>
|
||||
<label htmlFor="goal-maximize-contiguous">Maximize Contiguous</label>
|
||||
</div>
|
||||
|
||||
<div className="planning-goal-selector__option">
|
||||
<input
|
||||
type="radio"
|
||||
id="goal-minimize-stress"
|
||||
name="planning-goal"
|
||||
value="minimize_stress"
|
||||
checked={value === 'minimize_stress'}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
data-testid="goal-minimize-stress"
|
||||
/>
|
||||
<label htmlFor="goal-minimize-stress">Minimize Stress</label>
|
||||
</div>
|
||||
|
||||
<div className="planning-goal-selector__option">
|
||||
<input
|
||||
type="radio"
|
||||
id="goal-balance"
|
||||
name="planning-goal"
|
||||
value="balance"
|
||||
checked={value === 'balance'}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
data-testid="goal-balance"
|
||||
/>
|
||||
<label htmlFor="goal-balance">Balance</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user