refactor(rithmomachia): update capture components to use CaptureContext

Eliminate prop drilling in all capture dialog components by using the new
CaptureContext:

- CaptureErrorDialog: 4 props → 0 props
- CaptureRelationOptions: 11 props → 1 prop (availableRelations)
- HelperSelectionOptions: 11 props → 1 prop (helpers)
- NumberBondVisualization: 15 props → 3 props (onConfirm, positions)

All components now:
- Use useCaptureContext() for shared capture state
- Use useAbacusSettings() directly instead of prop drilling
- Have cleaner, more focused interfaces

Added missing getSquarePosition import to components that need it.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock 2025-11-02 06:59:11 -06:00
parent 0ab7a1df32
commit 2ab6ab5799
4 changed files with 46 additions and 89 deletions

View File

@ -1,21 +1,12 @@
import { animated, to, useSpring } from '@react-spring/web'
export interface CaptureErrorDialogProps {
targetPos: { x: number; y: number }
cellSize: number
onClose: () => void
closing: boolean
}
import { useCaptureContext } from '../../contexts/CaptureContext'
/**
* Error notification when no capture is possible
*/
export function CaptureErrorDialog({
targetPos,
cellSize,
onClose,
closing,
}: CaptureErrorDialogProps) {
export function CaptureErrorDialog() {
const { layout, closing, dismissDialog } = useCaptureContext()
const { targetPos, cellSize } = layout
const entranceSpring = useSpring({
from: { opacity: 0, y: -20 },
opacity: closing ? 0 : 1,
@ -77,7 +68,7 @@ export function CaptureErrorDialog({
<button
onClick={(e) => {
e.stopPropagation()
onClose()
dismissDialog()
}}
style={{
padding: `${cellSize * 0.06}px ${cellSize * 0.12}px`,

View File

@ -4,40 +4,23 @@ import * as Tooltip from '@radix-ui/react-tooltip'
import { animated, useSpring } from '@react-spring/web'
import { useEffect, useState } from 'react'
import { getRelationColor, getRelationOperator } from '../../constants/captureRelations'
import type { Piece, RelationKind } from '../../types'
import { getSquarePosition } from '../../utils/boardCoordinates'
import type { RelationKind } from '../../types'
import { useCaptureContext } from '../../contexts/CaptureContext'
import { getEffectiveValue } from '../../utils/pieceSetup'
import { getSquarePosition } from '../../utils/boardCoordinates'
interface CaptureRelationOptionsProps {
targetPos: { x: number; y: number }
cellSize: number
gap: number
padding: number
onSelectRelation: (relation: RelationKind) => void
closing?: boolean
availableRelations: RelationKind[]
moverPiece: Piece
targetPiece: Piece
allPieces: Piece[]
findValidHelpers: (moverValue: number, targetValue: number, relation: RelationKind) => Piece[]
}
/**
* Animated floating capture relation options with number bond preview on hover
*/
export function CaptureRelationOptions({
targetPos,
cellSize,
gap,
padding,
onSelectRelation,
closing = false,
availableRelations,
moverPiece,
targetPiece,
allPieces,
findValidHelpers,
}: CaptureRelationOptionsProps) {
export function CaptureRelationOptions({ availableRelations }: CaptureRelationOptionsProps) {
const { layout, pieces, closing, allPieces, findValidHelpers, selectRelation } =
useCaptureContext()
const { targetPos, cellSize, gap, padding } = layout
const { mover: moverPiece, target: targetPiece } = pieces
const [hoveredRelation, setHoveredRelation] = useState<RelationKind | null>(null)
const [currentHelperIndex, setCurrentHelperIndex] = useState(0)
@ -236,7 +219,7 @@ export function CaptureRelationOptions({
<animated.button
onClick={(e) => {
e.stopPropagation()
onSelectRelation(relation as RelationKind)
selectRelation(relation as RelationKind)
}}
style={{
width: buttonSize,

View File

@ -1,41 +1,29 @@
'use client'
import { useState } from 'react'
import { useAbacusSettings } from '@/hooks/useAbacusSettings'
import { useCaptureContext } from '../../contexts/CaptureContext'
import { getRelationColor, getRelationOperator } from '../../constants/captureRelations'
import type { Piece, RelationKind } from '../../types'
import type { Piece } from '../../types'
import { AnimatedHelperPiece } from './AnimatedHelperPiece'
interface HelperSelectionOptionsProps {
helpers: Array<{ piece: Piece; boardPos: { x: number; y: number } }>
targetPos: { x: number; y: number }
cellSize: number
gap: number
padding: number
onSelectHelper: (pieceId: string) => void
closing?: boolean
moverPiece: Piece
targetPiece: Piece
relation: RelationKind
useNativeAbacusNumbers?: boolean
}
/**
* Helper piece selection - pieces fly from board to selection ring
* Hovering over a helper shows a preview of the number bond
*/
export function HelperSelectionOptions({
helpers,
targetPos,
cellSize,
gap,
padding,
onSelectHelper,
closing = false,
moverPiece,
targetPiece,
relation,
useNativeAbacusNumbers = false,
}: HelperSelectionOptionsProps) {
export function HelperSelectionOptions({ helpers }: HelperSelectionOptionsProps) {
const { layout, pieces, selectedRelation, closing, selectHelper } = useCaptureContext()
const { targetPos, cellSize, gap, padding } = layout
const { mover: moverPiece, target: targetPiece } = pieces
const relation = selectedRelation!
// Get abacus settings
const { data: abacusSettings } = useAbacusSettings()
const useNativeAbacusNumbers = abacusSettings?.nativeAbacusNumbers ?? false
const [hoveredHelperId, setHoveredHelperId] = useState<string | null>(null)
const maxRadius = cellSize * 1.2
const angleStep = helpers.length > 1 ? 360 / helpers.length : 0
@ -84,7 +72,7 @@ export function HelperSelectionOptions({
ringX={ringX}
ringY={ringY}
cellSize={cellSize}
onSelectHelper={onSelectHelper}
onSelectHelper={selectHelper}
closing={closing}
useNativeAbacusNumbers={useNativeAbacusNumbers}
onHover={setHoveredHelperId}

View File

@ -2,27 +2,17 @@
import { animated, to, useSpring } from '@react-spring/web'
import { useEffect, useState } from 'react'
import { useAbacusSettings } from '@/hooks/useAbacusSettings'
import { useCaptureContext } from '../../contexts/CaptureContext'
import { getRelationColor, getRelationOperator } from '../../constants/captureRelations'
import type { Piece, RelationKind } from '../../types'
import { getSquarePosition } from '../../utils/boardCoordinates'
import { getEffectiveValue } from '../../utils/pieceSetup'
import { getSquarePosition } from '../../utils/boardCoordinates'
import { PieceRenderer } from '../PieceRenderer'
interface NumberBondVisualizationProps {
moverPiece: Piece
helperPiece: Piece
targetPiece: Piece
relation: RelationKind
targetPos: { x: number; y: number }
cellSize: number
onConfirm: () => void
closing?: boolean
autoAnimate?: boolean
moverStartPos: { x: number; y: number }
helperStartPos: { x: number; y: number }
useNativeAbacusNumbers?: boolean
padding: number
gap: number
}
/**
@ -31,21 +21,20 @@ interface NumberBondVisualizationProps {
* Animation: Rotate and collapse to target position, only mover remains
*/
export function NumberBondVisualization({
moverPiece,
helperPiece,
targetPiece,
relation,
targetPos,
cellSize,
onConfirm,
closing = false,
autoAnimate = true,
moverStartPos,
helperStartPos,
padding,
gap,
useNativeAbacusNumbers = false,
}: NumberBondVisualizationProps) {
const { layout, pieces, selectedRelation, closing } = useCaptureContext()
const { targetPos, cellSize, padding, gap } = layout
const { mover: moverPiece, target: targetPiece, helper: helperPiece } = pieces
const relation = selectedRelation!
// Get abacus settings
const { data: abacusSettings } = useAbacusSettings()
const useNativeAbacusNumbers = abacusSettings?.nativeAbacusNumbers ?? false
const autoAnimate = true
const [animating, setAnimating] = useState(false)
// Auto-trigger animation immediately when component mounts (after helper selection)
@ -77,6 +66,12 @@ export function NumberBondVisualization({
},
})
// Type guard - this component should only be rendered when helper is selected
// Must be after all hooks to follow Rules of Hooks
if (!helperPiece) {
return null
}
// Get piece values
const getMoverValue = () => getEffectiveValue(moverPiece)
const getHelperValue = () => getEffectiveValue(helperPiece)