Files
soroban-abacus-flashcards/apps/web/src/components/practice/PracticeHelpOverlay.stories.tsx
Thomas Hallock 9a4ab8296e feat(practice): add progressive help overlay with proper positioning
- Create PracticeHelpOverlay component showing interactive abacus with
  bead arrows and tooltips (uses same system as TutorialPlayer)
- Extract BeadTooltipContent to shared component for consistency
- Add helpOverlay prop to VerticalProblem for proper positioning
- Position help abacus directly above the term being helped using
  bottom: 100% on the term row (overflow visible)
- Dynamically size abacus columns based on max(currentValue, targetValue)
- Add timing configuration in helpTiming.ts (debug vs production)
- Add beadTooltipUtils.ts for tooltip positioning calculations

The help overlay now correctly covers the confirmed terms in the
vertical problem, with the "Adding: +X" badge and interactive abacus
positioned above the term being worked on.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-07 15:38:17 -06:00

201 lines
4.8 KiB
TypeScript

import type { Meta, StoryObj } from '@storybook/react'
import { useCallback, useState } from 'react'
import { css } from '../../../styled-system/css'
import { PracticeHelpOverlay } from './PracticeHelpOverlay'
const meta: Meta<typeof PracticeHelpOverlay> = {
title: 'Practice/PracticeHelpOverlay',
component: PracticeHelpOverlay,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
}
export default meta
type Story = StoryObj<typeof PracticeHelpOverlay>
/**
* Interactive demo showing the help overlay with time-based escalation
*/
function InteractiveDemo() {
const [currentValue, setCurrentValue] = useState(13)
const [targetValue] = useState(33) // 13 + 20
const [completed, setCompleted] = useState(false)
const handleTargetReached = useCallback(() => {
setCompleted(true)
console.log('Target reached!')
// After celebration, could reset or advance
setTimeout(() => {
setCompleted(false)
setCurrentValue(33)
}, 1000)
}, [])
return (
<div
className={css({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '1rem',
padding: '2rem',
maxWidth: '500px',
})}
>
<div
className={css({
textAlign: 'center',
marginBottom: '1rem',
})}
>
<h3 className={css({ fontSize: '1.25rem', fontWeight: 'bold', marginBottom: '0.5rem' })}>
Progressive Help Overlay Demo
</h3>
<p className={css({ fontSize: '0.875rem', color: 'gray.500' })}>
Just the abacus with arrows. Bead tooltip appears after 3s (debug timing).
</p>
<p className={css({ fontSize: '0.75rem', color: 'gray.400' })}>
Tooltip uses same system as TutorialPlayer.
</p>
</div>
{/* Simulating how it appears in ActiveSession - with "Adding: +20" badge above */}
<div
className={css({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '0.5rem',
})}
>
<div
className={css({
display: 'inline-flex',
alignItems: 'center',
gap: '0.5rem',
padding: '0.25rem 0.75rem',
backgroundColor: 'purple.100',
borderRadius: '20px',
fontSize: '0.875rem',
fontWeight: 'bold',
color: 'purple.700',
})}
>
<span>Adding:</span>
<span className={css({ fontFamily: 'monospace', fontSize: '1rem' })}>+20</span>
</div>
{!completed && (
<PracticeHelpOverlay
currentValue={currentValue}
targetValue={targetValue}
columns={3}
onTargetReached={handleTargetReached}
debugTiming={true}
/>
)}
</div>
{completed && (
<div
className={css({
padding: '2rem',
backgroundColor: 'green.100',
borderRadius: '12px',
textAlign: 'center',
})}
>
<span className={css({ fontSize: '2rem' })}>🎉</span>
<p className={css({ fontSize: '1rem', color: 'green.700', fontWeight: 'bold' })}>
Target Reached!
</p>
</div>
)}
</div>
)
}
export const Interactive: Story = {
render: () => <InteractiveDemo />,
}
/**
* Shows just the abacus overlay without any surrounding UI
*/
function MinimalDemo() {
return (
<div
className={css({
display: 'flex',
flexDirection: 'column',
gap: '2rem',
padding: '2rem',
})}
>
<div className={css({ textAlign: 'center' })}>
<h3
className={css({
fontSize: '0.875rem',
fontWeight: 'bold',
color: 'gray.500',
marginBottom: '0.5rem',
})}
>
Minimal: Just the Abacus
</h3>
<PracticeHelpOverlay currentValue={0} targetValue={23} columns={3} debugTiming={true} />
</div>
</div>
)
}
export const Minimal: Story = {
render: () => <MinimalDemo />,
}
// Static examples
export const Simple: Story = {
args: {
currentValue: 0,
targetValue: 5,
columns: 3,
debugTiming: true,
},
}
export const TwoDigit: Story = {
args: {
currentValue: 23,
targetValue: 68,
columns: 3,
debugTiming: true,
},
}
export const ThreeDigit: Story = {
args: {
currentValue: 123,
targetValue: 456,
columns: 3,
debugTiming: true,
},
}
export const ProductionTiming: Story = {
args: {
currentValue: 13,
targetValue: 33,
columns: 3,
debugTiming: false, // Use production timing (10s for tooltip)
},
parameters: {
docs: {
description: {
story: 'Uses production timing: Bead tooltip at 10s',
},
},
},
}