/* JewelLink EDU — shared primitives for the Parent App UI kit
Exposes: Icon, Avatar, Pill, color tokens. */
function jlPascal(name){
return name.split(/[-_ ]/).map(s => s.charAt(0).toUpperCase()+s.slice(1)).join('');
}
/* Lucide icon → inline SVG, built imperatively so React never fights it. */
function Icon({ name, size = 20, color = 'currentColor', stroke = 2, fill = 'none', style }) {
const ref = React.useRef(null);
React.useEffect(() => {
const node = window.lucide && window.lucide.icons && window.lucide.icons[jlPascal(name)];
const host = ref.current;
if (!host || !node) return;
const ns = 'http://www.w3.org/2000/svg';
const svg = document.createElementNS(ns, 'svg');
svg.setAttribute('viewBox', '0 0 24 24');
svg.setAttribute('width', size);
svg.setAttribute('height', size);
svg.setAttribute('fill', fill);
svg.setAttribute('stroke', color);
svg.setAttribute('stroke-width', stroke);
svg.setAttribute('stroke-linecap', 'round');
svg.setAttribute('stroke-linejoin', 'round');
node.forEach(([tag, attrs]) => {
const child = document.createElementNS(ns, tag);
for (const k in attrs) child.setAttribute(k, attrs[k]);
svg.appendChild(child);
});
host.innerHTML = '';
host.appendChild(svg);
}, [name, size, color, stroke, fill]);
return ;
}
/* Initials avatar — avoids shipping real faces, fully reusable. */
const JL_AV_COLORS = {
blue:'var(--blue-500)', navy:'var(--navy-600)', gold:'var(--gold-500)',
green:'var(--success-500)', red:'var(--danger-500)', cyan:'var(--cyan-500)',
};
function Avatar({ initials, color = 'blue', size = 42, ring = false }) {
const bg = JL_AV_COLORS[color] || color;
return (
{initials}
);
}
function Pill({ children, bg='var(--gray-200)', fg='var(--gray-600)', style }) {
return (
{children}
);
}
/* Lightweight toast — gives feedback for prototype actions that aren't wired to a full flow. */
function jlToast(msg, opts) {
opts = opts || {};
const host = document.body;
let wrap = document.getElementById('jl-toast-wrap');
if (!wrap) {
wrap = document.createElement('div');
wrap.id = 'jl-toast-wrap';
wrap.style.cssText = 'position:fixed;left:50%;bottom:34px;transform:translateX(-50%);z-index:9999;display:flex;flex-direction:column;gap:8px;align-items:center;pointer-events:none;';
host.appendChild(wrap);
}
const t = document.createElement('div');
const icon = opts.icon || 'check';
t.style.cssText = 'display:flex;align-items:center;gap:9px;background:#131C30;color:#fff;font-family:var(--font-sans);font-size:13.5px;font-weight:600;padding:11px 16px;border-radius:12px;box-shadow:0 10px 30px rgba(19,28,48,.35);opacity:0;transform:translateY(8px);transition:.18s ease;max-width:300px;';
const ic = (window.lucide && window.lucide.icons && window.lucide.icons[jlPascal(icon)]) || null;
let svg = '';
if (ic) {
const inner = ic.map(([tag, a]) => '<' + tag + ' ' + Object.entries(a).map(([k,v])=>k+'="'+v+'"').join(' ') + ' />').join('');
svg = '';
}
t.innerHTML = svg + '' + msg + '';
wrap.appendChild(t);
requestAnimationFrame(() => { t.style.opacity = '1'; t.style.transform = 'translateY(0)'; });
setTimeout(() => { t.style.opacity = '0'; t.style.transform = 'translateY(8px)'; setTimeout(() => t.remove(), 220); }, opts.ms || 2200);
}
Object.assign(window, { Icon, Avatar, Pill, jlPascal, jlToast });