claude-code-best/packages/pokemon/src/ui/SpeciesPicker.tsx
claude-code-best 02783e4f5d feat: PC Box 管理系统 + 全英文名统一 + 队伍补位机制
- 新增 PC Box tab(左侧 party + 右侧 box 网格,支持 party↔box 拾取/放置/交换)
- 空格键抓取/放下,左键在 col=0 时切到 party 面板
- 使用 useTabHeaderFocus 避免左右键被 Tabs 组件拦截
- 所有 1025 只精灵统一使用 Dex 英文名,移除中英混搭
- compactParty 补位机制:不允许前置空位,队伍最少保留一只
- PC Box tab 移至第二位(Buddy → PC Box → Pokédex → Egg)

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

80 lines
2.3 KiB
TypeScript

import React, { useState, useMemo, useCallback } from 'react'
import { Box, Text, FuzzyPicker } from '@anthropic/ink'
import type { SpeciesId } from '../types'
import { ALL_SPECIES_IDS } from '../types'
import { getSpeciesData } from '../dex/species'
/** Pre-computed species entry for picker */
type SpeciesEntry = {
id: SpeciesId
name: string
displayName: string // zh name or English name
dexNumber: number
types: string[]
}
// Build all entries once (species data is cached internally by getSpeciesData)
const ALL_ENTRIES: SpeciesEntry[] = ALL_SPECIES_IDS.map(id => {
const data = getSpeciesData(id)
return {
id,
name: data.name,
displayName: data.name,
dexNumber: data.dexNumber,
types: data.types as string[],
}
})
/** Searchable species picker using FuzzyPicker */
export function SpeciesPicker({
onSelect,
onCancel,
title = '选择精灵',
}: {
onSelect: (speciesId: SpeciesId) => void
onCancel: () => void
title?: string
}) {
const [filtered, setFiltered] = useState<SpeciesEntry[]>(ALL_ENTRIES.slice(0, 50))
const handleQueryChange = useCallback((q: string) => {
if (!q.trim()) {
setFiltered(ALL_ENTRIES.slice(0, 50))
return
}
const lower = q.toLowerCase()
const matched = ALL_ENTRIES.filter(e =>
e.id.includes(lower) ||
e.name.toLowerCase().includes(lower) ||
e.displayName.includes(q) ||
String(e.dexNumber).includes(q)
)
setFiltered(matched.slice(0, 100))
}, [])
return (
<FuzzyPicker<SpeciesEntry>
title={title}
placeholder="输入名称或编号搜索…"
items={filtered}
getKey={item => item.id}
renderItem={(item, focused) => (
<Box>
<Text color={focused ? 'claude' : undefined} bold={focused}>
#{String(item.dexNumber).padStart(3, '0')} {item.displayName}
</Text>
{item.displayName !== item.name && (
<Text dimColor> {item.name}</Text>
)}
<Text color="inactive"> {item.types.join('/')}</Text>
</Box>
)}
onQueryChange={handleQueryChange}
onSelect={item => onSelect(item.id)}
onCancel={onCancel}
emptyMessage={q => `没有找到 "${q}" 相关的精灵`}
matchLabel={filtered.length < ALL_ENTRIES.length ? `${filtered.length} 个结果` : undefined}
/>
)
}