UUUUPPPPP work
This commit is contained in:
142
components_/AgGrid.js
Normal file
142
components_/AgGrid.js
Normal file
@@ -0,0 +1,142 @@
|
||||
import {
|
||||
createGrid,
|
||||
ModuleRegistry,
|
||||
ValidationModule,
|
||||
ColumnAutoSizeModule,
|
||||
CellStyleModule,
|
||||
QuickFilterModule,
|
||||
RowSelectionModule,
|
||||
TextEditorModule,
|
||||
ClientSideRowModelModule,
|
||||
themeQuartz,
|
||||
iconSetQuartzLight,
|
||||
} from "ag-grid-community";
|
||||
|
||||
import {
|
||||
MultiFilterModule,
|
||||
CellSelectionModule,
|
||||
PivotModule,
|
||||
MasterDetailModule,
|
||||
SideBarModule,
|
||||
ColumnsToolPanelModule,
|
||||
ColumnMenuModule,
|
||||
StatusBarModule,
|
||||
ExcelExportModule,
|
||||
ClipboardModule,
|
||||
} from "ag-grid-enterprise";
|
||||
|
||||
import { $ } from "sigpro";
|
||||
import { isDark } from "../store.js";
|
||||
|
||||
// ✅ Registro de módulos (UNA VEZ, fuera del componente)
|
||||
ModuleRegistry.registerModules([
|
||||
ValidationModule,
|
||||
ColumnAutoSizeModule,
|
||||
CellStyleModule,
|
||||
QuickFilterModule,
|
||||
RowSelectionModule,
|
||||
TextEditorModule,
|
||||
ClientSideRowModelModule,
|
||||
MultiFilterModule,
|
||||
CellSelectionModule,
|
||||
PivotModule,
|
||||
MasterDetailModule,
|
||||
SideBarModule,
|
||||
ColumnsToolPanelModule,
|
||||
ColumnMenuModule,
|
||||
StatusBarModule,
|
||||
ExcelExportModule,
|
||||
ClipboardModule,
|
||||
]);
|
||||
|
||||
const getTheme = (isDark) =>
|
||||
themeQuartz.withPart(iconSetQuartzLight).withParams({
|
||||
browserColorScheme: isDark ? "dark" : "light",
|
||||
backgroundColor: isDark ? "#121212" : "#FDFDFD",
|
||||
foregroundColor: isDark ? "#E0E0E0" : "#181D1F",
|
||||
accentColor: isDark ? "#4FAAFF" : "#004B9C",
|
||||
headerBackgroundColor: isDark ? "#2A2A2A" : "#EEB111",
|
||||
headerTextColor: isDark ? "#4FAAFF" : "#004B9C",
|
||||
borderRadius: 4,
|
||||
columnBorder: false,
|
||||
headerFontSize: 14,
|
||||
headerFontWeight: 600,
|
||||
listItemHeight: 20,
|
||||
iconSize: 14,
|
||||
spacing: 3,
|
||||
wrapperBorderRadius: 4,
|
||||
});
|
||||
|
||||
$.component(
|
||||
"c-grid",
|
||||
(props, { onUnmount }) => {
|
||||
let gridApi = null;
|
||||
|
||||
// ✅ Crear contenedor específico para este grid
|
||||
const container = document.createElement("div");
|
||||
container.style.height = "100%";
|
||||
|
||||
const gridContainer = document.createElement("div");
|
||||
gridContainer.style.height = "100%";
|
||||
container.appendChild(gridContainer);
|
||||
|
||||
// Theme observer
|
||||
const currentTheme = $(document.querySelector("[data-theme]")?.getAttribute("data-theme") || "light");
|
||||
const observer = new MutationObserver(() => {
|
||||
const theme = document.querySelector("[data-theme]")?.getAttribute("data-theme");
|
||||
if (theme !== currentTheme()) currentTheme(theme);
|
||||
});
|
||||
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
subtree: true,
|
||||
attributeFilter: ["data-theme"],
|
||||
});
|
||||
|
||||
// ✅ LIMPIEZA COMPLETA
|
||||
onUnmount(() => {
|
||||
observer.disconnect();
|
||||
|
||||
// 1. Destruir el grid
|
||||
if (gridApi) {
|
||||
gridApi.destroy();
|
||||
gridApi = null;
|
||||
}
|
||||
|
||||
// 2. Eliminar el contenedor del DOM
|
||||
if (gridContainer.parentNode) {
|
||||
gridContainer.parentNode.removeChild(gridContainer);
|
||||
}
|
||||
|
||||
// 3. Limpiar referencias
|
||||
container.innerHTML = "";
|
||||
});
|
||||
|
||||
// Efecto para tema y creación inicial
|
||||
$.effect(() => {
|
||||
const dark = isDark();
|
||||
const agTheme = getTheme(dark);
|
||||
|
||||
if (!gridApi) {
|
||||
gridApi = createGrid(gridContainer, {
|
||||
...(props.options?.() || {}),
|
||||
theme: agTheme,
|
||||
rowData: props.data?.() || [],
|
||||
});
|
||||
} else {
|
||||
gridApi.setGridOption("theme", agTheme);
|
||||
}
|
||||
});
|
||||
|
||||
// Efecto para datos
|
||||
$.effect(() => {
|
||||
const data = props.data?.();
|
||||
if (gridApi && Array.isArray(data)) {
|
||||
gridApi.setGridOption("rowData", data);
|
||||
}
|
||||
});
|
||||
|
||||
return container;
|
||||
},
|
||||
["data", "options"],
|
||||
);
|
||||
31
components_/Button.js
Normal file
31
components_/Button.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import { $, html } from "sigpro";
|
||||
|
||||
$.component(
|
||||
"c-button",
|
||||
(props, { emit, slot }) => {
|
||||
const spinner = () => html`
|
||||
<span .class="${() => `loading loading-spinner loading-xs ${props.loading() ? "" : "hidden"}`}"></span>
|
||||
`;
|
||||
|
||||
return html`
|
||||
<div class="${props.tooltip() ? "tooltip" : ""}" data-tip=${() => props.tooltip() ?? ""}>
|
||||
<button
|
||||
class="${() => `btn ${props.cls() ?? ""} ${props.badge() ? "indicator" : ""}`}"
|
||||
?disabled=${props.loading}
|
||||
@click=${(e) => {
|
||||
e.stopPropagation();
|
||||
if (!props.loading()) emit("click", e);
|
||||
}}>
|
||||
${spinner} ${slot()}
|
||||
${() =>
|
||||
props.badge()
|
||||
? html`
|
||||
<span class="indicator-item badge badge-secondary">${props.badge()}</span>
|
||||
`
|
||||
: null}
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
["cls", "loading", "badge", "tooltip"],
|
||||
);
|
||||
26
components_/Card.js
Normal file
26
components_/Card.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { $, html } from "sigpro";
|
||||
|
||||
$.component(
|
||||
"c-card",
|
||||
(props, host) => {
|
||||
return html`
|
||||
<div class="${() => `card bg-base-100 shadow-sm ${props.class || ""}`}">
|
||||
${() =>
|
||||
props.img
|
||||
? html`
|
||||
<figure>
|
||||
<img src="${props.img}" alt="${props.alt || "Card image"}" />
|
||||
</figure>
|
||||
`
|
||||
: null}
|
||||
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">${host.slot("title")}</h2>
|
||||
<div class="card-content">${host.slot("body")}</div>
|
||||
<div class="card-actions justify-end">${host.slot("actions")}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
["img", "alt", "class"],
|
||||
);
|
||||
27
components_/Checkbox.js
Normal file
27
components_/Checkbox.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import { $ html } from "sigpro";
|
||||
|
||||
$.component(
|
||||
"c-check",
|
||||
(props, host) => {
|
||||
return html`
|
||||
<label class="label cursor-pointer flex gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
@change="${(e) => {
|
||||
e.stopPropagation();
|
||||
emit("change", e.target.checked);
|
||||
}}"
|
||||
.checked="${() => props.checked()}"
|
||||
.disabled="${() => props.disabled()}"
|
||||
class="${cls(props.toggle ? "toggle" : "checkbox")}" />
|
||||
${() =>
|
||||
props.label
|
||||
? html`
|
||||
<span class="label-text">${props.label}</span>
|
||||
`
|
||||
: ""}
|
||||
</label>
|
||||
`;
|
||||
},
|
||||
["checked", "label", "class", "disabled", "toggle"],
|
||||
);
|
||||
65
components_/ColorPicker.js
Normal file
65
components_/ColorPicker.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import { $, html } from "sigpro";
|
||||
|
||||
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];
|
||||
|
||||
$.component(
|
||||
"c-colorpicker",
|
||||
(props, { emit }) => {
|
||||
const handleSelect = (c) => {
|
||||
if (typeof props.color === "function") props.color(c);
|
||||
emit("select", c);
|
||||
};
|
||||
|
||||
const getColor = () => props.color() ?? "#000000";
|
||||
|
||||
return html`
|
||||
<div class="card bg-base-200 border-base-300 w-fit border p-2 shadow-sm select-none">
|
||||
<div class="grid grid-cols-8 gap-0.5">
|
||||
${() =>
|
||||
palette.map(
|
||||
(c) => html`
|
||||
<button
|
||||
type="button"
|
||||
.style=${`background-color: ${c}`}
|
||||
.class=${() => {
|
||||
const active = getColor() === c;
|
||||
return `size-5 rounded-xs cursor-pointer transition-all hover:scale-125 hover:z-10 active:scale-90 outline-none border border-black/5 ${
|
||||
active ? "ring-2 ring-offset-1 ring-primary z-10 scale-110" : ""
|
||||
}`;
|
||||
}}
|
||||
@click=${() => handleSelect(c)}></button>
|
||||
`,
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-1 mt-2">
|
||||
<input
|
||||
type="text"
|
||||
class="input input-bordered input-xs h-6 px-1 font-mono text-[10px] w-full"
|
||||
.value=${props.color}
|
||||
@input=${(e) => handleSelect(e.target.value)} />
|
||||
|
||||
<div class="tooltip" data-tip="Copiar">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-xs btn-square border border-base-content/20 shadow-inner"
|
||||
.style=${() => `background-color: ${getColor()}`}
|
||||
@click=${() => navigator.clipboard.writeText(getColor())}>
|
||||
<span class="icon-[lucide--copy] text-white mix-blend-difference"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
["color"],
|
||||
);
|
||||
168
components_/DatePicker.js
Normal file
168
components_/DatePicker.js
Normal file
@@ -0,0 +1,168 @@
|
||||
import { $, html } from "sigpro";
|
||||
|
||||
$.component(
|
||||
"c-datepicker",
|
||||
(props, { emit }) => {
|
||||
const viewDate = $(new Date());
|
||||
const hoveredDate = $(null);
|
||||
const todayISO = new Date().toLocaleDateString("en-CA");
|
||||
|
||||
const toISOLocal = (date) => {
|
||||
if (!date) return null;
|
||||
return date.toISOString().split("T")[0];
|
||||
};
|
||||
|
||||
// Función unificada para navegar tiempo
|
||||
const navigate = (type, offset) => {
|
||||
hoveredDate(null);
|
||||
const d = viewDate();
|
||||
if (type === "month") {
|
||||
viewDate(new Date(d.getFullYear(), d.getMonth() + offset, 1));
|
||||
} else if (type === "year") {
|
||||
viewDate(new Date(d.getFullYear() + offset, d.getMonth(), 1));
|
||||
}
|
||||
};
|
||||
|
||||
const selectDate = (dateObj) => {
|
||||
const isoDate = toISOLocal(dateObj);
|
||||
const isRange = props.range() === "true" || props.range() === true;
|
||||
const currentVal = typeof props.value === "function" ? props.value() : props.value;
|
||||
|
||||
let result;
|
||||
if (!isRange) {
|
||||
result = isoDate;
|
||||
} else {
|
||||
const s = currentVal?.start || null;
|
||||
const e = currentVal?.end || null;
|
||||
if (!s || (s && e)) {
|
||||
result = { start: isoDate, end: null };
|
||||
} else {
|
||||
result = isoDate < s ? { start: isoDate, end: s } : { start: s, end: isoDate };
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof props.value === "function") {
|
||||
props.value(isRange ? { ...result } : result);
|
||||
}
|
||||
emit("change", result);
|
||||
};
|
||||
|
||||
const handleGridClick = (e) => {
|
||||
const btn = e.target.closest("button[data-date]");
|
||||
if (!btn) return;
|
||||
selectDate(new Date(btn.getAttribute("data-date")));
|
||||
};
|
||||
|
||||
const days = $(() => {
|
||||
const d = viewDate();
|
||||
const year = d.getFullYear();
|
||||
const month = d.getMonth();
|
||||
const firstDay = new Date(year, month, 1).getDay();
|
||||
const offset = firstDay === 0 ? 6 : firstDay - 1;
|
||||
const total = new Date(year, month + 1, 0).getDate();
|
||||
let grid = Array(offset).fill(null);
|
||||
for (let i = 1; i <= total; i++) grid.push(new Date(year, month, i));
|
||||
return grid;
|
||||
});
|
||||
|
||||
const getWeekNumber = (d) => {
|
||||
const t = new Date(d.valueOf());
|
||||
t.setDate(t.getDate() - ((d.getDay() + 6) % 7) + 3);
|
||||
const firstThurs = t.valueOf();
|
||||
t.setMonth(0, 1);
|
||||
if (t.getDay() !== 4) t.setMonth(0, 1 + ((4 - t.getDay() + 7) % 7));
|
||||
return 1 + Math.ceil((firstThurs - t.getTime()) / 604800000);
|
||||
};
|
||||
|
||||
return html`
|
||||
<div class="card bg-base-100 shadow-xl border border-base-300 w-80 p-4 pb-6 rounded-box select-none">
|
||||
<div class="flex justify-between items-center mb-4 gap-1">
|
||||
<div class="flex gap-0.5">
|
||||
<button type="button" class="btn btn-ghost btn-xs px-1" @click=${() => navigate("year", -1)}>
|
||||
<span class="icon-[lucide--chevrons-left] w-4 h-4 opacity-50"></span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-ghost btn-xs px-1" @click=${() => navigate("month", -1)}>
|
||||
<span class="icon-[lucide--chevron-left] w-4 h-4"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<span class="text-xs font-bold capitalize flex-1 text-center">
|
||||
${() => viewDate().toLocaleString("es-ES", { month: "long" }).toUpperCase()}
|
||||
<span class="opacity-50 ml-1">${() => viewDate().getFullYear()}</span>
|
||||
</span>
|
||||
|
||||
<div class="flex gap-0.5">
|
||||
<button type="button" class="btn btn-ghost btn-xs px-1" @click=${() => navigate("month", 1)}>
|
||||
<span class="icon-[lucide--chevron-right] w-4 h-4"></span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-ghost btn-xs px-1" @click=${() => navigate("year", 1)}>
|
||||
<span class="icon-[lucide--chevrons-right] w-4 h-4 opacity-50"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-8 gap-1 px-1" @click=${handleGridClick}>
|
||||
<div class="flex items-center justify-center text-[10px] opacity-40 font-bold uppercase"></div>
|
||||
${() =>
|
||||
["L", "M", "X", "J", "V", "S", "D"].map(
|
||||
(l) => html`
|
||||
<div class="flex items-center justify-center text-[10px] opacity-40 font-bold uppercase">${l}</div>
|
||||
`,
|
||||
)}
|
||||
${() =>
|
||||
days().map((date, i) => {
|
||||
const isFirstCol = i % 7 === 0;
|
||||
const iso = date ? toISOLocal(date) : null;
|
||||
|
||||
const btnClass = () => {
|
||||
if (!date) return "";
|
||||
const val = typeof props.value === "function" ? props.value() : props.value;
|
||||
const isR = props.range() === "true" || props.range() === true;
|
||||
const sDate = isR ? val?.start : typeof val === "string" ? val : val?.start;
|
||||
const eDate = isR ? val?.end : null;
|
||||
const hDate = hoveredDate();
|
||||
|
||||
const isSel = iso === sDate || iso === eDate;
|
||||
const tEnd = eDate || hDate;
|
||||
const inRange = isR && sDate && tEnd && !isSel && ((iso > sDate && iso < tEnd) || (iso < sDate && iso > tEnd));
|
||||
|
||||
return `btn btn-xs p-0 aspect-square min-h-0 h-auto font-normal rounded-md relative
|
||||
${isSel ? "btn-primary !text-primary-content shadow-md" : "btn-ghost"}
|
||||
${inRange ? "!bg-primary/20 !text-base-content" : ""}`;
|
||||
};
|
||||
|
||||
return html`
|
||||
${isFirstCol
|
||||
? html`
|
||||
<div class="flex items-center justify-center text-[10px] opacity-30 italic bg-base-200/50 rounded-md aspect-square">
|
||||
${date ? getWeekNumber(date) : days()[i + 6] ? getWeekNumber(days()[i + 6]) : ""}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${date
|
||||
? html`
|
||||
<button
|
||||
type="button"
|
||||
class="${btnClass}"
|
||||
data-date="${date.toISOString()}"
|
||||
@mouseenter=${() => hoveredDate(iso)}
|
||||
@mouseleave=${() => hoveredDate(null)}>
|
||||
${iso === todayISO
|
||||
? html`
|
||||
<span class="absolute -inset-px border-2 border-primary/60 rounded-md pointer-events-none"></span>
|
||||
`
|
||||
: ""}
|
||||
<span class="relative z-10 pointer-events-none">${date.getDate()}</span>
|
||||
</button>
|
||||
`
|
||||
: html`
|
||||
<div class="aspect-square"></div>
|
||||
`}
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
["range", "value"],
|
||||
);
|
||||
37
components_/Dialog.js
Normal file
37
components_/Dialog.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import { $, html } from "sigpro";
|
||||
|
||||
$.component(
|
||||
"c-dialog",
|
||||
(props, { slot, emit }) => {
|
||||
return html`
|
||||
<dialog
|
||||
.class=${() => `modal ${props.open() ? "modal-open" : ""}`}
|
||||
.open=${props.open}
|
||||
@close=${(e) => {
|
||||
if (typeof props.open === "function") props.open(false);
|
||||
emit("close", e);
|
||||
}}>
|
||||
<div class="modal-box">
|
||||
<div class="flex flex-col gap-4">${slot()}</div>
|
||||
|
||||
<div class="modal-action">
|
||||
<form method="dialog" @submit=${() => props.open(false)}>
|
||||
${slot("buttons")}
|
||||
${() =>
|
||||
!slot("buttons").length
|
||||
? html`
|
||||
<button class="btn">Cerrar</button>
|
||||
`
|
||||
: ""}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="dialog" class="modal-backdrop" @submit=${() => props.open(false)}>
|
||||
<button>close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
`;
|
||||
},
|
||||
["open"],
|
||||
);
|
||||
31
components_/Drawer.js
Normal file
31
components_/Drawer.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import { $, html } from "sigpro";
|
||||
|
||||
$.component(
|
||||
"c-drawer",
|
||||
(props, { emit, slot }) => {
|
||||
const id = `drawer-${Math.random().toString(36).substring(2, 9)}`;
|
||||
|
||||
return html`
|
||||
<div class="drawer">
|
||||
<input
|
||||
id="${id}"
|
||||
type="checkbox"
|
||||
class="drawer-toggle"
|
||||
.checked=${props.open}
|
||||
@change=${(e) => {
|
||||
const isChecked = e.target.checked;
|
||||
if (typeof props.open === "function") props.open(isChecked);
|
||||
emit("change", isChecked);
|
||||
}} />
|
||||
|
||||
<div class="drawer-content">${slot("content")}</div>
|
||||
|
||||
<div class="drawer-side z-999">
|
||||
<label for="${id}" aria-label="close sidebar" class="drawer-overlay"></label>
|
||||
<div class="bg-base-200 min-h-full w-80">${slot()}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
["open"],
|
||||
);
|
||||
20
components_/Dropdown.js
Normal file
20
components_/Dropdown.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import { $, html } from "sigpro";
|
||||
|
||||
$.component(
|
||||
"c-dropdown",
|
||||
(props, { slot }) => {
|
||||
// Generamos un ID único para el anclaje nativo
|
||||
const id = props.id ?? `pop-${Math.random().toString(36).slice(2, 7)}`;
|
||||
|
||||
return html`
|
||||
<div class="inline-block">
|
||||
<button class="btn" popovertarget="${id}" style="anchor-name: --${id}">${slot("trigger")}</button>
|
||||
|
||||
<div popover id="${id}" style="position-anchor: --${id}" class="dropdown menu bg-base-100 rounded-box shadow-sm border border-base-300">
|
||||
${slot()}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
["id"],
|
||||
);
|
||||
37
components_/Fab.js
Normal file
37
components_/Fab.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import { $, html } from "sigpro";
|
||||
|
||||
$.component(
|
||||
"c-fab",
|
||||
(props, { emit }) => {
|
||||
const handleClick = (e, item) => {
|
||||
if (item.onclick) item.onclick(e);
|
||||
emit("select", item);
|
||||
if (document.activeElement instanceof HTMLElement) document.activeElement.blur();
|
||||
};
|
||||
|
||||
return html`
|
||||
<div class="dropdown dropdown-top dropdown-end fixed bottom-6 right-6 z-100">
|
||||
<div tabindex="0" role="button" .class=${() => `btn btn-lg btn-circle btn-primary shadow-2xl ${props.cls() ?? ""}`}>
|
||||
<span class="${() => props["main-icon"]() || "icon-[lucide--plus]"} w-6 h-6"></span>
|
||||
</div>
|
||||
|
||||
<ul tabindex="0" class="dropdown-content menu mb-4 p-0 flex flex-col gap-3 items-center">
|
||||
${() =>
|
||||
(props.actions() || []).map(
|
||||
(item) => html`
|
||||
<li class="p-0">
|
||||
<button
|
||||
.class=${() => `btn btn-circle shadow-lg ${item.cls || "btn-secondary"}`}
|
||||
@click=${(e) => handleClick(e, item)}
|
||||
.title=${item.label}>
|
||||
<span class="${item.icon} w-5 h-5"></span>
|
||||
</button>
|
||||
</li>
|
||||
`,
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
["main-icon", "actions", "cls"],
|
||||
);
|
||||
26
components_/Input.js
Normal file
26
components_/Input.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { $, html } from "sigpro";
|
||||
|
||||
$.component(
|
||||
"c-input",
|
||||
(props, { slot, emit }) => {
|
||||
return html`
|
||||
<div class="${props.tooltip() ? "tooltip" : ""}" data-tip=${() => props.tooltip() ?? ""}>
|
||||
<label class="floating-label">
|
||||
<span>${() => props.label() ?? ""}</span>
|
||||
<label class=${() => `input ${props.cls() ?? ""}`}>
|
||||
<input
|
||||
type=${() => props.type() ?? "text"}
|
||||
class="input"
|
||||
:value=${props.value}
|
||||
placeholder=${() => props.place() ?? props.label() ?? ""}
|
||||
@input=${(e) => emit("input", e.target.value)}
|
||||
@change=${(e) => emit("change", e.target.value)} />
|
||||
<span>${slot("icon-action")}</span>
|
||||
<span class=${() => props.icon() ?? ""}></span>
|
||||
</label>
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
["label", "value", "icon", "tooltip", "cls", "place", "type"],
|
||||
);
|
||||
17
components_/InputClear.js
Normal file
17
components_/InputClear.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { $, html } from "sigpro";
|
||||
|
||||
$.component(
|
||||
"c-input-clear",
|
||||
(props) => {
|
||||
return html`
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-ghost btn-xs btn-circle opacity-50 hover:opacity-100"
|
||||
?hidden=${() => !props.value() || props.value().length === 0}
|
||||
@click=${() => props.value("")}>
|
||||
<span class="icon-[lucide--x] size-4"></span>
|
||||
</button>
|
||||
`;
|
||||
},
|
||||
["value"],
|
||||
);
|
||||
13
components_/InputView.js
Normal file
13
components_/InputView.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { $, html } from "sigpro";
|
||||
|
||||
$.component(
|
||||
"c-input-view",
|
||||
(props) => {
|
||||
return html`
|
||||
<button type="button" class="btn btn-ghost btn-xs btn-circle opacity-50 hover:opacity-100" @click=${() => props.show(!props.show())}>
|
||||
<span class="${() => (props.show() ? "icon-[lucide--eye-off]" : "icon-[lucide--eye]")} size-4"></span>
|
||||
</button>
|
||||
`;
|
||||
},
|
||||
["show"],
|
||||
);
|
||||
39
components_/Loading.js
Normal file
39
components_/Loading.js
Normal file
@@ -0,0 +1,39 @@
|
||||
export const loading = (show = true, msg = "Cargando...") => {
|
||||
const body = document.body;
|
||||
|
||||
if (!show) {
|
||||
if (loadingEl) {
|
||||
loadingEl.classList.replace("opacity-100", "opacity-0");
|
||||
body.style.removeProperty("overflow"); // Restaurar scroll
|
||||
|
||||
const elToRemove = loadingEl; // Captura para el closure
|
||||
elToRemove.addEventListener("transitionend", () => {
|
||||
if (elToRemove === loadingEl) { // Solo si sigue siendo el actual
|
||||
elToRemove.remove();
|
||||
loadingEl = null;
|
||||
}
|
||||
}, { once: true }
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadingEl?.isConnected) {
|
||||
loadingEl.querySelector(".loading-text").textContent = msg;
|
||||
return;
|
||||
}
|
||||
|
||||
body.style.overflow = "hidden"; // Bloquear scroll
|
||||
|
||||
loadingEl = html`
|
||||
<div class="fixed inset-0 z-9999 flex items-center justify-center bg-base-300/40 backdrop-blur-md transition-opacity duration-300 opacity-0 pointer-events-auto select-none">
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<span class="loading loading-spinner loading-lg text-primary"></span>
|
||||
<span class="loading-text font-bold text-lg text-base-content">${msg}</span>
|
||||
</div>
|
||||
</div>
|
||||
`.firstElementChild;
|
||||
|
||||
body.appendChild(loadingEl);
|
||||
requestAnimationFrame(() => loadingEl.classList.replace("opacity-0", "opacity-100"));
|
||||
};
|
||||
57
components_/Menu.js
Normal file
57
components_/Menu.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import { $, html } from "sigpro";
|
||||
|
||||
$.component(
|
||||
"c-menu",
|
||||
(props, { emit }) => {
|
||||
const getItems = () => props.items() || [];
|
||||
|
||||
const renderItems = (data) => {
|
||||
return data.map((item) => {
|
||||
const hasChildren = item.sub && item.sub.length > 0;
|
||||
const content = html`
|
||||
${item.icon
|
||||
? html`
|
||||
<span class="${item.icon} h-4 w-4"></span>
|
||||
`
|
||||
: ""}
|
||||
<span>${item.label}</span>
|
||||
`;
|
||||
|
||||
if (hasChildren) {
|
||||
return html`
|
||||
<li>
|
||||
<details .open="${!!item.open}">
|
||||
<summary>${content}</summary>
|
||||
<ul>
|
||||
${renderItems(item.sub)}
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<li>
|
||||
<a
|
||||
href="${item.href || "#"}"
|
||||
.class=${item.active ? "active" : ""}
|
||||
@click="${(e) => {
|
||||
if (!item.href || item.href === "#") e.preventDefault();
|
||||
if (item.onClick) item.onClick(item);
|
||||
emit("select", item);
|
||||
}}">
|
||||
${content}
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
});
|
||||
};
|
||||
|
||||
return html`
|
||||
<ul .class=${() => `menu bg-base-200 rounded-box w-full ${props.cls() ?? ""}`}>
|
||||
${() => renderItems(getItems())}
|
||||
</ul>
|
||||
`;
|
||||
},
|
||||
["items", "cls"],
|
||||
);
|
||||
28
components_/Radio.js
Normal file
28
components_/Radio.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import { $.component, html } from "sigpro";
|
||||
|
||||
$.component(
|
||||
"c-radio",
|
||||
(props, { emit }) => {
|
||||
return html`
|
||||
<label class="label cursor-pointer flex justify-start gap-4">
|
||||
<input
|
||||
type="radio"
|
||||
.name=${props.name}
|
||||
.value=${props.value}
|
||||
.class=${() => `radio ${props.cls() ?? ""}`}
|
||||
.disabled=${props.disabled}
|
||||
.checked=${props.checked}
|
||||
@change=${(e) => {
|
||||
if (e.target.checked) emit("change", props.value());
|
||||
}} />
|
||||
${() =>
|
||||
props.label()
|
||||
? html`
|
||||
<span class="label-text">${props.label}</span>
|
||||
`
|
||||
: ""}
|
||||
</label>
|
||||
`;
|
||||
},
|
||||
["checked", "name", "label", "cls", "disabled", "value"],
|
||||
);
|
||||
24
components_/Range.js
Normal file
24
components_/Range.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import { $, html } from "sigpro";
|
||||
|
||||
$.component(
|
||||
"c-range",
|
||||
(props, { emit }) => {
|
||||
return html`
|
||||
<input
|
||||
type="range"
|
||||
.min=${() => props.min() ?? 0}
|
||||
.max=${() => props.max() ?? 100}
|
||||
.step=${() => props.step() ?? 1}
|
||||
.value=${props.value}
|
||||
.class=${() => `range ${props.cls() ?? ""}`}
|
||||
@input=${(e) => {
|
||||
const val = e.target.value;
|
||||
if (typeof props.value === "function") props.value(val);
|
||||
|
||||
emit("input", val);
|
||||
emit("change", val);
|
||||
}} />
|
||||
`;
|
||||
},
|
||||
["cls", "value", "min", "max", "step"],
|
||||
);
|
||||
34
components_/Rating.js
Normal file
34
components_/Rating.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import { $, html } from "sigpro";
|
||||
|
||||
$.component(
|
||||
"c-rating",
|
||||
(props, { emit }) => {
|
||||
const count = () => parseInt(props.count() ?? 5);
|
||||
|
||||
const getVal = () => {
|
||||
const v = props.value();
|
||||
return v === false || v == null ? 0 : Number(v);
|
||||
};
|
||||
|
||||
return html`
|
||||
<div .class=${() => `rating ${props.mask() ?? ""}`}>
|
||||
${() =>
|
||||
Array.from({ length: count() }).map((_, i) => {
|
||||
const radioValue = i + 1;
|
||||
return html`
|
||||
<input
|
||||
type="radio"
|
||||
.name=${props.name}
|
||||
.class=${() => `mask ${props.mask() ?? "mask-star"}`}
|
||||
.checked=${() => getVal() === radioValue}
|
||||
@change=${() => {
|
||||
if (typeof props.value === "function") props.value(radioValue);
|
||||
emit("change", radioValue);
|
||||
}} />
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
["value", "count", "name", "mask"],
|
||||
);
|
||||
38
components_/Tab.js
Normal file
38
components_/Tab.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import { $, html } from "sigpro";
|
||||
|
||||
$.component(
|
||||
"c-tab",
|
||||
(props, { emit, slot }) => {
|
||||
const groupName = `tab-group-${Math.random().toString(36).substring(2, 9)}`;
|
||||
const items = () => props.items() || [];
|
||||
|
||||
return html`
|
||||
<div .class=${() => `tabs ${props.class() ?? "tabs-lifted"}`}>
|
||||
${() =>
|
||||
items().map(
|
||||
(item) => html`
|
||||
<input
|
||||
type="radio"
|
||||
name="${groupName}"
|
||||
class="tab"
|
||||
aria-label="${item.label}"
|
||||
.checked=${() => props.value() === item.value}
|
||||
@change=${() => {
|
||||
if (typeof props.value === "function") props.value(item.value);
|
||||
emit("change", item.value);
|
||||
}} />
|
||||
<div class="tab-content bg-base-100 border-base-300 p-6">
|
||||
${item.icon
|
||||
? html`
|
||||
<span class="${item.icon} mr-2"></span>
|
||||
`
|
||||
: ""}
|
||||
${slot(item.label)}
|
||||
</div>
|
||||
`,
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
["items", "value", "class"],
|
||||
);
|
||||
49
components_/Toast.js
Normal file
49
components_/Toast.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import { html } from "sigpro";
|
||||
|
||||
let container = null;
|
||||
|
||||
export const toast = (msg, type = "alert-success", ms = 3500) => {
|
||||
if (!container || !container.isConnected) {
|
||||
container = document.createElement("div");
|
||||
container.className = "fixed top-0 right-0 z-9999 p-6 flex flex-col gap-4 pointer-events-none items-end";
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
const close = (n) => {
|
||||
if (!n || n._c) return;
|
||||
n._c = 1;
|
||||
Object.assign(n.style, { transform: "translateX(100%)", opacity: 0 });
|
||||
|
||||
setTimeout(() => {
|
||||
Object.assign(n.style, { maxHeight: "0px", marginBottom: "-1rem", marginTop: "0px", padding: "0px" });
|
||||
}, 100);
|
||||
|
||||
n.addEventListener("transitionend", (e) => {
|
||||
if (["max-height", "opacity"].includes(e.propertyName)) {
|
||||
n.remove();
|
||||
if (!container.hasChildNodes()) (container.remove(), (container = null));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const el = html`
|
||||
<div
|
||||
class="card bg-base-100 shadow-xl border border-base-200 w-80 sm:w-96 overflow-hidden transition-all duration-500 ease-in-out transform translate-x-full opacity-0 pointer-events-auto"
|
||||
style="max-height:200px">
|
||||
<div class="card-body p-1">
|
||||
<div role="alert" class="${`alert ${type} alert-soft border-none p-2`}">
|
||||
<div class="flex items-center justify-between w-full gap-2">
|
||||
<span class="font-medium text-sm">${msg}</span>
|
||||
<button class="btn btn-ghost btn-xs btn-circle" @click="${(e) => close(e.target.closest(".card"))}">
|
||||
<span class="icon-[lucide--circle-x] w-5 h-5"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`.firstElementChild;
|
||||
|
||||
container.appendChild(el);
|
||||
requestAnimationFrame(() => requestAnimationFrame(() => el.classList.remove("translate-x-full", "opacity-0")));
|
||||
setTimeout(() => close(el), ms);
|
||||
};
|
||||
Reference in New Issue
Block a user