| import { useState } from 'react'; |
|
|
| interface ResultCardProps { |
| title: string; |
| score: number; |
| snippet: string; |
| expanded?: boolean; |
| onToggle?: () => void; |
| } |
|
|
| function ScoreBadge({ score }: { score: number }) { |
| const pct = Math.round(score * 100); |
| const bg = pct >= 80 ? 'var(--score-good-bg)' : pct >= 50 ? 'var(--score-mid-bg)' : 'var(--score-bad-bg)'; |
| const color = pct >= 80 ? '#2e7d32' : pct >= 50 ? '#f57f17' : 'var(--text-secondary)'; |
|
|
| return ( |
| <span style={{ |
| display: 'inline-block', |
| padding: '0.15rem 0.45rem', |
| borderRadius: '4px', |
| background: bg, |
| color, |
| fontFamily: "'SF Mono', 'Fira Code', 'Cascadia Code', monospace", |
| fontSize: '0.72rem', |
| fontWeight: 700, |
| }}> |
| {pct}% |
| </span> |
| ); |
| } |
|
|
| export default function ResultCard({ title, score, snippet, expanded: expandedProp, onToggle }: ResultCardProps) { |
| const [localExpanded, setLocalExpanded] = useState(false); |
| const isControlled = expandedProp !== undefined; |
| const expanded = isControlled ? expandedProp : localExpanded; |
|
|
| function handleToggle() { |
| if (isControlled) { |
| onToggle?.(); |
| } else { |
| setLocalExpanded(e => !e); |
| } |
| } |
|
|
| const preview = snippet.length > 200 ? snippet.slice(0, 200) + '\u2026' : snippet; |
|
|
| return ( |
| <div |
| onClick={handleToggle} |
| style={{ |
| padding: '0.65rem 0.85rem', |
| background: 'var(--bg-card)', |
| border: '1px solid var(--border)', |
| borderRadius: '6px', |
| marginBottom: '0.4rem', |
| cursor: 'pointer', |
| transition: 'box-shadow 0.15s', |
| }} |
| onMouseEnter={e => { (e.currentTarget as HTMLDivElement).style.boxShadow = '0 2px 8px var(--shadow)'; }} |
| onMouseLeave={e => { (e.currentTarget as HTMLDivElement).style.boxShadow = 'none'; }} |
| > |
| <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '0.5rem' }}> |
| <span style={{ |
| fontFamily: 'system-ui, -apple-system, sans-serif', |
| fontSize: '0.85rem', |
| fontWeight: 600, |
| color: 'var(--text)', |
| overflow: 'hidden', |
| textOverflow: 'ellipsis', |
| whiteSpace: 'nowrap', |
| flex: 1, |
| }}> |
| {title} |
| </span> |
| <ScoreBadge score={score} /> |
| <span style={{ color: 'var(--text-muted)', fontSize: '0.75rem', flexShrink: 0 }}> |
| {expanded ? '\u25B2' : '\u25BC'} |
| </span> |
| </div> |
| |
| <div style={{ |
| marginTop: '0.4rem', |
| fontFamily: "'SF Mono', 'Fira Code', 'Cascadia Code', monospace", |
| fontSize: '0.72rem', |
| color: 'var(--text-secondary)', |
| lineHeight: 1.5, |
| whiteSpace: expanded ? 'pre-wrap' : 'nowrap', |
| overflow: 'hidden', |
| textOverflow: expanded ? 'unset' : 'ellipsis', |
| }}> |
| {expanded ? snippet : preview} |
| </div> |
| </div> |
| ); |
| } |
|
|