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