import { useEffect, useState } from 'react';
import { __ } from '@wordpress/i18n';
import { cfg } from './api.js';
// The full Lucide set the icon picker offers (assets/icons/lucide.json, { name: [paths, tags] }), fetched
// once, the first time an icon outside the preloaded map is drawn.
let lucide = null;
function loadLucide() {
if (!lucide) {
lucide = cfg.iconsJson
? fetch(cfg.iconsJson).then((r) => r.json()).then((all) => {
cfg.icons = cfg.icons || {};
Object.keys(all).forEach((k) => { if (!cfg.icons[k]) cfg.icons[k] = all[k][0]; });
}).catch(() => {})
: Promise.resolve();
}
return lucide;
}
/** Lucide icon by name: the map PHP localized, else the full set from lucide.json. */
export function Icon({ name, size = 18, className = '' }) {
const known = !name || !!(cfg.icons && cfg.icons[name]);
const [, loaded] = useState(0);
useEffect(() => {
let live = true;
if (!known) loadLucide().then(() => live && loaded((n) => n + 1));
return () => { live = false; };
}, [name, known]);
const inner = (cfg.icons && cfg.icons[name]) || cfg.icons?.circle || '';
return (
);
}
export function Card({ title, tools, children, className = '', pad = true }) {
return (
{title && {title}
{tools &&
{tools}
}
}
{children}
);
}
export function Button({ children, variant = '', size = '', icon, href, target, onClick, type = 'button', disabled, className = '', ...rest }) {
// `rest` forwards data-*/aria-*/title etc. so screens (and QA) can tag buttons.
const cls = `sa-btn ${variant ? 'sa-btn-' + variant : ''} ${size ? 'sa-btn-' + size : ''} ${className}`;
const body = <>{icon && }{children}>;
if (href) return {body};
return ;
}
export function Badge({ children, tone = '' }) {
return {children};
}
export function Field({ label, help, htmlFor, children, inline }) {
return (
{label &&
}
{children}{help &&
{help}
}
);
}
export function Switch({ checked, onChange, label, help }) {
return (
);
}
export function Notice({ tone = 'info', children, onClose }) {
const icon = { ok: 'circle-check', error: 'circle-x', warn: 'triangle-alert', info: 'sparkles' }[tone];
return (
);
}
/** Toast queue: `toast.show('Saved')`. */
const listeners = new Set();
export const toast = {
show(text, tone = 'ok') { listeners.forEach((l) => l({ id: Date.now() + Math.random(), text, tone })); },
};
export function Toasts() {
const [items, setItems] = useState([]);
useEffect(() => {
const l = (t) => {
setItems((s) => [...s, t]);
setTimeout(() => setItems((s) => s.filter((x) => x.id !== t.id)), 3500);
};
listeners.add(l);
return () => listeners.delete(l);
}, []);
return (
{items.map((t) =>
{t.text}
)}
);
}
export function Spinner({ label = __('Loading…', 'dashbread') }) {
return {label}
;
}
export function Empty({ icon = 'files', title, children, action }) {
return (
{title}
{children &&
{children}
}{action}
);
}
export function Stat({ value, label, tone = 'primary', icon }) {
return (
{value}
{label}
{icon &&
}
);
}