feat(practice): improve docked abacus UX and submit button behavior
- Force show submit button when abacus is docked (user needs manual submit) - Disable auto-help when docked; trigger help on submit for prefix sums - Fix dock animation to measure actual destination position - Keep problem centered when dock appears/disappears (absolute positioning) - Use scaleFactor prop for natural abacus sizing instead of manual calculations - Clean up unused dock size tracking and scale calculation code 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -4,12 +4,59 @@ import { animated, useSpring } from '@react-spring/web'
|
|||||||
import { ABACUS_THEMES, AbacusReact, useAbacusConfig } from '@soroban/abacus-react'
|
import { ABACUS_THEMES, AbacusReact, useAbacusConfig } from '@soroban/abacus-react'
|
||||||
import { usePathname } from 'next/navigation'
|
import { usePathname } from 'next/navigation'
|
||||||
import { useCallback, useContext, useEffect, useRef, useState } from 'react'
|
import { useCallback, useContext, useEffect, useRef, useState } from 'react'
|
||||||
import { createPortal } from 'react-dom'
|
import { createPortal, flushSync } from 'react-dom'
|
||||||
|
import { createRoot } from 'react-dom/client'
|
||||||
import { HomeHeroContext } from '@/contexts/HomeHeroContext'
|
import { HomeHeroContext } from '@/contexts/HomeHeroContext'
|
||||||
import { type DockAnimationState, useMyAbacus } from '@/contexts/MyAbacusContext'
|
import { type DockAnimationState, useMyAbacus } from '@/contexts/MyAbacusContext'
|
||||||
import { useTheme } from '@/contexts/ThemeContext'
|
import { useTheme } from '@/contexts/ThemeContext'
|
||||||
import { css } from '../../styled-system/css'
|
import { css } from '../../styled-system/css'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Measure the size and position an AbacusReact will have when rendered into a dock element.
|
||||||
|
* Temporarily renders an invisible abacus directly into the dock to get accurate positioning
|
||||||
|
* (important for absolutely positioned docks where transforms depend on content size).
|
||||||
|
*/
|
||||||
|
function measureDockedAbacus(
|
||||||
|
dockElement: HTMLElement,
|
||||||
|
columns: number,
|
||||||
|
scaleFactor: number | undefined,
|
||||||
|
customStyles: typeof ABACUS_THEMES.light
|
||||||
|
): { x: number; y: number; width: number; height: number } {
|
||||||
|
// Create a temporary wrapper that matches how we render the docked abacus
|
||||||
|
const measureWrapper = document.createElement('div')
|
||||||
|
measureWrapper.style.visibility = 'hidden'
|
||||||
|
measureWrapper.style.pointerEvents = 'none'
|
||||||
|
|
||||||
|
// Insert directly into the dock element so it gets proper size/position
|
||||||
|
dockElement.appendChild(measureWrapper)
|
||||||
|
|
||||||
|
// Create a React root and render the abacus synchronously
|
||||||
|
const root = createRoot(measureWrapper)
|
||||||
|
flushSync(() => {
|
||||||
|
root.render(
|
||||||
|
<AbacusReact
|
||||||
|
value={0}
|
||||||
|
columns={columns}
|
||||||
|
scaleFactor={scaleFactor}
|
||||||
|
showNumbers={false}
|
||||||
|
interactive={false}
|
||||||
|
animated={false}
|
||||||
|
customStyles={customStyles}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Measure the rendered size and position (dock element now has content, so transforms apply correctly)
|
||||||
|
const rect = measureWrapper.getBoundingClientRect()
|
||||||
|
const result = { x: rect.x, y: rect.y, width: rect.width, height: rect.height }
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
root.unmount()
|
||||||
|
dockElement.removeChild(measureWrapper)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
export function MyAbacus() {
|
export function MyAbacus() {
|
||||||
const {
|
const {
|
||||||
isOpen,
|
isOpen,
|
||||||
@@ -33,9 +80,6 @@ export function MyAbacus() {
|
|||||||
const { resolvedTheme } = useTheme()
|
const { resolvedTheme } = useTheme()
|
||||||
const isDark = resolvedTheme === 'dark'
|
const isDark = resolvedTheme === 'dark'
|
||||||
|
|
||||||
// Track dock container size for auto-scaling
|
|
||||||
const [dockSize, setDockSize] = useState<{ width: number; height: number } | null>(null)
|
|
||||||
|
|
||||||
// Local ref for the button container (we'll connect this to context's buttonRef)
|
// Local ref for the button container (we'll connect this to context's buttonRef)
|
||||||
const localButtonRef = useRef<HTMLDivElement>(null)
|
const localButtonRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
@@ -45,29 +89,6 @@ export function MyAbacus() {
|
|||||||
const abacusValue = homeHeroContext?.abacusValue ?? localAbacusValue
|
const abacusValue = homeHeroContext?.abacusValue ?? localAbacusValue
|
||||||
const setAbacusValue = homeHeroContext?.setAbacusValue ?? setLocalAbacusValue
|
const setAbacusValue = homeHeroContext?.setAbacusValue ?? setLocalAbacusValue
|
||||||
|
|
||||||
// Observe dock container size changes
|
|
||||||
useEffect(() => {
|
|
||||||
if (!dock?.element) {
|
|
||||||
setDockSize(null)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const element = dock.element
|
|
||||||
const updateSize = () => {
|
|
||||||
const rect = element.getBoundingClientRect()
|
|
||||||
setDockSize({ width: rect.width, height: rect.height })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initial size
|
|
||||||
updateSize()
|
|
||||||
|
|
||||||
// Watch for size changes
|
|
||||||
const resizeObserver = new ResizeObserver(updateSize)
|
|
||||||
resizeObserver.observe(element)
|
|
||||||
|
|
||||||
return () => resizeObserver.disconnect()
|
|
||||||
}, [dock?.element])
|
|
||||||
|
|
||||||
// Determine display mode - only hero mode on actual home page
|
// Determine display mode - only hero mode on actual home page
|
||||||
const isOnHomePage =
|
const isOnHomePage =
|
||||||
pathname === '/' ||
|
pathname === '/' ||
|
||||||
@@ -118,25 +139,6 @@ export function MyAbacus() {
|
|||||||
// This matches /arcade, /arcade/*, and /arcade-rooms/*
|
// This matches /arcade, /arcade/*, and /arcade-rooms/*
|
||||||
const isOnGameRoute = pathname?.startsWith('/arcade')
|
const isOnGameRoute = pathname?.startsWith('/arcade')
|
||||||
|
|
||||||
// Calculate scale factor for docked mode
|
|
||||||
// Base abacus dimensions are approximately 120px wide per column, 200px tall
|
|
||||||
const calculateDockedScale = () => {
|
|
||||||
if (!dockSize || !dock) return 1
|
|
||||||
if (dock.scaleFactor) return dock.scaleFactor
|
|
||||||
|
|
||||||
const columns = dock.columns ?? 5
|
|
||||||
// Approximate base dimensions of AbacusReact at scale 1
|
|
||||||
const baseWidth = columns * 24 + 20 // ~24px per column + padding
|
|
||||||
const baseHeight = 55 // approximate height
|
|
||||||
|
|
||||||
const scaleX = dockSize.width / baseWidth
|
|
||||||
const scaleY = dockSize.height / baseHeight
|
|
||||||
// Use the smaller scale to fit within container, with some padding
|
|
||||||
return Math.min(scaleX, scaleY) * 0.85
|
|
||||||
}
|
|
||||||
|
|
||||||
const dockedScale = calculateDockedScale()
|
|
||||||
|
|
||||||
// Sync local button ref with context's buttonRef
|
// Sync local button ref with context's buttonRef
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (buttonRef && localButtonRef.current) {
|
if (buttonRef && localButtonRef.current) {
|
||||||
@@ -212,13 +214,21 @@ export function MyAbacus() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Measure positions
|
// Measure the button's current position
|
||||||
const buttonRect = localButtonRef.current.getBoundingClientRect()
|
const buttonRect = localButtonRef.current.getBoundingClientRect()
|
||||||
const dockRect = dock.element.getBoundingClientRect()
|
|
||||||
|
|
||||||
// Calculate scales - button shows at 0.35 scale, dock uses dockedScale
|
// Measure where the docked abacus will appear (renders temporarily to get accurate position)
|
||||||
|
const dockColumns = dock.columns ?? 5
|
||||||
|
const targetRect = measureDockedAbacus(
|
||||||
|
dock.element,
|
||||||
|
dockColumns,
|
||||||
|
dock.scaleFactor,
|
||||||
|
structuralStyles
|
||||||
|
)
|
||||||
|
|
||||||
|
// Calculate scales - button shows at 0.35 scale, dock uses scaleFactor directly
|
||||||
const buttonScale = 0.35
|
const buttonScale = 0.35
|
||||||
const targetScale = dockedScale
|
const targetScale = dock.scaleFactor ?? 1
|
||||||
|
|
||||||
const animState: DockAnimationState = {
|
const animState: DockAnimationState = {
|
||||||
phase: 'docking',
|
phase: 'docking',
|
||||||
@@ -228,18 +238,13 @@ export function MyAbacus() {
|
|||||||
width: buttonRect.width,
|
width: buttonRect.width,
|
||||||
height: buttonRect.height,
|
height: buttonRect.height,
|
||||||
},
|
},
|
||||||
toRect: {
|
toRect: targetRect,
|
||||||
x: dockRect.x,
|
|
||||||
y: dockRect.y,
|
|
||||||
width: dockRect.width,
|
|
||||||
height: dockRect.height,
|
|
||||||
},
|
|
||||||
fromScale: buttonScale,
|
fromScale: buttonScale,
|
||||||
toScale: targetScale,
|
toScale: targetScale,
|
||||||
}
|
}
|
||||||
|
|
||||||
startDockAnimation(animState)
|
startDockAnimation(animState)
|
||||||
}, [dock, dockInto, dockedScale, startDockAnimation])
|
}, [dock, dockInto, structuralStyles, startDockAnimation])
|
||||||
|
|
||||||
// Handler to initiate undock animation
|
// Handler to initiate undock animation
|
||||||
const handleUndockClick = useCallback(() => {
|
const handleUndockClick = useCallback(() => {
|
||||||
@@ -249,8 +254,24 @@ export function MyAbacus() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Measure dock position (source)
|
// The abacus is currently docked - find the actual rendered abacus element
|
||||||
const dockRect = dock.element.getBoundingClientRect()
|
const dockedAbacus = dock.element.querySelector('[data-element="abacus-display"]')
|
||||||
|
let sourceRect: { x: number; y: number; width: number; height: number }
|
||||||
|
|
||||||
|
if (dockedAbacus) {
|
||||||
|
// Measure the actual docked abacus position
|
||||||
|
const rect = dockedAbacus.getBoundingClientRect()
|
||||||
|
sourceRect = { x: rect.x, y: rect.y, width: rect.width, height: rect.height }
|
||||||
|
} else {
|
||||||
|
// Fallback: measure what it would be
|
||||||
|
const dockColumns = dock.columns ?? 5
|
||||||
|
sourceRect = measureDockedAbacus(
|
||||||
|
dock.element,
|
||||||
|
dockColumns,
|
||||||
|
dock.scaleFactor,
|
||||||
|
structuralStyles
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate target button position (we don't need the ref - button has known fixed position)
|
// Calculate target button position (we don't need the ref - button has known fixed position)
|
||||||
// Button is fixed at bottom-right with some margin
|
// Button is fixed at bottom-right with some margin
|
||||||
@@ -262,16 +283,11 @@ export function MyAbacus() {
|
|||||||
const buttonY = viewportHeight - buttonSize - margin
|
const buttonY = viewportHeight - buttonSize - margin
|
||||||
|
|
||||||
const buttonScale = 0.35
|
const buttonScale = 0.35
|
||||||
const dockScale = dockedScale
|
const dockScale = dock.scaleFactor ?? 1
|
||||||
|
|
||||||
const animState: DockAnimationState = {
|
const animState: DockAnimationState = {
|
||||||
phase: 'undocking',
|
phase: 'undocking',
|
||||||
fromRect: {
|
fromRect: sourceRect,
|
||||||
x: dockRect.x,
|
|
||||||
y: dockRect.y,
|
|
||||||
width: dockRect.width,
|
|
||||||
height: dockRect.height,
|
|
||||||
},
|
|
||||||
toRect: {
|
toRect: {
|
||||||
x: buttonX,
|
x: buttonX,
|
||||||
y: buttonY,
|
y: buttonY,
|
||||||
@@ -283,7 +299,7 @@ export function MyAbacus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
startUndockAnimation(animState)
|
startUndockAnimation(animState)
|
||||||
}, [dock, undock, dockedScale, startUndockAnimation])
|
}, [dock, undock, structuralStyles, startUndockAnimation])
|
||||||
|
|
||||||
// Check if we're currently animating
|
// Check if we're currently animating
|
||||||
const isAnimating = dockAnimationState !== null
|
const isAnimating = dockAnimationState !== null
|
||||||
@@ -365,8 +381,6 @@ export function MyAbacus() {
|
|||||||
data-mode="docked"
|
data-mode="docked"
|
||||||
data-dock-id={dock.id}
|
data-dock-id={dock.id}
|
||||||
className={css({
|
className={css({
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
@@ -414,10 +428,7 @@ export function MyAbacus() {
|
|||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
data-element="abacus-display"
|
data-element="abacus-display"
|
||||||
style={{ transform: `scale(${dockedScale})` }}
|
|
||||||
className={css({
|
className={css({
|
||||||
transformOrigin: 'center center',
|
|
||||||
transition: 'transform 0.3s ease',
|
|
||||||
filter: 'drop-shadow(0 4px 12px rgba(251, 191, 36, 0.2))',
|
filter: 'drop-shadow(0 4px 12px rgba(251, 191, 36, 0.2))',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
@@ -426,6 +437,7 @@ export function MyAbacus() {
|
|||||||
value={dock.value ?? abacusValue}
|
value={dock.value ?? abacusValue}
|
||||||
defaultValue={dock.defaultValue}
|
defaultValue={dock.defaultValue}
|
||||||
columns={dock.columns ?? 5}
|
columns={dock.columns ?? 5}
|
||||||
|
scaleFactor={dock.scaleFactor}
|
||||||
beadShape={appConfig.beadShape}
|
beadShape={appConfig.beadShape}
|
||||||
showNumbers={dock.showNumbers ?? true}
|
showNumbers={dock.showNumbers ?? true}
|
||||||
interactive={dock.interactive ?? true}
|
interactive={dock.interactive ?? true}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { animated, useSpring } from '@react-spring/web'
|
import { animated, useSpring } from '@react-spring/web'
|
||||||
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { flushSync } from 'react-dom'
|
import { flushSync } from 'react-dom'
|
||||||
|
import { useMyAbacus } from '@/contexts/MyAbacusContext'
|
||||||
import { useTheme } from '@/contexts/ThemeContext'
|
import { useTheme } from '@/contexts/ThemeContext'
|
||||||
import type {
|
import type {
|
||||||
ProblemSlot,
|
ProblemSlot,
|
||||||
@@ -198,6 +199,9 @@ export function ActiveSession({
|
|||||||
const { resolvedTheme } = useTheme()
|
const { resolvedTheme } = useTheme()
|
||||||
const isDark = resolvedTheme === 'dark'
|
const isDark = resolvedTheme === 'dark'
|
||||||
|
|
||||||
|
// Check if abacus is docked (to force show submit button)
|
||||||
|
const { isDockedByUser } = useMyAbacus()
|
||||||
|
|
||||||
// Sound effects
|
// Sound effects
|
||||||
const { playSound } = usePracticeSoundEffects()
|
const { playSound } = usePracticeSoundEffects()
|
||||||
|
|
||||||
@@ -288,10 +292,12 @@ export function ActiveSession({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
// Spring for submit button entrance animation
|
// Spring for submit button entrance animation
|
||||||
|
// Show submit button when: manual submit is required OR abacus is docked (user needs way to submit)
|
||||||
|
const showSubmitButton = attempt?.manualSubmitRequired || isDockedByUser
|
||||||
const submitButtonSpring = useSpring({
|
const submitButtonSpring = useSpring({
|
||||||
transform: attempt?.manualSubmitRequired ? 'translateY(0px)' : 'translateY(60px)',
|
transform: showSubmitButton ? 'translateY(0px)' : 'translateY(60px)',
|
||||||
opacity: attempt?.manualSubmitRequired ? 1 : 0,
|
opacity: showSubmitButton ? 1 : 0,
|
||||||
scale: attempt?.manualSubmitRequired ? 1 : 0.8,
|
scale: showSubmitButton ? 1 : 0.8,
|
||||||
config: { tension: 280, friction: 14 },
|
config: { tension: 280, friction: 14 },
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -391,7 +397,11 @@ export function ActiveSession({
|
|||||||
// Auto-trigger help when an unambiguous prefix sum is detected
|
// Auto-trigger help when an unambiguous prefix sum is detected
|
||||||
// The awaitingDisambiguation phase handles the timer and auto-transitions to helpMode when it expires
|
// The awaitingDisambiguation phase handles the timer and auto-transitions to helpMode when it expires
|
||||||
// This effect only handles the inputting phase case for unambiguous matches
|
// This effect only handles the inputting phase case for unambiguous matches
|
||||||
|
// DISABLED when abacus is docked - user controls when to submit, help triggers on submit if needed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// Skip auto-help when abacus is docked - user has manual control
|
||||||
|
if (isDockedByUser) return
|
||||||
|
|
||||||
// Only handle unambiguous prefix matches in inputting phase
|
// Only handle unambiguous prefix matches in inputting phase
|
||||||
// Ambiguous cases are handled by awaitingDisambiguation phase, which auto-transitions to helpMode
|
// Ambiguous cases are handled by awaitingDisambiguation phase, which auto-transitions to helpMode
|
||||||
if (phase.phase !== 'inputting') return
|
if (phase.phase !== 'inputting') return
|
||||||
@@ -403,7 +413,7 @@ export function ActiveSession({
|
|||||||
enterHelpMode(newConfirmedCount)
|
enterHelpMode(newConfirmedCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [phase, matchedPrefixIndex, prefixSums.length, enterHelpMode])
|
}, [phase, matchedPrefixIndex, prefixSums.length, enterHelpMode, isDockedByUser])
|
||||||
|
|
||||||
// Handle when student reaches target value on help abacus
|
// Handle when student reaches target value on help abacus
|
||||||
// Sequence: show target value → dismiss abacus → show value in answer boxes → fade to empty → exit
|
// Sequence: show target value → dismiss abacus → show value in answer boxes → fade to empty → exit
|
||||||
@@ -458,6 +468,21 @@ export function ActiveSession({
|
|||||||
const answerNum = parseInt(attemptData.userAnswer, 10)
|
const answerNum = parseInt(attemptData.userAnswer, 10)
|
||||||
if (Number.isNaN(answerNum)) return
|
if (Number.isNaN(answerNum)) return
|
||||||
|
|
||||||
|
// When abacus is docked and not already in help mode, check if answer is a prefix sum
|
||||||
|
// If so, trigger help mode instead of submitting (mimic auto-help behavior on submit)
|
||||||
|
if (isDockedByUser && phase.phase !== 'helpMode') {
|
||||||
|
// Check if the answer matches a prefix sum (but not the final answer)
|
||||||
|
const prefixIndex = prefixSums.indexOf(answerNum)
|
||||||
|
if (prefixIndex >= 0 && prefixIndex < prefixSums.length - 1) {
|
||||||
|
// Answer matches a prefix sum - enter help mode instead of submitting
|
||||||
|
const newConfirmedCount = prefixIndex + 1
|
||||||
|
if (newConfirmedCount < attemptData.problem.terms.length) {
|
||||||
|
enterHelpMode(newConfirmedCount)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Transition to submitting phase
|
// Transition to submitting phase
|
||||||
startSubmit()
|
startSubmit()
|
||||||
|
|
||||||
@@ -512,10 +537,14 @@ export function ActiveSession({
|
|||||||
onAnswer,
|
onAnswer,
|
||||||
currentSlotIndex,
|
currentSlotIndex,
|
||||||
currentPart,
|
currentPart,
|
||||||
|
currentPartIndex,
|
||||||
startSubmit,
|
startSubmit,
|
||||||
completeSubmit,
|
completeSubmit,
|
||||||
startTransition,
|
startTransition,
|
||||||
clearToLoading,
|
clearToLoading,
|
||||||
|
isDockedByUser,
|
||||||
|
prefixSums,
|
||||||
|
enterHelpMode,
|
||||||
])
|
])
|
||||||
|
|
||||||
// Auto-submit when correct answer is entered
|
// Auto-submit when correct answer is entered
|
||||||
@@ -1160,8 +1189,7 @@ export function ActiveSession({
|
|||||||
</DecompositionProvider>
|
</DecompositionProvider>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{/* Abacus dock - positioned absolutely so it doesn't affect problem centering */}
|
||||||
{/* Abacus dock - positioned to the right of the problem when in abacus mode and not in help mode */}
|
|
||||||
{currentPart.type === 'abacus' && !showHelpOverlay && (
|
{currentPart.type === 'abacus' && !showHelpOverlay && (
|
||||||
<AbacusDock
|
<AbacusDock
|
||||||
id="practice-abacus"
|
id="practice-abacus"
|
||||||
@@ -1169,22 +1197,14 @@ export function ActiveSession({
|
|||||||
interactive={true}
|
interactive={true}
|
||||||
showNumbers={false}
|
showNumbers={false}
|
||||||
animated={true}
|
animated={true}
|
||||||
|
scaleFactor={2.5}
|
||||||
onValueChange={handleAbacusDockValueChange}
|
onValueChange={handleAbacusDockValueChange}
|
||||||
className={css({
|
className={css({
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: '100%',
|
left: '100%',
|
||||||
top: 0,
|
top: '50%',
|
||||||
bottom: 0,
|
transform: 'translateY(-50%)',
|
||||||
marginLeft: '1rem',
|
marginLeft: '1.5rem',
|
||||||
width: '120px',
|
|
||||||
'@media (min-width: 768px)': {
|
|
||||||
marginLeft: '1.5rem',
|
|
||||||
width: '150px',
|
|
||||||
},
|
|
||||||
'@media (min-width: 1024px)': {
|
|
||||||
marginLeft: '2rem',
|
|
||||||
width: '180px',
|
|
||||||
},
|
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -1226,9 +1246,9 @@ export function ActiveSession({
|
|||||||
<animated.button
|
<animated.button
|
||||||
type="button"
|
type="button"
|
||||||
data-action="submit"
|
data-action="submit"
|
||||||
data-visible={attempt.manualSubmitRequired}
|
data-visible={showSubmitButton}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={!canSubmit || isSubmitting || !attempt.manualSubmitRequired}
|
disabled={!canSubmit || isSubmitting || !showSubmitButton}
|
||||||
style={submitButtonSpring}
|
style={submitButtonSpring}
|
||||||
className={css({
|
className={css({
|
||||||
padding: '0.75rem 2rem',
|
padding: '0.75rem 2rem',
|
||||||
@@ -1236,16 +1256,12 @@ export function ActiveSession({
|
|||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
cursor: !canSubmit || !attempt.manualSubmitRequired ? 'not-allowed' : 'pointer',
|
cursor: !canSubmit || !showSubmitButton ? 'not-allowed' : 'pointer',
|
||||||
backgroundColor: canSubmit ? 'blue.500' : isDark ? 'gray.700' : 'gray.300',
|
backgroundColor: canSubmit ? 'blue.500' : isDark ? 'gray.700' : 'gray.300',
|
||||||
color: !canSubmit ? (isDark ? 'gray.400' : 'gray.500') : 'white',
|
color: !canSubmit ? (isDark ? 'gray.400' : 'gray.500') : 'white',
|
||||||
_hover: {
|
_hover: {
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
canSubmit && attempt.manualSubmitRequired
|
canSubmit && showSubmitButton ? 'blue.600' : isDark ? 'gray.600' : 'gray.300',
|
||||||
? 'blue.600'
|
|
||||||
: isDark
|
|
||||||
? 'gray.600'
|
|
||||||
: 'gray.300',
|
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
@@ -1261,7 +1277,7 @@ export function ActiveSession({
|
|||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
currentValue={attempt.userAnswer}
|
currentValue={attempt.userAnswer}
|
||||||
showSubmitButton={attempt.manualSubmitRequired}
|
showSubmitButton={showSubmitButton}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user