// Scarf v2 shared components — calmer density, full state matrices. // Exports to window: Btn, IconBtn, Pill, Dot, Card, StatCard, Section, ContentHeader, // Field, TextInput, NumberInput, TextArea, Toggle, Checkbox, Radio, RadioGroup, // Segmented, Select, SettingsGroup, SettingsRow, Tabs, Menu, MenuItem, Divider, // EmptyState, KbdKey, HelpIcon, Tooltip, Avatar, ProgressBar, Spinner. const SF = "var(--font-sans)"; // ─────────────── ContentHeader ─────────────── function ContentHeader({ title, subtitle, actions, right, breadcrumb }) { return (
{breadcrumb && (
{breadcrumb}
)}
{title}
{subtitle &&
{subtitle}
}
{right} {actions &&
{actions}
}
); } // ─────────────── Buttons ─────────────── function Btn({ kind = 'secondary', size = 'md', icon, iconRight, children, onClick, disabled, loading, fullWidth, type = 'button' }) { const sizes = { sm: { padding: '5px 11px', fontSize: 12, gap: 5, iconSize: 13 }, md: { padding: '7px 14px', fontSize: 13, gap: 6, iconSize: 14 }, lg: { padding: '10px 18px', fontSize: 14, gap: 7, iconSize: 16 }, }; const kinds = { primary: { background: 'var(--accent)', color: 'var(--on-accent)', border: '1px solid transparent', shadow: '0 1px 0 rgba(0,0,0,0.08), inset 0 1px 0 rgba(255,255,255,0.18)' }, secondary: { background: 'var(--bg-card)', color: 'var(--fg)', border: '1px solid var(--border-strong)', shadow: 'var(--shadow-sm)' }, ghost: { background: 'transparent', color: 'var(--fg)', border: '1px solid transparent' }, danger: { background: 'var(--bg-card)', color: 'var(--red-600)', border: '1px solid var(--red-500)' }, 'danger-solid': { background: 'var(--red-500)', color: '#fff', border: '1px solid transparent' }, accent: { background: 'var(--accent-tint)', color: 'var(--accent-active)', border: '1px solid transparent' }, }; const s = sizes[size]; const k = kinds[kind]; const [hover, setHover] = React.useState(false); const hoverStyle = !disabled && hover ? { primary: { background: 'var(--accent-hover)' }, secondary: { background: 'var(--gray-50)', borderColor: 'var(--accent)' }, ghost: { background: 'var(--bg-quaternary)' }, danger: { background: 'var(--red-100)' }, 'danger-solid': { background: 'var(--red-600)' }, accent: { background: 'var(--accent-tint-strong)' }, }[kind] : {}; return ( ); } function IconBtn({ icon, onClick, size = 28, tooltip, active, disabled }) { const [hover, setHover] = React.useState(false); return ( ); } function Spinner({ size = 14, color = 'currentColor' }) { return ( ); } // ─────────────── Pills / Dots ─────────────── function Pill({ tone = 'gray', dot, icon, children, size = 'md' }) { const tones = { gray: { bg: 'var(--bg-quaternary)', fg: 'var(--fg-muted)', dotc: 'var(--gray-500)' }, green: { bg: 'var(--green-100)', fg: 'var(--green-600)', dotc: 'var(--green-500)' }, red: { bg: 'var(--red-100)', fg: 'var(--red-600)', dotc: 'var(--red-500)' }, orange: { bg: 'var(--orange-100)', fg: '#A8741F', dotc: 'var(--orange-500)' }, blue: { bg: 'var(--blue-100)', fg: '#1F70A8', dotc: 'var(--blue-500)' }, accent: { bg: 'var(--accent-tint)', fg: 'var(--accent-active)', dotc: 'var(--accent)' }, amber: { bg: 'var(--orange-100)', fg: '#A8741F', dotc: 'var(--orange-500)' }, purple: { bg: '#EFE0F8', fg: '#5E4080', dotc: '#7E5BA9' }, idle: { bg: 'var(--bg-quaternary)', fg: 'var(--fg-faint)', dotc: 'var(--gray-400)' }, }; const t = tones[tone]; const sizes = { sm: { p: '2px 7px', f: 10 }, md: { p: '3px 9px', f: 11 }, lg: { p: '4px 11px', f: 12 } }; const sz = sizes[size]; return ( {dot && } {icon && } {children} ); } function Dot({ tone = 'gray', size = 8 }) { const tones = { gray: 'var(--gray-400)', green: 'var(--green-500)', red: 'var(--red-500)', orange: 'var(--orange-500)', blue: 'var(--blue-500)', accent: 'var(--accent)' }; return ; } // ─────────────── Cards / Sections ─────────────── function Card({ children, padding = 18, style = {}, onClick, interactive }) { return (
{children}
); } function StatCard({ label, value, sub, accent, icon }) { return (
{icon && } {label}
{value}
{sub &&
{sub}
}
); } function Section({ title, hint, right, children, gap = 12 }) { return (
{title}
{hint &&
{hint}
}
{right}
{children}
); } function Divider({ vertical, label }) { if (vertical) return
; if (label) return (
{label}
); return
; } // ─────────────── Form fields ─────────────── function Field({ label, hint, error, help, children, required, inline }) { return ( ); } function HelpIcon({ text }) { return ( ); } function inputStyle(invalid) { return { fontFamily: SF, fontSize: 13, padding: '7px 11px', border: `1px solid ${invalid ? 'var(--red-500)' : 'var(--border-strong)'}`, borderRadius: 7, background: 'var(--bg-card)', color: 'var(--fg)', outline: 'none', transition: 'all 120ms', width: '100%', boxSizing: 'border-box', }; } function TextInput({ value, onChange, placeholder, mono, invalid, leftIcon, rightSlot, type = 'text' }) { const [v, setV] = React.useState(value ?? ''); React.useEffect(() => setV(value ?? ''), [value]); const ref = React.useRef(); return (
{leftIcon && } { setV(e.target.value); onChange && onChange(e.target.value); }} placeholder={placeholder} style={{ ...inputStyle(invalid), fontFamily: mono ? 'var(--font-mono)' : SF, paddingLeft: leftIcon ? 32 : 11, paddingRight: rightSlot ? 36 : 11, }} onFocus={e => { if (!invalid) { e.target.style.borderColor = 'var(--accent)'; e.target.style.boxShadow = 'var(--shadow-focus)'; }}} onBlur={e => { e.target.style.borderColor = invalid ? 'var(--red-500)' : 'var(--border-strong)'; e.target.style.boxShadow = 'none'; }} /> {rightSlot &&
{rightSlot}
}
); } function TextArea({ value, onChange, placeholder, rows = 3, invalid, mono }) { const [v, setV] = React.useState(value ?? ''); React.useEffect(() => setV(value ?? ''), [value]); return (