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`).

View File

@@ -1,6 +1,7 @@
# 🚀 Interactive Examples
# Interactive Examples
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.
*/
import { $, $if, $for, $watch, $html, $mount } from 'sigpro';
export const UI = (defaultLang = "es") => {
const ui = {};
// --- I18N CORE ---
const i18n = {
@@ -12,10 +10,10 @@ export const UI = (defaultLang = "es") => {
en: { close: "Close", confirm: "Confirm", cancel: "Cancel", search: "Search...", loading: "Loading..." },
};
const currentLocale = $(defaultLang);
let currentLocale = $("es");
/** SET LOCALE */
ui.SetLocale = (locale) => currentLocale(locale);
export const SetLocale = (locale) => currentLocale(locale);
/** TRANSLATE */
const tt = (key) => () => i18n[currentLocale()][key] || key;
@@ -64,76 +62,10 @@ export const UI = (defaultLang = "es") => {
const iconRRight =
"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 ---
/** BUTTON */
ui.Button = (props, children) => {
export const Button = (props, children) => {
const { badge, badgeClass, tooltip, icon, loading, ...rest } = props;
const btn = $html(
"button",
@@ -159,7 +91,7 @@ export const UI = (defaultLang = "es") => {
};
/** INPUT */
ui.Input = (props) => {
export const Input = (props) => {
const { label, tip, value, error, isSearch, icon, type = "text", ...rest } = props;
const isPassword = type === "password";
const visible = $(false);
@@ -224,7 +156,7 @@ export const UI = (defaultLang = "es") => {
};
/** SELECT */
ui.Select = (props) => {
export const Select = (props) => {
const { label, options, value, ...rest } = props;
const selectEl = $html(
@@ -255,7 +187,7 @@ export const UI = (defaultLang = "es") => {
};
/** AUTOCOMPLETE */
ui.Autocomplete = (props) => {
export const Autocomplete = (props) => {
const { options = [], value, onSelect, label, placeholder, ...rest } = props;
const query = $(val(value) || "");
@@ -298,7 +230,7 @@ export const UI = (defaultLang = "es") => {
};
return $html("div", { class: "relative w-full" }, [
ui.Input({
Input({
label,
placeholder: placeholder || tt("search")(),
value: query,
@@ -344,7 +276,7 @@ export const UI = (defaultLang = "es") => {
};
/** DATEPICKER */
ui.Datepicker = (props) => {
export const Datepicker = (props) => {
const { value, range, label, placeholder, ...rest } = props;
const isOpen = $(false);
@@ -402,7 +334,7 @@ export const UI = (defaultLang = "es") => {
};
return $html("div", { class: "relative w-full" }, [
ui.Input({
Input({
label,
placeholder: placeholder || (isRangeMode() ? "Seleccionar rango..." : "Seleccionar fecha..."),
value: displayValue,
@@ -518,7 +450,7 @@ export const UI = (defaultLang = "es") => {
};
/** COLORPICKER */
ui.Colorpicker = (props) => {
export const Colorpicker = (props) => {
const { value, label, ...rest } = props;
const isOpen = $(false);
@@ -597,7 +529,7 @@ export const UI = (defaultLang = "es") => {
};
/** CHECKBOX */
ui.CheckBox = (props) => {
export const CheckBox = (props) => {
const { value, tooltip, toggle, ...rest } = props;
const checkEl = $html("input", {
...rest,
@@ -615,7 +547,7 @@ export const UI = (defaultLang = "es") => {
};
/** RADIO */
ui.Radio = (props) => {
export const Radio = (props) => {
const { label, tooltip, value, ...rest } = props;
const radioEl = $html("input", {
@@ -638,7 +570,7 @@ export const UI = (defaultLang = "es") => {
};
/** RANGE */
ui.Range = (props) => {
export const Range = (props) => {
const { label, tooltip, value, ...rest } = props;
const rangeEl = $html("input", {
@@ -660,7 +592,7 @@ export const UI = (defaultLang = "es") => {
};
/** MODAL */
ui.Modal = (props, children) => {
export const Modal = (props, children) => {
const { title, buttons, open, ...rest } = props;
const close = () => open(false);
@@ -671,7 +603,7 @@ export const UI = (defaultLang = "es") => {
typeof children === "function" ? children() : children,
$html("div", { class: "modal-action flex gap-2" }, [
...(Array.isArray(buttons) ? buttons : [buttons]).filter(Boolean),
ui.Button({ onclick: close }, tt("close")()),
Button({ onclick: close }, tt("close")()),
]),
]),
$html(
@@ -688,7 +620,7 @@ export const UI = (defaultLang = "es") => {
};
/** GRID */
ui.Grid = (props) => {
export const Grid = (props) => {
const { data, options, class: className } = props;
let gridApi = null;
@@ -744,7 +676,7 @@ export const UI = (defaultLang = "es") => {
};
/** DROPDOWN */
ui.Dropdown = (props, children) => {
export const Dropdown = (props, children) => {
const { label, icon, ...rest } = props;
return $html(
@@ -776,7 +708,7 @@ export const UI = (defaultLang = "es") => {
};
/** ACCORDION */
ui.Accordion = (props, children) => {
export const Accordion = (props, children) => {
const { title, name, open, ...rest } = props;
return $html(
@@ -798,7 +730,7 @@ export const UI = (defaultLang = "es") => {
};
/** TABS */
ui.Tabs = (props) => {
export const Tabs = (props) => {
const { items, ...rest } = props;
const itemsSignal = typeof items === "function" ? items : () => items || [];
@@ -835,18 +767,18 @@ export const UI = (defaultLang = "es") => {
};
/** 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 */
ui.Tooltip = (props, children) =>
export const Tooltip = (props, children) =>
$html("div", { ...props, class: joinClass("tooltip", props.class || props.class), "data-tip": props.tip }, children);
/** 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);
/** MENU */
ui.Menu = (props) => {
export const Menu = (props) => {
const renderItems = (items) =>
$for(
() => items || [],
@@ -869,7 +801,7 @@ export const UI = (defaultLang = "es") => {
};
/** DRAWER */
ui.Drawer = (props) =>
export const Drawer = (props) =>
$html("div", { class: joinClass("drawer", props.class || props.class) }, [
$html("input", {
id: props.id,
@@ -885,7 +817,7 @@ export const UI = (defaultLang = "es") => {
]);
/** FIELDSET */
ui.Fieldset = (props, children) =>
export const Fieldset = (props, children) =>
$html(
"fieldset",
{
@@ -902,7 +834,7 @@ export const UI = (defaultLang = "es") => {
);
/** LIST */
ui.List = (props) => {
export const List = (props) => {
const { items, header, render, keyFn, class: className } = props;
return $html(
@@ -918,10 +850,10 @@ export const UI = (defaultLang = "es") => {
};
/** 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 */
ui.Stat = (props) =>
export const Stat = (props) =>
$html("div", { ...props, class: joinClass("stat", props.class || props.class) }, [
props.icon && $html("div", { class: "stat-figure text-secondary" }, props.icon),
props.label && $html("div", { class: "stat-title" }, props.label),
@@ -930,7 +862,7 @@ export const UI = (defaultLang = "es") => {
]);
/** SWAP */
ui.Swap = (props) =>
export const Swap = (props) =>
$html("label", { class: joinClass("swap", props.class || props.class) }, [
$html("input", {
type: "checkbox",
@@ -941,14 +873,14 @@ export const UI = (defaultLang = "es") => {
]);
/** INDICATOR */
ui.Indicator = (props, children) =>
export const Indicator = (props, children) =>
$html("div", { class: joinClass("indicator", props.class || props.class) }, [
children,
$html("span", { class: joinClass("indicator-item badge", props.badgeClass) }, props.badge),
]);
/** RATING */
ui.Rating = (props) => {
export const Rating = (props) => {
const { value, count = 5, mask = "mask-star", readonly = false, ...rest } = props;
const ratingGroup = `rating-${Math.random().toString(36).slice(2, 7)}`;
@@ -975,7 +907,7 @@ export const UI = (defaultLang = "es") => {
};
/** ALERT */
ui.Alert = (props, children) => {
export const Alert = (props, children) => {
const { type = "info", soft = true, ...rest } = props;
const icons = {
info: iconInfo,
@@ -1017,7 +949,7 @@ export const UI = (defaultLang = "es") => {
};
/** TIMELINE */
ui.Timeline = (props) => {
export const Timeline = (props) => {
const { items = [], vertical = true, compact = false, ...rest } = props;
const icons = {
@@ -1064,7 +996,7 @@ export const UI = (defaultLang = "es") => {
};
/** FAB */
ui.Fab = (props) => {
export const Fab = (props) => {
const { icon, label, actions = [], position = "bottom-6 right-6", ...rest } = props;
return $html(
@@ -1106,7 +1038,7 @@ export const UI = (defaultLang = "es") => {
};
/** 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");
if (!container) {
container = $html("div", {
@@ -1144,7 +1076,7 @@ export const UI = (defaultLang = "es") => {
},
[
$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 */
ui.Loading = (props) => {
export const Loading = (props) => {
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("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) => {
$[key] = ui[key];
Object.defineProperty(window, key, {
value: ui[key],
writable: false,