/* =========================================================== Forja 3D — shared UI components → window =========================================================== */ const { useState, useEffect, useRef, useMemo } = React; /* ---- simple line icons (geometric, stroke-based) ---- */ const ICONS = { overview: 'M3 3h7v7H3zM14 3h7v7h-7zM14 14h7v7h-7zM3 14h7v7H3z', calc: 'M5 3h14v18H5zM8 7h8M8 11h2M8 15h2M14 11v6', spool: 'M12 3a9 9 0 100 18 9 9 0 000-18zM12 9a3 3 0 100 6 3 3 0 000-6zM12 3v3M12 18v3', money: 'M3 6h18v12H3zM12 9a3 3 0 100 6 3 3 0 000-6M6 9v.01M18 15v.01', sale: 'M4 13l4-9 8 0 4 9-8 7zM12 4v9', debt: 'M12 3v18M7 7h7a3 3 0 010 6H7M7 13h8', box: 'M12 3l8 4.5v9L12 21l-8-4.5v-9zM4 7.5l8 4.5 8-4.5M12 12v9', history: 'M3 12a9 9 0 109-9 9 9 0 00-9 9zM3 12H1m11-5v5l3 2', gear: 'M12 9a3 3 0 100 6 3 3 0 000-6zM19 12l2-1-1-3-2 .5-2-2 .5-2-3-1-1 2h-3l-1-2-3 1 .5 2-2 2-2-.5-1 3 2 1v3l-2 1 1 3 2-.5 2 2-.5 2 3 1 1-2h3l1 2 3-1-.5-2 2-2 2 .5 1-3-2-1z', plus: 'M12 5v14M5 12h14', close: 'M6 6l12 12M18 6L6 18', trash: 'M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13', edit: 'M4 20h4L19 9l-4-4L4 16zM14 6l4 4', search: 'M11 4a7 7 0 105 12 7 7 0 00-5-12zM21 21l-5-5', pay: 'M12 5v14M19 12l-7 7-7-7', bolt: 'M13 2L4 14h7l-1 8 9-12h-7z', clock: 'M12 3a9 9 0 100 18 9 9 0 000-18zM12 7v5l3 2', chevron: 'M9 6l6 6-6 6', menu: 'M4 6h16M4 12h16M4 18h16', check: 'M5 12l5 5L20 6', }; function Icon({ name, size = 18, stroke = 1.7, fill = 'none', ...p }) { return ( ); } /* ---- Sparkline ---- */ function Sparkline({ data, color = 'var(--orange)' }) { const max = Math.max(...data, 1); return (
{data.map((v, i) => ( ))}
); } /* ---- KPI card ---- */ function Kpi({ label, value, sub, dot = '', spark, sparkColor, delta, deltaDir }) { return (
{label}
{value}
{delta &&
{delta}
} {spark && } {sub && !spark &&
{sub}
}
); } /* ---- Progress bar ---- */ function Bar({ value, max = 100, thin }) { const pct = Math.max(0, Math.min(100, (value / max) * 100)); return
; } /* ---- Pill ---- */ function Pill({ tone = 'n', children }) { return {children}; } /* ---- Button ---- */ function Button({ variant = 'ghost', size, icon, children, ...p }) { return ( ); } function IconBtn({ icon, danger, ...p }) { return ; } /* ---- Modal (portal) ---- */ function Modal({ open, onClose, title, children, footer, wide }) { useEffect(() => { if (!open) return; const h = (e) => e.key === 'Escape' && onClose(); window.addEventListener('keydown', h); return () => window.removeEventListener('keydown', h); }, [open, onClose]); if (!open) return null; return ReactDOM.createPortal(
e.target === e.currentTarget && onClose()}>

{title}

{children}
{footer &&
{footer}
}
, document.body); } /* ---- Form fields ---- */ function Field({ label, hint, children }) { return (
{label && } {children} {hint &&
{hint}
}
); } function Text({ value, onChange, ...p }) { return onChange(e.target.value)} {...p} />; } function Num({ value, onChange, prefix, suffix, step = 'any', ...p }) { return (
{prefix && {prefix}} onChange(e.target.value === '' ? '' : parseFloat(e.target.value))} style={suffix ? { paddingRight: 46 } : null} {...p} /> {suffix && {suffix}}
); } function Select({ value, onChange, options, ...p }) { return ( ); } /* ---- Empty state ---- */ function Empty({ icon = 'box', title, children, action }) { return (

{title}

{children}
{action}
); } /* ---- Page header ---- */ function PageHead({ title, sub, action }) { return ( <>
{title}
{sub &&
{sub}
}
{action}
); } /* ---- Confirm helper (window.confirm wrapper for clarity) ---- */ function confirmDel(msg) { return window.confirm(msg); } Object.assign(window, { Icon, Sparkline, Kpi, Bar, Pill, Button, IconBtn, Modal, Field, Text, Num, Select, Empty, PageHead, confirmDel });