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:
parent
995966ffbc
commit
0b7382f1b6
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
Loading…
Reference in New Issue