105 lines
3.3 KiB
JavaScript
105 lines
3.3 KiB
JavaScript
// components/Tabs.js
|
|
import { $, Tag, Watch } from "../sigpro.js";
|
|
import { val, ui, getIcon } from "..//core/utils.js";
|
|
|
|
/**
|
|
* Tabs component
|
|
*
|
|
* daisyUI classes used:
|
|
* - tabs, tabs-box, tabs-lift, tabs-border
|
|
* - tab, tab-content
|
|
* - bg-base-100, border-base-300, p-6
|
|
*/
|
|
export const Tabs = (props) => {
|
|
const { items, class: className, onTabClose, ...rest } = props;
|
|
const itemsSignal = typeof items === "function" ? items : () => items || [];
|
|
const activeIndex = $(0);
|
|
|
|
Watch(() => {
|
|
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(item);
|
|
if (onTabClose) onTabClose(item, indexToRemove);
|
|
|
|
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 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 buttonBase = Tag("button", {
|
|
class: () => ui("tab", activeIndex() === i ? "tab-active" : ""),
|
|
onclick: (e) => {
|
|
e.preventDefault();
|
|
if (!val(item.disabled)) {
|
|
if (item.onclick) item.onclick();
|
|
activeIndex(i);
|
|
}
|
|
}
|
|
}, buttonChildren);
|
|
|
|
const button = item.tip
|
|
? Tag("div", { class: "tooltip", "data-tip": item.tip }, buttonBase)
|
|
: buttonBase;
|
|
|
|
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: () => activeIndex() === i ? "display: block" : "display: none"
|
|
}, inner);
|
|
elements.push(panel);
|
|
}
|
|
return elements;
|
|
});
|
|
}; |