// ui.jsx — Small reusable UI primitives const { useState, useEffect, useRef, useMemo } = React; // --- Card --- const Card = ({ pad = "md", className = "", style = {}, children, title, action, ...rest }) => { const padClass = pad === "sm" ? "card-pad-sm" : pad === "lg" ? "card-pad-lg" : "card-pad"; return (
{title && (

{title}

{action}
)} {children}
); }; // --- Pill / Badge --- const Pill = ({ tone = "default", children, dot, className = "" }) => { const toneClass = { good: "pill-good", watch: "pill-watch", alert: "pill-alert", info: "pill-info", skin: "pill-skin", outline: "pill-outline", }[tone] || ""; return ( {dot && } {children} ); }; // --- Stat block --- const Stat = ({ label, value, delta, deltaSign = "up", sub, mini }) => (
{label}
{value} {delta && {deltaSign === "up" ? "▲" : "▼"} {delta}}
{sub &&
{sub}
}
); // --- Stepper for multi-step flows --- const Stepper = ({ steps, current }) => (
{steps.map((s, i) => (
))}
); // --- Avatar --- const Avatar = ({ initials, size = 36, color = "#1D1D1F", textColor = "#FFF" }) => (
{initials}
); // --- Tabs --- const Tabs = ({ tabs, value, onChange }) => (
{tabs.map(t => ( ))}
); // --- Chip group (multi-select chips) --- const ChipGroup = ({ options, value = [], onChange, single }) => (
{options.map(opt => { const v = typeof opt === "string" ? opt : opt.v; const l = typeof opt === "string" ? opt : opt.l; const ico = typeof opt === "object" ? opt.icon : null; const selected = single ? value === v : value.includes(v); return ( ); })}
); // --- Loader spinner --- const Spinner = ({ size = 16 }) => ( ); // --- Verdict pill --- const VerdictPill = ({ verdict, note }) => { const tone = verdict === "good" ? "good" : verdict === "alert" ? "alert" : "watch"; const label = verdict === "good" ? "양호" : verdict === "alert" ? "주의" : "관찰"; return {note || label}; }; // --- KRW formatter --- const krw = (n) => { if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(2)}M`; if (n >= 10_000) return `${(n / 10_000).toFixed(0)}만`; return n.toLocaleString(); }; const krwFull = (n) => "₩" + n.toLocaleString(); Object.assign(window, { Card, Pill, Stat, Stepper, Avatar, Tabs, ChipGroup, Spinner, VerdictPill, krw, krwFull, });