feat: add theme support to config panel components

Add light/dark mode support to all config panel components:
- StudentNameInput: themed input backgrounds and borders
- DigitRangeSection: themed section backgrounds and text
- OperatorSection: themed button states
- ModeSelector: themed mode selection cards
- ProgressiveDifficultyToggle: themed switch and backgrounds
- ToggleOption: themed toggle containers and text
- SmartModeControls & ManualModeControls: pass isDark prop through

All components now respond to light/dark theme with appropriate
backgrounds, borders, and text colors for optimal readability.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock
2025-11-08 13:11:07 -06:00
parent 5c14925d7d
commit c8684213fa
9 changed files with 96 additions and 54 deletions

View File

@@ -14,9 +14,10 @@ import { ManualModeControls } from './config-panel/ManualModeControls'
interface ConfigPanelProps {
formState: WorksheetFormState
onChange: (updates: Partial<WorksheetFormState>) => void
isDark?: boolean
}
export function ConfigPanel({ formState, onChange }: ConfigPanelProps) {
export function ConfigPanel({ formState, onChange, isDark = false }: ConfigPanelProps) {
// Handler for mode switching
const handleModeChange = (newMode: 'smart' | 'manual') => {
if (formState.mode === newMode) {
@@ -68,37 +69,48 @@ export function ConfigPanel({ formState, onChange }: ConfigPanelProps) {
return (
<div data-component="config-panel" className={stack({ gap: '3' })}>
{/* Student Name */}
<StudentNameInput value={formState.name} onChange={(name) => onChange({ name })} />
<StudentNameInput
value={formState.name}
onChange={(name) => onChange({ name })}
isDark={isDark}
/>
{/* Digit Range Selector */}
<DigitRangeSection
digitRange={formState.digitRange}
onChange={(digitRange) => onChange({ digitRange })}
isDark={isDark}
/>
{/* Operator Selector */}
<OperatorSection
operator={formState.operator}
onChange={(operator) => onChange({ operator })}
isDark={isDark}
/>
{/* Mode Selector */}
<ModeSelector currentMode={formState.mode ?? 'smart'} onChange={handleModeChange} />
<ModeSelector
currentMode={formState.mode ?? 'smart'}
onChange={handleModeChange}
isDark={isDark}
/>
{/* Progressive Difficulty Toggle - Available for both modes */}
<ProgressiveDifficultyToggle
interpolate={formState.interpolate}
onChange={(interpolate) => onChange({ interpolate })}
isDark={isDark}
/>
{/* Smart Mode Controls */}
{(!formState.mode || formState.mode === 'smart') && (
<SmartModeControls formState={formState} onChange={onChange} />
<SmartModeControls formState={formState} onChange={onChange} isDark={isDark} />
)}
{/* Manual Mode Controls */}
{formState.mode === 'manual' && (
<ManualModeControls formState={formState} onChange={onChange} />
<ManualModeControls formState={formState} onChange={onChange} isDark={isDark} />
)}
</div>
)

View File

@@ -5,30 +5,31 @@ import { css } from '../../../../../../styled-system/css'
interface ModeSelectorProps {
currentMode: 'smart' | 'manual'
onChange: (mode: 'smart' | 'manual') => void
isDark?: boolean
}
/**
* Mode selector for worksheet generation
* Allows switching between Smart Difficulty and Manual Control modes
*/
export function ModeSelector({ currentMode, onChange }: ModeSelectorProps) {
export function ModeSelector({ currentMode, onChange, isDark = false }: ModeSelectorProps) {
return (
<div
data-component="mode-selector"
className={css({
marginBottom: '1.5rem',
padding: '1rem',
backgroundColor: 'gray.50',
backgroundColor: isDark ? 'gray.700' : 'gray.50',
borderRadius: '8px',
border: '1px solid',
borderColor: 'gray.200',
borderColor: isDark ? 'gray.600' : 'gray.200',
})}
>
<h3
className={css({
fontSize: '0.875rem',
fontWeight: '600',
color: 'gray.700',
color: isDark ? 'gray.200' : 'gray.700',
marginBottom: '0.75rem',
textTransform: 'uppercase',
letterSpacing: '0.05em',
@@ -55,8 +56,8 @@ export function ModeSelector({ currentMode, onChange }: ModeSelectorProps) {
padding: '1rem',
borderRadius: '6px',
border: '2px solid',
borderColor: currentMode === 'smart' ? 'blue.500' : 'gray.300',
backgroundColor: currentMode === 'smart' ? 'blue.50' : 'white',
borderColor: currentMode === 'smart' ? 'blue.500' : isDark ? 'gray.500' : 'gray.300',
backgroundColor: currentMode === 'smart' ? 'blue.50' : isDark ? 'gray.600' : 'white',
cursor: 'pointer',
transition: 'all 0.2s',
textAlign: 'left',
@@ -85,7 +86,7 @@ export function ModeSelector({ currentMode, onChange }: ModeSelectorProps) {
className={css({
fontSize: '0.875rem',
fontWeight: '600',
color: currentMode === 'smart' ? 'blue.700' : 'gray.700',
color: currentMode === 'smart' ? 'blue.700' : isDark ? 'gray.200' : 'gray.700',
})}
>
Smart Difficulty
@@ -94,7 +95,7 @@ export function ModeSelector({ currentMode, onChange }: ModeSelectorProps) {
<p
className={css({
fontSize: '0.75rem',
color: currentMode === 'smart' ? 'blue.600' : 'gray.600',
color: currentMode === 'smart' ? 'blue.600' : isDark ? 'gray.400' : 'gray.600',
lineHeight: '1.4',
})}
>
@@ -113,8 +114,8 @@ export function ModeSelector({ currentMode, onChange }: ModeSelectorProps) {
padding: '1rem',
borderRadius: '6px',
border: '2px solid',
borderColor: currentMode === 'manual' ? 'blue.500' : 'gray.300',
backgroundColor: currentMode === 'manual' ? 'blue.50' : 'white',
borderColor: currentMode === 'manual' ? 'blue.500' : isDark ? 'gray.500' : 'gray.300',
backgroundColor: currentMode === 'manual' ? 'blue.50' : isDark ? 'gray.600' : 'white',
cursor: 'pointer',
transition: 'all 0.2s',
textAlign: 'left',
@@ -143,7 +144,7 @@ export function ModeSelector({ currentMode, onChange }: ModeSelectorProps) {
className={css({
fontSize: '0.875rem',
fontWeight: '600',
color: currentMode === 'manual' ? 'blue.700' : 'gray.700',
color: currentMode === 'manual' ? 'blue.700' : isDark ? 'gray.200' : 'gray.700',
})}
>
Manual Control
@@ -152,7 +153,7 @@ export function ModeSelector({ currentMode, onChange }: ModeSelectorProps) {
<p
className={css({
fontSize: '0.75rem',
color: currentMode === 'manual' ? 'blue.600' : 'gray.600',
color: currentMode === 'manual' ? 'blue.600' : isDark ? 'gray.400' : 'gray.600',
lineHeight: '1.4',
})}
>

View File

@@ -4,9 +4,14 @@ import { css } from '../../../../../../../styled-system/css'
export interface DigitRangeSectionProps {
digitRange: { min: number; max: number } | undefined
onChange: (digitRange: { min: number; max: number }) => void
isDark?: boolean
}
export function DigitRangeSection({ digitRange, onChange }: DigitRangeSectionProps) {
export function DigitRangeSection({
digitRange,
onChange,
isDark = false,
}: DigitRangeSectionProps) {
const min = digitRange?.min ?? 2
const max = digitRange?.max ?? 2
@@ -14,9 +19,9 @@ export function DigitRangeSection({ digitRange, onChange }: DigitRangeSectionPro
<div
data-section="digit-range"
className={css({
bg: 'gray.50',
bg: isDark ? 'gray.700' : 'gray.50',
border: '1px solid',
borderColor: 'gray.200',
borderColor: isDark ? 'gray.600' : 'gray.200',
rounded: 'xl',
p: '4',
})}
@@ -29,14 +34,20 @@ export function DigitRangeSection({ digitRange, onChange }: DigitRangeSectionPro
alignItems: 'center',
})}
>
<label className={css({ fontSize: 'sm', fontWeight: 'semibold', color: 'gray.700' })}>
<label
className={css({
fontSize: 'sm',
fontWeight: 'semibold',
color: isDark ? 'gray.200' : 'gray.700',
})}
>
Problem Size (Digits per Number)
</label>
<span className={css({ fontSize: 'sm', fontWeight: 'bold', color: 'brand.600' })}>
{min === max ? `${min}` : `${min}-${max}`}
</span>
</div>
<p className={css({ fontSize: 'xs', color: 'gray.500', mt: '1' })}>
<p className={css({ fontSize: 'xs', color: isDark ? 'gray.400' : 'gray.500', mt: '1' })}>
{min === max
? `All problems: exactly ${min} digit${min > 1 ? 's' : ''}`
: `Mixed problem sizes from ${min} to ${max} digits`}

View File

@@ -11,9 +11,10 @@ import { SubOption } from './SubOption'
export interface ManualModeControlsProps {
formState: WorksheetFormState
onChange: (updates: Partial<WorksheetFormState>) => void
isDark?: boolean
}
export function ManualModeControls({ formState, onChange }: ManualModeControlsProps) {
export function ManualModeControls({ formState, onChange, isDark = false }: ManualModeControlsProps) {
return (
<>
<>
@@ -124,6 +125,7 @@ export function ManualModeControls({ formState, onChange }: ManualModeControlsPr
onChange={(checked) => onChange({ showAnswerBoxes: checked })}
label="Answer Boxes"
description="Guide students to write organized, aligned answers"
isDark={isDark}
/>
<ToggleOption
@@ -131,6 +133,7 @@ export function ManualModeControls({ formState, onChange }: ManualModeControlsPr
onChange={(checked) => onChange({ showPlaceValueColors: checked })}
label="Place Value Colors"
description="Reinforce place value understanding visually"
isDark={isDark}
/>
<ToggleOption
@@ -138,6 +141,7 @@ export function ManualModeControls({ formState, onChange }: ManualModeControlsPr
onChange={(checked) => onChange({ showProblemNumbers: checked })}
label="Problem Numbers"
description="Help students track progress and reference problems"
isDark={isDark}
/>
<ToggleOption
@@ -145,6 +149,7 @@ export function ManualModeControls({ formState, onChange }: ManualModeControlsPr
onChange={(checked) => onChange({ showCellBorder: checked })}
label="Cell Borders"
description="Organize problems visually for easier focus"
isDark={isDark}
/>
<ToggleOption
@@ -166,6 +171,7 @@ export function ManualModeControls({ formState, onChange }: ManualModeControlsPr
? 'Help students track regrouping (carrying in addition, borrowing in subtraction)'
: 'Help students track regrouping during addition'
}
isDark={isDark}
/>
{(formState.operator === 'subtraction' || formState.operator === 'mixed') && (
@@ -174,6 +180,7 @@ export function ManualModeControls({ formState, onChange }: ManualModeControlsPr
onChange={(checked) => onChange({ showBorrowNotation: checked })}
label="Borrowed 10s Box"
description="Box for adding 10 to borrowing digit"
isDark={isDark}
/>
)}
@@ -183,6 +190,7 @@ export function ManualModeControls({ formState, onChange }: ManualModeControlsPr
onChange={(checked) => onChange({ showBorrowingHints: checked })}
label="Borrowing Hints"
description="Show arrows and calculations guiding the borrowing process"
isDark={isDark}
/>
)}
@@ -193,6 +201,7 @@ export function ManualModeControls({ formState, onChange }: ManualModeControlsPr
}}
label="Ten-Frames"
description="Visualize regrouping with concrete counting tools"
isDark={isDark}
>
<SubOption
checked={formState.showTenFramesForAll ?? false}

View File

@@ -3,16 +3,17 @@ import { css } from '../../../../../../../styled-system/css'
export interface OperatorSectionProps {
operator: 'addition' | 'subtraction' | 'mixed' | undefined
onChange: (operator: 'addition' | 'subtraction' | 'mixed') => void
isDark?: boolean
}
export function OperatorSection({ operator, onChange }: OperatorSectionProps) {
export function OperatorSection({ operator, onChange, isDark = false }: OperatorSectionProps) {
return (
<div
data-section="operator-selection"
className={css({
bg: 'gray.50',
bg: isDark ? 'gray.700' : 'gray.50',
border: '1px solid',
borderColor: 'gray.200',
borderColor: isDark ? 'gray.600' : 'gray.200',
rounded: 'xl',
p: '4',
})}
@@ -21,7 +22,7 @@ export function OperatorSection({ operator, onChange }: OperatorSectionProps) {
className={css({
fontSize: 'sm',
fontWeight: 'semibold',
color: 'gray.700',
color: isDark ? 'gray.200' : 'gray.700',
mb: '2',
display: 'block',
})}
@@ -49,10 +50,10 @@ export function OperatorSection({ operator, onChange }: OperatorSectionProps) {
color: 'white',
}
: {
bg: 'white',
borderColor: 'gray.300',
color: 'gray.700',
_hover: { borderColor: 'gray.400' },
bg: isDark ? 'gray.600' : 'white',
borderColor: isDark ? 'gray.500' : 'gray.300',
color: isDark ? 'gray.200' : 'gray.700',
_hover: { borderColor: isDark ? 'gray.400' : 'gray.400' },
}),
})}
>
@@ -78,10 +79,10 @@ export function OperatorSection({ operator, onChange }: OperatorSectionProps) {
color: 'white',
}
: {
bg: 'white',
borderColor: 'gray.300',
color: 'gray.700',
_hover: { borderColor: 'gray.400' },
bg: isDark ? 'gray.600' : 'white',
borderColor: isDark ? 'gray.500' : 'gray.300',
color: isDark ? 'gray.200' : 'gray.700',
_hover: { borderColor: isDark ? 'gray.400' : 'gray.400' },
}),
})}
>
@@ -107,10 +108,10 @@ export function OperatorSection({ operator, onChange }: OperatorSectionProps) {
color: 'white',
}
: {
bg: 'white',
borderColor: 'gray.300',
color: 'gray.700',
_hover: { borderColor: 'gray.400' },
bg: isDark ? 'gray.600' : 'white',
borderColor: isDark ? 'gray.500' : 'gray.300',
color: isDark ? 'gray.200' : 'gray.700',
_hover: { borderColor: isDark ? 'gray.400' : 'gray.400' },
}),
})}
>

View File

@@ -4,19 +4,21 @@ import { css } from '../../../../../../../styled-system/css'
export interface ProgressiveDifficultyToggleProps {
interpolate: boolean | undefined
onChange: (interpolate: boolean) => void
isDark?: boolean
}
export function ProgressiveDifficultyToggle({
interpolate,
onChange,
isDark = false,
}: ProgressiveDifficultyToggleProps) {
return (
<div
data-section="progressive-difficulty"
className={css({
bg: 'gray.50',
bg: isDark ? 'gray.700' : 'gray.50',
border: '1px solid',
borderColor: 'gray.200',
borderColor: isDark ? 'gray.600' : 'gray.200',
rounded: 'xl',
p: '3',
})}
@@ -34,7 +36,7 @@ export function ProgressiveDifficultyToggle({
className={css({
fontSize: 'sm',
fontWeight: 'medium',
color: 'gray.700',
color: isDark ? 'gray.200' : 'gray.700',
cursor: 'pointer',
})}
>
@@ -47,7 +49,7 @@ export function ProgressiveDifficultyToggle({
className={css({
width: '11',
height: '6',
bg: 'gray.300',
bg: isDark ? 'gray.600' : 'gray.300',
rounded: 'full',
position: 'relative',
cursor: 'pointer',
@@ -76,7 +78,7 @@ export function ProgressiveDifficultyToggle({
<div
className={css({
fontSize: 'xs',
color: 'gray.500',
color: isDark ? 'gray.400' : 'gray.500',
mt: '1',
})}
>

View File

@@ -28,9 +28,10 @@ import { getScaffoldingSummary } from './utils'
export interface SmartModeControlsProps {
formState: WorksheetFormState
onChange: (updates: Partial<WorksheetFormState>) => void
isDark?: boolean
}
export function SmartModeControls({ formState, onChange }: SmartModeControlsProps) {
export function SmartModeControls({ formState, onChange, isDark = false }: SmartModeControlsProps) {
const [showDebugPlot, setShowDebugPlot] = useState(false)
const [hoverPoint, setHoverPoint] = useState<{ x: number; y: number } | null>(null)
const [hoverPreview, setHoverPreview] = useState<{

View File

@@ -3,9 +3,10 @@ import { css } from '../../../../../../../styled-system/css'
export interface StudentNameInputProps {
value: string | undefined
onChange: (value: string) => void
isDark?: boolean
}
export function StudentNameInput({ value, onChange }: StudentNameInputProps) {
export function StudentNameInput({ value, onChange, isDark = false }: StudentNameInputProps) {
return (
<input
type="text"
@@ -17,7 +18,9 @@ export function StudentNameInput({ value, onChange }: StudentNameInputProps) {
px: '3',
py: '2',
border: '1px solid',
borderColor: 'gray.300',
borderColor: isDark ? 'gray.600' : 'gray.300',
bg: isDark ? 'gray.700' : 'white',
color: isDark ? 'gray.100' : 'gray.900',
rounded: 'lg',
fontSize: 'sm',
_focus: {
@@ -26,7 +29,7 @@ export function StudentNameInput({ value, onChange }: StudentNameInputProps) {
ring: '2px',
ringColor: 'brand.200',
},
_placeholder: { color: 'gray.400' },
_placeholder: { color: isDark ? 'gray.500' : 'gray.400' },
})}
/>
)

View File

@@ -8,6 +8,7 @@ export interface ToggleOptionProps {
label: string
description: string
children?: React.ReactNode
isDark?: boolean
}
export function ToggleOption({
@@ -16,6 +17,7 @@ export function ToggleOption({
label,
description,
children,
isDark = false,
}: ToggleOptionProps) {
return (
<div
@@ -24,14 +26,14 @@ export function ToggleOption({
display: 'flex',
flexDirection: 'column',
h: children ? 'auto' : '20',
bg: checked ? 'brand.50' : 'white',
bg: checked ? 'brand.50' : isDark ? 'gray.700' : 'white',
border: '2px solid',
borderColor: checked ? 'brand.500' : 'gray.200',
borderColor: checked ? 'brand.500' : isDark ? 'gray.600' : 'gray.200',
rounded: 'lg',
transition: 'all 0.15s',
_hover: {
borderColor: checked ? 'brand.600' : 'gray.300',
bg: checked ? 'brand.100' : 'gray.50',
borderColor: checked ? 'brand.600' : isDark ? 'gray.500' : 'gray.300',
bg: checked ? 'brand.100' : isDark ? 'gray.600' : 'gray.50',
},
})}
>
@@ -70,7 +72,7 @@ export function ToggleOption({
className={css({
fontSize: 'xs',
fontWeight: 'semibold',
color: checked ? 'brand.700' : 'gray.700',
color: checked ? 'brand.700' : isDark ? 'gray.200' : 'gray.700',
})}
>
{label}
@@ -104,7 +106,7 @@ export function ToggleOption({
<div
className={css({
fontSize: '2xs',
color: checked ? 'brand.600' : 'gray.500',
color: checked ? 'brand.600' : isDark ? 'gray.400' : 'gray.500',
lineHeight: '1.3',
})}
>