Files
claude-code/packages/pokemon/src/ui/SpriteAnimator.tsx
claude-code-best f22caf0e97 feat: 集成 Battle tab 到 BuddyPanel,重命名 data/ 为 dex/ 规避 gitignore
- BuddyPanel 新增 Battle tab,BattleFlow 加 isActive 控制
- BattleFlow configSelect 阶段支持 ↑↓ 选择物种
- packages/pokemon/src/data/ → dex/,解决根 .gitignore 匹配问题
- 全量 Tab→2空格 缩进转换

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-22 08:35:19 +08:00

73 lines
2.0 KiB
TypeScript

import React, { useEffect, useState } from 'react'
import { Box, Text, type Color } from '@anthropic/ink'
import type { AnimMode } from '../types'
import { renderAnimatedSprite, getIdleAnimMode, getPetOverlay } from '../sprites/renderer'
/** Vertical padding — bounce shifts within this space */
const V_PAD = 4
interface SpriteAnimatorProps {
/** Base sprite lines (ANSI is preserved) */
lines: string[]
/** Text color for the sprite */
color?: Color
/** Tick interval in ms (default 250) */
tickMs?: number
/** Single mode; omit for idle auto-play */
mode?: AnimMode
/** Center horizontally (default true) */
centered?: boolean
/** Show pet hearts overlay */
petting?: boolean
}
/**
* Animated sprite renderer with built-in tick loop.
*
* - Keeps ANSI intact (parse → pixel grid → transform → render)
* - Pads vertically so bounce never shifts layout
* - Grid transforms guarantee fixed output height
*/
export function SpriteAnimator({
lines,
color,
tickMs = 100,
mode,
centered = true,
petting,
}: SpriteAnimatorProps) {
const [tick, setTick] = useState(0)
useEffect(() => {
const timer = setInterval(() => setTick(t => t + 1), tickMs)
return () => clearInterval(timer)
}, [tickMs])
// Add vertical padding — bounce shifts within this space
const padded = [...Array(V_PAD).fill(''), ...lines, ...Array(V_PAD).fill('')]
// Apply animation (renderer parses to pixels, transforms, renders back)
const currentMode = mode ?? getIdleAnimMode(tick)
const animated = renderAnimatedSprite(padded, tick, currentMode)
// Pet hearts overlay
const overlay = petting ? getPetOverlay(tick) : null
const displayLines = overlay ? [...overlay, ...animated] : animated
const spriteBlock = (
<Box flexDirection="column">
{displayLines.map((line, i) => (
<Text key={i} color={color}>{line || ' '}</Text>
))}
</Box>
)
if (!centered) return spriteBlock
return (
<Box flexDirection="row" justifyContent="center" width="100%">
{spriteBlock}
</Box>
)
}