Tabs Con pestañas cerrables

This commit is contained in:
2026-04-13 16:21:42 +02:00
parent 0697b4b4b7
commit 3c3938b354
12 changed files with 362 additions and 140 deletions

110
dist/sigpro-ui.esm.js vendored
View File

@@ -579,11 +579,11 @@ var exports_utils = {};
__export(exports_utils, {
val: () => val,
ui: () => ui,
getIcon: () => getIcon
getIcon: () => getIcon2
});
var val = (t) => typeof t === "function" ? t() : t;
var ui = (baseClass, additionalClassOrFn) => typeof additionalClassOrFn === "function" ? () => `${baseClass} ${additionalClassOrFn() || ""}`.trim() : `${baseClass} ${additionalClassOrFn || ""}`.trim();
var getIcon = (icon) => {
var getIcon2 = (icon) => {
if (!icon)
return null;
if (typeof icon === "function") {
@@ -644,7 +644,7 @@ var Alert = (props, children) => {
role: "alert",
class: ui("alert", allClasses)
}, () => [
getIcon(iconMap[type]),
getIcon2(iconMap[type]),
Tag("div", { class: "flex-1" }, [
Tag("span", {}, [typeof content === "function" ? content() : content])
]),
@@ -713,8 +713,8 @@ var Input = (props) => {
tel: "icon-[lucide--phone]",
url: "icon-[lucide--link]"
};
const leftIcon = icon ? getIcon(icon) : iconMap[type] ? getIcon(iconMap[type]) : null;
const getPasswordIcon = () => getIcon(visible() ? "icon-[lucide--eye-off]" : "icon-[lucide--eye]");
const leftIcon = icon ? getIcon2(icon) : iconMap[type] ? getIcon2(iconMap[type]) : null;
const getPasswordIcon = () => getIcon2(visible() ? "icon-[lucide--eye-off]" : "icon-[lucide--eye]");
const paddingLeft = leftIcon ? "pl-10" : "";
const paddingRight = isPassword ? "pr-10" : "";
const buttonSize = () => {
@@ -871,7 +871,7 @@ __export(exports_Button, {
});
var Button = (props, children) => {
const { class: className, loading, icon, ...rest } = props;
const iconEl = getIcon(icon);
const iconEl = getIcon2(icon);
return Tag("button", {
...rest,
class: ui("btn", className),
@@ -1067,7 +1067,7 @@ var Datepicker = (props) => {
placeholder: placeholder || (isRangeMode() ? "Seleccionar rango..." : "Seleccionar fecha..."),
value: displayValue,
readonly: true,
icon: getIcon("icon-[lucide--calendar]"),
icon: getIcon2("icon-[lucide--calendar]"),
onclick: (e) => {
e.stopPropagation();
isOpen(!isOpen());
@@ -1080,15 +1080,15 @@ var Datepicker = (props) => {
}, [
Tag("div", { class: "flex justify-between items-center mb-4 gap-1" }, [
Tag("div", { class: "flex gap-0.5" }, [
Tag("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(-1) }, getIcon("icon-[lucide--chevrons-left]")),
Tag("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(-1) }, getIcon("icon-[lucide--chevron-left]"))
Tag("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(-1) }, getIcon2("icon-[lucide--chevrons-left]")),
Tag("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(-1) }, getIcon2("icon-[lucide--chevron-left]"))
]),
Tag("span", { class: "font-bold uppercase flex-1 text-center" }, [
() => internalDate().toLocaleString("es-ES", { month: "short", year: "numeric" })
]),
Tag("div", { class: "flex gap-0.5" }, [
Tag("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(1) }, getIcon("icon-[lucide--chevron-right]")),
Tag("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(1) }, getIcon("icon-[lucide--chevrons-right]"))
Tag("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(1) }, getIcon2("icon-[lucide--chevron-right]")),
Tag("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(1) }, getIcon2("icon-[lucide--chevrons-right]"))
])
]),
Tag("div", { class: "grid grid-cols-7 gap-1", onmouseleave: () => hoverDate(null) }, [
@@ -1296,7 +1296,7 @@ var Fab = (props) => {
role: "button",
class: "btn btn-lg btn-circle btn-primary shadow-2xl"
}, [
icon ? getIcon(icon) : null,
icon ? getIcon2(icon) : null,
!icon && label ? label : null
]),
...val(actions).map((act) => Tag("div", { class: "flex items-center gap-3 transition-all duration-300" }, [
@@ -1308,7 +1308,7 @@ var Fab = (props) => {
e.stopPropagation();
act.onclick?.(e);
}
}, [act.icon ? getIcon(act.icon) : act.text || ""])
}, [act.icon ? getIcon2(act.icon) : act.text || ""])
]))
]);
};
@@ -1383,7 +1383,7 @@ var Fileinput = (props) => {
}
}, [
Tag("div", { class: "flex items-center gap-3 w-full" }, [
getIcon("icon-[lucide--upload]"),
getIcon2("icon-[lucide--upload]"),
Tag("span", { class: "text-sm opacity-70 truncate grow text-left" }, "Arrastra o selecciona archivos..."),
Tag("span", { class: "text-[10px] opacity-40 shrink-0" }, `Máx ${max}MB`)
]),
@@ -1412,7 +1412,7 @@ var Fileinput = (props) => {
e.stopPropagation();
removeFile(index);
}
}, [getIcon("icon-[lucide--x]")])
}, [getIcon2("icon-[lucide--x]")])
]), (file) => file.name + file.lastModified)
]))
]);
@@ -1764,18 +1764,57 @@ var Tabs = (props) => {
const itemsSignal = typeof items === "function" ? items : () => items || [];
const activeIndex = $(0);
Watch(() => {
const idx = itemsSignal().findIndex((it) => val(it.active) === true);
if (idx !== -1 && idx !== activeIndex())
const list = itemsSignal();
const idx = list.findIndex((it) => val(it.active) === true);
if (idx !== -1 && activeIndex() !== idx) {
activeIndex(idx);
}
});
const removeTab = (indexToRemove, item) => {
if (item.onClose)
item.onClose();
const currentItems = itemsSignal();
const newItems = currentItems.filter((_, idx) => idx !== indexToRemove);
const isWritableSignal = typeof items === "function" && !items._isComputed;
if (!isWritableSignal) {
console.warn("Tabs: items must be a writable signal to support closable tabs");
return;
}
items(newItems);
if (newItems.length === 0)
return;
let newActive = activeIndex();
if (indexToRemove < newActive)
newActive--;
else if (indexToRemove === newActive)
newActive = Math.min(newActive, newItems.length - 1);
activeIndex(newActive);
};
return Tag("div", { ...rest, class: ui("tabs", className) }, () => {
const list = itemsSignal();
const elements = [];
for (let i = 0;i < list.length; i++) {
const item = list[i];
const isActive = () => activeIndex() === i;
const label = val(item.label);
const labelNode = label instanceof Node ? label : document.createTextNode(String(label));
const buttonChildren = [];
if (item.closable) {
const closeIcon = getIcon("icon-[lucide--x]");
closeIcon.classList.add("w-3.5", "h-3.5", "ml-2", "cursor-pointer", "hover:opacity-70");
closeIcon.onclick = (e) => {
e.stopPropagation();
removeTab(i, item);
};
const wrapper = Tag("span", { class: "flex items-center" }, [labelNode, closeIcon]);
buttonChildren.push(wrapper);
} else {
buttonChildren.push(labelNode);
}
const button = Tag("button", {
class: () => ui("tab", isActive() ? "tab-active" : ""),
class: () => {
const isActive = activeIndex() === i;
return ui("tab", isActive ? "tab-active" : "");
},
onclick: (e) => {
e.preventDefault();
if (!val(item.disabled)) {
@@ -1783,21 +1822,24 @@ var Tabs = (props) => {
item.onclick();
activeIndex(i);
}
}
});
const label = val(item.label);
if (label instanceof Node) {
button.replaceChildren(label);
} else {
button.textContent = String(label);
}
},
title: item.tip || ""
}, buttonChildren);
elements.push(button);
let contentNode;
const rawContent = val(item.content);
if (typeof rawContent === "function") {
contentNode = rawContent();
} else if (rawContent instanceof Node) {
contentNode = rawContent;
} else {
contentNode = document.createTextNode(String(rawContent));
}
const inner = Tag("div", { class: "tab-content-inner" }, contentNode);
const panel = Tag("div", {
class: "tab-content bg-base-100 border-base-300 p-6",
style: () => isActive() ? "display: block" : "display: none"
}, [
Tag("div", { class: "tab-content-inner" }, () => val(item.content))
]);
style: () => activeIndex() === i ? "display: block" : "display: none"
}, inner);
elements.push(panel);
}
return elements;
@@ -1833,7 +1875,7 @@ var Timeline = (props) => {
!isFirst ? Tag("hr", { class: () => prevCompleted() ? "bg-primary" : "" }) : null,
Tag("div", { class: "timeline-start" }, [() => renderSlot(item.title)]),
Tag("div", { class: "timeline-middle" }, [
() => item.icon ? getIcon(item.icon) : getIcon(iconMap[itemType] || iconMap.success)
() => item.icon ? getIcon2(item.icon) : getIcon2(iconMap[itemType] || iconMap.success)
]),
Tag("div", { class: "timeline-end timeline-box shadow-sm" }, [() => renderSlot(item.detail)]),
!isLast ? Tag("hr", { class: () => isCompleted() ? "bg-primary" : "" }) : null
@@ -1876,7 +1918,7 @@ var Toast = (message, type = "alert-success", duration = 3500) => {
}
};
const ToastComponent = () => {
const closeIcon = getIcon("icon-[lucide--x]");
const closeIcon = getIcon2("icon-[lucide--x]");
const el = Tag("div", {
class: `alert alert-soft ${type} shadow-lg transition-all duration-300 translate-x-10 opacity-0 pointer-events-auto`
}, [
@@ -1987,7 +2029,7 @@ export {
val,
ui,
tt,
getIcon,
getIcon2 as getIcon,
Watch2 as Watch,
Tooltip,
Toast,