import { html, useState, useEffect } from './core.js'; export const Card = ({ title, right, children }) => html`
${title && html`

${title}

${right}
`} ${children}
`; export const Stat = ({ label, value, sub, tone }) => html`

${label}

${value}
${sub && html`
${sub}
`}
`; export const Dot = ({ tone = 'idle', live }) => html` `; export const Pill = ({ tone, children }) => html`${children}`; export const Bar = ({ value, max, tone }) => { const width = max > 0 ? Math.min(100, (Number(value) / Number(max)) * 100) : 0; return html`
`; }; export const Skeleton = ({ rows = 3 }) => html`
${Array.from({ length: rows }, (_, i) => html`
`)}
`; export const Empty = ({ children }) => html`
${children}
`; export function Toast({ toast }) { const [shown, setShown] = useState(toast); useEffect(() => { setShown(toast); if (!toast) return undefined; const timer = setTimeout(() => setShown(null), toast.sticky ? 12000 : 4500); return () => clearTimeout(timer); }, [toast]); if (!shown) return null; return html`
${shown.message}
`; } // Anything that changes production asks twice. The second click is a different // button label so muscle memory cannot carry you through both. export function Confirm({ label, confirmLabel, onConfirm, danger, disabled, busy }) { const [armed, setArmed] = useState(false); useEffect(() => { if (!armed) return undefined; const timer = setTimeout(() => setArmed(false), 5000); return () => clearTimeout(timer); }, [armed]); if (busy) return html``; if (!armed) { return html``; } return html` `; }