Files
jellybloom/src/components/detail/AwardsBlock.tsx
T
2026-03-27 23:06:44 +02:00

96 lines
3.0 KiB
TypeScript

import { useMemo, useState } from 'react'
import { motion } from 'framer-motion'
import type { WikidataAward } from '../../api/wikidata'
interface Props {
awards: WikidataAward[] | null | undefined
}
const PRIORITY_KEYWORDS = [
'Academy Award',
'Oscar',
'Golden Globe',
'BAFTA',
'Emmy',
'Cannes',
'Palme',
'Berlin',
'Sundance',
'Venice',
'Critics',
'SAG',
'Hugo',
'Nebula',
'Saturn',
'MTV',
]
/**
* Compact award grid pulled from Wikidata P166. We surface the most
* recognised prizes first (Oscars, Globes, BAFTAs, Emmys, etc.), then
* dedupe and cap at a sensible count. A "Show all" toggle reveals the
* remainder when there are many.
*/
export default function AwardsBlock({ awards }: Props) {
const [expanded, setExpanded] = useState(false)
const ordered = useMemo(() => {
if (!awards) return []
const seen = new Set<string>()
const unique: WikidataAward[] = []
for (const a of awards) {
const key = a.label
if (seen.has(key)) continue
seen.add(key)
unique.push(a)
}
return unique.sort((a, b) => priority(a) - priority(b))
}, [awards])
if (ordered.length === 0) return null
const visible = expanded ? ordered : ordered.slice(0, 12)
return (
<div>
<div className="flex flex-wrap gap-1.5">
{visible.map((a, i) => (
<motion.div
key={a.id || a.label}
initial={{ opacity: 0, y: 4 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25, delay: Math.min(i * 0.02, 0.3) }}
title={[a.ceremony, a.point_in_time?.slice(0, 4), a.for_work].filter(Boolean).join(' · ')}
className="inline-flex items-center gap-1.5 h-7 px-3 bg-elevated/60 ring-1 ring-border hover:ring-border-strong rounded-full text-[11.5px] text-text-2 tracking-tight"
>
<svg className="w-3 h-3 text-amber-400" viewBox="0 0 20 20" fill="currentColor">
<path d="M10 2a1 1 0 011 1v.5a4.5 4.5 0 014.5 4.5v.5h.5a1.5 1.5 0 010 3H15v.5a5 5 0 01-4 4.9V18h2a1 1 0 110 2H7a1 1 0 110-2h2v-1.1A5 5 0 015 12v-.5h-.5a1.5 1.5 0 010-3H5V8a4.5 4.5 0 014.5-4.5V3a1 1 0 011-1z" />
</svg>
<span className="font-medium">{a.label}</span>
{a.point_in_time && (
<span className="text-text-4 tabular-nums">
{a.point_in_time.slice(0, 4)}
</span>
)}
</motion.div>
))}
</div>
{ordered.length > 12 && (
<button
onClick={() => setExpanded(v => !v)}
className="mt-3 text-[11.5px] text-text-3 hover:text-accent transition tracking-tight focus-ring rounded"
>
{expanded ? 'Show fewer' : `Show all ${ordered.length}`}
</button>
)}
</div>
)
}
function priority(a: WikidataAward): number {
const label = a.label || ''
for (let i = 0; i < PRIORITY_KEYWORDS.length; i++) {
if (label.includes(PRIORITY_KEYWORDS[i])) return i
}
return PRIORITY_KEYWORDS.length
}