feat: create unified difficulty interface with 2-tab selector

Replace 3-way mode tabs (Smart/Manual/Mastery) with unified interface:
- Smart and Mastery are now "quick presets" via 2-tab selector
- Manual controls (thermometers) always visible for customization
- All modes share same displayRules system (1:1 correspondence)

Changes:
- Add DifficultyMethodSelector component with tab-style UI
- Update ConfigPanel to use 2-tab selector
- Smart/Mastery populate the always-visible display controls
- Preserve displayRules when switching between methods

🤖 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-10 18:12:18 -06:00
parent 995966ffbc
commit 0b7382f1b6
2 changed files with 128 additions and 65 deletions

View File

@ -3,14 +3,13 @@
import { stack } from '../../../../../../styled-system/patterns'
import type { WorksheetFormState } from '../types'
import { defaultAdditionConfig } from '@/app/create/worksheets/config-schemas'
import { ModeSelector } from './ModeSelector'
import { DifficultyMethodSelector } from './DifficultyMethodSelector'
import { StudentNameInput } from './config-panel/StudentNameInput'
import { DigitRangeSection } from './config-panel/DigitRangeSection'
import { OperatorSection } from './config-panel/OperatorSection'
import { ProgressiveDifficultyToggle } from './config-panel/ProgressiveDifficultyToggle'
import { SmartModeControls } from './config-panel/SmartModeControls'
import { ManualModeControls } from './config-panel/ManualModeControls'
import { MasteryModePanel } from './config-panel/MasteryModePanel'
import { DisplayControlsPanel } from './DisplayControlsPanel'
interface ConfigPanelProps {
formState: WorksheetFormState
@ -19,55 +18,23 @@ interface ConfigPanelProps {
}
export function ConfigPanel({ formState, onChange, isDark = false }: ConfigPanelProps) {
// Handler for mode switching
const handleModeChange = (newMode: 'smart' | 'manual' | 'mastery') => {
if (formState.mode === newMode) {
// Handler for difficulty method switching (smart vs mastery)
const handleMethodChange = (newMethod: 'smart' | 'mastery') => {
const currentMethod = formState.mode === 'mastery' ? 'mastery' : 'smart'
if (currentMethod === newMethod) {
return // No change needed
}
if (newMode === 'smart') {
// Switching to Smart mode
// Use current displayRules if available, otherwise default to earlyLearner
const displayRules = formState.displayRules ?? defaultAdditionConfig.displayRules
// Preserve displayRules when switching
const displayRules = formState.displayRules ?? defaultAdditionConfig.displayRules
if (newMethod === 'smart') {
onChange({
mode: 'smart',
displayRules,
difficultyProfile: 'earlyLearner',
} as unknown as Partial<WorksheetFormState>)
} else if (newMode === 'manual') {
// Switching to Manual mode
// Convert current displayRules to boolean flags if available
let booleanFlags = {
showCarryBoxes: true,
showAnswerBoxes: true,
showPlaceValueColors: true,
showTenFrames: false,
showProblemNumbers: true,
showCellBorder: true,
showTenFramesForAll: false,
}
if (formState.displayRules) {
// Convert 'always' to true, everything else to false
booleanFlags = {
showCarryBoxes: formState.displayRules.carryBoxes === 'always',
showAnswerBoxes: formState.displayRules.answerBoxes === 'always',
showPlaceValueColors: formState.displayRules.placeValueColors === 'always',
showTenFrames: formState.displayRules.tenFrames === 'always',
showProblemNumbers: formState.displayRules.problemNumbers === 'always',
showCellBorder: formState.displayRules.cellBorders === 'always',
showTenFramesForAll: false,
}
}
onChange({
mode: 'manual',
...booleanFlags,
} as unknown as Partial<WorksheetFormState>)
} else {
// Switching to Mastery mode
// Mastery mode uses Smart mode under the hood with skill-based configuration
const displayRules = formState.displayRules ?? defaultAdditionConfig.displayRules
onChange({
mode: 'mastery',
displayRules,
@ -75,6 +42,9 @@ export function ConfigPanel({ formState, onChange, isDark = false }: ConfigPanel
}
}
// Determine current method for selector
const currentMethod = formState.mode === 'mastery' ? 'mastery' : 'smart'
return (
<div data-component="config-panel" className={stack({ gap: '3' })}>
{/* Student Name */}
@ -84,13 +54,6 @@ export function ConfigPanel({ formState, onChange, isDark = false }: ConfigPanel
isDark={isDark}
/>
{/* Digit Range Selector */}
<DigitRangeSection
digitRange={formState.digitRange}
onChange={(digitRange) => onChange({ digitRange })}
isDark={isDark}
/>
{/* Operator Selector */}
<OperatorSection
operator={formState.operator}
@ -98,33 +61,29 @@ export function ConfigPanel({ formState, onChange, isDark = false }: ConfigPanel
isDark={isDark}
/>
{/* Progressive Difficulty Toggle - Available for all modes */}
{/* Progressive Difficulty Toggle */}
<ProgressiveDifficultyToggle
interpolate={formState.interpolate}
onChange={(interpolate) => onChange({ interpolate })}
isDark={isDark}
/>
{/* Mode Selector Tabs with description */}
<ModeSelector
currentMode={formState.mode ?? 'smart'}
onChange={handleModeChange}
{/* Display Controls - Always visible for manual adjustment */}
<DisplayControlsPanel formState={formState} onChange={onChange} isDark={isDark} />
{/* Difficulty Method Selector (Smart vs Mastery) */}
<DifficultyMethodSelector
currentMethod={currentMethod}
onChange={handleMethodChange}
isDark={isDark}
/>
{/* Mode-specific controls - no wrapper, let controls style themselves */}
{/* Smart Mode Controls */}
{(!formState.mode || formState.mode === 'smart') && (
{/* Method-specific preset controls */}
{currentMethod === 'smart' && (
<SmartModeControls formState={formState} onChange={onChange} isDark={isDark} />
)}
{/* Manual Mode Controls */}
{formState.mode === 'manual' && (
<ManualModeControls formState={formState} onChange={onChange} isDark={isDark} />
)}
{/* Mastery Mode Controls */}
{formState.mode === 'mastery' && (
{currentMethod === 'mastery' && (
<MasteryModePanel formState={formState} onChange={onChange} isDark={isDark} />
)}
</div>

View File

@ -0,0 +1,104 @@
'use client'
import { css } from '../../../../../../styled-system/css'
interface DifficultyMethodSelectorProps {
currentMethod: 'smart' | 'mastery'
onChange: (method: 'smart' | 'mastery') => void
isDark?: boolean
}
export function DifficultyMethodSelector({
currentMethod,
onChange,
isDark = false,
}: DifficultyMethodSelectorProps) {
return (
<div data-component="difficulty-method-selector">
{/* Tab buttons */}
<div
className={css({
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gap: '0',
bg: isDark ? 'gray.800' : 'gray.100',
p: '1',
rounded: 'lg',
})}
>
{/* Smart Difficulty Tab */}
<button
type="button"
data-action="select-smart"
onClick={() => onChange('smart')}
className={css({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '2',
px: '4',
py: '2.5',
bg: currentMethod === 'smart' ? (isDark ? 'gray.700' : 'white') : 'transparent',
color: currentMethod === 'smart' ? (isDark ? 'gray.100' : 'gray.900') : (isDark ? 'gray.400' : 'gray.600'),
fontWeight: currentMethod === 'smart' ? 'semibold' : 'medium',
fontSize: 'sm',
rounded: 'md',
cursor: 'pointer',
transition: 'all 0.2s',
boxShadow: currentMethod === 'smart' ? (isDark ? '0 1px 3px rgba(0,0,0,0.3)' : '0 1px 3px rgba(0,0,0,0.1)') : 'none',
_hover: {
color: isDark ? 'gray.200' : 'gray.700',
},
})}
>
<span>🎯</span>
<span>Smart Difficulty</span>
</button>
{/* Mastery Progression Tab */}
<button
type="button"
data-action="select-mastery"
onClick={() => onChange('mastery')}
className={css({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '2',
px: '4',
py: '2.5',
bg: currentMethod === 'mastery' ? (isDark ? 'gray.700' : 'white') : 'transparent',
color: currentMethod === 'mastery' ? (isDark ? 'gray.100' : 'gray.900') : (isDark ? 'gray.400' : 'gray.600'),
fontWeight: currentMethod === 'mastery' ? 'semibold' : 'medium',
fontSize: 'sm',
rounded: 'md',
cursor: 'pointer',
transition: 'all 0.2s',
boxShadow: currentMethod === 'mastery' ? (isDark ? '0 1px 3px rgba(0,0,0,0.3)' : '0 1px 3px rgba(0,0,0,0.1)') : 'none',
_hover: {
color: isDark ? 'gray.200' : 'gray.700',
},
})}
>
<span>🎓</span>
<span>Mastery Progression</span>
</button>
</div>
{/* Description text */}
<div
className={css({
mt: '2',
px: '1',
fontSize: 'xs',
color: isDark ? 'gray.400' : 'gray.600',
textAlign: 'center',
})}
>
{currentMethod === 'smart'
? 'Choose a difficulty preset, then customize display options below'
: 'Follow a structured skill progression with recommended scaffolding'}
</div>
</div>
)
}