ui updated

This commit is contained in:
2026-03-25 17:35:40 +01:00
parent 60ab8e1797
commit 8f9f87a84e
2 changed files with 116 additions and 103 deletions

View File

@@ -62,7 +62,7 @@ const Profile = (params) => {
}),
Datepicker({ $value: miFecha, label: "Fecha", placeholder: textoInput }),
Datepicker({ $value: miRango, label: "Fecha", placeholder: textoInput, range: true }),
Colorpicker({ label: "Color del tema", $value: colorFondo, placeholder: "Elige un color..." }),
Colorpicker({ show:true, label: "Color del tema", $value: colorFondo }),
Input({ type: "number", label: "Number" }),
Input({ type: "email", label: "Email" }),
Input({ label: "Text" }),

View File

@@ -78,22 +78,17 @@ export const UI = ($, defaultLang = "es") => {
const key = keyFn(item, index);
newKeys.add(key);
if (cache.has(key)) {
const runtime = cache.get(key);
container.insertBefore(runtime.container, marker);
} else {
const runtime = $.view(() => {
return $.html("div", { style: "display:contents" }, [render(item, index)]);
});
let runtime = cache.get(key);
if (!runtime) {
runtime = $.view(() => render(item, index));
cache.set(key, runtime);
container.insertBefore(runtime.container, marker);
}
container.insertBefore(runtime.container, marker);
});
cache.forEach((runtime, key) => {
if (!newKeys.has(key)) {
runtime.destroy();
runtime.container.remove();
cache.delete(key);
}
});
@@ -152,7 +147,7 @@ export const UI = ($, defaultLang = "es") => {
return { data, loading, error, success, reload: (p) => execute(p) };
};
/** RES */
/** RESPONSE */
ui.Response = (reqObj, renderFn) =>
$.html("div", { class: "res-container" }, [
ui.If(reqObj.loading, $.html("div", { class: "flex justify-center p-4" }, $.html("span", { class: "loading loading-dots text-primary" }))),
@@ -200,7 +195,6 @@ export const UI = ($, defaultLang = "es") => {
};
/** INPUT */
ui.Input = (props) => {
const { label, tip, $value, $error, isSearch, icon, type = "text", ...rest } = props;
@@ -318,11 +312,11 @@ export const UI = ($, defaultLang = "es") => {
});
const pick = (opt) => {
const value = typeof opt === "string" ? opt : opt.value;
const label = typeof opt === "string" ? opt : opt.label;
const valStr = typeof opt === "string" ? opt : opt.value;
const labelStr = typeof opt === "string" ? opt : opt.label;
query(label);
$value?.(value);
query(labelStr);
if (typeof $value === "function") $value(valStr);
onSelect?.(opt);
isOpen(false);
@@ -331,24 +325,19 @@ export const UI = ($, defaultLang = "es") => {
const nav = (e) => {
const items = list();
if (e.key === "ArrowDown") {
e.preventDefault();
isOpen(true);
cursor((i) => Math.min(i + 1, items.length - 1));
}
if (e.key === "ArrowUp") {
cursor(Math.min(cursor() + 1, items.length - 1));
} else if (e.key === "ArrowUp") {
e.preventDefault();
cursor((i) => Math.max(i - 1, 0));
}
if (e.key === "Enter" && cursor() >= 0) {
cursor(Math.max(cursor() - 1, 0));
} else if (e.key === "Enter" && cursor() >= 0) {
e.preventDefault();
pick(items[cursor()]);
} else if (e.key === "Escape") {
isOpen(false);
}
if (e.key === "Escape") isOpen(false);
};
return $.html("div", { class: "relative w-full" }, [
@@ -360,13 +349,14 @@ export const UI = ($, defaultLang = "es") => {
onblur: () => setTimeout(() => isOpen(false), 150),
onkeydown: nav,
oninput: (e) => {
query(e.target.value);
const v = e.target.value;
query(v);
if (typeof $value === "function") $value(v);
isOpen(true);
cursor(-1);
},
...rest,
}),
$.html(
"ul",
{
@@ -390,23 +380,19 @@ export const UI = ($, defaultLang = "es") => {
]),
(opt, i) => (typeof opt === "string" ? opt : opt.value) + i,
),
() => (!list().length ? $.html("li", { class: "p-2 text-center opacity-50" }, "No results") : null),
() => (list().length ? null : $.html("li", { class: "p-2 text-center opacity-50" }, "No results")),
],
),
]);
};
/** DATEPICKER */
ui.Datepicker = (props) => {
const { $value, range, label, placeholder, ...rest } = props;
const isOpen = $(false);
const internalDate = $(new Date());
const hoverDate = $(null);
// Determinamos si es rango (si no se pasa nada, por defecto es falso para fecha única)
const isRangeMode = () => val(range) === true;
const now = new Date();
@@ -424,17 +410,17 @@ export const UI = ($, defaultLang = "es") => {
const current = val($value);
if (isRangeMode()) {
// MODO RANGO
if (!current?.start || (current.start && current.end)) {
$value({ start: dateStr, end: null });
if (typeof $value === "function") $value({ start: dateStr, end: null });
} else {
const start = current.start;
$value(dateStr < start ? { start: dateStr, end: start } : { start, end: dateStr });
if (typeof $value === "function") {
$value(dateStr < start ? { start: dateStr, end: start } : { start, end: dateStr });
}
isOpen(false);
}
} else {
// MODO FECHA ÚNICA
$value(dateStr);
if (typeof $value === "function") $value(dateStr);
isOpen(false);
}
};
@@ -442,19 +428,17 @@ export const UI = ($, defaultLang = "es") => {
const displayValue = $(() => {
const v = val($value);
if (!v) return "";
// Si es un string (fecha única)
if (typeof v === "string") return v;
// Si es un objeto (rango)
if (v.start && v.end) return `${v.start} - ${v.end}`;
if (v.start) return `${v.start}...`;
return "";
});
// ... resto del move, moveYear ...
const move = (m) => {
const d = internalDate();
internalDate(new Date(d.getFullYear(), d.getMonth() + m, 1));
};
const moveYear = (y) => {
const d = internalDate();
internalDate(new Date(d.getFullYear() + y, d.getMonth(), 1));
@@ -482,7 +466,6 @@ export const UI = ($, defaultLang = "es") => {
onclick: (e) => e.stopPropagation(),
},
[
// Header (Igual que el tuyo)
$.html("div", { class: "flex justify-between items-center mb-4 gap-1" }, [
$.html("div", { class: "flex gap-0.5" }, [
$.html("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(-1) }, "<<"),
@@ -497,7 +480,6 @@ export const UI = ($, defaultLang = "es") => {
]),
]),
// Grid
$.html("div", { class: "grid grid-cols-7 gap-1", onmouseleave: () => hoverDate(null) }, [
...["L", "M", "X", "J", "V", "S", "D"].map((d) => $.html("div", { class: "text-[10px] opacity-40 font-bold text-center" }, d)),
() => {
@@ -523,30 +505,31 @@ export const UI = ($, defaultLang = "es") => {
class: () => {
const v = val($value);
const h = hoverDate();
const isToday = dStr === todayStr;
// Lógica de selección según el tipo de dato
const isStart = typeof v === "string" ? v === dStr : v?.start === dStr;
const isEnd = v?.end === dStr;
let inRange = false;
// Solo calculamos sombreado si estamos en modo rango
if (isRangeMode()) {
if (v?.start && !v.end && h) {
inRange = (dStr > v.start && dStr <= h) || (dStr < v.start && dStr >= h);
} else if (v?.start && v?.end) {
inRange = dStr > v.start && dStr < v.end;
if (isRangeMode() && v?.start) {
const start = v.start;
if (!v.end && h) {
inRange = (dStr > start && dStr <= h) || (dStr < start && dStr >= h);
} else if (v.end) {
inRange = dStr > start && dStr < v.end;
}
}
return `btn btn-xs p-0 aspect-square min-h-0 h-auto font-normal relative
${isStart || isEnd ? "btn-primary z-10" : inRange ? "bg-primary/20 border-none rounded-none" : "btn-ghost"}
${isToday ? "ring-1 ring-primary ring-inset font-black text-primary" : ""}`;
const base = "btn btn-xs p-0 aspect-square min-h-0 h-auto font-normal relative";
const state = isStart || isEnd ? "btn-primary z-10" : inRange ? "bg-primary/20 border-none rounded-none" : "btn-ghost";
const today = dStr === todayStr ? "ring-1 ring-primary ring-inset font-black text-primary" : "";
return `${base} ${state} ${today}`;
},
onmouseenter: () => {
if (isRangeMode()) hoverDate(dStr);
},
onmouseenter: () => isRangeMode() && hoverDate(dStr),
onclick: () => selectDate(date),
},
i.toString(),
[i.toString()],
),
);
}
@@ -563,40 +546,42 @@ export const UI = ($, defaultLang = "es") => {
/** COLORPICKER */
ui.Colorpicker = (props) => {
const { $value, label, placeholder, ...rest } = props;
const { $value, label, ...rest } = props;
const isOpen = $(false);
const p1 = ["#000", "#1A1A1A", "#333", "#4D4D4D", "#666", "#808080", "#B3B3B3", "#FFF"];
const p2 = ["#450a0a", "#7f1d1d", "#991b1b", "#b91c1c", "#dc2626", "#ef4444", "#f87171", "#fca5a5"];
const p3 = ["#431407", "#7c2d12", "#9a3412", "#c2410c", "#ea580c", "#f97316", "#fb923c", "#ffedd5"];
const p4 = ["#713f12", "#a16207", "#ca8a04", "#eab308", "#facc15", "#fde047", "#fef08a", "#fff9c4"];
const p5 = ["#064e3b", "#065f46", "#059669", "#10b981", "#34d399", "#4ade80", "#84cc16", "#d9f99d"];
const p6 = ["#082f49", "#075985", "#0284c7", "#0ea5e9", "#38bdf8", "#7dd3fc", "#22d3ee", "#cffafe"];
const p7 = ["#1e1b4b", "#312e81", "#4338ca", "#4f46e5", "#6366f1", "#818cf8", "#a5b4fc", "#e0e7ff"];
const p8 = ["#2e1065", "#4c1d95", "#6d28d9", "#7c3aed", "#8b5cf6", "#a855f7", "#d946ef", "#fae8ff"];
const palette = [...p1, ...p2, ...p3, ...p4, ...p5, ...p6, ...p7, ...p8];
const palette = [
...["#000", "#1A1A1A", "#333", "#4D4D4D", "#666", "#808080", "#B3B3B3", "#FFF"],
...["#450a0a", "#7f1d1d", "#991b1b", "#b91c1c", "#dc2626", "#ef4444", "#f87171", "#fca5a5"],
...["#431407", "#7c2d12", "#9a3412", "#c2410c", "#ea580c", "#f97316", "#fb923c", "#ffedd5"],
...["#713f12", "#a16207", "#ca8a04", "#eab308", "#facc15", "#fde047", "#fef08a", "#fff9c4"],
...["#064e3b", "#065f46", "#059669", "#10b981", "#34d399", "#4ade80", "#84cc16", "#d9f99d"],
...["#082f49", "#075985", "#0284c7", "#0ea5e9", "#38bdf8", "#7dd3fc", "#22d3ee", "#cffafe"],
...["#1e1b4b", "#312e81", "#4338ca", "#4f46e5", "#6366f1", "#818cf8", "#a5b4fc", "#e0e7ff"],
...["#2e1065", "#4c1d95", "#6d28d9", "#7c3aed", "#8b5cf6", "#a855f7", "#d946ef", "#fae8ff"],
];
const getColor = () => val($value) || "#000000";
return $.html("div", { class: "relative w-full" }, [
ui.Input({
label,
$value,
readonly: true,
placeholder: placeholder || "#000000",
class: "cursor-pointer font-mono uppercase",
onclick: (e) => {
e.stopPropagation();
isOpen(!isOpen());
return $.html("div", { class: "relative w-fit" }, [
$.html(
"button",
{
type: "button",
class: "btn px-3 bg-base-100 border-base-300 hover:border-primary/50 flex items-center gap-2 shadow-sm font-normal normal-case",
onclick: (e) => {
e.stopPropagation();
isOpen(!isOpen());
},
...rest,
},
icon: () =>
[
$.html("div", {
class: "size-5 rounded shadow-inner border border-base-content/10 transition-colors",
style: `background-color: ${getColor()}`,
class: "size-5 rounded-sm shadow-inner border border-black/10 shrink-0",
style: () => `background-color: ${getColor()}`,
}),
...rest,
}),
label ? $.html("span", { class: "opacity-80" }, label) : null,
],
),
ui.If(isOpen, () =>
$.html(
@@ -616,7 +601,7 @@ export const UI = ($, defaultLang = "es") => {
class: () => {
const active = getColor().toLowerCase() === c.toLowerCase();
return `size-6 rounded-sm cursor-pointer transition-all hover:scale-125 hover:z-10 active:scale-95 outline-none border border-black/5
${active ? "ring-2 ring-offset-1 ring-primary z-10 scale-110" : ""}`;
${active ? "ring-2 ring-offset-1 ring-primary z-10 scale-110" : ""}`;
},
onclick: () => {
$value(c);
@@ -624,7 +609,7 @@ export const UI = ($, defaultLang = "es") => {
},
}),
),
)
),
],
),
),
@@ -644,7 +629,7 @@ export const UI = ($, defaultLang = "es") => {
const checkEl = $.html("input", {
...rest,
type: "checkbox",
class: () => (toggle() ? "toggle" : "checkbox"),
class: () => (val(toggle) ? "toggle" : "checkbox"),
$checked: $value,
onchange: (e) => $value?.(e.target.checked),
});
@@ -709,8 +694,11 @@ export const UI = ($, defaultLang = "es") => {
$.html("dialog", { ...rest, class: "modal modal-open" }, [
$.html("div", { class: "modal-box" }, [
title ? $.html("h3", { class: "text-lg font-bold mb-4" }, title) : null,
children,
$.html("div", { class: "modal-action flex gap-2" }, [buttons, ui.Button({ onclick: close }, tt("close"))]),
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")()),
]),
]),
$.html(
"form",
@@ -719,7 +707,7 @@ export const UI = ($, defaultLang = "es") => {
class: "modal-backdrop",
onclick: (e) => (e.preventDefault(), close()),
},
$.html("button", "close"),
[$.html("button", {}, "close")],
),
]),
);
@@ -727,23 +715,31 @@ export const UI = ($, defaultLang = "es") => {
/** DROPDOWN */
ui.Dropdown = (props, children) => {
const { label, ...rest } = props;
const { label, icon, ...rest } = props;
return $.html(
"div",
{
...rest,
class: joinClass("dropdown", props.$class || props.class),
class: () => `dropdown ${val(props.$class) || props.class || ""}`,
},
[
label ? $.html("div", { tabindex: 0, role: "button", class: "btn m-1" }, label) : null,
$.html(
"div",
{
tabindex: 0,
class: "dropdown-content z-[50] p-2 shadow bg-base-100 rounded-box min-w-max",
role: "button",
class: "btn m-1 flex items-center gap-2",
},
children,
[icon ? (typeof icon === "function" ? icon() : icon) : null, label ? (typeof label === "function" ? label() : label) : null],
),
$.html(
"ul",
{
tabindex: 0,
class: "dropdown-content z-[50] menu p-2 shadow bg-base-100 rounded-box min-w-max border border-base-300",
},
[typeof children === "function" ? children() : children],
),
],
);
@@ -904,21 +900,35 @@ export const UI = ($, defaultLang = "es") => {
ui.Toast = (message, type = "alert-success", duration = 3500) => {
let container = document.getElementById("sigpro-toast-container");
if (!container) {
container = $.html("div", { id: "sigpro-toast-container", class: "fixed top-0 right-0 z-[9999] p-4 flex flex-col gap-2" });
container = $.html("div", {
id: "sigpro-toast-container",
class: "fixed top-0 right-0 z-[9999] p-4 flex flex-col gap-2",
});
document.body.appendChild(container);
}
const runtime = $.view(() => {
const el = $.html("div", { class: `alert alert-soft ${type} shadow-lg transition-all duration-300 translate-x-10 opacity-0` }, [
$.html("span", message),
ui.Button({ class: "btn-xs btn-circle btn-ghost", onclick: () => remove() }, "✕"),
]);
const el = $.html(
"div",
{
class: `alert alert-soft ${type} shadow-lg transition-all duration-300 translate-x-10 opacity-0`,
},
[
$.html("span", typeof message === "function" ? message() : message),
ui.Button(
{
class: "btn-xs btn-circle btn-ghost",
onclick: () => remove(),
},
"✕",
),
],
);
const remove = () => {
el.classList.add("translate-x-full", "opacity-0");
setTimeout(() => {
runtime.destroy();
el.remove();
if (!container.hasChildNodes()) container.remove();
}, 300);
};
@@ -928,7 +938,10 @@ export const UI = ($, defaultLang = "es") => {
});
container.appendChild(runtime.container);
requestAnimationFrame(() => runtime.container.firstChild.classList.remove("translate-x-10", "opacity-0"));
requestAnimationFrame(() => {
const el = runtime.container.firstElementChild;
if (el) el.classList.remove("translate-x-10", "opacity-0");
});
};
/** LOADING */