feat(practice): add dark mode support and fix doubled answer digits
- Add dark mode support to all practice components: - ActiveSession, VerticalProblem, NumericKeypad, HelpAbacus - StudentSelector, ProgressDashboard, PlanReview, SessionSummary - OfflineSessionForm, ManualSkillSelector, PlacementTest, PracticeHelpPanel - Fix doubled answer digit cells in VerticalProblem by consolidating two separate cell-rendering loops into a single unified loop 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -98,7 +98,40 @@
|
||||
"Bash(apps/web/src/arcade-games/know-your-world/features/interaction/ )",
|
||||
"Bash(apps/web/src/arcade-games/know-your-world/utils/heatStyles.ts)",
|
||||
"Bash(ping:*)",
|
||||
"WebFetch(domain:typst.app)"
|
||||
"WebFetch(domain:typst.app)",
|
||||
"WebFetch(domain:finemotormath.com)",
|
||||
"WebFetch(domain:learnabacusathome.com)",
|
||||
"WebFetch(domain:totton.idirect.com)",
|
||||
"Bash(npx drizzle-kit:*)",
|
||||
"Bash(npm run db:migrate:*)",
|
||||
"mcp__sqlite__list_tables",
|
||||
"Bash(sqlite3:*)",
|
||||
"Bash(npx eslint:*)",
|
||||
"Bash(src/hooks/useDeviceCapabilities.ts )",
|
||||
"Bash(src/arcade-games/know-your-world/hooks/useDeviceCapabilities.ts )",
|
||||
"Bash(src/components/practice/hooks/useDeviceDetection.ts )",
|
||||
"Bash(src/arcade-games/memory-quiz/components/InputPhase.tsx )",
|
||||
"Bash(src/app/api/curriculum/*/sessions/plans/route.ts)",
|
||||
"Bash(src/app/api/curriculum/*/sessions/plans/*/route.ts)",
|
||||
"Bash(src/components/practice/SessionSummary.tsx )",
|
||||
"Bash(src/components/practice/ )",
|
||||
"Bash(src/app/practice/ )",
|
||||
"Bash(src/app/api/curriculum/ )",
|
||||
"Bash(src/hooks/usePlayerCurriculum.ts )",
|
||||
"Bash(src/hooks/useSessionPlan.ts )",
|
||||
"Bash(src/lib/curriculum/ )",
|
||||
"Bash(src/db/schema/player-curriculum.ts )",
|
||||
"Bash(src/db/schema/player-skill-mastery.ts )",
|
||||
"Bash(src/db/schema/practice-sessions.ts )",
|
||||
"Bash(src/db/schema/session-plans.ts )",
|
||||
"Bash(src/db/schema/index.ts )",
|
||||
"Bash(src/types/tutorial.ts )",
|
||||
"Bash(src/utils/problemGenerator.ts )",
|
||||
"Bash(drizzle/ )",
|
||||
"Bash(docs/DAILY_PRACTICE_SYSTEM.md )",
|
||||
"Bash(../../README.md )",
|
||||
"Bash(.claude/CLAUDE.md)",
|
||||
"Bash(mcp__sqlite__describe_table:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
||||
@@ -11,6 +11,7 @@ import type {
|
||||
SlotResult,
|
||||
} from '@/db/schema/session-plans'
|
||||
import type { StudentHelpSettings } from '@/db/schema/players'
|
||||
import { useTheme } from '@/contexts/ThemeContext'
|
||||
import { usePracticeHelp } from '@/hooks/usePracticeHelp'
|
||||
import { createBasicSkillSet, type SkillSet } from '@/types/tutorial'
|
||||
import {
|
||||
@@ -79,20 +80,29 @@ function getPartTypeEmoji(type: SessionPart['type']): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get part type colors
|
||||
* Get part type colors (dark mode aware)
|
||||
*/
|
||||
function getPartTypeColors(type: SessionPart['type']): {
|
||||
function getPartTypeColors(
|
||||
type: SessionPart['type'],
|
||||
isDark: boolean
|
||||
): {
|
||||
bg: string
|
||||
border: string
|
||||
text: string
|
||||
} {
|
||||
switch (type) {
|
||||
case 'abacus':
|
||||
return { bg: 'blue.50', border: 'blue.200', text: 'blue.700' }
|
||||
return isDark
|
||||
? { bg: 'blue.900', border: 'blue.700', text: 'blue.200' }
|
||||
: { bg: 'blue.50', border: 'blue.200', text: 'blue.700' }
|
||||
case 'visualization':
|
||||
return { bg: 'purple.50', border: 'purple.200', text: 'purple.700' }
|
||||
return isDark
|
||||
? { bg: 'purple.900', border: 'purple.700', text: 'purple.200' }
|
||||
: { bg: 'purple.50', border: 'purple.200', text: 'purple.700' }
|
||||
case 'linear':
|
||||
return { bg: 'orange.50', border: 'orange.200', text: 'orange.700' }
|
||||
return isDark
|
||||
? { bg: 'orange.900', border: 'orange.700', text: 'orange.200' }
|
||||
: { bg: 'orange.50', border: 'orange.200', text: 'orange.700' }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,12 +115,14 @@ function LinearProblem({
|
||||
isFocused,
|
||||
isCompleted,
|
||||
correctAnswer,
|
||||
isDark,
|
||||
}: {
|
||||
terms: number[]
|
||||
userAnswer: string
|
||||
isFocused: boolean
|
||||
isCompleted: boolean
|
||||
correctAnswer: number
|
||||
isDark: boolean
|
||||
}) {
|
||||
// Build the equation string
|
||||
const equation = terms
|
||||
@@ -133,7 +145,7 @@ function LinearProblem({
|
||||
fontWeight: 'bold',
|
||||
})}
|
||||
>
|
||||
<span className={css({ color: 'gray.800' })}>{equation} =</span>
|
||||
<span className={css({ color: isDark ? 'gray.200' : 'gray.800' })}>{equation} =</span>
|
||||
<span
|
||||
className={css({
|
||||
minWidth: '80px',
|
||||
@@ -142,16 +154,28 @@ function LinearProblem({
|
||||
textAlign: 'center',
|
||||
backgroundColor: isCompleted
|
||||
? userAnswer === String(correctAnswer)
|
||||
? 'green.100'
|
||||
: 'red.100'
|
||||
: 'gray.100',
|
||||
? isDark
|
||||
? 'green.900'
|
||||
: 'green.100'
|
||||
: isDark
|
||||
? 'red.900'
|
||||
: 'red.100'
|
||||
: isDark
|
||||
? 'gray.800'
|
||||
: 'gray.100',
|
||||
color: isCompleted
|
||||
? userAnswer === String(correctAnswer)
|
||||
? 'green.700'
|
||||
: 'red.700'
|
||||
: 'gray.800',
|
||||
? isDark
|
||||
? 'green.200'
|
||||
: 'green.700'
|
||||
: isDark
|
||||
? 'red.200'
|
||||
: 'red.700'
|
||||
: isDark
|
||||
? 'gray.200'
|
||||
: 'gray.800',
|
||||
border: '2px solid',
|
||||
borderColor: isFocused ? 'blue.400' : 'gray.300',
|
||||
borderColor: isFocused ? 'blue.400' : isDark ? 'gray.600' : 'gray.300',
|
||||
})}
|
||||
>
|
||||
{userAnswer || (isFocused ? '?' : '')}
|
||||
@@ -182,6 +206,9 @@ export function ActiveSession({
|
||||
helpSettings,
|
||||
isBeginnerMode = false,
|
||||
}: ActiveSessionProps) {
|
||||
const { resolvedTheme } = useTheme()
|
||||
const isDark = resolvedTheme === 'dark'
|
||||
|
||||
const [currentProblem, setCurrentProblem] = useState<CurrentProblem | null>(null)
|
||||
const [userAnswer, setUserAnswer] = useState('')
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
@@ -534,7 +561,7 @@ export function ActiveSession({
|
||||
<div
|
||||
className={css({
|
||||
fontSize: '1.25rem',
|
||||
color: 'gray.500',
|
||||
color: isDark ? 'gray.400' : 'gray.500',
|
||||
})}
|
||||
>
|
||||
Loading next problem...
|
||||
@@ -543,7 +570,7 @@ export function ActiveSession({
|
||||
)
|
||||
}
|
||||
|
||||
const partColors = getPartTypeColors(currentPart.type)
|
||||
const partColors = getPartTypeColors(currentPart.type, isDark)
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -791,7 +818,7 @@ export function ActiveSession({
|
||||
alignItems: 'center',
|
||||
gap: '1.5rem',
|
||||
padding: '2rem',
|
||||
backgroundColor: 'white',
|
||||
backgroundColor: isDark ? 'gray.800' : 'white',
|
||||
borderRadius: '16px',
|
||||
boxShadow: 'md',
|
||||
})}
|
||||
@@ -807,20 +834,36 @@ export function ActiveSession({
|
||||
textTransform: 'uppercase',
|
||||
backgroundColor:
|
||||
currentSlot?.purpose === 'focus'
|
||||
? 'blue.100'
|
||||
? isDark
|
||||
? 'blue.900'
|
||||
: 'blue.100'
|
||||
: currentSlot?.purpose === 'reinforce'
|
||||
? 'orange.100'
|
||||
? isDark
|
||||
? 'orange.900'
|
||||
: 'orange.100'
|
||||
: currentSlot?.purpose === 'review'
|
||||
? 'green.100'
|
||||
: 'purple.100',
|
||||
? isDark
|
||||
? 'green.900'
|
||||
: 'green.100'
|
||||
: isDark
|
||||
? 'purple.900'
|
||||
: 'purple.100',
|
||||
color:
|
||||
currentSlot?.purpose === 'focus'
|
||||
? 'blue.700'
|
||||
? isDark
|
||||
? 'blue.200'
|
||||
: 'blue.700'
|
||||
: currentSlot?.purpose === 'reinforce'
|
||||
? 'orange.700'
|
||||
? isDark
|
||||
? 'orange.200'
|
||||
: 'orange.700'
|
||||
: currentSlot?.purpose === 'review'
|
||||
? 'green.700'
|
||||
: 'purple.700',
|
||||
? isDark
|
||||
? 'green.200'
|
||||
: 'green.700'
|
||||
: isDark
|
||||
? 'purple.200'
|
||||
: 'purple.700',
|
||||
})}
|
||||
>
|
||||
{currentSlot?.purpose}
|
||||
@@ -856,6 +899,7 @@ export function ActiveSession({
|
||||
isFocused={!isPaused && !isSubmitting}
|
||||
isCompleted={feedback !== 'none'}
|
||||
correctAnswer={currentProblem.problem.answer}
|
||||
isDark={isDark}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -865,10 +909,10 @@ export function ActiveSession({
|
||||
data-section="term-help"
|
||||
className={css({
|
||||
padding: '1rem',
|
||||
backgroundColor: 'purple.50',
|
||||
backgroundColor: isDark ? 'purple.900' : 'purple.50',
|
||||
borderRadius: '12px',
|
||||
border: '2px solid',
|
||||
borderColor: 'purple.200',
|
||||
borderColor: isDark ? 'purple.700' : 'purple.200',
|
||||
minWidth: '200px',
|
||||
})}
|
||||
>
|
||||
@@ -884,7 +928,7 @@ export function ActiveSession({
|
||||
className={css({
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'purple.700',
|
||||
color: isDark ? 'purple.200' : 'purple.700',
|
||||
})}
|
||||
>
|
||||
{helpContext.term >= 0 ? '+' : ''}
|
||||
@@ -895,12 +939,12 @@ export function ActiveSession({
|
||||
onClick={handleDismissHelp}
|
||||
className={css({
|
||||
fontSize: '0.75rem',
|
||||
color: 'gray.500',
|
||||
color: isDark ? 'gray.400' : 'gray.500',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
padding: '0.25rem',
|
||||
_hover: { color: 'gray.700' },
|
||||
_hover: { color: isDark ? 'gray.200' : 'gray.700' },
|
||||
})}
|
||||
>
|
||||
✕
|
||||
@@ -928,8 +972,22 @@ export function ActiveSession({
|
||||
borderRadius: '8px',
|
||||
fontSize: '1.25rem',
|
||||
fontWeight: 'bold',
|
||||
backgroundColor: feedback === 'correct' ? 'green.100' : 'red.100',
|
||||
color: feedback === 'correct' ? 'green.700' : 'red.700',
|
||||
backgroundColor:
|
||||
feedback === 'correct'
|
||||
? isDark
|
||||
? 'green.900'
|
||||
: 'green.100'
|
||||
: isDark
|
||||
? 'red.900'
|
||||
: 'red.100',
|
||||
color:
|
||||
feedback === 'correct'
|
||||
? isDark
|
||||
? 'green.200'
|
||||
: 'green.700'
|
||||
: isDark
|
||||
? 'red.200'
|
||||
: 'red.700',
|
||||
})}
|
||||
>
|
||||
{feedback === 'correct'
|
||||
@@ -969,8 +1027,10 @@ export function ActiveSession({
|
||||
? 'purple.500'
|
||||
: buttonState === 'submit'
|
||||
? 'blue.500'
|
||||
: 'gray.300',
|
||||
color: buttonState === 'disabled' ? 'gray.500' : 'white',
|
||||
: isDark
|
||||
? 'gray.700'
|
||||
: 'gray.300',
|
||||
color: buttonState === 'disabled' ? (isDark ? 'gray.400' : 'gray.500') : 'white',
|
||||
opacity: buttonState === 'disabled' ? 0.5 : 1,
|
||||
_hover: {
|
||||
backgroundColor:
|
||||
@@ -978,7 +1038,9 @@ export function ActiveSession({
|
||||
? 'purple.600'
|
||||
: buttonState === 'submit'
|
||||
? 'blue.600'
|
||||
: 'gray.300',
|
||||
: isDark
|
||||
? 'gray.600'
|
||||
: 'gray.300',
|
||||
},
|
||||
})}
|
||||
>
|
||||
@@ -995,7 +1057,7 @@ export function ActiveSession({
|
||||
<div
|
||||
className={css({
|
||||
textAlign: 'center',
|
||||
color: 'gray.500',
|
||||
color: isDark ? 'gray.400' : 'gray.500',
|
||||
fontSize: '0.875rem',
|
||||
marginBottom: '1rem',
|
||||
})}
|
||||
@@ -1028,7 +1090,7 @@ export function ActiveSession({
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
backgroundColor: isDark ? 'rgba(0, 0, 0, 0.7)' : 'rgba(0, 0, 0, 0.5)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
@@ -1038,7 +1100,7 @@ export function ActiveSession({
|
||||
<div
|
||||
className={css({
|
||||
padding: '2rem',
|
||||
backgroundColor: 'white',
|
||||
backgroundColor: isDark ? 'gray.800' : 'white',
|
||||
borderRadius: '16px',
|
||||
textAlign: 'center',
|
||||
})}
|
||||
@@ -1055,7 +1117,7 @@ export function ActiveSession({
|
||||
className={css({
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'gray.800',
|
||||
color: isDark ? 'gray.100' : 'gray.800',
|
||||
marginBottom: '0.5rem',
|
||||
})}
|
||||
>
|
||||
@@ -1064,7 +1126,7 @@ export function ActiveSession({
|
||||
<div
|
||||
className={css({
|
||||
fontSize: '1rem',
|
||||
color: 'gray.600',
|
||||
color: isDark ? 'gray.400' : 'gray.600',
|
||||
})}
|
||||
>
|
||||
Take a break! Tap Resume when ready.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useTheme } from '@/contexts/ThemeContext'
|
||||
import {
|
||||
AbacusReact,
|
||||
calculateBeadDiffFromValues,
|
||||
@@ -51,6 +52,8 @@ export function HelpAbacus({
|
||||
onValueChange,
|
||||
interactive = false,
|
||||
}: HelpAbacusProps) {
|
||||
const { resolvedTheme } = useTheme()
|
||||
const isDark = resolvedTheme === 'dark'
|
||||
const { config: abacusConfig } = useAbacusDisplay()
|
||||
const [currentStep] = useState(0)
|
||||
|
||||
@@ -115,10 +118,10 @@ export function HelpAbacus({
|
||||
return {
|
||||
// Subtle background to indicate this is a help visualization
|
||||
frame: {
|
||||
fill: 'rgba(59, 130, 246, 0.05)',
|
||||
fill: isDark ? 'rgba(59, 130, 246, 0.15)' : 'rgba(59, 130, 246, 0.05)',
|
||||
},
|
||||
}
|
||||
}, [])
|
||||
}, [isDark])
|
||||
|
||||
if (!hasChanges) {
|
||||
return (
|
||||
@@ -128,7 +131,7 @@ export function HelpAbacus({
|
||||
className={css({
|
||||
textAlign: 'center',
|
||||
padding: '1rem',
|
||||
color: 'green.600',
|
||||
color: isDark ? 'green.400' : 'green.600',
|
||||
fontSize: '0.875rem',
|
||||
})}
|
||||
>
|
||||
@@ -153,10 +156,10 @@ export function HelpAbacus({
|
||||
data-element="help-summary"
|
||||
className={css({
|
||||
padding: '0.5rem 1rem',
|
||||
backgroundColor: 'blue.50',
|
||||
backgroundColor: isDark ? 'blue.900' : 'blue.50',
|
||||
borderRadius: '8px',
|
||||
fontSize: '0.875rem',
|
||||
color: 'blue.700',
|
||||
color: isDark ? 'blue.200' : 'blue.700',
|
||||
fontWeight: 'medium',
|
||||
textAlign: 'center',
|
||||
})}
|
||||
@@ -169,10 +172,10 @@ export function HelpAbacus({
|
||||
<div
|
||||
className={css({
|
||||
padding: '1rem',
|
||||
backgroundColor: 'white',
|
||||
backgroundColor: isDark ? 'gray.800' : 'white',
|
||||
borderRadius: '12px',
|
||||
border: '2px solid',
|
||||
borderColor: 'blue.200',
|
||||
borderColor: isDark ? 'blue.700' : 'blue.200',
|
||||
boxShadow: 'md',
|
||||
})}
|
||||
>
|
||||
@@ -203,18 +206,44 @@ export function HelpAbacus({
|
||||
fontSize: '0.875rem',
|
||||
})}
|
||||
>
|
||||
<div className={css({ color: isAtTarget ? 'green.600' : 'gray.600' })}>
|
||||
<div
|
||||
className={css({
|
||||
color: isAtTarget ? (isDark ? 'green.400' : 'green.600') : isDark ? 'gray.400' : 'gray.600',
|
||||
})}
|
||||
>
|
||||
Current:{' '}
|
||||
<span
|
||||
className={css({ fontWeight: 'bold', color: isAtTarget ? 'green.700' : 'gray.800' })}
|
||||
className={css({
|
||||
fontWeight: 'bold',
|
||||
color: isAtTarget
|
||||
? isDark
|
||||
? 'green.300'
|
||||
: 'green.700'
|
||||
: isDark
|
||||
? 'gray.200'
|
||||
: 'gray.800',
|
||||
})}
|
||||
>
|
||||
{displayedValue}
|
||||
</span>
|
||||
</div>
|
||||
<div className={css({ color: isAtTarget ? 'green.600' : 'blue.600' })}>
|
||||
<div
|
||||
className={css({
|
||||
color: isAtTarget ? (isDark ? 'green.400' : 'green.600') : isDark ? 'blue.400' : 'blue.600',
|
||||
})}
|
||||
>
|
||||
Target:{' '}
|
||||
<span
|
||||
className={css({ fontWeight: 'bold', color: isAtTarget ? 'green.700' : 'blue.800' })}
|
||||
className={css({
|
||||
fontWeight: 'bold',
|
||||
color: isAtTarget
|
||||
? isDark
|
||||
? 'green.300'
|
||||
: 'green.700'
|
||||
: isDark
|
||||
? 'blue.300'
|
||||
: 'blue.800',
|
||||
})}
|
||||
>
|
||||
{targetValue}
|
||||
</span>
|
||||
@@ -227,10 +256,10 @@ export function HelpAbacus({
|
||||
data-element="target-reached"
|
||||
className={css({
|
||||
padding: '0.5rem 1rem',
|
||||
backgroundColor: 'green.100',
|
||||
backgroundColor: isDark ? 'green.900' : 'green.100',
|
||||
borderRadius: '8px',
|
||||
fontSize: '0.875rem',
|
||||
color: 'green.700',
|
||||
color: isDark ? 'green.200' : 'green.700',
|
||||
fontWeight: 'bold',
|
||||
textAlign: 'center',
|
||||
})}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import * as Dialog from '@radix-ui/react-dialog'
|
||||
import * as Accordion from '@radix-ui/react-accordion'
|
||||
import * as Dialog from '@radix-ui/react-dialog'
|
||||
import { useState } from 'react'
|
||||
import { useTheme } from '@/contexts/ThemeContext'
|
||||
import { css } from '../../../styled-system/css'
|
||||
|
||||
/**
|
||||
@@ -185,6 +186,8 @@ export function ManualSkillSelector({
|
||||
currentMasteredSkills = [],
|
||||
onSave,
|
||||
}: ManualSkillSelectorProps) {
|
||||
const { resolvedTheme } = useTheme()
|
||||
const isDark = resolvedTheme === 'dark'
|
||||
const [selectedSkills, setSelectedSkills] = useState<Set<string>>(new Set(currentMasteredSkills))
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
const [expandedCategories, setExpandedCategories] = useState<string[]>([])
|
||||
@@ -276,7 +279,7 @@ export function ManualSkillSelector({
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
bg: 'white',
|
||||
bg: isDark ? 'gray.800' : 'white',
|
||||
borderRadius: 'xl',
|
||||
boxShadow: 'xl',
|
||||
p: '6',
|
||||
@@ -293,7 +296,7 @@ export function ManualSkillSelector({
|
||||
className={css({
|
||||
fontSize: 'xl',
|
||||
fontWeight: 'bold',
|
||||
color: 'gray.900',
|
||||
color: isDark ? 'gray.100' : 'gray.900',
|
||||
})}
|
||||
>
|
||||
Set Skills for {studentName}
|
||||
@@ -301,7 +304,7 @@ export function ManualSkillSelector({
|
||||
<Dialog.Description
|
||||
className={css({
|
||||
fontSize: 'sm',
|
||||
color: 'gray.600',
|
||||
color: isDark ? 'gray.400' : 'gray.600',
|
||||
mt: '1',
|
||||
})}
|
||||
>
|
||||
@@ -318,7 +321,7 @@ export function ManualSkillSelector({
|
||||
display: 'block',
|
||||
fontSize: 'sm',
|
||||
fontWeight: 'semibold',
|
||||
color: 'gray.700',
|
||||
color: isDark ? 'gray.300' : 'gray.700',
|
||||
mb: '2',
|
||||
})}
|
||||
>
|
||||
@@ -333,9 +336,10 @@ export function ManualSkillSelector({
|
||||
px: '3',
|
||||
py: '2',
|
||||
border: '1px solid',
|
||||
borderColor: 'gray.300',
|
||||
borderColor: isDark ? 'gray.600' : 'gray.300',
|
||||
borderRadius: 'md',
|
||||
bg: 'white',
|
||||
bg: isDark ? 'gray.700' : 'white',
|
||||
color: isDark ? 'gray.100' : 'gray.900',
|
||||
fontSize: 'sm',
|
||||
cursor: 'pointer',
|
||||
_focus: {
|
||||
@@ -359,7 +363,7 @@ export function ManualSkillSelector({
|
||||
<div
|
||||
className={css({
|
||||
fontSize: 'sm',
|
||||
color: 'gray.600',
|
||||
color: isDark ? 'gray.400' : 'gray.600',
|
||||
mb: '3',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
@@ -374,7 +378,7 @@ export function ManualSkillSelector({
|
||||
onClick={() => setSelectedSkills(new Set())}
|
||||
className={css({
|
||||
fontSize: 'xs',
|
||||
color: 'red.600',
|
||||
color: isDark ? 'red.400' : 'red.600',
|
||||
bg: 'transparent',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
@@ -392,7 +396,7 @@ export function ManualSkillSelector({
|
||||
onValueChange={setExpandedCategories}
|
||||
className={css({
|
||||
border: '1px solid',
|
||||
borderColor: 'gray.200',
|
||||
borderColor: isDark ? 'gray.600' : 'gray.200',
|
||||
borderRadius: 'lg',
|
||||
overflow: 'hidden',
|
||||
})}
|
||||
@@ -418,7 +422,7 @@ export function ManualSkillSelector({
|
||||
value={categoryKey}
|
||||
className={css({
|
||||
borderBottom: '1px solid',
|
||||
borderColor: 'gray.200',
|
||||
borderColor: isDark ? 'gray.600' : 'gray.200',
|
||||
_last: { borderBottom: 'none' },
|
||||
})}
|
||||
>
|
||||
@@ -430,11 +434,11 @@ export function ManualSkillSelector({
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '12px 16px',
|
||||
bg: 'gray.50',
|
||||
bg: isDark ? 'gray.700' : 'gray.50',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
textAlign: 'left',
|
||||
_hover: { bg: 'gray.100' },
|
||||
_hover: { bg: isDark ? 'gray.600' : 'gray.100' },
|
||||
})}
|
||||
>
|
||||
<div
|
||||
@@ -464,7 +468,7 @@ export function ManualSkillSelector({
|
||||
<span
|
||||
className={css({
|
||||
fontWeight: 'semibold',
|
||||
color: 'gray.800',
|
||||
color: isDark ? 'gray.100' : 'gray.800',
|
||||
})}
|
||||
>
|
||||
{category.name}
|
||||
@@ -480,7 +484,7 @@ export function ManualSkillSelector({
|
||||
<span
|
||||
className={css({
|
||||
fontSize: 'xs',
|
||||
color: 'gray.500',
|
||||
color: isDark ? 'gray.400' : 'gray.500',
|
||||
})}
|
||||
>
|
||||
{selectedInCategory}/{categorySkillIds.length}
|
||||
@@ -498,7 +502,7 @@ export function ManualSkillSelector({
|
||||
<Accordion.Content
|
||||
className={css({
|
||||
overflow: 'hidden',
|
||||
bg: 'white',
|
||||
bg: isDark ? 'gray.800' : 'white',
|
||||
})}
|
||||
>
|
||||
<div className={css({ p: '3' })}>
|
||||
@@ -516,7 +520,7 @@ export function ManualSkillSelector({
|
||||
padding: '8px 12px',
|
||||
borderRadius: 'md',
|
||||
cursor: 'pointer',
|
||||
_hover: { bg: 'gray.50' },
|
||||
_hover: { bg: isDark ? 'gray.700' : 'gray.50' },
|
||||
})}
|
||||
>
|
||||
<input
|
||||
@@ -532,7 +536,13 @@ export function ManualSkillSelector({
|
||||
<span
|
||||
className={css({
|
||||
fontSize: 'sm',
|
||||
color: isSelected ? 'green.700' : 'gray.700',
|
||||
color: isSelected
|
||||
? isDark
|
||||
? 'green.400'
|
||||
: 'green.700'
|
||||
: isDark
|
||||
? 'gray.300'
|
||||
: 'gray.700',
|
||||
fontWeight: isSelected ? 'medium' : 'normal',
|
||||
})}
|
||||
>
|
||||
@@ -542,8 +552,8 @@ export function ManualSkillSelector({
|
||||
<span
|
||||
className={css({
|
||||
fontSize: 'xs',
|
||||
color: 'green.600',
|
||||
bg: 'green.50',
|
||||
color: isDark ? 'green.300' : 'green.600',
|
||||
bg: isDark ? 'green.900' : 'green.50',
|
||||
px: '2',
|
||||
py: '0.5',
|
||||
borderRadius: 'full',
|
||||
@@ -581,13 +591,13 @@ export function ManualSkillSelector({
|
||||
py: '2',
|
||||
fontSize: 'sm',
|
||||
fontWeight: 'medium',
|
||||
color: 'gray.700',
|
||||
color: isDark ? 'gray.300' : 'gray.700',
|
||||
bg: 'transparent',
|
||||
border: '1px solid',
|
||||
borderColor: 'gray.300',
|
||||
borderColor: isDark ? 'gray.600' : 'gray.300',
|
||||
borderRadius: 'md',
|
||||
cursor: 'pointer',
|
||||
_hover: { bg: 'gray.50' },
|
||||
_hover: { bg: isDark ? 'gray.700' : 'gray.50' },
|
||||
_disabled: { opacity: 0.5, cursor: 'not-allowed' },
|
||||
})}
|
||||
>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useTheme } from '@/contexts/ThemeContext'
|
||||
import { useCallback, useRef } from 'react'
|
||||
import Keyboard from 'react-simple-keyboard'
|
||||
import 'react-simple-keyboard/build/css/index.css'
|
||||
@@ -29,6 +30,8 @@ export function NumericKeypad({
|
||||
disabled = false,
|
||||
currentValue = '',
|
||||
}: NumericKeypadProps) {
|
||||
const { resolvedTheme } = useTheme()
|
||||
const isDark = resolvedTheme === 'dark'
|
||||
const keyboardRef = useRef<any>(null)
|
||||
|
||||
// Numeric layout with backspace and submit
|
||||
@@ -69,20 +72,20 @@ export function NumericKeypad({
|
||||
>
|
||||
<style>{`
|
||||
.practice-numeric-keyboard .simple-keyboard {
|
||||
background: #f8fafc;
|
||||
background: ${isDark ? '#1f2937' : '#f8fafc'};
|
||||
border-radius: 12px;
|
||||
padding: 8px;
|
||||
border: 1px solid #e2e8f0;
|
||||
border: 1px solid ${isDark ? '#374151' : '#e2e8f0'};
|
||||
}
|
||||
.practice-numeric-keyboard .hg-button {
|
||||
height: 56px;
|
||||
border-radius: 8px;
|
||||
background: white;
|
||||
color: #1e293b;
|
||||
border: 1px solid #e2e8f0;
|
||||
background: ${isDark ? '#374151' : 'white'};
|
||||
color: ${isDark ? '#f3f4f6' : '#1e293b'};
|
||||
border: 1px solid ${isDark ? '#4b5563' : '#e2e8f0'};
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: ${isDark ? '0 1px 3px rgba(0, 0, 0, 0.3)' : '0 1px 3px rgba(0, 0, 0, 0.1)'};
|
||||
transition: all 0.1s ease;
|
||||
flex: 1;
|
||||
margin: 3px;
|
||||
@@ -93,18 +96,18 @@ export function NumericKeypad({
|
||||
transform: scale(0.95);
|
||||
}
|
||||
.practice-numeric-keyboard .hg-button[data-skbtn="{bksp}"] {
|
||||
background: #fee2e2;
|
||||
color: #dc2626;
|
||||
border-color: #fecaca;
|
||||
background: ${isDark ? '#7f1d1d' : '#fee2e2'};
|
||||
color: ${isDark ? '#fca5a5' : '#dc2626'};
|
||||
border-color: ${isDark ? '#991b1b' : '#fecaca'};
|
||||
}
|
||||
.practice-numeric-keyboard .hg-button[data-skbtn="{bksp}"]:active {
|
||||
background: #dc2626;
|
||||
color: white;
|
||||
}
|
||||
.practice-numeric-keyboard .hg-button[data-skbtn="{enter}"] {
|
||||
background: #dcfce7;
|
||||
color: #16a34a;
|
||||
border-color: #bbf7d0;
|
||||
background: ${isDark ? '#14532d' : '#dcfce7'};
|
||||
color: ${isDark ? '#86efac' : '#16a34a'};
|
||||
border-color: ${isDark ? '#166534' : '#bbf7d0'};
|
||||
}
|
||||
.practice-numeric-keyboard .hg-button[data-skbtn="{enter}"]:active {
|
||||
background: #16a34a;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import * as Dialog from '@radix-ui/react-dialog'
|
||||
import { useState } from 'react'
|
||||
import { useTheme } from '@/contexts/ThemeContext'
|
||||
import { css } from '../../../styled-system/css'
|
||||
|
||||
/**
|
||||
@@ -52,6 +53,8 @@ export function OfflineSessionForm({
|
||||
playerId,
|
||||
onSubmit,
|
||||
}: OfflineSessionFormProps) {
|
||||
const { resolvedTheme } = useTheme()
|
||||
const isDark = resolvedTheme === 'dark'
|
||||
// Form state
|
||||
const [date, setDate] = useState(() => new Date().toISOString().split('T')[0])
|
||||
const [problemCount, setProblemCount] = useState(20)
|
||||
@@ -140,7 +143,7 @@ export function OfflineSessionForm({
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
bg: 'white',
|
||||
bg: isDark ? 'gray.800' : 'white',
|
||||
borderRadius: 'xl',
|
||||
boxShadow: 'xl',
|
||||
p: '6',
|
||||
@@ -157,7 +160,7 @@ export function OfflineSessionForm({
|
||||
className={css({
|
||||
fontSize: 'xl',
|
||||
fontWeight: 'bold',
|
||||
color: 'gray.900',
|
||||
color: isDark ? 'gray.100' : 'gray.900',
|
||||
})}
|
||||
>
|
||||
Record Offline Practice
|
||||
@@ -165,7 +168,7 @@ export function OfflineSessionForm({
|
||||
<Dialog.Description
|
||||
className={css({
|
||||
fontSize: 'sm',
|
||||
color: 'gray.600',
|
||||
color: isDark ? 'gray.400' : 'gray.600',
|
||||
mt: '1',
|
||||
})}
|
||||
>
|
||||
@@ -183,7 +186,7 @@ export function OfflineSessionForm({
|
||||
display: 'block',
|
||||
fontSize: 'sm',
|
||||
fontWeight: 'semibold',
|
||||
color: 'gray.700',
|
||||
color: isDark ? 'gray.300' : 'gray.700',
|
||||
mb: '1',
|
||||
})}
|
||||
>
|
||||
@@ -203,9 +206,11 @@ export function OfflineSessionForm({
|
||||
px: '3',
|
||||
py: '2',
|
||||
border: '1px solid',
|
||||
borderColor: errors.date ? 'red.500' : 'gray.300',
|
||||
borderColor: errors.date ? 'red.500' : isDark ? 'gray.600' : 'gray.300',
|
||||
borderRadius: 'md',
|
||||
fontSize: 'sm',
|
||||
bg: isDark ? 'gray.700' : 'white',
|
||||
color: isDark ? 'gray.100' : 'gray.900',
|
||||
_focus: {
|
||||
outline: 'none',
|
||||
borderColor: 'blue.500',
|
||||
@@ -215,7 +220,15 @@ export function OfflineSessionForm({
|
||||
})}
|
||||
/>
|
||||
{errors.date && (
|
||||
<p className={css({ fontSize: 'xs', color: 'red.600', mt: '1' })}>{errors.date}</p>
|
||||
<p
|
||||
className={css({
|
||||
fontSize: 'xs',
|
||||
color: isDark ? 'red.400' : 'red.600',
|
||||
mt: '1',
|
||||
})}
|
||||
>
|
||||
{errors.date}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -227,7 +240,7 @@ export function OfflineSessionForm({
|
||||
display: 'block',
|
||||
fontSize: 'sm',
|
||||
fontWeight: 'semibold',
|
||||
color: 'gray.700',
|
||||
color: isDark ? 'gray.300' : 'gray.700',
|
||||
mb: '1',
|
||||
})}
|
||||
>
|
||||
@@ -248,9 +261,11 @@ export function OfflineSessionForm({
|
||||
px: '3',
|
||||
py: '2',
|
||||
border: '1px solid',
|
||||
borderColor: errors.problemCount ? 'red.500' : 'gray.300',
|
||||
borderColor: errors.problemCount ? 'red.500' : isDark ? 'gray.600' : 'gray.300',
|
||||
borderRadius: 'md',
|
||||
fontSize: 'sm',
|
||||
bg: isDark ? 'gray.700' : 'white',
|
||||
color: isDark ? 'gray.100' : 'gray.900',
|
||||
_focus: {
|
||||
outline: 'none',
|
||||
borderColor: 'blue.500',
|
||||
@@ -260,7 +275,13 @@ export function OfflineSessionForm({
|
||||
})}
|
||||
/>
|
||||
{errors.problemCount && (
|
||||
<p className={css({ fontSize: 'xs', color: 'red.600', mt: '1' })}>
|
||||
<p
|
||||
className={css({
|
||||
fontSize: 'xs',
|
||||
color: isDark ? 'red.400' : 'red.600',
|
||||
mt: '1',
|
||||
})}
|
||||
>
|
||||
{errors.problemCount}
|
||||
</p>
|
||||
)}
|
||||
@@ -276,7 +297,7 @@ export function OfflineSessionForm({
|
||||
alignItems: 'baseline',
|
||||
fontSize: 'sm',
|
||||
fontWeight: 'semibold',
|
||||
color: 'gray.700',
|
||||
color: isDark ? 'gray.300' : 'gray.700',
|
||||
mb: '1',
|
||||
})}
|
||||
>
|
||||
@@ -284,7 +305,17 @@ export function OfflineSessionForm({
|
||||
<span
|
||||
className={css({
|
||||
fontWeight: 'bold',
|
||||
color: accuracy >= 85 ? 'green.600' : accuracy >= 70 ? 'yellow.600' : 'red.600',
|
||||
color: isDark
|
||||
? accuracy >= 85
|
||||
? 'green.400'
|
||||
: accuracy >= 70
|
||||
? 'yellow.400'
|
||||
: 'red.400'
|
||||
: accuracy >= 85
|
||||
? 'green.600'
|
||||
: accuracy >= 70
|
||||
? 'yellow.600'
|
||||
: 'red.600',
|
||||
})}
|
||||
>
|
||||
{accuracy}%
|
||||
@@ -311,7 +342,7 @@ export function OfflineSessionForm({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
fontSize: 'xs',
|
||||
color: 'gray.500',
|
||||
color: isDark ? 'gray.400' : 'gray.500',
|
||||
})}
|
||||
>
|
||||
<span>0%</span>
|
||||
@@ -321,7 +352,13 @@ export function OfflineSessionForm({
|
||||
<span>100%</span>
|
||||
</div>
|
||||
{errors.accuracy && (
|
||||
<p className={css({ fontSize: 'xs', color: 'red.600', mt: '1' })}>
|
||||
<p
|
||||
className={css({
|
||||
fontSize: 'xs',
|
||||
color: isDark ? 'red.400' : 'red.600',
|
||||
mt: '1',
|
||||
})}
|
||||
>
|
||||
{errors.accuracy}
|
||||
</p>
|
||||
)}
|
||||
@@ -335,7 +372,7 @@ export function OfflineSessionForm({
|
||||
display: 'block',
|
||||
fontSize: 'sm',
|
||||
fontWeight: 'semibold',
|
||||
color: 'gray.700',
|
||||
color: isDark ? 'gray.300' : 'gray.700',
|
||||
mb: '1',
|
||||
})}
|
||||
>
|
||||
@@ -353,9 +390,10 @@ export function OfflineSessionForm({
|
||||
px: '3',
|
||||
py: '2',
|
||||
border: '1px solid',
|
||||
borderColor: errors.focusSkill ? 'red.500' : 'gray.300',
|
||||
borderColor: errors.focusSkill ? 'red.500' : isDark ? 'gray.600' : 'gray.300',
|
||||
borderRadius: 'md',
|
||||
bg: 'white',
|
||||
bg: isDark ? 'gray.700' : 'white',
|
||||
color: isDark ? 'gray.100' : 'gray.900',
|
||||
fontSize: 'sm',
|
||||
cursor: 'pointer',
|
||||
_focus: {
|
||||
@@ -373,7 +411,13 @@ export function OfflineSessionForm({
|
||||
))}
|
||||
</select>
|
||||
{errors.focusSkill && (
|
||||
<p className={css({ fontSize: 'xs', color: 'red.600', mt: '1' })}>
|
||||
<p
|
||||
className={css({
|
||||
fontSize: 'xs',
|
||||
color: isDark ? 'red.400' : 'red.600',
|
||||
mt: '1',
|
||||
})}
|
||||
>
|
||||
{errors.focusSkill}
|
||||
</p>
|
||||
)}
|
||||
@@ -387,7 +431,7 @@ export function OfflineSessionForm({
|
||||
display: 'block',
|
||||
fontSize: 'sm',
|
||||
fontWeight: 'semibold',
|
||||
color: 'gray.700',
|
||||
color: isDark ? 'gray.300' : 'gray.700',
|
||||
mb: '1',
|
||||
})}
|
||||
>
|
||||
@@ -404,16 +448,21 @@ export function OfflineSessionForm({
|
||||
px: '3',
|
||||
py: '2',
|
||||
border: '1px solid',
|
||||
borderColor: 'gray.300',
|
||||
borderColor: isDark ? 'gray.600' : 'gray.300',
|
||||
borderRadius: 'md',
|
||||
fontSize: 'sm',
|
||||
resize: 'vertical',
|
||||
bg: isDark ? 'gray.700' : 'white',
|
||||
color: isDark ? 'gray.100' : 'gray.900',
|
||||
_focus: {
|
||||
outline: 'none',
|
||||
borderColor: 'blue.500',
|
||||
ring: '2px',
|
||||
ringColor: 'blue.500/20',
|
||||
},
|
||||
_placeholder: {
|
||||
color: isDark ? 'gray.500' : 'gray.400',
|
||||
},
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
@@ -422,13 +471,13 @@ export function OfflineSessionForm({
|
||||
<div
|
||||
className={css({
|
||||
p: '3',
|
||||
bg: 'blue.50',
|
||||
bg: isDark ? 'blue.900' : 'blue.50',
|
||||
borderRadius: 'md',
|
||||
border: '1px solid',
|
||||
borderColor: 'blue.100',
|
||||
borderColor: isDark ? 'blue.700' : 'blue.100',
|
||||
})}
|
||||
>
|
||||
<p className={css({ fontSize: 'sm', color: 'blue.800' })}>
|
||||
<p className={css({ fontSize: 'sm', color: isDark ? 'blue.200' : 'blue.800' })}>
|
||||
This will record <strong>{problemCount} problems</strong> at{' '}
|
||||
<strong>{accuracy}% accuracy</strong> (~{estimatedCorrect} correct) for{' '}
|
||||
<strong>{studentName}</strong> on{' '}
|
||||
@@ -456,13 +505,13 @@ export function OfflineSessionForm({
|
||||
py: '2',
|
||||
fontSize: 'sm',
|
||||
fontWeight: 'medium',
|
||||
color: 'gray.700',
|
||||
color: isDark ? 'gray.300' : 'gray.700',
|
||||
bg: 'transparent',
|
||||
border: '1px solid',
|
||||
borderColor: 'gray.300',
|
||||
borderColor: isDark ? 'gray.600' : 'gray.300',
|
||||
borderRadius: 'md',
|
||||
cursor: 'pointer',
|
||||
_hover: { bg: 'gray.50' },
|
||||
_hover: { bg: isDark ? 'gray.700' : 'gray.50' },
|
||||
_disabled: { opacity: 0.5, cursor: 'not-allowed' },
|
||||
})}
|
||||
>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { css } from '../../../styled-system/css'
|
||||
import { useTheme } from '@/contexts/ThemeContext'
|
||||
import {
|
||||
DEFAULT_THRESHOLDS,
|
||||
generateProblemForSkill,
|
||||
@@ -9,12 +9,13 @@ import {
|
||||
initializePlacementTest,
|
||||
type PlacementTestState,
|
||||
type PlacementThresholds,
|
||||
type PresetKey,
|
||||
recordAnswer,
|
||||
SKILL_NAMES,
|
||||
SKILL_ORDER,
|
||||
THRESHOLD_PRESETS,
|
||||
type PresetKey,
|
||||
} from '@/lib/curriculum/placement-test'
|
||||
import { css } from '../../../styled-system/css'
|
||||
import { NumericKeypad } from './NumericKeypad'
|
||||
import { VerticalProblem } from './VerticalProblem'
|
||||
|
||||
@@ -54,6 +55,8 @@ export function PlacementTest({
|
||||
onCancel,
|
||||
initialThresholds = DEFAULT_THRESHOLDS,
|
||||
}: PlacementTestProps) {
|
||||
const { resolvedTheme } = useTheme()
|
||||
const isDark = resolvedTheme === 'dark'
|
||||
const [phase, setPhase] = useState<TestPhase>('setup')
|
||||
const [thresholds, setThresholds] = useState<PlacementThresholds>(initialThresholds)
|
||||
const [selectedPreset, setSelectedPreset] = useState<PresetKey>('standard')
|
||||
@@ -166,7 +169,7 @@ export function PlacementTest({
|
||||
className={css({
|
||||
fontSize: '1.75rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'gray.800',
|
||||
color: isDark ? 'gray.100' : 'gray.800',
|
||||
textAlign: 'center',
|
||||
})}
|
||||
>
|
||||
@@ -176,7 +179,7 @@ export function PlacementTest({
|
||||
<p
|
||||
className={css({
|
||||
fontSize: '1rem',
|
||||
color: 'gray.600',
|
||||
color: isDark ? 'gray.400' : 'gray.600',
|
||||
textAlign: 'center',
|
||||
})}
|
||||
>
|
||||
@@ -190,7 +193,7 @@ export function PlacementTest({
|
||||
display: 'block',
|
||||
fontSize: 'sm',
|
||||
fontWeight: 'semibold',
|
||||
color: 'gray.700',
|
||||
color: isDark ? 'gray.300' : 'gray.700',
|
||||
mb: '2',
|
||||
})}
|
||||
>
|
||||
@@ -213,19 +216,35 @@ export function PlacementTest({
|
||||
px: '3',
|
||||
borderRadius: 'lg',
|
||||
border: '2px solid',
|
||||
borderColor: selectedPreset === key ? 'blue.500' : 'gray.200',
|
||||
bg: selectedPreset === key ? 'blue.50' : 'white',
|
||||
borderColor:
|
||||
selectedPreset === key ? 'blue.500' : isDark ? 'gray.600' : 'gray.200',
|
||||
bg:
|
||||
selectedPreset === key
|
||||
? isDark
|
||||
? 'blue.900'
|
||||
: 'blue.50'
|
||||
: isDark
|
||||
? 'gray.800'
|
||||
: 'white',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s',
|
||||
_hover: {
|
||||
borderColor: selectedPreset === key ? 'blue.500' : 'gray.300',
|
||||
borderColor:
|
||||
selectedPreset === key ? 'blue.500' : isDark ? 'gray.500' : 'gray.300',
|
||||
},
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={css({
|
||||
fontWeight: 'bold',
|
||||
color: selectedPreset === key ? 'blue.700' : 'gray.800',
|
||||
color:
|
||||
selectedPreset === key
|
||||
? isDark
|
||||
? 'blue.200'
|
||||
: 'blue.700'
|
||||
: isDark
|
||||
? 'gray.100'
|
||||
: 'gray.800',
|
||||
fontSize: 'sm',
|
||||
})}
|
||||
>
|
||||
@@ -234,7 +253,14 @@ export function PlacementTest({
|
||||
<div
|
||||
className={css({
|
||||
fontSize: 'xs',
|
||||
color: selectedPreset === key ? 'blue.600' : 'gray.500',
|
||||
color:
|
||||
selectedPreset === key
|
||||
? isDark
|
||||
? 'blue.300'
|
||||
: 'blue.600'
|
||||
: isDark
|
||||
? 'gray.400'
|
||||
: 'gray.500',
|
||||
mt: '1',
|
||||
})}
|
||||
>
|
||||
@@ -250,17 +276,17 @@ export function PlacementTest({
|
||||
className={css({
|
||||
width: '100%',
|
||||
p: '4',
|
||||
bg: 'gray.50',
|
||||
bg: isDark ? 'gray.700' : 'gray.50',
|
||||
borderRadius: 'lg',
|
||||
border: '1px solid',
|
||||
borderColor: 'gray.200',
|
||||
borderColor: isDark ? 'gray.600' : 'gray.200',
|
||||
})}
|
||||
>
|
||||
<h3
|
||||
className={css({
|
||||
fontSize: 'sm',
|
||||
fontWeight: 'semibold',
|
||||
color: 'gray.700',
|
||||
color: isDark ? 'gray.300' : 'gray.700',
|
||||
mb: '2',
|
||||
})}
|
||||
>
|
||||
@@ -269,7 +295,7 @@ export function PlacementTest({
|
||||
<ul
|
||||
className={css({
|
||||
fontSize: 'sm',
|
||||
color: 'gray.600',
|
||||
color: isDark ? 'gray.400' : 'gray.600',
|
||||
listStyle: 'none',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
@@ -295,12 +321,12 @@ export function PlacementTest({
|
||||
py: '3',
|
||||
fontSize: '1rem',
|
||||
fontWeight: 'medium',
|
||||
color: 'gray.700',
|
||||
bg: 'gray.100',
|
||||
color: isDark ? 'gray.300' : 'gray.700',
|
||||
bg: isDark ? 'gray.700' : 'gray.100',
|
||||
borderRadius: '8px',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
_hover: { bg: 'gray.200' },
|
||||
_hover: { bg: isDark ? 'gray.600' : 'gray.200' },
|
||||
})}
|
||||
>
|
||||
Cancel
|
||||
@@ -365,8 +391,10 @@ export function PlacementTest({
|
||||
mb: '2',
|
||||
})}
|
||||
>
|
||||
<span className={css({ fontSize: 'sm', color: 'gray.600' })}>Testing: {skillName}</span>
|
||||
<span className={css({ fontSize: 'sm', color: 'gray.500' })}>
|
||||
<span className={css({ fontSize: 'sm', color: isDark ? 'gray.400' : 'gray.600' })}>
|
||||
Testing: {skillName}
|
||||
</span>
|
||||
<span className={css({ fontSize: 'sm', color: isDark ? 'gray.500' : 'gray.500' })}>
|
||||
{testState.problemsAnswered} problems answered
|
||||
</span>
|
||||
</div>
|
||||
@@ -374,7 +402,7 @@ export function PlacementTest({
|
||||
className={css({
|
||||
width: '100%',
|
||||
height: '8px',
|
||||
bg: 'gray.200',
|
||||
bg: isDark ? 'gray.700' : 'gray.200',
|
||||
borderRadius: '4px',
|
||||
overflow: 'hidden',
|
||||
})}
|
||||
@@ -395,7 +423,7 @@ export function PlacementTest({
|
||||
gap: '3',
|
||||
mt: '2',
|
||||
fontSize: 'xs',
|
||||
color: 'gray.500',
|
||||
color: isDark ? 'gray.400' : 'gray.500',
|
||||
})}
|
||||
>
|
||||
<span>
|
||||
@@ -426,7 +454,13 @@ export function PlacementTest({
|
||||
className={css({
|
||||
fontSize: '2rem',
|
||||
fontWeight: 'bold',
|
||||
color: lastAnswerCorrect ? 'green.600' : 'red.600',
|
||||
color: lastAnswerCorrect
|
||||
? isDark
|
||||
? 'green.400'
|
||||
: 'green.600'
|
||||
: isDark
|
||||
? 'red.400'
|
||||
: 'red.600',
|
||||
animation: 'pulse 0.5s ease-in-out',
|
||||
})}
|
||||
>
|
||||
@@ -451,11 +485,11 @@ export function PlacementTest({
|
||||
className={css({
|
||||
mt: '2',
|
||||
fontSize: 'sm',
|
||||
color: 'gray.500',
|
||||
color: isDark ? 'gray.400' : 'gray.500',
|
||||
bg: 'transparent',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
_hover: { color: 'gray.700', textDecoration: 'underline' },
|
||||
_hover: { color: isDark ? 'gray.200' : 'gray.700', textDecoration: 'underline' },
|
||||
})}
|
||||
>
|
||||
End Test Early
|
||||
@@ -487,13 +521,13 @@ export function PlacementTest({
|
||||
className={css({
|
||||
fontSize: '2rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'gray.800',
|
||||
color: isDark ? 'gray.100' : 'gray.800',
|
||||
mb: '2',
|
||||
})}
|
||||
>
|
||||
Placement Complete!
|
||||
</h1>
|
||||
<p className={css({ fontSize: '1.25rem', color: 'blue.600' })}>
|
||||
<p className={css({ fontSize: '1.25rem', color: isDark ? 'blue.300' : 'blue.600' })}>
|
||||
{studentName} placed at: <strong>{results.suggestedLevel}</strong>
|
||||
</p>
|
||||
</div>
|
||||
@@ -510,7 +544,7 @@ export function PlacementTest({
|
||||
className={css({
|
||||
flex: 1,
|
||||
p: '3',
|
||||
bg: 'green.50',
|
||||
bg: isDark ? 'green.900' : 'green.50',
|
||||
borderRadius: 'lg',
|
||||
textAlign: 'center',
|
||||
})}
|
||||
@@ -519,18 +553,20 @@ export function PlacementTest({
|
||||
className={css({
|
||||
fontSize: '2rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'green.700',
|
||||
color: isDark ? 'green.200' : 'green.700',
|
||||
})}
|
||||
>
|
||||
{results.masteredSkills.length}
|
||||
</div>
|
||||
<div className={css({ fontSize: 'sm', color: 'green.600' })}>Skills Mastered</div>
|
||||
<div className={css({ fontSize: 'sm', color: isDark ? 'green.300' : 'green.600' })}>
|
||||
Skills Mastered
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={css({
|
||||
flex: 1,
|
||||
p: '3',
|
||||
bg: 'yellow.50',
|
||||
bg: isDark ? 'yellow.900' : 'yellow.50',
|
||||
borderRadius: 'lg',
|
||||
textAlign: 'center',
|
||||
})}
|
||||
@@ -539,18 +575,20 @@ export function PlacementTest({
|
||||
className={css({
|
||||
fontSize: '2rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'yellow.700',
|
||||
color: isDark ? 'yellow.200' : 'yellow.700',
|
||||
})}
|
||||
>
|
||||
{results.practicingSkills.length}
|
||||
</div>
|
||||
<div className={css({ fontSize: 'sm', color: 'yellow.600' })}>Skills Practicing</div>
|
||||
<div className={css({ fontSize: 'sm', color: isDark ? 'yellow.300' : 'yellow.600' })}>
|
||||
Skills Practicing
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={css({
|
||||
flex: 1,
|
||||
p: '3',
|
||||
bg: 'blue.50',
|
||||
bg: isDark ? 'blue.900' : 'blue.50',
|
||||
borderRadius: 'lg',
|
||||
textAlign: 'center',
|
||||
})}
|
||||
@@ -559,12 +597,14 @@ export function PlacementTest({
|
||||
className={css({
|
||||
fontSize: '2rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'blue.700',
|
||||
color: isDark ? 'blue.200' : 'blue.700',
|
||||
})}
|
||||
>
|
||||
{Math.round(results.overallAccuracy * 100)}%
|
||||
</div>
|
||||
<div className={css({ fontSize: 'sm', color: 'blue.600' })}>Accuracy</div>
|
||||
<div className={css({ fontSize: 'sm', color: isDark ? 'blue.300' : 'blue.600' })}>
|
||||
Accuracy
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -575,7 +615,7 @@ export function PlacementTest({
|
||||
className={css({
|
||||
fontSize: 'sm',
|
||||
fontWeight: 'semibold',
|
||||
color: 'gray.700',
|
||||
color: isDark ? 'gray.300' : 'gray.700',
|
||||
mb: '2',
|
||||
})}
|
||||
>
|
||||
@@ -588,8 +628,8 @@ export function PlacementTest({
|
||||
className={css({
|
||||
px: '3',
|
||||
py: '1',
|
||||
bg: 'green.100',
|
||||
color: 'green.700',
|
||||
bg: isDark ? 'green.900' : 'green.100',
|
||||
color: isDark ? 'green.200' : 'green.700',
|
||||
borderRadius: 'full',
|
||||
fontSize: 'xs',
|
||||
fontWeight: 'medium',
|
||||
@@ -608,7 +648,7 @@ export function PlacementTest({
|
||||
className={css({
|
||||
fontSize: 'sm',
|
||||
fontWeight: 'semibold',
|
||||
color: 'gray.700',
|
||||
color: isDark ? 'gray.300' : 'gray.700',
|
||||
mb: '2',
|
||||
})}
|
||||
>
|
||||
@@ -621,8 +661,8 @@ export function PlacementTest({
|
||||
className={css({
|
||||
px: '3',
|
||||
py: '1',
|
||||
bg: 'yellow.100',
|
||||
color: 'yellow.700',
|
||||
bg: isDark ? 'yellow.900' : 'yellow.100',
|
||||
color: isDark ? 'yellow.200' : 'yellow.700',
|
||||
borderRadius: 'full',
|
||||
fontSize: 'xs',
|
||||
fontWeight: 'medium',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useTheme } from '@/contexts/ThemeContext'
|
||||
import type {
|
||||
PartSummary,
|
||||
ProblemSlot,
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
SessionPlan,
|
||||
SessionSummary,
|
||||
} from '@/db/schema/session-plans'
|
||||
import { useState } from 'react'
|
||||
import { css } from '../../../styled-system/css'
|
||||
|
||||
interface PlanReviewProps {
|
||||
@@ -32,20 +33,29 @@ function getPartTypeEmoji(type: SessionPart['type']): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get part type colors
|
||||
* Get part type colors (dark mode aware)
|
||||
*/
|
||||
function getPartTypeColors(type: SessionPart['type']): {
|
||||
function getPartTypeColors(
|
||||
type: SessionPart['type'],
|
||||
isDark: boolean
|
||||
): {
|
||||
bg: string
|
||||
border: string
|
||||
text: string
|
||||
} {
|
||||
switch (type) {
|
||||
case 'abacus':
|
||||
return { bg: 'blue.50', border: 'blue.200', text: 'blue.700' }
|
||||
return isDark
|
||||
? { bg: 'blue.900', border: 'blue.700', text: 'blue.200' }
|
||||
: { bg: 'blue.50', border: 'blue.200', text: 'blue.700' }
|
||||
case 'visualization':
|
||||
return { bg: 'purple.50', border: 'purple.200', text: 'purple.700' }
|
||||
return isDark
|
||||
? { bg: 'purple.900', border: 'purple.700', text: 'purple.200' }
|
||||
: { bg: 'purple.50', border: 'purple.200', text: 'purple.700' }
|
||||
case 'linear':
|
||||
return { bg: 'orange.50', border: 'orange.200', text: 'orange.700' }
|
||||
return isDark
|
||||
? { bg: 'orange.900', border: 'orange.700', text: 'orange.200' }
|
||||
: { bg: 'orange.50', border: 'orange.200', text: 'orange.700' }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +69,8 @@ function getPartTypeColors(type: SessionPart['type']): {
|
||||
* - "Let's Go!" button to start
|
||||
*/
|
||||
export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanReviewProps) {
|
||||
const { resolvedTheme } = useTheme()
|
||||
const isDark = resolvedTheme === 'dark'
|
||||
const [showConfig, setShowConfig] = useState(false)
|
||||
|
||||
const summary = plan.summary as SessionSummary
|
||||
@@ -94,7 +106,7 @@ export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanRevie
|
||||
className={css({
|
||||
fontSize: '1.75rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'gray.800',
|
||||
color: isDark ? 'gray.100' : 'gray.800',
|
||||
marginBottom: '0.5rem',
|
||||
})}
|
||||
>
|
||||
@@ -103,7 +115,7 @@ export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanRevie
|
||||
<p
|
||||
className={css({
|
||||
fontSize: '1rem',
|
||||
color: 'gray.600',
|
||||
color: isDark ? 'gray.400' : 'gray.600',
|
||||
})}
|
||||
>
|
||||
Review your plan before starting
|
||||
@@ -117,10 +129,10 @@ export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanRevie
|
||||
width: '100%',
|
||||
padding: '1.5rem',
|
||||
borderRadius: '12px',
|
||||
backgroundColor: 'white',
|
||||
backgroundColor: isDark ? 'gray.800' : 'white',
|
||||
boxShadow: 'md',
|
||||
border: '1px solid',
|
||||
borderColor: 'gray.200',
|
||||
borderColor: isDark ? 'gray.700' : 'gray.200',
|
||||
})}
|
||||
>
|
||||
{/* Time and problem count */}
|
||||
@@ -132,7 +144,7 @@ export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanRevie
|
||||
marginBottom: '1.5rem',
|
||||
paddingBottom: '1rem',
|
||||
borderBottom: '1px solid',
|
||||
borderColor: 'gray.200',
|
||||
borderColor: isDark ? 'gray.700' : 'gray.200',
|
||||
})}
|
||||
>
|
||||
<div>
|
||||
@@ -140,7 +152,7 @@ export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanRevie
|
||||
className={css({
|
||||
fontSize: '2rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'blue.600',
|
||||
color: isDark ? 'blue.400' : 'blue.600',
|
||||
})}
|
||||
>
|
||||
~{summary.estimatedMinutes} min
|
||||
@@ -148,7 +160,7 @@ export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanRevie
|
||||
<div
|
||||
className={css({
|
||||
fontSize: '0.875rem',
|
||||
color: 'gray.500',
|
||||
color: isDark ? 'gray.400' : 'gray.500',
|
||||
})}
|
||||
>
|
||||
{summary.totalProblemCount} problems
|
||||
@@ -162,7 +174,7 @@ export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanRevie
|
||||
<div
|
||||
className={css({
|
||||
fontSize: '0.875rem',
|
||||
color: 'gray.600',
|
||||
color: isDark ? 'gray.400' : 'gray.600',
|
||||
})}
|
||||
>
|
||||
Focus: <strong>{summary.focusDescription}</strong>
|
||||
@@ -180,7 +192,7 @@ export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanRevie
|
||||
className={css({
|
||||
fontSize: '1rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'gray.800',
|
||||
color: isDark ? 'gray.100' : 'gray.800',
|
||||
marginBottom: '0.75rem',
|
||||
})}
|
||||
>
|
||||
@@ -195,7 +207,7 @@ export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanRevie
|
||||
})}
|
||||
>
|
||||
{summary.parts.map((partSummary: PartSummary) => {
|
||||
const colors = getPartTypeColors(partSummary.type)
|
||||
const colors = getPartTypeColors(partSummary.type, isDark)
|
||||
return (
|
||||
<div
|
||||
key={partSummary.partNumber}
|
||||
@@ -251,7 +263,7 @@ export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanRevie
|
||||
className={css({
|
||||
padding: '0.5rem',
|
||||
borderRadius: '8px',
|
||||
backgroundColor: 'blue.50',
|
||||
backgroundColor: isDark ? 'blue.900' : 'blue.50',
|
||||
textAlign: 'center',
|
||||
})}
|
||||
>
|
||||
@@ -259,7 +271,7 @@ export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanRevie
|
||||
className={css({
|
||||
fontSize: '1.125rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'blue.700',
|
||||
color: isDark ? 'blue.200' : 'blue.700',
|
||||
})}
|
||||
>
|
||||
{focusCount}
|
||||
@@ -267,7 +279,7 @@ export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanRevie
|
||||
<div
|
||||
className={css({
|
||||
fontSize: '0.625rem',
|
||||
color: 'blue.600',
|
||||
color: isDark ? 'blue.300' : 'blue.600',
|
||||
})}
|
||||
>
|
||||
Focus
|
||||
@@ -278,7 +290,7 @@ export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanRevie
|
||||
className={css({
|
||||
padding: '0.5rem',
|
||||
borderRadius: '8px',
|
||||
backgroundColor: 'orange.50',
|
||||
backgroundColor: isDark ? 'orange.900' : 'orange.50',
|
||||
textAlign: 'center',
|
||||
})}
|
||||
>
|
||||
@@ -286,7 +298,7 @@ export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanRevie
|
||||
className={css({
|
||||
fontSize: '1.125rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'orange.700',
|
||||
color: isDark ? 'orange.200' : 'orange.700',
|
||||
})}
|
||||
>
|
||||
{reinforceCount}
|
||||
@@ -294,7 +306,7 @@ export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanRevie
|
||||
<div
|
||||
className={css({
|
||||
fontSize: '0.625rem',
|
||||
color: 'orange.600',
|
||||
color: isDark ? 'orange.300' : 'orange.600',
|
||||
})}
|
||||
>
|
||||
Reinforce
|
||||
@@ -305,7 +317,7 @@ export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanRevie
|
||||
className={css({
|
||||
padding: '0.5rem',
|
||||
borderRadius: '8px',
|
||||
backgroundColor: 'green.50',
|
||||
backgroundColor: isDark ? 'green.900' : 'green.50',
|
||||
textAlign: 'center',
|
||||
})}
|
||||
>
|
||||
@@ -313,7 +325,7 @@ export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanRevie
|
||||
className={css({
|
||||
fontSize: '1.125rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'green.700',
|
||||
color: isDark ? 'green.200' : 'green.700',
|
||||
})}
|
||||
>
|
||||
{reviewCount}
|
||||
@@ -321,7 +333,7 @@ export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanRevie
|
||||
<div
|
||||
className={css({
|
||||
fontSize: '0.625rem',
|
||||
color: 'green.600',
|
||||
color: isDark ? 'green.300' : 'green.600',
|
||||
})}
|
||||
>
|
||||
Review
|
||||
@@ -332,7 +344,7 @@ export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanRevie
|
||||
className={css({
|
||||
padding: '0.5rem',
|
||||
borderRadius: '8px',
|
||||
backgroundColor: 'purple.50',
|
||||
backgroundColor: isDark ? 'purple.900' : 'purple.50',
|
||||
textAlign: 'center',
|
||||
})}
|
||||
>
|
||||
@@ -340,7 +352,7 @@ export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanRevie
|
||||
className={css({
|
||||
fontSize: '1.125rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'purple.700',
|
||||
color: isDark ? 'purple.200' : 'purple.700',
|
||||
})}
|
||||
>
|
||||
{challengeCount}
|
||||
@@ -348,7 +360,7 @@ export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanRevie
|
||||
<div
|
||||
className={css({
|
||||
fontSize: '0.625rem',
|
||||
color: 'purple.600',
|
||||
color: isDark ? 'purple.300' : 'purple.600',
|
||||
})}
|
||||
>
|
||||
Challenge
|
||||
@@ -368,14 +380,14 @@ export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanRevie
|
||||
gap: '0.5rem',
|
||||
padding: '0.5rem 1rem',
|
||||
fontSize: '0.875rem',
|
||||
color: 'gray.600',
|
||||
color: isDark ? 'gray.400' : 'gray.600',
|
||||
backgroundColor: 'transparent',
|
||||
border: '1px solid',
|
||||
borderColor: 'gray.300',
|
||||
borderColor: isDark ? 'gray.600' : 'gray.300',
|
||||
borderRadius: '6px',
|
||||
cursor: 'pointer',
|
||||
_hover: {
|
||||
backgroundColor: 'gray.50',
|
||||
backgroundColor: isDark ? 'gray.800' : 'gray.50',
|
||||
},
|
||||
})}
|
||||
>
|
||||
@@ -391,9 +403,9 @@ export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanRevie
|
||||
width: '100%',
|
||||
padding: '1rem',
|
||||
borderRadius: '8px',
|
||||
backgroundColor: 'gray.50',
|
||||
backgroundColor: isDark ? 'gray.800' : 'gray.50',
|
||||
border: '1px solid',
|
||||
borderColor: 'gray.200',
|
||||
borderColor: isDark ? 'gray.700' : 'gray.200',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '0.75rem',
|
||||
overflow: 'auto',
|
||||
@@ -404,7 +416,7 @@ export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanRevie
|
||||
className={css({
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'gray.700',
|
||||
color: isDark ? 'gray.300' : 'gray.700',
|
||||
marginBottom: '1rem',
|
||||
})}
|
||||
>
|
||||
@@ -416,7 +428,7 @@ export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanRevie
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '0.5rem',
|
||||
color: 'gray.600',
|
||||
color: isDark ? 'gray.400' : 'gray.600',
|
||||
})}
|
||||
>
|
||||
<div>
|
||||
@@ -438,7 +450,7 @@ export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanRevie
|
||||
<strong>Created:</strong> {new Date(plan.createdAt).toLocaleString()}
|
||||
</div>
|
||||
|
||||
<hr className={css({ margin: '0.5rem 0', borderColor: 'gray.300' })} />
|
||||
<hr className={css({ margin: '0.5rem 0', borderColor: isDark ? 'gray.600' : 'gray.300' })} />
|
||||
|
||||
{parts.map((part: SessionPart) => (
|
||||
<details key={part.partNumber}>
|
||||
@@ -456,7 +468,7 @@ export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanRevie
|
||||
className={css({
|
||||
padding: '0.25rem 0',
|
||||
borderBottom: '1px dashed',
|
||||
borderColor: 'gray.200',
|
||||
borderColor: isDark ? 'gray.600' : 'gray.200',
|
||||
})}
|
||||
>
|
||||
<div>
|
||||
@@ -517,14 +529,14 @@ export function PlanReview({ plan, studentName, onApprove, onCancel }: PlanRevie
|
||||
className={css({
|
||||
padding: '0.75rem',
|
||||
fontSize: '1rem',
|
||||
color: 'gray.600',
|
||||
color: isDark ? 'gray.400' : 'gray.600',
|
||||
backgroundColor: 'transparent',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid',
|
||||
borderColor: 'gray.300',
|
||||
borderColor: isDark ? 'gray.600' : 'gray.300',
|
||||
cursor: 'pointer',
|
||||
_hover: {
|
||||
backgroundColor: 'gray.50',
|
||||
backgroundColor: isDark ? 'gray.800' : 'gray.50',
|
||||
},
|
||||
})}
|
||||
>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTheme } from '@/contexts/ThemeContext'
|
||||
import type { HelpLevel } from '@/db/schema/session-plans'
|
||||
import type { PracticeHelpState } from '@/hooks/usePracticeHelp'
|
||||
import { css } from '../../../styled-system/css'
|
||||
@@ -58,6 +59,8 @@ export function PracticeHelpPanel({
|
||||
currentValue,
|
||||
targetValue,
|
||||
}: PracticeHelpPanelProps) {
|
||||
const { resolvedTheme } = useTheme()
|
||||
const isDark = resolvedTheme === 'dark'
|
||||
const { currentLevel, content, isAvailable, maxLevelUsed } = helpState
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
|
||||
@@ -104,16 +107,16 @@ export function PracticeHelpPanel({
|
||||
width: '100%',
|
||||
padding: '0.75rem',
|
||||
fontSize: '0.875rem',
|
||||
color: 'blue.600',
|
||||
backgroundColor: 'blue.50',
|
||||
color: isDark ? 'blue.300' : 'blue.600',
|
||||
backgroundColor: isDark ? 'blue.900' : 'blue.50',
|
||||
border: '1px solid',
|
||||
borderColor: 'blue.200',
|
||||
borderColor: isDark ? 'blue.700' : 'blue.200',
|
||||
borderRadius: '8px',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
_hover: {
|
||||
backgroundColor: 'blue.100',
|
||||
borderColor: 'blue.300',
|
||||
backgroundColor: isDark ? 'blue.800' : 'blue.100',
|
||||
borderColor: isDark ? 'blue.600' : 'blue.300',
|
||||
},
|
||||
})}
|
||||
>
|
||||
@@ -134,10 +137,10 @@ export function PracticeHelpPanel({
|
||||
flexDirection: 'column',
|
||||
gap: '0.75rem',
|
||||
padding: '1rem',
|
||||
backgroundColor: 'blue.50',
|
||||
backgroundColor: isDark ? 'blue.900' : 'blue.50',
|
||||
borderRadius: '12px',
|
||||
border: '2px solid',
|
||||
borderColor: 'blue.200',
|
||||
borderColor: isDark ? 'blue.700' : 'blue.200',
|
||||
})}
|
||||
>
|
||||
{/* Header with level indicator */}
|
||||
@@ -161,7 +164,7 @@ export function PracticeHelpPanel({
|
||||
className={css({
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'blue.700',
|
||||
color: isDark ? 'blue.200' : 'blue.700',
|
||||
})}
|
||||
>
|
||||
{HELP_LEVEL_LABELS[currentLevel]}
|
||||
@@ -181,7 +184,8 @@ export function PracticeHelpPanel({
|
||||
width: '8px',
|
||||
height: '8px',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: level <= currentLevel ? 'blue.500' : 'blue.200',
|
||||
backgroundColor:
|
||||
level <= currentLevel ? 'blue.500' : isDark ? 'blue.700' : 'blue.200',
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
@@ -195,12 +199,12 @@ export function PracticeHelpPanel({
|
||||
className={css({
|
||||
padding: '0.25rem 0.5rem',
|
||||
fontSize: '0.75rem',
|
||||
color: 'gray.500',
|
||||
color: isDark ? 'gray.400' : 'gray.500',
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
_hover: {
|
||||
color: 'gray.700',
|
||||
color: isDark ? 'gray.200' : 'gray.700',
|
||||
},
|
||||
})}
|
||||
>
|
||||
@@ -214,16 +218,16 @@ export function PracticeHelpPanel({
|
||||
data-element="coach-hint"
|
||||
className={css({
|
||||
padding: '0.75rem',
|
||||
backgroundColor: 'white',
|
||||
backgroundColor: isDark ? 'gray.800' : 'white',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid',
|
||||
borderColor: 'blue.100',
|
||||
borderColor: isDark ? 'blue.800' : 'blue.100',
|
||||
})}
|
||||
>
|
||||
<p
|
||||
className={css({
|
||||
fontSize: '1rem',
|
||||
color: 'gray.700',
|
||||
color: isDark ? 'gray.300' : 'gray.700',
|
||||
lineHeight: '1.5',
|
||||
})}
|
||||
>
|
||||
@@ -238,17 +242,17 @@ export function PracticeHelpPanel({
|
||||
data-element="decomposition"
|
||||
className={css({
|
||||
padding: '0.75rem',
|
||||
backgroundColor: 'white',
|
||||
backgroundColor: isDark ? 'gray.800' : 'white',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid',
|
||||
borderColor: 'blue.100',
|
||||
borderColor: isDark ? 'blue.800' : 'blue.100',
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={css({
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'blue.600',
|
||||
color: isDark ? 'blue.300' : 'blue.600',
|
||||
marginBottom: '0.5rem',
|
||||
textTransform: 'uppercase',
|
||||
})}
|
||||
@@ -259,7 +263,7 @@ export function PracticeHelpPanel({
|
||||
className={css({
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '1.125rem',
|
||||
color: 'gray.800',
|
||||
color: isDark ? 'gray.100' : 'gray.800',
|
||||
wordBreak: 'break-word',
|
||||
})}
|
||||
>
|
||||
@@ -281,7 +285,7 @@ export function PracticeHelpPanel({
|
||||
key={segment.id}
|
||||
className={css({
|
||||
padding: '0.5rem',
|
||||
backgroundColor: 'gray.50',
|
||||
backgroundColor: isDark ? 'gray.700' : 'gray.50',
|
||||
borderRadius: '6px',
|
||||
fontSize: '0.875rem',
|
||||
})}
|
||||
@@ -289,12 +293,12 @@ export function PracticeHelpPanel({
|
||||
<span
|
||||
className={css({
|
||||
fontWeight: 'bold',
|
||||
color: 'gray.700',
|
||||
color: isDark ? 'gray.200' : 'gray.700',
|
||||
})}
|
||||
>
|
||||
{segment.readable?.title || `Column ${segment.place + 1}`}:
|
||||
</span>{' '}
|
||||
<span className={css({ color: 'gray.600' })}>
|
||||
<span className={css({ color: isDark ? 'gray.400' : 'gray.600' })}>
|
||||
{segment.readable?.summary || segment.expression}
|
||||
</span>
|
||||
</div>
|
||||
@@ -310,17 +314,17 @@ export function PracticeHelpPanel({
|
||||
data-element="help-abacus"
|
||||
className={css({
|
||||
padding: '0.75rem',
|
||||
backgroundColor: 'white',
|
||||
backgroundColor: isDark ? 'gray.800' : 'white',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid',
|
||||
borderColor: 'purple.200',
|
||||
borderColor: isDark ? 'purple.700' : 'purple.200',
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={css({
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'purple.600',
|
||||
color: isDark ? 'purple.300' : 'purple.600',
|
||||
marginBottom: '0.75rem',
|
||||
textTransform: 'uppercase',
|
||||
textAlign: 'center',
|
||||
@@ -341,10 +345,10 @@ export function PracticeHelpPanel({
|
||||
className={css({
|
||||
marginTop: '0.75rem',
|
||||
padding: '0.5rem',
|
||||
backgroundColor: 'purple.50',
|
||||
backgroundColor: isDark ? 'purple.900' : 'purple.50',
|
||||
borderRadius: '6px',
|
||||
fontSize: '0.75rem',
|
||||
color: 'purple.700',
|
||||
color: isDark ? 'purple.200' : 'purple.700',
|
||||
textAlign: 'center',
|
||||
})}
|
||||
>
|
||||
@@ -363,17 +367,17 @@ export function PracticeHelpPanel({
|
||||
data-element="bead-steps-text"
|
||||
className={css({
|
||||
padding: '0.75rem',
|
||||
backgroundColor: 'white',
|
||||
backgroundColor: isDark ? 'gray.800' : 'white',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid',
|
||||
borderColor: 'blue.100',
|
||||
borderColor: isDark ? 'blue.800' : 'blue.100',
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={css({
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'purple.600',
|
||||
color: isDark ? 'purple.300' : 'purple.600',
|
||||
marginBottom: '0.5rem',
|
||||
textTransform: 'uppercase',
|
||||
})}
|
||||
@@ -394,19 +398,22 @@ export function PracticeHelpPanel({
|
||||
key={index}
|
||||
className={css({
|
||||
fontSize: '0.875rem',
|
||||
color: 'gray.700',
|
||||
color: isDark ? 'gray.300' : 'gray.700',
|
||||
})}
|
||||
>
|
||||
<span
|
||||
className={css({
|
||||
fontWeight: 'bold',
|
||||
color: 'purple.700',
|
||||
color: isDark ? 'purple.300' : 'purple.700',
|
||||
})}
|
||||
>
|
||||
{step.mathematicalTerm}
|
||||
</span>
|
||||
{step.englishInstruction && (
|
||||
<span className={css({ color: 'gray.600' })}> — {step.englishInstruction}</span>
|
||||
<span className={css({ color: isDark ? 'gray.400' : 'gray.600' })}>
|
||||
{' '}
|
||||
— {step.englishInstruction}
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
@@ -428,15 +435,15 @@ export function PracticeHelpPanel({
|
||||
width: '100%',
|
||||
padding: '0.5rem',
|
||||
fontSize: '0.875rem',
|
||||
color: 'blue.600',
|
||||
backgroundColor: 'white',
|
||||
color: isDark ? 'blue.300' : 'blue.600',
|
||||
backgroundColor: isDark ? 'gray.800' : 'white',
|
||||
border: '1px solid',
|
||||
borderColor: 'blue.200',
|
||||
borderColor: isDark ? 'blue.700' : 'blue.200',
|
||||
borderRadius: '6px',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
_hover: {
|
||||
backgroundColor: 'blue.50',
|
||||
backgroundColor: isDark ? 'gray.700' : 'blue.50',
|
||||
},
|
||||
})}
|
||||
>
|
||||
@@ -450,7 +457,7 @@ export function PracticeHelpPanel({
|
||||
<div
|
||||
className={css({
|
||||
fontSize: '0.75rem',
|
||||
color: 'gray.400',
|
||||
color: isDark ? 'gray.500' : 'gray.400',
|
||||
textAlign: 'center',
|
||||
})}
|
||||
>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useTheme } from '@/contexts/ThemeContext'
|
||||
import type { MasteryLevel } from '@/db/schema/player-skill-mastery'
|
||||
import { css } from '../../../styled-system/css'
|
||||
import type { StudentWithProgress } from './StudentSelector'
|
||||
@@ -58,17 +59,23 @@ interface ProgressDashboardProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* Mastery level badge colors
|
||||
* Mastery level badge colors (dark mode aware)
|
||||
*/
|
||||
function getMasteryColor(level: MasteryLevel): { bg: string; text: string } {
|
||||
function getMasteryColor(level: MasteryLevel, isDark: boolean): { bg: string; text: string } {
|
||||
switch (level) {
|
||||
case 'mastered':
|
||||
return { bg: 'green.100', text: 'green.700' }
|
||||
return isDark
|
||||
? { bg: 'green.900', text: 'green.200' }
|
||||
: { bg: 'green.100', text: 'green.700' }
|
||||
case 'practicing':
|
||||
return { bg: 'yellow.100', text: 'yellow.700' }
|
||||
return isDark
|
||||
? { bg: 'yellow.900', text: 'yellow.200' }
|
||||
: { bg: 'yellow.100', text: 'yellow.700' }
|
||||
default:
|
||||
// 'learning' and any unknown values use gray
|
||||
return { bg: 'gray.100', text: 'gray.600' }
|
||||
return isDark
|
||||
? { bg: 'gray.700', text: 'gray.300' }
|
||||
: { bg: 'gray.100', text: 'gray.600' }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +103,9 @@ export function ProgressDashboard({
|
||||
onClearReinforcement,
|
||||
onClearAllReinforcement,
|
||||
}: ProgressDashboardProps) {
|
||||
const { resolvedTheme } = useTheme()
|
||||
const isDark = resolvedTheme === 'dark'
|
||||
|
||||
const progressPercent =
|
||||
currentPhase.totalSkills > 0
|
||||
? Math.round((currentPhase.masteredSkills / currentPhase.totalSkills) * 100)
|
||||
@@ -141,7 +151,7 @@ export function ProgressDashboard({
|
||||
className={css({
|
||||
fontSize: '1.75rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'gray.800',
|
||||
color: isDark ? 'gray.100' : 'gray.800',
|
||||
})}
|
||||
>
|
||||
Hi {student.name}!
|
||||
@@ -151,7 +161,7 @@ export function ProgressDashboard({
|
||||
onClick={onChangeStudent}
|
||||
className={css({
|
||||
fontSize: '0.875rem',
|
||||
color: 'blue.500',
|
||||
color: isDark ? 'blue.400' : 'blue.500',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
@@ -173,10 +183,10 @@ export function ProgressDashboard({
|
||||
width: '100%',
|
||||
padding: '1.5rem',
|
||||
borderRadius: '12px',
|
||||
backgroundColor: 'white',
|
||||
backgroundColor: isDark ? 'gray.800' : 'white',
|
||||
boxShadow: 'md',
|
||||
border: '1px solid',
|
||||
borderColor: 'gray.200',
|
||||
borderColor: isDark ? 'gray.700' : 'gray.200',
|
||||
})}
|
||||
>
|
||||
<div
|
||||
@@ -192,7 +202,7 @@ export function ProgressDashboard({
|
||||
className={css({
|
||||
fontSize: '1.25rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'gray.800',
|
||||
color: isDark ? 'gray.100' : 'gray.800',
|
||||
})}
|
||||
>
|
||||
{currentPhase.levelName}
|
||||
@@ -200,7 +210,7 @@ export function ProgressDashboard({
|
||||
<p
|
||||
className={css({
|
||||
fontSize: '1rem',
|
||||
color: 'gray.600',
|
||||
color: isDark ? 'gray.400' : 'gray.600',
|
||||
})}
|
||||
>
|
||||
{currentPhase.phaseName}
|
||||
@@ -210,7 +220,7 @@ export function ProgressDashboard({
|
||||
className={css({
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'blue.600',
|
||||
color: isDark ? 'blue.400' : 'blue.600',
|
||||
})}
|
||||
>
|
||||
{progressPercent}% mastered
|
||||
@@ -222,7 +232,7 @@ export function ProgressDashboard({
|
||||
className={css({
|
||||
width: '100%',
|
||||
height: '12px',
|
||||
backgroundColor: 'gray.200',
|
||||
backgroundColor: isDark ? 'gray.700' : 'gray.200',
|
||||
borderRadius: '6px',
|
||||
overflow: 'hidden',
|
||||
marginBottom: '1rem',
|
||||
@@ -231,7 +241,7 @@ export function ProgressDashboard({
|
||||
<div
|
||||
className={css({
|
||||
height: '100%',
|
||||
backgroundColor: 'green.500',
|
||||
backgroundColor: isDark ? 'green.400' : 'green.500',
|
||||
borderRadius: '6px',
|
||||
transition: 'width 0.5s ease',
|
||||
})}
|
||||
@@ -242,7 +252,7 @@ export function ProgressDashboard({
|
||||
<p
|
||||
className={css({
|
||||
fontSize: '0.875rem',
|
||||
color: 'gray.500',
|
||||
color: isDark ? 'gray.400' : 'gray.500',
|
||||
})}
|
||||
>
|
||||
{currentPhase.description}
|
||||
@@ -294,14 +304,14 @@ export function ProgressDashboard({
|
||||
flex: 1,
|
||||
padding: '0.75rem',
|
||||
fontSize: '1rem',
|
||||
color: 'gray.700',
|
||||
backgroundColor: 'gray.100',
|
||||
color: isDark ? 'gray.200' : 'gray.700',
|
||||
backgroundColor: isDark ? 'gray.700' : 'gray.100',
|
||||
borderRadius: '8px',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
transition: 'background-color 0.2s ease',
|
||||
_hover: {
|
||||
backgroundColor: 'gray.200',
|
||||
backgroundColor: isDark ? 'gray.600' : 'gray.200',
|
||||
},
|
||||
})}
|
||||
>
|
||||
@@ -316,14 +326,14 @@ export function ProgressDashboard({
|
||||
flex: 1,
|
||||
padding: '0.75rem',
|
||||
fontSize: '1rem',
|
||||
color: 'gray.700',
|
||||
backgroundColor: 'gray.100',
|
||||
color: isDark ? 'gray.200' : 'gray.700',
|
||||
backgroundColor: isDark ? 'gray.700' : 'gray.100',
|
||||
borderRadius: '8px',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
transition: 'background-color 0.2s ease',
|
||||
_hover: {
|
||||
backgroundColor: 'gray.200',
|
||||
backgroundColor: isDark ? 'gray.600' : 'gray.200',
|
||||
},
|
||||
})}
|
||||
>
|
||||
@@ -339,10 +349,10 @@ export function ProgressDashboard({
|
||||
className={css({
|
||||
width: '100%',
|
||||
padding: '1rem',
|
||||
backgroundColor: 'orange.50',
|
||||
backgroundColor: isDark ? 'orange.900' : 'orange.50',
|
||||
borderRadius: '12px',
|
||||
border: '1px solid',
|
||||
borderColor: 'orange.200',
|
||||
borderColor: isDark ? 'orange.700' : 'orange.200',
|
||||
})}
|
||||
>
|
||||
<div
|
||||
@@ -357,7 +367,7 @@ export function ProgressDashboard({
|
||||
className={css({
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'orange.700',
|
||||
color: isDark ? 'orange.200' : 'orange.700',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.5rem',
|
||||
@@ -373,12 +383,12 @@ export function ProgressDashboard({
|
||||
onClick={onClearAllReinforcement}
|
||||
className={css({
|
||||
fontSize: '0.75rem',
|
||||
color: 'gray.500',
|
||||
color: isDark ? 'gray.400' : 'gray.500',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
_hover: {
|
||||
color: 'gray.700',
|
||||
color: isDark ? 'gray.200' : 'gray.700',
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
})}
|
||||
@@ -390,7 +400,7 @@ export function ProgressDashboard({
|
||||
<p
|
||||
className={css({
|
||||
fontSize: '0.75rem',
|
||||
color: 'orange.600',
|
||||
color: isDark ? 'orange.300' : 'orange.600',
|
||||
marginBottom: '0.75rem',
|
||||
})}
|
||||
>
|
||||
@@ -411,17 +421,17 @@ export function ProgressDashboard({
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
padding: '0.5rem 0.75rem',
|
||||
backgroundColor: 'white',
|
||||
backgroundColor: isDark ? 'gray.800' : 'white',
|
||||
borderRadius: '6px',
|
||||
border: '1px solid',
|
||||
borderColor: 'orange.100',
|
||||
borderColor: isDark ? 'orange.800' : 'orange.100',
|
||||
})}
|
||||
>
|
||||
<div className={css({ display: 'flex', alignItems: 'center', gap: '0.5rem' })}>
|
||||
<span
|
||||
className={css({
|
||||
fontSize: '0.875rem',
|
||||
color: 'gray.700',
|
||||
color: isDark ? 'gray.200' : 'gray.700',
|
||||
fontWeight: 'medium',
|
||||
})}
|
||||
>
|
||||
@@ -431,7 +441,7 @@ export function ProgressDashboard({
|
||||
<span
|
||||
className={css({
|
||||
fontSize: '0.75rem',
|
||||
color: 'green.600',
|
||||
color: isDark ? 'green.400' : 'green.600',
|
||||
})}
|
||||
title={`${skill.reinforcementStreak} correct answers toward clearing`}
|
||||
>
|
||||
@@ -446,13 +456,13 @@ export function ProgressDashboard({
|
||||
onClick={() => onClearReinforcement(skill.skillId)}
|
||||
className={css({
|
||||
fontSize: '0.75rem',
|
||||
color: 'gray.400',
|
||||
color: isDark ? 'gray.500' : 'gray.400',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
padding: '0.25rem',
|
||||
_hover: {
|
||||
color: 'gray.600',
|
||||
color: isDark ? 'gray.300' : 'gray.600',
|
||||
},
|
||||
})}
|
||||
title="Mark as mastered (teacher only)"
|
||||
@@ -473,17 +483,17 @@ export function ProgressDashboard({
|
||||
className={css({
|
||||
width: '100%',
|
||||
padding: '1rem',
|
||||
backgroundColor: 'gray.50',
|
||||
backgroundColor: isDark ? 'gray.800' : 'gray.50',
|
||||
borderRadius: '12px',
|
||||
border: '1px solid',
|
||||
borderColor: 'gray.200',
|
||||
borderColor: isDark ? 'gray.700' : 'gray.200',
|
||||
})}
|
||||
>
|
||||
<h3
|
||||
className={css({
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 'semibold',
|
||||
color: 'gray.600',
|
||||
color: isDark ? 'gray.400' : 'gray.600',
|
||||
marginBottom: '0.75rem',
|
||||
})}
|
||||
>
|
||||
@@ -504,16 +514,16 @@ export function ProgressDashboard({
|
||||
className={css({
|
||||
padding: '0.5rem 0.75rem',
|
||||
fontSize: '0.875rem',
|
||||
color: 'blue.700',
|
||||
backgroundColor: 'blue.50',
|
||||
color: isDark ? 'blue.300' : 'blue.700',
|
||||
backgroundColor: isDark ? 'blue.900' : 'blue.50',
|
||||
borderRadius: '6px',
|
||||
border: '1px solid',
|
||||
borderColor: 'blue.200',
|
||||
borderColor: isDark ? 'blue.700' : 'blue.200',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
_hover: {
|
||||
backgroundColor: 'blue.100',
|
||||
borderColor: 'blue.300',
|
||||
backgroundColor: isDark ? 'blue.800' : 'blue.100',
|
||||
borderColor: isDark ? 'blue.600' : 'blue.300',
|
||||
},
|
||||
})}
|
||||
>
|
||||
@@ -528,16 +538,16 @@ export function ProgressDashboard({
|
||||
className={css({
|
||||
padding: '0.5rem 0.75rem',
|
||||
fontSize: '0.875rem',
|
||||
color: 'purple.700',
|
||||
backgroundColor: 'purple.50',
|
||||
color: isDark ? 'purple.300' : 'purple.700',
|
||||
backgroundColor: isDark ? 'purple.900' : 'purple.50',
|
||||
borderRadius: '6px',
|
||||
border: '1px solid',
|
||||
borderColor: 'purple.200',
|
||||
borderColor: isDark ? 'purple.700' : 'purple.200',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
_hover: {
|
||||
backgroundColor: 'purple.100',
|
||||
borderColor: 'purple.300',
|
||||
backgroundColor: isDark ? 'purple.800' : 'purple.100',
|
||||
borderColor: isDark ? 'purple.600' : 'purple.300',
|
||||
},
|
||||
})}
|
||||
>
|
||||
@@ -552,16 +562,16 @@ export function ProgressDashboard({
|
||||
className={css({
|
||||
padding: '0.5rem 0.75rem',
|
||||
fontSize: '0.875rem',
|
||||
color: 'green.700',
|
||||
backgroundColor: 'green.50',
|
||||
color: isDark ? 'green.300' : 'green.700',
|
||||
backgroundColor: isDark ? 'green.900' : 'green.50',
|
||||
borderRadius: '6px',
|
||||
border: '1px solid',
|
||||
borderColor: 'green.200',
|
||||
borderColor: isDark ? 'green.700' : 'green.200',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
_hover: {
|
||||
backgroundColor: 'green.100',
|
||||
borderColor: 'green.300',
|
||||
backgroundColor: isDark ? 'green.800' : 'green.100',
|
||||
borderColor: isDark ? 'green.600' : 'green.300',
|
||||
},
|
||||
})}
|
||||
>
|
||||
@@ -584,7 +594,7 @@ export function ProgressDashboard({
|
||||
className={css({
|
||||
fontSize: '1rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'gray.700',
|
||||
color: isDark ? 'gray.300' : 'gray.700',
|
||||
marginBottom: '0.75rem',
|
||||
})}
|
||||
>
|
||||
@@ -598,7 +608,7 @@ export function ProgressDashboard({
|
||||
})}
|
||||
>
|
||||
{recentSkills.map((skill) => {
|
||||
const colors = getMasteryColor(skill.masteryLevel)
|
||||
const colors = getMasteryColor(skill.masteryLevel, isDark)
|
||||
return (
|
||||
<span
|
||||
key={skill.skillId}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useTheme } from '@/contexts/ThemeContext'
|
||||
import type { SessionPlan, SlotResult } from '@/db/schema/session-plans'
|
||||
import { css } from '../../../styled-system/css'
|
||||
|
||||
@@ -35,6 +36,8 @@ export function SessionSummary({
|
||||
onPracticeAgain,
|
||||
onBackToDashboard,
|
||||
}: SessionSummaryProps) {
|
||||
const { resolvedTheme } = useTheme()
|
||||
const isDark = resolvedTheme === 'dark'
|
||||
const results = plan.results as SlotResult[]
|
||||
const totalProblems = results.length
|
||||
const correctProblems = results.filter((r) => r.isCorrect).length
|
||||
@@ -78,12 +81,30 @@ export function SessionSummary({
|
||||
className={css({
|
||||
textAlign: 'center',
|
||||
padding: '1.5rem',
|
||||
backgroundColor:
|
||||
accuracy >= 0.8 ? 'green.50' : accuracy >= 0.6 ? 'yellow.50' : 'orange.50',
|
||||
backgroundColor: isDark
|
||||
? accuracy >= 0.8
|
||||
? 'green.900'
|
||||
: accuracy >= 0.6
|
||||
? 'yellow.900'
|
||||
: 'orange.900'
|
||||
: accuracy >= 0.8
|
||||
? 'green.50'
|
||||
: accuracy >= 0.6
|
||||
? 'yellow.50'
|
||||
: 'orange.50',
|
||||
borderRadius: '16px',
|
||||
border: '2px solid',
|
||||
borderColor:
|
||||
accuracy >= 0.8 ? 'green.200' : accuracy >= 0.6 ? 'yellow.200' : 'orange.200',
|
||||
borderColor: isDark
|
||||
? accuracy >= 0.8
|
||||
? 'green.700'
|
||||
: accuracy >= 0.6
|
||||
? 'yellow.700'
|
||||
: 'orange.700'
|
||||
: accuracy >= 0.8
|
||||
? 'green.200'
|
||||
: accuracy >= 0.6
|
||||
? 'yellow.200'
|
||||
: 'orange.200',
|
||||
})}
|
||||
>
|
||||
<div
|
||||
@@ -98,7 +119,7 @@ export function SessionSummary({
|
||||
className={css({
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'gray.800',
|
||||
color: isDark ? 'gray.100' : 'gray.800',
|
||||
marginBottom: '0.25rem',
|
||||
})}
|
||||
>
|
||||
@@ -107,7 +128,7 @@ export function SessionSummary({
|
||||
<p
|
||||
className={css({
|
||||
fontSize: '1rem',
|
||||
color: 'gray.600',
|
||||
color: isDark ? 'gray.400' : 'gray.600',
|
||||
})}
|
||||
>
|
||||
{performanceMessage}
|
||||
@@ -128,7 +149,7 @@ export function SessionSummary({
|
||||
className={css({
|
||||
textAlign: 'center',
|
||||
padding: '1rem',
|
||||
backgroundColor: 'white',
|
||||
backgroundColor: isDark ? 'gray.800' : 'white',
|
||||
borderRadius: '12px',
|
||||
boxShadow: 'sm',
|
||||
})}
|
||||
@@ -137,7 +158,17 @@ export function SessionSummary({
|
||||
className={css({
|
||||
fontSize: '2rem',
|
||||
fontWeight: 'bold',
|
||||
color: accuracy >= 0.8 ? 'green.600' : accuracy >= 0.6 ? 'yellow.600' : 'orange.600',
|
||||
color: isDark
|
||||
? accuracy >= 0.8
|
||||
? 'green.400'
|
||||
: accuracy >= 0.6
|
||||
? 'yellow.400'
|
||||
: 'orange.400'
|
||||
: accuracy >= 0.8
|
||||
? 'green.600'
|
||||
: accuracy >= 0.6
|
||||
? 'yellow.600'
|
||||
: 'orange.600',
|
||||
})}
|
||||
>
|
||||
{Math.round(accuracy * 100)}%
|
||||
@@ -145,7 +176,7 @@ export function SessionSummary({
|
||||
<div
|
||||
className={css({
|
||||
fontSize: '0.75rem',
|
||||
color: 'gray.500',
|
||||
color: isDark ? 'gray.400' : 'gray.500',
|
||||
})}
|
||||
>
|
||||
Accuracy
|
||||
@@ -157,7 +188,7 @@ export function SessionSummary({
|
||||
className={css({
|
||||
textAlign: 'center',
|
||||
padding: '1rem',
|
||||
backgroundColor: 'white',
|
||||
backgroundColor: isDark ? 'gray.800' : 'white',
|
||||
borderRadius: '12px',
|
||||
boxShadow: 'sm',
|
||||
})}
|
||||
@@ -166,7 +197,7 @@ export function SessionSummary({
|
||||
className={css({
|
||||
fontSize: '2rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'blue.600',
|
||||
color: isDark ? 'blue.400' : 'blue.600',
|
||||
})}
|
||||
>
|
||||
{correctProblems}/{totalProblems}
|
||||
@@ -174,7 +205,7 @@ export function SessionSummary({
|
||||
<div
|
||||
className={css({
|
||||
fontSize: '0.75rem',
|
||||
color: 'gray.500',
|
||||
color: isDark ? 'gray.400' : 'gray.500',
|
||||
})}
|
||||
>
|
||||
Correct
|
||||
@@ -186,7 +217,7 @@ export function SessionSummary({
|
||||
className={css({
|
||||
textAlign: 'center',
|
||||
padding: '1rem',
|
||||
backgroundColor: 'white',
|
||||
backgroundColor: isDark ? 'gray.800' : 'white',
|
||||
borderRadius: '12px',
|
||||
boxShadow: 'sm',
|
||||
})}
|
||||
@@ -195,7 +226,7 @@ export function SessionSummary({
|
||||
className={css({
|
||||
fontSize: '2rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'purple.600',
|
||||
color: isDark ? 'purple.400' : 'purple.600',
|
||||
})}
|
||||
>
|
||||
{Math.round(sessionDurationMinutes)}
|
||||
@@ -203,7 +234,7 @@ export function SessionSummary({
|
||||
<div
|
||||
className={css({
|
||||
fontSize: '0.75rem',
|
||||
color: 'gray.500',
|
||||
color: isDark ? 'gray.400' : 'gray.500',
|
||||
})}
|
||||
>
|
||||
Minutes
|
||||
@@ -219,7 +250,7 @@ export function SessionSummary({
|
||||
flexDirection: 'column',
|
||||
gap: '0.75rem',
|
||||
padding: '1rem',
|
||||
backgroundColor: 'white',
|
||||
backgroundColor: isDark ? 'gray.800' : 'white',
|
||||
borderRadius: '12px',
|
||||
boxShadow: 'sm',
|
||||
})}
|
||||
@@ -228,7 +259,7 @@ export function SessionSummary({
|
||||
className={css({
|
||||
fontSize: '1rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'gray.700',
|
||||
color: isDark ? 'gray.300' : 'gray.700',
|
||||
marginBottom: '0.5rem',
|
||||
})}
|
||||
>
|
||||
@@ -242,8 +273,10 @@ export function SessionSummary({
|
||||
fontSize: '0.875rem',
|
||||
})}
|
||||
>
|
||||
<span className={css({ color: 'gray.600' })}>Average time per problem</span>
|
||||
<span className={css({ fontWeight: 'bold', color: 'gray.800' })}>
|
||||
<span className={css({ color: isDark ? 'gray.400' : 'gray.600' })}>
|
||||
Average time per problem
|
||||
</span>
|
||||
<span className={css({ fontWeight: 'bold', color: isDark ? 'gray.200' : 'gray.800' })}>
|
||||
{Math.round(avgTimeMs / 1000)}s
|
||||
</span>
|
||||
</div>
|
||||
@@ -255,8 +288,10 @@ export function SessionSummary({
|
||||
fontSize: '0.875rem',
|
||||
})}
|
||||
>
|
||||
<span className={css({ color: 'gray.600' })}>On-screen abacus used</span>
|
||||
<span className={css({ fontWeight: 'bold', color: 'gray.800' })}>
|
||||
<span className={css({ color: isDark ? 'gray.400' : 'gray.600' })}>
|
||||
On-screen abacus used
|
||||
</span>
|
||||
<span className={css({ fontWeight: 'bold', color: isDark ? 'gray.200' : 'gray.800' })}>
|
||||
{abacusUsageCount} times ({Math.round(abacusUsagePercent)}%)
|
||||
</span>
|
||||
</div>
|
||||
@@ -268,7 +303,7 @@ export function SessionSummary({
|
||||
data-section="skill-breakdown"
|
||||
className={css({
|
||||
padding: '1rem',
|
||||
backgroundColor: 'white',
|
||||
backgroundColor: isDark ? 'gray.800' : 'white',
|
||||
borderRadius: '12px',
|
||||
boxShadow: 'sm',
|
||||
})}
|
||||
@@ -277,7 +312,7 @@ export function SessionSummary({
|
||||
className={css({
|
||||
fontSize: '1rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'gray.700',
|
||||
color: isDark ? 'gray.300' : 'gray.700',
|
||||
marginBottom: '1rem',
|
||||
})}
|
||||
>
|
||||
@@ -305,7 +340,7 @@ export function SessionSummary({
|
||||
className={css({
|
||||
flex: 1,
|
||||
fontSize: '0.875rem',
|
||||
color: 'gray.700',
|
||||
color: isDark ? 'gray.300' : 'gray.700',
|
||||
})}
|
||||
>
|
||||
{formatSkillName(skill.skillId)}
|
||||
@@ -314,7 +349,7 @@ export function SessionSummary({
|
||||
className={css({
|
||||
width: '120px',
|
||||
height: '8px',
|
||||
backgroundColor: 'gray.200',
|
||||
backgroundColor: isDark ? 'gray.700' : 'gray.200',
|
||||
borderRadius: '4px',
|
||||
overflow: 'hidden',
|
||||
})}
|
||||
@@ -322,8 +357,13 @@ export function SessionSummary({
|
||||
<div
|
||||
className={css({
|
||||
height: '100%',
|
||||
backgroundColor:
|
||||
skill.accuracy >= 0.8
|
||||
backgroundColor: isDark
|
||||
? skill.accuracy >= 0.8
|
||||
? 'green.400'
|
||||
: skill.accuracy >= 0.6
|
||||
? 'yellow.400'
|
||||
: 'red.400'
|
||||
: skill.accuracy >= 0.8
|
||||
? 'green.500'
|
||||
: skill.accuracy >= 0.6
|
||||
? 'yellow.500'
|
||||
@@ -338,8 +378,13 @@ export function SessionSummary({
|
||||
className={css({
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: 'bold',
|
||||
color:
|
||||
skill.accuracy >= 0.8
|
||||
color: isDark
|
||||
? skill.accuracy >= 0.8
|
||||
? 'green.400'
|
||||
: skill.accuracy >= 0.6
|
||||
? 'yellow.400'
|
||||
: 'red.400'
|
||||
: skill.accuracy >= 0.8
|
||||
? 'green.600'
|
||||
: skill.accuracy >= 0.6
|
||||
? 'yellow.600'
|
||||
@@ -361,20 +406,20 @@ export function SessionSummary({
|
||||
data-section="problem-review"
|
||||
className={css({
|
||||
padding: '1rem',
|
||||
backgroundColor: 'gray.50',
|
||||
backgroundColor: isDark ? 'gray.800' : 'gray.50',
|
||||
borderRadius: '12px',
|
||||
border: '1px solid',
|
||||
borderColor: 'gray.200',
|
||||
borderColor: isDark ? 'gray.700' : 'gray.200',
|
||||
})}
|
||||
>
|
||||
<summary
|
||||
className={css({
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'gray.700',
|
||||
color: isDark ? 'gray.300' : 'gray.700',
|
||||
cursor: 'pointer',
|
||||
_hover: {
|
||||
color: 'gray.900',
|
||||
color: isDark ? 'gray.100' : 'gray.900',
|
||||
},
|
||||
})}
|
||||
>
|
||||
@@ -398,9 +443,16 @@ export function SessionSummary({
|
||||
alignItems: 'center',
|
||||
gap: '0.75rem',
|
||||
padding: '0.5rem',
|
||||
backgroundColor: result.isCorrect ? 'green.50' : 'red.50',
|
||||
backgroundColor: isDark
|
||||
? result.isCorrect
|
||||
? 'green.900'
|
||||
: 'red.900'
|
||||
: result.isCorrect
|
||||
? 'green.50'
|
||||
: 'red.50',
|
||||
borderRadius: '8px',
|
||||
fontSize: '0.875rem',
|
||||
color: isDark ? 'gray.200' : 'inherit',
|
||||
})}
|
||||
>
|
||||
<span>{result.isCorrect ? '✓' : '✗'}</span>
|
||||
@@ -415,7 +467,7 @@ export function SessionSummary({
|
||||
{!result.isCorrect && (
|
||||
<span
|
||||
className={css({
|
||||
color: 'red.600',
|
||||
color: isDark ? 'red.400' : 'red.600',
|
||||
textDecoration: 'line-through',
|
||||
})}
|
||||
>
|
||||
@@ -465,13 +517,13 @@ export function SessionSummary({
|
||||
className={css({
|
||||
padding: '0.75rem',
|
||||
fontSize: '1rem',
|
||||
color: 'gray.600',
|
||||
backgroundColor: 'gray.100',
|
||||
color: isDark ? 'gray.300' : 'gray.600',
|
||||
backgroundColor: isDark ? 'gray.700' : 'gray.100',
|
||||
borderRadius: '12px',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
_hover: {
|
||||
backgroundColor: 'gray.200',
|
||||
backgroundColor: isDark ? 'gray.600' : 'gray.200',
|
||||
},
|
||||
})}
|
||||
>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useTheme } from '@/contexts/ThemeContext'
|
||||
import type { Player } from '@/types/player'
|
||||
import { css } from '../../../styled-system/css'
|
||||
|
||||
@@ -22,6 +23,8 @@ interface StudentCardProps {
|
||||
* Individual student card showing avatar, name, and progress
|
||||
*/
|
||||
function StudentCard({ student, isSelected, onSelect }: StudentCardProps) {
|
||||
const { resolvedTheme } = useTheme()
|
||||
const isDark = resolvedTheme === 'dark'
|
||||
const levelLabel = student.currentLevel ? `Lv.${student.currentLevel}` : 'New'
|
||||
|
||||
return (
|
||||
@@ -38,8 +41,8 @@ function StudentCard({ student, isSelected, onSelect }: StudentCardProps) {
|
||||
padding: '1rem',
|
||||
borderRadius: '12px',
|
||||
border: isSelected ? '3px solid' : '2px solid',
|
||||
borderColor: isSelected ? 'blue.500' : 'gray.200',
|
||||
backgroundColor: isSelected ? 'blue.50' : 'white',
|
||||
borderColor: isSelected ? 'blue.500' : isDark ? 'gray.600' : 'gray.200',
|
||||
backgroundColor: isSelected ? (isDark ? 'blue.900' : 'blue.50') : isDark ? 'gray.800' : 'white',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
minWidth: '100px',
|
||||
@@ -71,7 +74,7 @@ function StudentCard({ student, isSelected, onSelect }: StudentCardProps) {
|
||||
className={css({
|
||||
fontWeight: 'bold',
|
||||
fontSize: '1rem',
|
||||
color: 'gray.800',
|
||||
color: isDark ? 'gray.100' : 'gray.800',
|
||||
})}
|
||||
>
|
||||
{student.name}
|
||||
@@ -83,8 +86,8 @@ function StudentCard({ student, isSelected, onSelect }: StudentCardProps) {
|
||||
fontSize: '0.75rem',
|
||||
padding: '0.125rem 0.5rem',
|
||||
borderRadius: '9999px',
|
||||
backgroundColor: 'gray.100',
|
||||
color: 'gray.600',
|
||||
backgroundColor: isDark ? 'gray.700' : 'gray.100',
|
||||
color: isDark ? 'gray.300' : 'gray.600',
|
||||
})}
|
||||
>
|
||||
{levelLabel}
|
||||
@@ -96,7 +99,7 @@ function StudentCard({ student, isSelected, onSelect }: StudentCardProps) {
|
||||
className={css({
|
||||
width: '100%',
|
||||
height: '4px',
|
||||
backgroundColor: 'gray.200',
|
||||
backgroundColor: isDark ? 'gray.700' : 'gray.200',
|
||||
borderRadius: '2px',
|
||||
overflow: 'hidden',
|
||||
})}
|
||||
@@ -104,7 +107,7 @@ function StudentCard({ student, isSelected, onSelect }: StudentCardProps) {
|
||||
<div
|
||||
className={css({
|
||||
height: '100%',
|
||||
backgroundColor: 'green.500',
|
||||
backgroundColor: isDark ? 'green.400' : 'green.500',
|
||||
transition: 'width 0.3s ease',
|
||||
})}
|
||||
style={{ width: `${student.masteryPercent}%` }}
|
||||
@@ -123,6 +126,9 @@ interface AddStudentButtonProps {
|
||||
* Button to add a new student
|
||||
*/
|
||||
function AddStudentButton({ onClick }: AddStudentButtonProps) {
|
||||
const { resolvedTheme } = useTheme()
|
||||
const isDark = resolvedTheme === 'dark'
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
@@ -137,22 +143,22 @@ function AddStudentButton({ onClick }: AddStudentButtonProps) {
|
||||
padding: '1rem',
|
||||
borderRadius: '12px',
|
||||
border: '2px dashed',
|
||||
borderColor: 'gray.300',
|
||||
backgroundColor: 'gray.50',
|
||||
borderColor: isDark ? 'gray.600' : 'gray.300',
|
||||
backgroundColor: isDark ? 'gray.800' : 'gray.50',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
minWidth: '100px',
|
||||
minHeight: '140px',
|
||||
_hover: {
|
||||
borderColor: 'blue.400',
|
||||
backgroundColor: 'blue.50',
|
||||
backgroundColor: isDark ? 'blue.900' : 'blue.50',
|
||||
},
|
||||
})}
|
||||
>
|
||||
<span
|
||||
className={css({
|
||||
fontSize: '2rem',
|
||||
color: 'gray.400',
|
||||
color: isDark ? 'gray.500' : 'gray.400',
|
||||
})}
|
||||
>
|
||||
➕
|
||||
@@ -160,7 +166,7 @@ function AddStudentButton({ onClick }: AddStudentButtonProps) {
|
||||
<span
|
||||
className={css({
|
||||
fontSize: '0.875rem',
|
||||
color: 'gray.500',
|
||||
color: isDark ? 'gray.400' : 'gray.500',
|
||||
})}
|
||||
>
|
||||
Add New
|
||||
@@ -191,6 +197,9 @@ export function StudentSelector({
|
||||
onAddStudent,
|
||||
title = 'Who is practicing today?',
|
||||
}: StudentSelectorProps) {
|
||||
const { resolvedTheme } = useTheme()
|
||||
const isDark = resolvedTheme === 'dark'
|
||||
|
||||
return (
|
||||
<div
|
||||
data-component="student-selector"
|
||||
@@ -207,7 +216,7 @@ export function StudentSelector({
|
||||
className={css({
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'gray.800',
|
||||
color: isDark ? 'gray.100' : 'gray.800',
|
||||
})}
|
||||
>
|
||||
{title}
|
||||
@@ -245,11 +254,11 @@ export function StudentSelector({
|
||||
<p
|
||||
className={css({
|
||||
fontSize: '1rem',
|
||||
color: 'gray.600',
|
||||
color: isDark ? 'gray.400' : 'gray.600',
|
||||
marginBottom: '1rem',
|
||||
})}
|
||||
>
|
||||
Selected: <strong>{selectedStudent.name}</strong> {selectedStudent.emoji}
|
||||
Selected: <strong className={css({ color: isDark ? 'gray.100' : 'inherit' })}>{selectedStudent.name}</strong> {selectedStudent.emoji}
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useTheme } from '@/contexts/ThemeContext'
|
||||
import { css } from '../../../styled-system/css'
|
||||
|
||||
interface VerticalProblemProps {
|
||||
@@ -40,6 +41,9 @@ export function VerticalProblem({
|
||||
confirmedTermCount = 0,
|
||||
currentHelpTermIndex,
|
||||
}: VerticalProblemProps) {
|
||||
const { resolvedTheme } = useTheme()
|
||||
const isDark = resolvedTheme === 'dark'
|
||||
|
||||
// Calculate max digits needed for alignment
|
||||
const maxDigits = Math.max(
|
||||
...terms.map((t) => Math.abs(t).toString().length),
|
||||
@@ -70,19 +74,33 @@ export function VerticalProblem({
|
||||
borderRadius: '8px',
|
||||
backgroundColor: isCompleted
|
||||
? isCorrect
|
||||
? 'green.50'
|
||||
: 'red.50'
|
||||
? isDark
|
||||
? 'green.900'
|
||||
: 'green.50'
|
||||
: isDark
|
||||
? 'red.900'
|
||||
: 'red.50'
|
||||
: isFocused
|
||||
? 'blue.50'
|
||||
: 'gray.50',
|
||||
? isDark
|
||||
? 'blue.900'
|
||||
: 'blue.50'
|
||||
: isDark
|
||||
? 'gray.800'
|
||||
: 'gray.50',
|
||||
border: '2px solid',
|
||||
borderColor: isCompleted
|
||||
? isCorrect
|
||||
? 'green.400'
|
||||
: 'red.400'
|
||||
? isDark
|
||||
? 'green.600'
|
||||
: 'green.400'
|
||||
: isDark
|
||||
? 'red.600'
|
||||
: 'red.400'
|
||||
: isFocused
|
||||
? 'blue.400'
|
||||
: 'gray.200',
|
||||
: isDark
|
||||
? 'gray.600'
|
||||
: 'gray.200',
|
||||
transition: 'all 0.2s ease',
|
||||
})}
|
||||
>
|
||||
@@ -109,7 +127,11 @@ export function VerticalProblem({
|
||||
// Confirmed terms are dimmed with checkmark
|
||||
opacity: isConfirmed ? 0.5 : 1,
|
||||
// Current help term is highlighted
|
||||
backgroundColor: isCurrentHelp ? 'purple.100' : 'transparent',
|
||||
backgroundColor: isCurrentHelp
|
||||
? isDark
|
||||
? 'purple.800'
|
||||
: 'purple.100'
|
||||
: 'transparent',
|
||||
borderRadius: isCurrentHelp ? '4px' : '0',
|
||||
padding: isCurrentHelp ? '2px 4px' : '0',
|
||||
marginLeft: isCurrentHelp ? '-4px' : '0',
|
||||
@@ -123,7 +145,7 @@ export function VerticalProblem({
|
||||
className={css({
|
||||
position: 'absolute',
|
||||
left: '-1.5rem',
|
||||
color: 'green.500',
|
||||
color: isDark ? 'green.400' : 'green.500',
|
||||
fontSize: '0.875rem',
|
||||
})}
|
||||
>
|
||||
@@ -138,7 +160,7 @@ export function VerticalProblem({
|
||||
className={css({
|
||||
position: 'absolute',
|
||||
left: '-1.5rem',
|
||||
color: 'purple.600',
|
||||
color: isDark ? 'purple.300' : 'purple.600',
|
||||
fontSize: '0.875rem',
|
||||
})}
|
||||
>
|
||||
@@ -155,7 +177,7 @@ export function VerticalProblem({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: isNegative ? 'red.600' : 'transparent',
|
||||
color: isNegative ? (isDark ? 'red.400' : 'red.600') : 'transparent',
|
||||
})}
|
||||
>
|
||||
{isNegative ? '−' : ''}
|
||||
@@ -172,7 +194,13 @@ export function VerticalProblem({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: isCurrentHelp ? 'purple.800' : 'gray.800',
|
||||
color: isCurrentHelp
|
||||
? isDark
|
||||
? 'purple.200'
|
||||
: 'purple.800'
|
||||
: isDark
|
||||
? 'gray.200'
|
||||
: 'gray.800',
|
||||
fontWeight: isCurrentHelp ? 'bold' : 'inherit',
|
||||
})}
|
||||
>
|
||||
@@ -189,7 +217,7 @@ export function VerticalProblem({
|
||||
className={css({
|
||||
width: '100%',
|
||||
height: '2px',
|
||||
backgroundColor: 'gray.400',
|
||||
backgroundColor: isDark ? 'gray.600' : 'gray.400',
|
||||
marginTop: '4px',
|
||||
marginBottom: '4px',
|
||||
})}
|
||||
@@ -213,58 +241,80 @@ export function VerticalProblem({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: 'gray.500',
|
||||
color: isDark ? 'gray.400' : 'gray.500',
|
||||
})}
|
||||
>
|
||||
=
|
||||
</div>
|
||||
|
||||
{/* Answer digit cells */}
|
||||
{(isCompleted && isIncorrect ? correctAnswer?.toString() || '' : userAnswer)
|
||||
.padStart(maxDigits, ' ')
|
||||
.split('')
|
||||
.map((digit, index) => (
|
||||
<div
|
||||
key={index}
|
||||
data-element="answer-cell"
|
||||
className={css({
|
||||
width: cellWidth,
|
||||
height: cellHeight,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: isCompleted ? (isCorrect ? 'green.100' : 'red.100') : 'white',
|
||||
borderRadius: '4px',
|
||||
border: '1px solid',
|
||||
borderColor: isCompleted ? (isCorrect ? 'green.300' : 'red.300') : 'gray.300',
|
||||
color: isCompleted ? (isCorrect ? 'green.700' : 'red.700') : 'gray.800',
|
||||
})}
|
||||
>
|
||||
{digit}
|
||||
</div>
|
||||
))}
|
||||
{/* Answer digit cells - show maxDigits cells total */}
|
||||
{Array(maxDigits)
|
||||
.fill(null)
|
||||
.map((_, index) => {
|
||||
// Determine what to show in this cell
|
||||
const displayValue =
|
||||
isCompleted && isIncorrect ? correctAnswer?.toString() || '' : userAnswer
|
||||
const paddedValue = displayValue.padStart(maxDigits, '')
|
||||
const digit = paddedValue[index] || ''
|
||||
const isEmpty = digit === ''
|
||||
|
||||
{/* Empty cells to fill remaining space */}
|
||||
{!isCompleted &&
|
||||
Array(Math.max(0, maxDigits - userAnswer.length))
|
||||
.fill(null)
|
||||
.map((_, index) => (
|
||||
return (
|
||||
<div
|
||||
key={`empty-${index}`}
|
||||
data-element="empty-cell"
|
||||
key={index}
|
||||
data-element={isEmpty ? 'empty-cell' : 'answer-cell'}
|
||||
className={css({
|
||||
width: cellWidth,
|
||||
height: cellHeight,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'white',
|
||||
backgroundColor: isCompleted
|
||||
? isCorrect
|
||||
? isDark
|
||||
? 'green.800'
|
||||
: 'green.100'
|
||||
: isDark
|
||||
? 'red.800'
|
||||
: 'red.100'
|
||||
: isDark
|
||||
? 'gray.700'
|
||||
: 'white',
|
||||
borderRadius: '4px',
|
||||
border: '1px dashed',
|
||||
borderColor: isFocused ? 'blue.300' : 'gray.300',
|
||||
border: isEmpty && !isCompleted ? '1px dashed' : '1px solid',
|
||||
borderColor: isCompleted
|
||||
? isCorrect
|
||||
? isDark
|
||||
? 'green.600'
|
||||
: 'green.300'
|
||||
: isDark
|
||||
? 'red.600'
|
||||
: 'red.300'
|
||||
: isEmpty
|
||||
? isFocused
|
||||
? 'blue.400'
|
||||
: isDark
|
||||
? 'gray.600'
|
||||
: 'gray.300'
|
||||
: isDark
|
||||
? 'gray.600'
|
||||
: 'gray.300',
|
||||
color: isCompleted
|
||||
? isCorrect
|
||||
? isDark
|
||||
? 'green.200'
|
||||
: 'green.700'
|
||||
: isDark
|
||||
? 'red.200'
|
||||
: 'red.700'
|
||||
: isDark
|
||||
? 'gray.200'
|
||||
: 'gray.800',
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
>
|
||||
{digit}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Show user's incorrect answer below correct answer */}
|
||||
@@ -273,7 +323,7 @@ export function VerticalProblem({
|
||||
data-element="user-answer"
|
||||
className={css({
|
||||
fontSize: '0.875rem',
|
||||
color: 'red.500',
|
||||
color: isDark ? 'red.400' : 'red.500',
|
||||
marginTop: '4px',
|
||||
textDecoration: 'line-through',
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user