// Shared UI primitives — Apple-inspired light system
const { useState, useEffect, useRef, useMemo } = React;
// ───────── Icons (line, 1.6 stroke, rounded) ─────────
function Icon({ name, size = 18, stroke = 1.6, color = "currentColor" }) {
const props = { width: size, height: size, viewBox: "0 0 24 24", fill: "none",
stroke: color, strokeWidth: stroke, strokeLinecap: "round", strokeLinejoin: "round" };
switch (name) {
case "server": return ();
case "users": return ();
case "settings": return ();
case "link": return ();
case "lock": return ();
case "tools": return ();
case "gauge": return ();
case "check": return ();
case "plus": return ();
case "chevron-down": return ();
case "chevron-right": return ();
case "back": return ();
case "trash": return ();
case "config": return ();
case "stop": return ();
case "play": return ();
case "qr": return ();
case "calendar": return ();
case "clock": return ();
case "download": return ();
case "upload": return ();
case "cpu": return ();
case "ram": return ();
case "disk": return ();
case "globe": return ();
case "refresh": return ();
case "logout": return ();
case "sun": return ();
case "moon": return ();
case "key": return ();
case "sparkles": return ();
case "satellite":return ();
case "bolt": return ();
case "plane": return ();
case "shield": return ();
case "wand": return ();
case "brain": return ();
case "star": return ();
case "github": return ();
case "doc": return ();
case "edit": return ();
case "more": return ();
case "search": return ();
case "heart": return ();
case "qr-scan": return ();
case "clipboard":return ();
default: return null;
}
}
// ───────── Buttons ─────────
function Button({ children, variant = "primary", size = "md", icon, iconRight, onClick, type, disabled, style, full }) {
const sizes = {
sm: { h: 30, px: 12, fs: 13, gap: 6, r: 10 },
md: { h: 38, px: 16, fs: 14, gap: 8, r: 12 },
lg: { h: 46, px: 22, fs: 15, gap: 10, r: 14 },
}[size];
const variants = {
primary: { bg: "var(--accent)", color: "#fff", border: "transparent",
shadow:"0 1px 0 rgba(255,255,255,.18) inset, 0 6px 16px rgba(124,92,255,.35)" },
primaryGrad: { bg: "linear-gradient(180deg, #8a6cff, #6a44f5)", color:"#fff", border:"transparent",
shadow:"0 1px 0 rgba(255,255,255,.22) inset, 0 8px 22px rgba(106,68,245,.35)" },
secondary: { bg: "#fff", color:"var(--ink)", border: "var(--line)",
shadow:"0 1px 0 rgba(255,255,255,.6) inset, 0 1px 1px rgba(16,24,40,.04), 0 3px 8px rgba(16,24,40,.05)" },
ghost: { bg: "transparent", color:"var(--ink-2)", border:"transparent", shadow:"none" },
danger: { bg: "#fff", color:"var(--red)", border:"rgba(255,59,48,.22)",
shadow:"0 1px 1px rgba(16,24,40,.03)" },
dangerSolid:{ bg:"var(--red)", color:"#fff", border:"transparent",
shadow:"0 1px 0 rgba(255,255,255,.18) inset, 0 6px 14px rgba(255,59,48,.3)" },
success: { bg:"#fff", color:"var(--green)", border:"rgba(52,199,89,.25)" , shadow:"0 1px 1px rgba(16,24,40,.03)"},
};
const v = variants[variant] || variants.primary;
return (
);
}
// ───────── Card ─────────
function Card({ children, padding = 22, radius, hover, style, onClick }) {
const [h, setH] = useState(false);
return (
setH(true)} onMouseLeave={()=>setH(false)}
style={{
background:"var(--bg-elev)", borderRadius: radius || "var(--radius-card)",
border:"1px solid var(--line)", boxShadow:"var(--shadow-card)",
padding, transition:"transform .2s ease, box-shadow .25s ease",
transform: hover && h ? "translateY(-2px)" : "translateY(0)",
boxShadow: hover && h ? "var(--shadow-pop)" : "var(--shadow-card)",
cursor: onClick ? "pointer" : "default",
...style,
}}>
{children}
);
}
// ───────── Badge ─────────
function Badge({ children, tone = "neutral", dot, size="md", style }) {
const tones = {
neutral:{ bg:"#f1f2f6", color:"#3a3a3c", dot:"#8e8e93"},
green: { bg:"var(--green-soft)", color:"#1f7a3a", dot:"var(--green)" },
blue: { bg:"var(--blue-soft)", color:"#0a4ea3", dot:"var(--blue)" },
purple: { bg:"var(--accent-soft)", color:"#4a2fcc", dot:"var(--accent)" },
orange: { bg:"var(--orange-soft)", color:"#a05a00", dot:"var(--orange)" },
red: { bg:"var(--red-soft)", color:"#a8261d", dot:"var(--red)" },
};
const t = tones[tone] || tones.neutral;
const sizes = { sm:{h:20, fs:10.5, px:7}, md:{h:24, fs:11.5, px:9}, lg:{h:28, fs:12.5, px:11}};
const s = sizes[size] || sizes.md;
return (
{dot && }
{children}
);
}
// ───────── Glyph tile (app-icon style with gradient) ─────────
function GlyphTile({ icon, size = 44, gradient, radius = 12, glyph }) {
return (
{glyph ? {glyph} : }
);
}
// ───────── Stat chip (used in server header) ─────────
function Stat({ icon, label, value, tone="neutral" }) {
const tones = {
neutral:{c:"#3a3a3c", bg:"#f4f5f7"},
green:{c:"#1f7a3a", bg:"var(--green-soft)"},
orange:{c:"#a05a00", bg:"var(--orange-soft)"},
blue:{c:"#0a4ea3", bg:"var(--blue-soft)"},
};
const t = tones[tone] || tones.neutral;
return (
);
}
// ───────── Avatar (initial) ─────────
function Avatar({ name, color = "var(--accent)", size = 36 }) {
const letter = (name || "?").trim().charAt(0).toUpperCase();
return (
{letter}
);
}
// ───────── Toggle ─────────
function Toggle({ on, onChange }) {
return (
);
}
// ───────── Input ─────────
function Input({ label, value, onChange, placeholder, mono, type="text", suffix, hint }) {
return (
);
}
// ───────── Checkbox ─────────
function Checkbox({ checked, onChange, label }) {
return (
);
}
// ───────── Section header ─────────
function SectionHeader({ title, subtitle, right }) {
return (
{title}
{subtitle &&
{subtitle}
}
{right}
);
}
// expose globally
Object.assign(window, {
Icon, Button, Card, Badge, GlyphTile, Stat, Avatar, Toggle, Input, Checkbox, SectionHeader,
});