diff --git a/apps/web/src/app/create/worksheets/addition/components/config-panel/DifficultyPresetDropdown.tsx b/apps/web/src/app/create/worksheets/addition/components/config-panel/DifficultyPresetDropdown.tsx new file mode 100644 index 00000000..4d8dee11 --- /dev/null +++ b/apps/web/src/app/create/worksheets/addition/components/config-panel/DifficultyPresetDropdown.tsx @@ -0,0 +1,322 @@ +'use client' + +import type React from 'react' +import * as DropdownMenu from '@radix-ui/react-dropdown-menu' +import { css } from '../../../../../../../styled-system/css' +import { + DIFFICULTY_PROFILES, + DIFFICULTY_PROGRESSION, + calculateRegroupingIntensity, + type DifficultyLevel, +} from '../../difficultyProfiles' +import type { DisplayRules } from '../../displayRules' +import { getScaffoldingSummary } from './utils' + +export interface DifficultyPresetDropdownProps { + currentProfile: DifficultyLevel | null + isCustom: boolean + nearestEasier: DifficultyLevel | null + nearestHarder: DifficultyLevel | null + customDescription: React.ReactNode + hoverPreview: { + pAnyStart: number + pAllStart: number + displayRules: DisplayRules + matchedProfile: string | 'custom' + } | null + operator: 'addition' | 'subtraction' | 'mixed' + onChange: (updates: { + difficultyProfile: DifficultyLevel + pAnyStart: number + pAllStart: number + displayRules: DisplayRules + }) => void + isDark?: boolean +} + +export function DifficultyPresetDropdown({ + currentProfile, + isCustom, + nearestEasier, + nearestHarder, + customDescription, + hoverPreview, + operator, + onChange, + isDark = false, +}: DifficultyPresetDropdownProps) { + return ( +
+
+ Difficulty Preset +
+ + + + + + + + {DIFFICULTY_PROGRESSION.map((presetName) => { + const preset = DIFFICULTY_PROFILES[presetName] + const isSelected = currentProfile === presetName && !isCustom + + // Generate preset description + const regroupingPercent = Math.round( + calculateRegroupingIntensity( + preset.regrouping.pAnyStart, + preset.regrouping.pAllStart + ) * 10 + ) + const scaffoldingSummary = getScaffoldingSummary(preset.displayRules, operator) + const presetDescription = ( + <> +
{regroupingPercent}% regrouping
+ {scaffoldingSummary} + + ) + + return ( + { + // Apply preset configuration + onChange({ + difficultyProfile: presetName, + pAnyStart: preset.regrouping.pAnyStart, + pAllStart: preset.regrouping.pAllStart, + displayRules: preset.displayRules, + }) + }} + className={css({ + display: 'flex', + flexDirection: 'column', + gap: '1', + px: '3', + py: '2.5', + rounded: 'md', + cursor: 'pointer', + outline: 'none', + bg: isSelected ? 'brand.50' : 'transparent', + _hover: { + bg: 'brand.50', + }, + _focus: { + bg: 'brand.100', + }, + })} + > +
+ {preset.label} +
+
+ {presetDescription} +
+
+ ) + })} +
+
+
+
+ ) +} diff --git a/apps/web/src/app/create/worksheets/addition/components/config-panel/MakeEasierHarderButtons.tsx b/apps/web/src/app/create/worksheets/addition/components/config-panel/MakeEasierHarderButtons.tsx new file mode 100644 index 00000000..c59aa3f2 --- /dev/null +++ b/apps/web/src/app/create/worksheets/addition/components/config-panel/MakeEasierHarderButtons.tsx @@ -0,0 +1,342 @@ +'use client' + +import type React from 'react' +import * as Tooltip from '@radix-ui/react-tooltip' +import { css } from '../../../../../../../styled-system/css' +import type { DifficultyMode } from '../../difficultyProfiles' + +export interface DifficultyChangeResult { + changeDescription: string + pAnyStart: number + pAllStart: number + displayRules: any + difficultyProfile?: string +} + +export interface MakeEasierHarderButtonsProps { + easierResultBoth: DifficultyChangeResult + easierResultChallenge: DifficultyChangeResult + easierResultSupport: DifficultyChangeResult + harderResultBoth: DifficultyChangeResult + harderResultChallenge: DifficultyChangeResult + harderResultSupport: DifficultyChangeResult + canMakeEasierBoth: boolean + canMakeEasierChallenge: boolean + canMakeEasierSupport: boolean + canMakeHarderBoth: boolean + canMakeHarderChallenge: boolean + canMakeHarderSupport: boolean + onEasier: (mode: DifficultyMode) => void + onHarder: (mode: DifficultyMode) => void + isDark?: boolean +} + +export function MakeEasierHarderButtons({ + easierResultBoth, + easierResultChallenge, + easierResultSupport, + harderResultBoth, + harderResultChallenge, + harderResultSupport, + canMakeEasierBoth, + canMakeEasierChallenge, + canMakeEasierSupport, + canMakeHarderBoth, + canMakeHarderChallenge, + canMakeHarderSupport, + onEasier, + onHarder, + isDark = false, +}: MakeEasierHarderButtonsProps) { + // Determine which mode is alternative for easier + const easierAlternativeMode = + easierResultBoth.changeDescription === easierResultChallenge.changeDescription + ? 'support' + : 'challenge' + const easierAlternativeResult = + easierAlternativeMode === 'support' ? easierResultSupport : easierResultChallenge + const easierAlternativeLabel = + easierAlternativeMode === 'support' ? '↑ More support' : '← Less challenge' + const canEasierAlternative = + easierAlternativeMode === 'support' ? canMakeEasierSupport : canMakeEasierChallenge + + // Determine which mode is alternative for harder + const harderAlternativeMode = + harderResultBoth.changeDescription === harderResultChallenge.changeDescription + ? 'support' + : 'challenge' + const harderAlternativeResult = + harderAlternativeMode === 'support' ? harderResultSupport : harderResultChallenge + const harderAlternativeLabel = + harderAlternativeMode === 'support' ? '↓ Less support' : '→ More challenge' + const canHarderAlternative = + harderAlternativeMode === 'support' ? canMakeHarderSupport : canMakeHarderChallenge + + return ( +
+ {/* Four-Button Layout: [Alt-35%][Rec-65%][Rec-65%][Alt-35%] */} + +
+ {/* EASIER SECTION */} +
+ {/* Alternative Easier Button - Hidden if disabled and main is enabled */} + {canEasierAlternative && ( + + + + + + + {easierAlternativeResult.changeDescription} + + + + + )} + + {/* Recommended Easier Button - Expands to full width if alternative is hidden */} + +
+ + {/* HARDER SECTION */} +
+ {/* Recommended Harder Button - Expands to full width if alternative is hidden */} + + + {/* Alternative Harder Button - Hidden if disabled and main is enabled */} + {canHarderAlternative && ( + + + + + {canHarderAlternative && ( + + + {harderAlternativeResult.changeDescription} + + + + )} + + )} +
+
+
+
+ ) +} diff --git a/apps/web/src/app/create/worksheets/addition/components/config-panel/SMART_MODE_REFACTORING_PLAN.md b/apps/web/src/app/create/worksheets/addition/components/config-panel/SMART_MODE_REFACTORING_PLAN.md new file mode 100644 index 00000000..fba15323 --- /dev/null +++ b/apps/web/src/app/create/worksheets/addition/components/config-panel/SMART_MODE_REFACTORING_PLAN.md @@ -0,0 +1,143 @@ +# SmartModeControls.tsx Refactoring Plan + +## Current State +- **File size**: 1505 lines +- **Complexity**: Multiple large, self-contained UI sections embedded in one component +- **Issues**: Hard to maintain, test, and reuse + +## Proposed Refactoring + +### 1. Extract DifficultyPresetDropdown Component (~270 lines) +**Lines**: 275-546 +**Purpose**: Dropdown for selecting difficulty presets +**Props**: +```typescript +interface DifficultyPresetDropdownProps { + currentProfile: DifficultyLevel | null + isCustom: boolean + nearestEasier: DifficultyLevel | null + nearestHarder: DifficultyLevel | null + customDescription: React.ReactNode + hoverPreview: { pAnyStart: number; pAllStart: number; displayRules: DisplayRules; matchedProfile: string | 'custom' } | null + operator: 'addition' | 'subtraction' | 'mixed' + onChange: (updates: { difficultyProfile: DifficultyLevel; pAnyStart: number; pAllStart: number; displayRules: DisplayRules }) => void + isDark?: boolean +} +``` + +### 2. Extract MakeEasierHarderButtons Component (~240 lines) +**Lines**: 548-850 +**Purpose**: Four-button layout for adjusting difficulty +**Props**: +```typescript +interface MakeEasierHarderButtonsProps { + canMakeEasier: { recommended: boolean; alternative: boolean } + canMakeHarder: { recommended: boolean; alternative: boolean } + onEasier: (mode: DifficultyMode) => void + onHarder: (mode: DifficultyMode) => void + isDark?: boolean +} +``` + +### 3. Extract OverallDifficultySlider Component (~200 lines) +**Lines**: 859-1110 +**Purpose**: Slider with preset markers for difficulty adjustment +**Props**: +```typescript +interface OverallDifficultySliderProps { + overallDifficulty: number + currentProfile: DifficultyLevel | null + isCustom: boolean + onChange: (difficulty: number) => void + isDark?: boolean +} +``` + +### 4. Extract DifficultySpaceMap Component (~390 lines) +**Lines**: 1113-1505 +**Purpose**: 2D visualization of difficulty space with interactive hover +**Props**: +```typescript +interface DifficultySpaceMapProps { + currentState: { pAnyStart: number; pAllStart: number; displayRules: DisplayRules } + hoverPoint: { x: number; y: number } | null + setHoverPoint: (point: { x: number; y: number } | null) => void + setHoverPreview: (preview: { pAnyStart: number; pAllStart: number; displayRules: DisplayRules; matchedProfile: string | 'custom' } | null) => void + operator: 'addition' | 'subtraction' | 'mixed' + isDark?: boolean +} +``` + +### 5. Create Shared Style Utilities +**File**: `src/app/create/worksheets/addition/components/config-panel/buttonStyles.ts` +**Purpose**: Reusable button style generators to reduce duplication + +```typescript +export function getDifficultyButtonStyles( + isEnabled: boolean, + isDark: boolean, + variant: 'primary' | 'secondary' +): CSSProperties { + // Common button styling logic +} +``` + +## Benefits +1. **Maintainability**: Each component focuses on single responsibility +2. **Testability**: Smaller components are easier to test in isolation +3. **Reusability**: Components can be reused in other contexts (e.g., MasteryModePanel) +4. **Readability**: Main SmartModeControls becomes a clean composition +5. **Performance**: React can optimize smaller component trees better +6. **Dark mode**: Easier to audit and maintain consistent theming + +## Refactored SmartModeControls Structure +```tsx +export function SmartModeControls({ formState, onChange, isDark }: SmartModeControlsProps) { + // State and logic + const [showDebugPlot, setShowDebugPlot] = useState(false) + const [hoverPoint, setHoverPoint] = useState<{ x: number; y: number } | null>(null) + const [hoverPreview, setHoverPreview] = useState<...>(null) + + // Computed values + const currentProfile = getProfileFromConfig(...) + const isCustom = currentProfile === null + + return ( +
+ + +
+ + + + +
+ + +
+ ) +} +``` + +## Risks & Mitigation +- **Risk**: Breaking existing functionality + - **Mitigation**: Extract one component at a time, test thoroughly +- **Risk**: Props become too complex + - **Mitigation**: Create intermediate types, use composition patterns +- **Risk**: Performance regression from more components + - **Mitigation**: Use React.memo where appropriate + +## Implementation Steps +1. ✅ Create this plan document +2. Extract DifficultyPresetDropdown +3. Extract MakeEasierHarderButtons +4. Extract OverallDifficultySlider +5. Extract DifficultySpaceMap +6. Create shared button utilities +7. Test all components +8. Commit and push + +## Questions for User +1. Should we proceed with this refactoring plan? +2. Any components you'd prefer to keep inline? +3. Any additional concerns or requirements?