SPB Git

spb/rareindex

Public
3.6 KB · 79 lines tsx
1import Link from 'next/link';2import { ExternalLink } from 'lucide-react';3import { cn, fmtMoney } from '@/lib/format';4import { catName, gradeLabel } from '@/lib/taxonomy';5import { Badge } from '@/components/ui/primitives';67/** Small shared building blocks for market tables and cards. */89import { Thumb } from '@/components/ui/tile-image';10export { Thumb };1112export function CategoryTag({ slug, className }: { slug: string | null | undefined; className?: string }) {13  if (!slug) return null;14  return (15    <Link href={`/markets/${slug}`} className={cn('text-[11px] text-muted hover:text-fg', className)}>16      {catName(slug)}17    </Link>18  );19}2021export function GradeBadge({ grader, grade, className }: { grader: string | null | undefined; grade: string | null | undefined; className?: string }) {22  const label = gradeLabel(grader, grade);23  if (!label) return <span className={cn('text-subtle', className)}>—</span>;24  return (25    <Badge tone={grader && grader !== 'raw' ? 'index' : 'neutral'} className={className}>26      {label}27    </Badge>28  );29}3031/** §178: source attribution with outbound link. */32export function SourceLink({ name, url, className }: { name: string; url: string | null | undefined; className?: string }) {33  if (!url) return <span className={cn('text-muted', className)}>{name}</span>;34  return (35    <a href={url} target="_blank" rel="noopener nofollow" className={cn('inline-flex items-center gap-1 text-muted hover:text-fg', className)} title={`View on ${name}`}>36      {name}37      <ExternalLink className="h-3 w-3" aria-hidden />38    </a>39  );40}4142export function PriceCell({ usd, native, currency, className }: { usd: number | null | undefined; native?: number | null; currency?: string | null; className?: string }) {43  const showNative = native !== null && native !== undefined && currency && currency !== 'USD';44  return (45    <span className={cn('num inline-flex flex-col items-end leading-tight', className)}>46      <span className="font-medium text-fg">{fmtMoney(usd)}</span>47      {showNative ? <span className="text-[10px] text-subtle">{fmtMoney(native, currency!)}</span> : null}48    </span>49  );50}5152export function AssetTitleCell({ slug, title, categorySlug, image, sub, size = 36 }: { slug: string; title: string; categorySlug?: string | null; image?: string | null; sub?: string | null; size?: number }) {53  return (54    <span className="flex min-w-0 items-center gap-2.5">55      <Thumb src={image} alt="" size={size} />56      <span className="min-w-0">57        <Link href={`/asset/${slug}`} className="block truncate font-medium text-fg hover:underline" title={title}>58          {title}59        </Link>60        <span className="block truncate text-[11px] text-muted">61          {categorySlug ? catName(categorySlug) : null}62          {sub ? (categorySlug ? ` · ${sub}` : sub) : null}63        </span>64      </span>65    </span>66  );67}6869/** Heuristic sale verification label (§39, §205): verified · likely · unverified · excluded. Never a claim of independent confirmation. */70export function VerificationBadge({ label, reason, className }: { label: 'verified' | 'likely' | 'unverified' | 'excluded'; reason?: string; className?: string }) {71  const tone = label === 'verified' ? 'gain' : label === 'likely' ? 'neutral' : 'alert';72  const text = label === 'verified' ? 'Verified' : label === 'likely' ? 'Likely' : label === 'unverified' ? 'Unverified' : 'Excluded';73  return (74    <Badge tone={tone} className={className}>75      <span title={`${text} sale — heuristic on source type, sale type, match confidence and source trust${reason ? `: ${reason}` : ''}`}>{text}</span>76    </Badge>77  );78}79
Files · b5d6968a47f5d2546937866bdabc967739349047
Menu

Theme