import { type ReactNode, useEffect } from 'react'
import { X } from 'lucide-react'
import { num } from '../lib/format'
// Modal is a dependency free overlay: a dimmed backdrop plus a centered, scrollable panel. Escape and a
// backdrop click close it. The body scroll is locked while it is open so a tall result does not jump.
export function Modal({
open,
onClose,
title,
children,
}: {
open: boolean
onClose: () => void
title: string
children: ReactNode
}) {
useEffect(() => {
if (!open) return
const onKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose()
}
document.addEventListener('keydown', onKey)
const prev = document.body.style.overflow
document.body.style.overflow = 'hidden'
return () => {
document.removeEventListener('keydown', onKey)
document.body.style.overflow = prev
}
}, [open, onClose])
if (!open) return null
return (
e.stopPropagation()}
>
{children}
)
}
export function Panel({
children,
className = '',
title,
hint,
}: {
children: ReactNode
className?: string
title?: string
hint?: string
}) {
return (
{title && (
)}
{children}
)
}
export function Bar({ value, color }: { value: number; color: string }) {
return (
)
}
export function Badge({
children,
color,
subtle = false,
}: {
children: ReactNode
color: string
subtle?: boolean
}) {
return (
{children}
)
}
export function Dot({ color }: { color: string }) {
return
}
export function Stat({
label,
value,
accent,
icon,
}: {
label: string
value: ReactNode
accent?: string
icon?: ReactNode
}) {
return (
{label}
{icon && {icon}}
{value}
)
}
// CheckRow renders a met (✓) / unmet (⚠) requirement line.
export function CheckRow({ ok, label }: { ok: boolean; label: string }) {
return (
{ok ? '✓' : '!'}
{label}
)
}
export function ScoreRow({
name,
weight,
score,
color,
}: {
name: string
weight: string
score: string
color: string
}) {
return (
{name}
{Math.round(Number(weight) * 100)}%
{num(score).toFixed(0)}%
)
}