New export for UI

This commit is contained in:
2026-03-28 14:54:15 +01:00
parent 0809d4666c
commit 2b5b47ffe6
3 changed files with 814 additions and 869 deletions

View File

@@ -57,7 +57,7 @@ Explore the reactive building blocks of SigPro.
--- ---
## 🏗️ Element Constructors (Tags) ## Element Constructors (Tags)
SigPro provides **PascalCase** wrappers for all standard HTML5 tags (e.g., `Div`, `Span`, `Button`). SigPro provides **PascalCase** wrappers for all standard HTML5 tags (e.g., `Div`, `Span`, `Button`).

View File

@@ -1,6 +1,7 @@
# 🚀 Interactive Examples # Interactive Examples
Explore the power of SigPro through practical patterns. From basic reactivity to advanced composition. Explore the power of SigPro through practical patterns. From basic reactivity to advanced composition.
NOTE: Here we use DaisyUI for styles.
--- ---

View File

@@ -3,8 +3,6 @@
* Provides a set of reactive functional components, flow control and i18n. * Provides a set of reactive functional components, flow control and i18n.
*/ */
import { $, $if, $for, $watch, $html, $mount } from 'sigpro'; import { $, $if, $for, $watch, $html, $mount } from 'sigpro';
export const UI = (defaultLang = "es") => {
const ui = {};
// --- I18N CORE --- // --- I18N CORE ---
const i18n = { const i18n = {
@@ -12,10 +10,10 @@ export const UI = (defaultLang = "es") => {
en: { close: "Close", confirm: "Confirm", cancel: "Cancel", search: "Search...", loading: "Loading..." }, en: { close: "Close", confirm: "Confirm", cancel: "Cancel", search: "Search...", loading: "Loading..." },
}; };
const currentLocale = $(defaultLang); let currentLocale = $("es");
/** SET LOCALE */ /** SET LOCALE */
ui.SetLocale = (locale) => currentLocale(locale); export const SetLocale = (locale) => currentLocale(locale);
/** TRANSLATE */ /** TRANSLATE */
const tt = (key) => () => i18n[currentLocale()][key] || key; const tt = (key) => () => i18n[currentLocale()][key] || key;
@@ -64,76 +62,10 @@ export const UI = (defaultLang = "es") => {
const iconRRight = const iconRRight =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAABmSURBVDiN3dGxCoAgEMbxfz1dL1BTREJzmUv08trgDYcg6VCD3/YD7zvkoLmMgFEegLmmwAAecOJVvNeUWCAAt7IHjt9LThkyiRf9qC8oCom70u0BuDL+bngj/tNm/JqJePucW8wDvGYdzT0nMUkAAAAASUVORK5CYII="; "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAABmSURBVDiN3dGxCoAgEMbxfz1dL1BTREJzmUv08trgDYcg6VCD3/YD7zvkoLmMgFEegLmmwAAecOJVvNeUWCAAt7IHjt9LThkyiRf9qC8oCom70u0BuDL+bngj/tNm/JqJePucW8wDvGYdzT0nMUkAAAAASUVORK5CYII=";
/** REQ */
ui.Request = (url, payload = null, options = {}) => {
const data = $(null),
loading = $(false),
error = $(null),
success = $(false);
let abortController = null;
const execute = async (customPayload = null) => {
const targetUrl = val(url);
if (!targetUrl) return;
if (abortController) abortController.abort();
abortController = new AbortController();
loading(true);
error(null);
success(false);
try {
const bodyData = customPayload || payload;
const res = await fetch(targetUrl, {
method: options.method || (bodyData ? "POST" : "GET"),
headers: { "Content-Type": "application/json", ...options.headers },
body: bodyData ? JSON.stringify(bodyData) : null,
signal: abortController.signal,
...options,
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
let json = await res.json();
if (typeof options.transform === "function") json = options.transform(json);
data(json);
success(true);
} catch (err) {
if (err.name !== "AbortError") error(err.message);
} finally {
loading(false);
}
};
$(() => {
execute();
return () => abortController?.abort();
});
return { data, loading, error, success, reload: (p) => execute(p) };
};
/** RESPONSE */
ui.Response = (reqObj, renderFn) =>
$html("div", { class: "res-container" }, [
$if(reqObj.loading, $html("div", { class: "flex justify-center p-4" }, $html("span", { class: "loading loading-dots text-primary" }))),
$if(reqObj.error, () =>
$html("div", { role: "alert", class: "alert alert-error" }, [
$html("span", {}, reqObj.error()),
ui.Button({ class: "btn-xs btn-ghost border-current", onclick: () => reqObj.reload() }, "Retry"),
]),
),
$if(reqObj.success, () => {
const current = reqObj.data();
return current !== null ? renderFn(current) : null;
}),
]);
// --- UI COMPONENTS --- // --- UI COMPONENTS ---
/** BUTTON */ /** BUTTON */
ui.Button = (props, children) => { export const Button = (props, children) => {
const { badge, badgeClass, tooltip, icon, loading, ...rest } = props; const { badge, badgeClass, tooltip, icon, loading, ...rest } = props;
const btn = $html( const btn = $html(
"button", "button",
@@ -159,7 +91,7 @@ export const UI = (defaultLang = "es") => {
}; };
/** INPUT */ /** INPUT */
ui.Input = (props) => { export const Input = (props) => {
const { label, tip, value, error, isSearch, icon, type = "text", ...rest } = props; const { label, tip, value, error, isSearch, icon, type = "text", ...rest } = props;
const isPassword = type === "password"; const isPassword = type === "password";
const visible = $(false); const visible = $(false);
@@ -224,7 +156,7 @@ export const UI = (defaultLang = "es") => {
}; };
/** SELECT */ /** SELECT */
ui.Select = (props) => { export const Select = (props) => {
const { label, options, value, ...rest } = props; const { label, options, value, ...rest } = props;
const selectEl = $html( const selectEl = $html(
@@ -255,7 +187,7 @@ export const UI = (defaultLang = "es") => {
}; };
/** AUTOCOMPLETE */ /** AUTOCOMPLETE */
ui.Autocomplete = (props) => { export const Autocomplete = (props) => {
const { options = [], value, onSelect, label, placeholder, ...rest } = props; const { options = [], value, onSelect, label, placeholder, ...rest } = props;
const query = $(val(value) || ""); const query = $(val(value) || "");
@@ -298,7 +230,7 @@ export const UI = (defaultLang = "es") => {
}; };
return $html("div", { class: "relative w-full" }, [ return $html("div", { class: "relative w-full" }, [
ui.Input({ Input({
label, label,
placeholder: placeholder || tt("search")(), placeholder: placeholder || tt("search")(),
value: query, value: query,
@@ -344,7 +276,7 @@ export const UI = (defaultLang = "es") => {
}; };
/** DATEPICKER */ /** DATEPICKER */
ui.Datepicker = (props) => { export const Datepicker = (props) => {
const { value, range, label, placeholder, ...rest } = props; const { value, range, label, placeholder, ...rest } = props;
const isOpen = $(false); const isOpen = $(false);
@@ -402,7 +334,7 @@ export const UI = (defaultLang = "es") => {
}; };
return $html("div", { class: "relative w-full" }, [ return $html("div", { class: "relative w-full" }, [
ui.Input({ Input({
label, label,
placeholder: placeholder || (isRangeMode() ? "Seleccionar rango..." : "Seleccionar fecha..."), placeholder: placeholder || (isRangeMode() ? "Seleccionar rango..." : "Seleccionar fecha..."),
value: displayValue, value: displayValue,
@@ -518,7 +450,7 @@ export const UI = (defaultLang = "es") => {
}; };
/** COLORPICKER */ /** COLORPICKER */
ui.Colorpicker = (props) => { export const Colorpicker = (props) => {
const { value, label, ...rest } = props; const { value, label, ...rest } = props;
const isOpen = $(false); const isOpen = $(false);
@@ -597,7 +529,7 @@ export const UI = (defaultLang = "es") => {
}; };
/** CHECKBOX */ /** CHECKBOX */
ui.CheckBox = (props) => { export const CheckBox = (props) => {
const { value, tooltip, toggle, ...rest } = props; const { value, tooltip, toggle, ...rest } = props;
const checkEl = $html("input", { const checkEl = $html("input", {
...rest, ...rest,
@@ -615,7 +547,7 @@ export const UI = (defaultLang = "es") => {
}; };
/** RADIO */ /** RADIO */
ui.Radio = (props) => { export const Radio = (props) => {
const { label, tooltip, value, ...rest } = props; const { label, tooltip, value, ...rest } = props;
const radioEl = $html("input", { const radioEl = $html("input", {
@@ -638,7 +570,7 @@ export const UI = (defaultLang = "es") => {
}; };
/** RANGE */ /** RANGE */
ui.Range = (props) => { export const Range = (props) => {
const { label, tooltip, value, ...rest } = props; const { label, tooltip, value, ...rest } = props;
const rangeEl = $html("input", { const rangeEl = $html("input", {
@@ -660,7 +592,7 @@ export const UI = (defaultLang = "es") => {
}; };
/** MODAL */ /** MODAL */
ui.Modal = (props, children) => { export const Modal = (props, children) => {
const { title, buttons, open, ...rest } = props; const { title, buttons, open, ...rest } = props;
const close = () => open(false); const close = () => open(false);
@@ -671,7 +603,7 @@ export const UI = (defaultLang = "es") => {
typeof children === "function" ? children() : children, typeof children === "function" ? children() : children,
$html("div", { class: "modal-action flex gap-2" }, [ $html("div", { class: "modal-action flex gap-2" }, [
...(Array.isArray(buttons) ? buttons : [buttons]).filter(Boolean), ...(Array.isArray(buttons) ? buttons : [buttons]).filter(Boolean),
ui.Button({ onclick: close }, tt("close")()), Button({ onclick: close }, tt("close")()),
]), ]),
]), ]),
$html( $html(
@@ -688,7 +620,7 @@ export const UI = (defaultLang = "es") => {
}; };
/** GRID */ /** GRID */
ui.Grid = (props) => { export const Grid = (props) => {
const { data, options, class: className } = props; const { data, options, class: className } = props;
let gridApi = null; let gridApi = null;
@@ -744,7 +676,7 @@ export const UI = (defaultLang = "es") => {
}; };
/** DROPDOWN */ /** DROPDOWN */
ui.Dropdown = (props, children) => { export const Dropdown = (props, children) => {
const { label, icon, ...rest } = props; const { label, icon, ...rest } = props;
return $html( return $html(
@@ -776,7 +708,7 @@ export const UI = (defaultLang = "es") => {
}; };
/** ACCORDION */ /** ACCORDION */
ui.Accordion = (props, children) => { export const Accordion = (props, children) => {
const { title, name, open, ...rest } = props; const { title, name, open, ...rest } = props;
return $html( return $html(
@@ -798,7 +730,7 @@ export const UI = (defaultLang = "es") => {
}; };
/** TABS */ /** TABS */
ui.Tabs = (props) => { export const Tabs = (props) => {
const { items, ...rest } = props; const { items, ...rest } = props;
const itemsSignal = typeof items === "function" ? items : () => items || []; const itemsSignal = typeof items === "function" ? items : () => items || [];
@@ -835,18 +767,18 @@ export const UI = (defaultLang = "es") => {
}; };
/** BADGE */ /** BADGE */
ui.Badge = (props, children) => $html("span", { ...props, class: joinClass("badge", props.class || props.class) }, children); export const Badge = (props, children) => $html("span", { ...props, class: joinClass("badge", props.class || props.class) }, children);
/** TOOLTIP */ /** TOOLTIP */
ui.Tooltip = (props, children) => export const Tooltip = (props, children) =>
$html("div", { ...props, class: joinClass("tooltip", props.class || props.class), "data-tip": props.tip }, children); $html("div", { ...props, class: joinClass("tooltip", props.class || props.class), "data-tip": props.tip }, children);
/** NAVBAR */ /** NAVBAR */
ui.Navbar = (props, children) => export const Navbar = (props, children) =>
$html("div", { ...props, class: joinClass("navbar bg-base-100 shadow-sm px-4", props.class || props.class) }, children); $html("div", { ...props, class: joinClass("navbar bg-base-100 shadow-sm px-4", props.class || props.class) }, children);
/** MENU */ /** MENU */
ui.Menu = (props) => { export const Menu = (props) => {
const renderItems = (items) => const renderItems = (items) =>
$for( $for(
() => items || [], () => items || [],
@@ -869,7 +801,7 @@ export const UI = (defaultLang = "es") => {
}; };
/** DRAWER */ /** DRAWER */
ui.Drawer = (props) => export const Drawer = (props) =>
$html("div", { class: joinClass("drawer", props.class || props.class) }, [ $html("div", { class: joinClass("drawer", props.class || props.class) }, [
$html("input", { $html("input", {
id: props.id, id: props.id,
@@ -885,7 +817,7 @@ export const UI = (defaultLang = "es") => {
]); ]);
/** FIELDSET */ /** FIELDSET */
ui.Fieldset = (props, children) => export const Fieldset = (props, children) =>
$html( $html(
"fieldset", "fieldset",
{ {
@@ -902,7 +834,7 @@ export const UI = (defaultLang = "es") => {
); );
/** LIST */ /** LIST */
ui.List = (props) => { export const List = (props) => {
const { items, header, render, keyFn, class: className } = props; const { items, header, render, keyFn, class: className } = props;
return $html( return $html(
@@ -918,10 +850,10 @@ export const UI = (defaultLang = "es") => {
}; };
/** STACK */ /** STACK */
ui.Stack = (props, children) => $html("div", { ...props, class: joinClass("stack", props.class || props.class) }, children); export const Stack = (props, children) => $html("div", { ...props, class: joinClass("stack", props.class || props.class) }, children);
/** STAT */ /** STAT */
ui.Stat = (props) => export const Stat = (props) =>
$html("div", { ...props, class: joinClass("stat", props.class || props.class) }, [ $html("div", { ...props, class: joinClass("stat", props.class || props.class) }, [
props.icon && $html("div", { class: "stat-figure text-secondary" }, props.icon), props.icon && $html("div", { class: "stat-figure text-secondary" }, props.icon),
props.label && $html("div", { class: "stat-title" }, props.label), props.label && $html("div", { class: "stat-title" }, props.label),
@@ -930,7 +862,7 @@ export const UI = (defaultLang = "es") => {
]); ]);
/** SWAP */ /** SWAP */
ui.Swap = (props) => export const Swap = (props) =>
$html("label", { class: joinClass("swap", props.class || props.class) }, [ $html("label", { class: joinClass("swap", props.class || props.class) }, [
$html("input", { $html("input", {
type: "checkbox", type: "checkbox",
@@ -941,14 +873,14 @@ export const UI = (defaultLang = "es") => {
]); ]);
/** INDICATOR */ /** INDICATOR */
ui.Indicator = (props, children) => export const Indicator = (props, children) =>
$html("div", { class: joinClass("indicator", props.class || props.class) }, [ $html("div", { class: joinClass("indicator", props.class || props.class) }, [
children, children,
$html("span", { class: joinClass("indicator-item badge", props.badgeClass) }, props.badge), $html("span", { class: joinClass("indicator-item badge", props.badgeClass) }, props.badge),
]); ]);
/** RATING */ /** RATING */
ui.Rating = (props) => { export const Rating = (props) => {
const { value, count = 5, mask = "mask-star", readonly = false, ...rest } = props; const { value, count = 5, mask = "mask-star", readonly = false, ...rest } = props;
const ratingGroup = `rating-${Math.random().toString(36).slice(2, 7)}`; const ratingGroup = `rating-${Math.random().toString(36).slice(2, 7)}`;
@@ -975,7 +907,7 @@ export const UI = (defaultLang = "es") => {
}; };
/** ALERT */ /** ALERT */
ui.Alert = (props, children) => { export const Alert = (props, children) => {
const { type = "info", soft = true, ...rest } = props; const { type = "info", soft = true, ...rest } = props;
const icons = { const icons = {
info: iconInfo, info: iconInfo,
@@ -1017,7 +949,7 @@ export const UI = (defaultLang = "es") => {
}; };
/** TIMELINE */ /** TIMELINE */
ui.Timeline = (props) => { export const Timeline = (props) => {
const { items = [], vertical = true, compact = false, ...rest } = props; const { items = [], vertical = true, compact = false, ...rest } = props;
const icons = { const icons = {
@@ -1064,7 +996,7 @@ export const UI = (defaultLang = "es") => {
}; };
/** FAB */ /** FAB */
ui.Fab = (props) => { export const Fab = (props) => {
const { icon, label, actions = [], position = "bottom-6 right-6", ...rest } = props; const { icon, label, actions = [], position = "bottom-6 right-6", ...rest } = props;
return $html( return $html(
@@ -1106,7 +1038,7 @@ export const UI = (defaultLang = "es") => {
}; };
/** TOAST */ /** TOAST */
ui.Toast = (message, type = "alert-success", duration = 3500) => { export const Toast = (message, type = "alert-success", duration = 3500) => {
let container = document.getElementById("sigpro-toast-container"); let container = document.getElementById("sigpro-toast-container");
if (!container) { if (!container) {
container = $html("div", { container = $html("div", {
@@ -1144,7 +1076,7 @@ export const UI = (defaultLang = "es") => {
}, },
[ [
$html("span", typeof message === "function" ? message : () => message), $html("span", typeof message === "function" ? message : () => message),
ui.Button({ class: "btn-xs btn-circle btn-ghost", onclick: close }, "✕"), Button({ class: "btn-xs btn-circle btn-ghost", onclick: close }, "✕"),
], ],
); );
@@ -1162,7 +1094,7 @@ export const UI = (defaultLang = "es") => {
}; };
/** LOADING */ /** LOADING */
ui.Loading = (props) => { export const Loading = (props) => {
return $if(props.$show, () => return $if(props.$show, () =>
$html("div", { class: "fixed inset-0 z-[100] flex items-center justify-center backdrop-blur-sm bg-base-100/30" }, [ $html("div", { class: "fixed inset-0 z-[100] flex items-center justify-center backdrop-blur-sm bg-base-100/30" }, [
$html("span", { class: "loading loading-spinner loading-lg text-primary" }), $html("span", { class: "loading loading-spinner loading-lg text-primary" }),
@@ -1170,9 +1102,21 @@ export const UI = (defaultLang = "es") => {
); );
}; };
ui.tt = tt; // --- EXPORT UI FUNCTION ---
export const UI = (defaultLang = "es") => {
// Set initial locale
if (defaultLang) currentLocale(defaultLang);
// Create UI object with all components
const ui = {
SetLocale, tt, Button, Input, Select, Autocomplete, Datepicker, Colorpicker,
CheckBox, Radio, Range, Modal, Grid, Dropdown, Accordion, Tabs, Badge,
Tooltip, Navbar, Menu, Drawer, Fieldset, List, Stack, Stat, Swap, Indicator,
Rating, Alert, Timeline, Fab, Toast, Loading
};
// Inject all components into window
Object.keys(ui).forEach((key) => { Object.keys(ui).forEach((key) => {
$[key] = ui[key];
Object.defineProperty(window, key, { Object.defineProperty(window, key, {
value: ui[key], value: ui[key],
writable: false, writable: false,