Docs Updated

This commit is contained in:
2026-04-03 01:41:07 +02:00
parent b0629ef3d0
commit 257107e198
42 changed files with 1529 additions and 6255 deletions

View File

@@ -12,8 +12,17 @@ import { ui } from "../core/utils.js";
* - badge-outline, badge-soft, badge-dash
* - badge-xs, badge-sm, badge-md, badge-lg
*/
export const Indicator = (props, children) =>
$html("div", { class: "indicator" }, () => [
children,
props.value && $html("span", { class: ui('indicator-item badge', props.class) }, props.value),
].filter(Boolean));
export const Indicator = (props, children) => {
const { value, class: className, ...rest } = props;
return $html("div", {
...rest,
class: "indicator"
}, () => [
// El indicador debe ir PRIMERO (antes que el children)
value ? $html("span", {
class: ui('indicator-item badge', className)
}, () => typeof value === "function" ? value() : value) : null,
children
].filter(Boolean));
};

View File

@@ -1,13 +0,0 @@
import { $html, $if } from "sigpro";
/** LOADING (Overlay Component) */
export const Loading = (props) => {
// Se espera un signal props.$show para controlar la visibilidad
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" }),
]),
);
};

View File

@@ -1,6 +1,6 @@
// components/Modal.js
import { $html, $watch } from "sigpro";
import { ui, val } from "../core/utils.js";
import { ui } from "../core/utils.js";
import { tt } from "../core/i18n.js";
import { Button } from "./Button.js";
@@ -15,45 +15,50 @@ import { Button } from "./Button.js";
*/
export const Modal = (props, children) => {
const { class: className, title, buttons, open, ...rest } = props;
const dialogRef = { current: null };
let dialogElement = null;
$watch(() => {
const dialog = dialogRef.current;
if (!dialog) return;
if (val(open)) {
if (!dialog.open) dialog.showModal();
const handleOpen = () => {
const isOpen = typeof open === "function" ? open() : open;
if (!dialogElement) return;
if (isOpen) {
if (!dialogElement.open) dialogElement.showModal();
} else {
if (dialog.open) dialog.close();
if (dialogElement.open) dialogElement.close();
}
});
};
const close = (e) => {
if (e && e.preventDefault) e.preventDefault();
$watch(() => handleOpen());
const close = () => {
if (typeof open === "function") open(false);
};
return $html("dialog", {
...rest,
ref: dialogRef,
ref: (el) => {
dialogElement = el;
if (el) handleOpen();
},
class: ui('modal', className),
oncancel: () => typeof open === "function" && open(false)
onclose: close,
oncancel: close
}, [
$html("div", { class: "modal-box" }, [
title ? $html("h3", { class: "text-lg font-bold mb-4" }, title) : null,
title ? $html("h3", { class: "text-lg font-bold mb-4" }, () =>
typeof title === "function" ? title() : title
) : null,
$html("div", { class: "py-2" }, [
typeof children === "function" ? children() : children
]),
$html("div", { class: "modal-action flex gap-2" }, [
...(Array.isArray(buttons) ? buttons : [buttons]).filter(Boolean),
Button({ type: "button", onclick: close }, tt("close")()),
]),
$html("div", { class: "modal-action" }, [
$html("form", { method: "dialog", class: "flex gap-2" }, [
...(Array.isArray(buttons) ? buttons : [buttons]).filter(Boolean),
Button({ type: "submit" }, tt("close")())
])
])
]),
$html("form", {
method: "dialog",
class: "modal-backdrop",
onsubmit: close
}, [
$html("form", { method: "dialog", class: "modal-backdrop" }, [
$html("button", {}, "close")
])
]);

View File

@@ -15,7 +15,12 @@ export const Swap = (props) => {
return $html("label", { ...rest, class: ui('swap', className) }, [
$html("input", {
type: "checkbox",
checked: val(value)
checked: () => val(value), // ← FUNCIÓN: se reevalúa cuando la señal cambia
onclick: (e) => {
if (typeof value === "function") {
value(e.target.checked);
}
}
}),
$html("div", { class: "swap-on" }, on),
$html("div", { class: "swap-off" }, off),

View File

@@ -7,59 +7,64 @@ import { val, ui } from "../core/utils.js";
*
* daisyUI classes used:
* - tabs, tabs-box, tabs-lifted, tabs-bordered
* - tab, tab-active, tab-disabled
* - flex, flex-col, gap-4, w-full, p-4
* - tab, tab-content
* - bg-base-100, border-base-300, p-6
*/
export const Tabs = (props) => {
const { class: className, items, activeIndex = $(0), ...rest } = props;
const { items, class: className, ...rest } = props;
const itemsSignal = typeof items === "function" ? items : () => items || [];
const name = `tabs-${Math.random().toString(36).slice(2, 9)}`;
// Si no se provee activeIndex, creamos uno interno
const internalActive = $(0);
const currentActive = activeIndex !== undefined ? activeIndex : internalActive;
const handleTabClick = (idx, onClick) => (e) => {
if (typeof currentActive === "function") {
currentActive(idx);
}
onClick?.(e);
// Encontrar el índice activo
const getActiveIndex = () => {
const arr = itemsSignal();
const idx = arr.findIndex(it => val(it.active) === true);
return idx === -1 ? 0 : idx;
};
return $html("div", { ...rest, class: "flex flex-col gap-4 w-full" }, [
$html(
"div",
{
role: "tablist",
class: ui('tabs tabs-box', className),
},
$for(
itemsSignal,
(it, idx) => {
const isActive = val(it.active) ?? (currentActive() === idx);
return $html(
"a",
{
role: "tab",
class: () => ui('tab', isActive ? 'tab-active' : '', val(it.disabled) ? 'tab-disabled' : ''),
onclick: !val(it.disabled) ? handleTabClick(idx, it.onclick) : undefined,
},
it.label,
);
},
(t, idx) => t.label + idx,
),
),
() => {
const activeItem = itemsSignal().find((it, idx) =>
val(it.active) ?? (currentActive() === idx)
);
if (!activeItem) return null;
const content = val(activeItem.content);
return $html("div", { class: "p-4" }, [
typeof content === "function" ? content() : content
]);
},
const activeIndex = $(getActiveIndex);
const updateActiveIndex = () => {
const newIndex = getActiveIndex();
if (newIndex !== activeIndex()) activeIndex(newIndex);
};
$watch(() => updateActiveIndex());
return $html("div", {
...rest,
class: ui('tabs', className || 'tabs-box')
}, [
$for(itemsSignal, (it, idx) => {
const isChecked = () => activeIndex() === idx;
const getLabelText = () => {
const label = typeof it.label === "function" ? it.label() : it.label;
return typeof label === "string" ? label : `Tab ${idx + 1}`;
};
return [
$html("input", {
type: "radio",
name: name,
class: "tab",
"aria-label": getLabelText(),
checked: isChecked, // ← función reactiva, no string hardcodeado
disabled: () => val(it.disabled),
onchange: (e) => {
if (e.target.checked && !val(it.disabled)) {
if (it.onclick) it.onclick();
if (typeof it.active === "function") it.active(true);
activeIndex(idx);
}
}
}),
$html("div", {
class: "tab-content bg-base-100 border-base-300 p-6",
style: () => isChecked() ? "display: block" : "display: none"
}, [
typeof it.content === "function" ? it.content() : it.content
])
];
}, (it, idx) => idx)
]);
};

View File

@@ -21,6 +21,8 @@ export const Timeline = (props) => {
error: "icon-[lucide--alert-circle]",
};
const itemsSource = typeof items === "function" ? items : () => items || [];
return $html(
"ul",
{
@@ -30,23 +32,23 @@ export const Timeline = (props) => {
},
[
$for(
items,
itemsSource,
(item, i) => {
const isFirst = i === 0;
const isLast = i === val(items).length - 1;
const isLast = i === itemsSource().length - 1;
const itemType = item.type || "success";
const renderSlot = (content) => (typeof content === "function" ? content() : content);
return $html("li", { class: "flex-1" }, [
!isFirst ? $html("hr", { class: item.completed ? "bg-primary" : "" }) : null,
$html("div", { class: "timeline-start" }, [renderSlot(item.title)]),
$html("div", { class: "timeline-middle" }, [
!isFirst ? $html("hr", { class: () => item.completed ? "bg-primary" : "" }) : null,
$html("div", { class: "timeline-start" }, () => renderSlot(item.title)),
$html("div", { class: "timeline-middle" }, () => [
item.icon
? getIcon(item.icon)
: getIcon(iconMap[itemType] || iconMap.success)
]),
$html("div", { class: "timeline-end timeline-box shadow-sm" }, [renderSlot(item.detail)]),
!isLast ? $html("hr", { class: item.completed ? "bg-primary" : "" }) : null,
$html("div", { class: "timeline-end timeline-box shadow-sm" }, () => renderSlot(item.detail)),
!isLast ? $html("hr", { class: () => item.completed ? "bg-primary" : "" }) : null,
]);
},
(item, i) => item.id || i,

View File

@@ -15,7 +15,6 @@ import * as IndicatorModule from './Indicator.js';
import * as InputModule from './Input.js';
import * as LabelModule from './Label.js';
import * as ListModule from './List.js';
import * as LoadingModule from './Loading.js';
import * as MenuModule from './Menu.js';
import * as ModalModule from './Modal.js';
import * as NavbarModule from './Navbar.js';
@@ -49,7 +48,6 @@ export * from './Indicator.js';
export * from './Input.js';
export * from './Label.js';
export * from './List.js';
export * from './Loading.js';
export * from './Menu.js';
export * from './Modal.js';
export * from './Navbar.js';
@@ -84,7 +82,6 @@ const Components = {
...InputModule,
...LabelModule,
...ListModule,
...LoadingModule,
...MenuModule,
...ModalModule,
...NavbarModule,