change components
All checks were successful
Deploy Docs to Synology / deploy (push) Successful in 3s

This commit is contained in:
2026-04-20 00:18:27 +02:00
parent 600c78510a
commit dfd358c950
12 changed files with 562 additions and 513 deletions

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

@@ -825,14 +825,15 @@ var exports_Datepicker = {};
__export(exports_Datepicker, { __export(exports_Datepicker, {
Datepicker: () => Datepicker Datepicker: () => Datepicker
}); });
var Datepicker = (props) => {
const { class: className, value, range, label, placeholder, hour = false, ...rest } = props; // src/components/Calendar.js
const isOpen = B(false); var Calendar = (props) => {
const { value, range = false, hour = false, onChange, class: className = "" } = props;
const internalDate = B(new Date); const internalDate = B(new Date);
const hoverDate = B(null); const hoverDate = B(null);
const startHour = B(0); const startHour = B(0);
const endHour = B(0); const endHour = B(0);
const isRangeMode = () => val2(range) === true; const isRangeMode = () => range === true;
const now = new Date; const now = new Date;
const todayStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`; const todayStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
const formatDate = (d) => { const formatDate = (d) => {
@@ -846,52 +847,31 @@ var Datepicker = (props) => {
const current = val2(value); const current = val2(value);
if (isRangeMode()) { if (isRangeMode()) {
if (!current?.start || current.start && current.end) { if (!current?.start || current.start && current.end) {
if (typeof value === "function") { const newValue = {
value({
start: dateStr, start: dateStr,
end: null, end: null,
...hour && { startHour: startHour() } ...hour && { startHour: startHour() }
}); };
} onChange?.(newValue);
} else { } else {
const start = current.start; const start = current.start;
if (typeof value === "function") { let newValue;
const newValue = dateStr < start ? { start: dateStr, end: start } : { start, end: dateStr }; if (dateStr < start) {
newValue = { start: dateStr, end: start };
} else {
newValue = { start, end: dateStr };
}
if (hour) { if (hour) {
newValue.startHour = current.startHour || startHour(); newValue.startHour = current.startHour !== undefined ? current.startHour : startHour();
newValue.endHour = current.endHour || endHour(); newValue.endHour = endHour();
} }
value(newValue); onChange?.(newValue);
}
isOpen(false);
} }
} else { } else {
if (typeof value === "function") { const newValue = hour ? `${dateStr}T${String(startHour()).padStart(2, "0")}:00:00` : dateStr;
value(hour ? `${dateStr}T${String(startHour()).padStart(2, "0")}:00:00` : dateStr); onChange?.(newValue);
}
isOpen(false);
} }
}; };
const displayValue = B("");
Watch(() => {
const v2 = val2(value);
if (!v2) {
displayValue("");
return;
}
let text = "";
if (typeof v2 === "string") {
text = hour && v2.includes("T") ? v2.replace("T", " ") : v2;
} else if (v2.start && v2.end) {
const startStr = hour && v2.startHour !== undefined ? `${v2.start} ${String(v2.startHour).padStart(2, "0")}:00` : v2.start;
const endStr = hour && v2.endHour !== undefined ? `${v2.end} ${String(v2.endHour).padStart(2, "0")}:00` : v2.end;
text = `${startStr} - ${endStr}`;
} else if (v2.start) {
const startStr = hour && v2.startHour !== undefined ? `${v2.start} ${String(v2.startHour).padStart(2, "0")}:00` : v2.start;
text = `${startStr}...`;
}
displayValue(text);
});
const move = (m2) => { const move = (m2) => {
const d = internalDate(); const d = internalDate();
internalDate(new Date(d.getFullYear(), d.getMonth() + m2, 1)); internalDate(new Date(d.getFullYear(), d.getMonth() + m2, 1));
@@ -900,7 +880,7 @@ var Datepicker = (props) => {
const d = internalDate(); const d = internalDate();
internalDate(new Date(d.getFullYear() + y, d.getMonth(), 1)); internalDate(new Date(d.getFullYear() + y, d.getMonth(), 1));
}; };
const HourSlider = ({ value: hVal, onChange }) => { const HourSlider = ({ value: hVal, onChange: onHourChange }) => {
return S("div", { class: "flex-1" }, [ return S("div", { class: "flex-1" }, [
S("div", { class: "flex gap-2 items-center" }, [ S("div", { class: "flex gap-2 items-center" }, [
S("input", { S("input", {
@@ -911,30 +891,14 @@ var Datepicker = (props) => {
class: "range range-xs flex-1", class: "range range-xs flex-1",
oninput: (e) => { oninput: (e) => {
const newHour = parseInt(e.target.value); const newHour = parseInt(e.target.value);
onChange(newHour); onHourChange(newHour);
} }
}), }),
S("span", { class: "text-sm font-mono min-w-[48px] text-center" }, () => String(val2(hVal)).padStart(2, "0") + ":00") S("span", { class: "text-sm font-mono min-w-[48px] text-center" }, () => String(val2(hVal)).padStart(2, "0") + ":00")
]) ])
]); ]);
}; };
return S("div", { class: ui("relative w-full", className) }, [ return S("div", { class: `p-4 bg-base-100 border border-base-300 shadow-2xl rounded-box w-80 select-none ${className}` }, [
Input({
label,
placeholder: placeholder || (isRangeMode() ? "Seleccionar rango..." : "Seleccionar fecha..."),
value: displayValue,
readonly: true,
icon: getIcon("icon-[lucide--calendar]"),
onclick: (e) => {
e.stopPropagation();
isOpen(!isOpen());
},
...rest
}),
J(isOpen, () => S("div", {
class: "absolute left-0 mt-2 p-4 bg-base-100 border border-base-300 shadow-2xl rounded-box z-[100] w-80 select-none",
onclick: (e) => e.stopPropagation()
}, [
S("div", { class: "flex justify-between items-center mb-4 gap-1" }, [ S("div", { class: "flex justify-between items-center mb-4 gap-1" }, [
S("div", { class: "flex gap-0.5" }, [ S("div", { class: "flex gap-0.5" }, [
S("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(-1) }, getIcon("icon-[lucide--chevrons-left]")), S("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(-1) }, getIcon("icon-[lucide--chevrons-left]")),
@@ -998,33 +962,76 @@ var Datepicker = (props) => {
isRangeMode() ? S("div", { class: "flex gap-4" }, [ isRangeMode() ? S("div", { class: "flex gap-4" }, [
HourSlider({ HourSlider({
value: startHour, value: startHour,
onChange: (newHour) => { onChange: (newHour) => startHour(newHour)
startHour(newHour);
const currentVal = val2(value);
if (currentVal?.start)
value({ ...currentVal, startHour: newHour });
}
}), }),
HourSlider({ HourSlider({
value: endHour, value: endHour,
onChange: (newHour) => { onChange: (newHour) => endHour(newHour)
endHour(newHour);
const currentVal = val2(value);
if (currentVal?.end)
value({ ...currentVal, endHour: newHour });
}
}) })
]) : HourSlider({ ]) : HourSlider({
value: startHour, value: startHour,
onChange: (newHour) => { onChange: (newHour) => startHour(newHour)
startHour(newHour);
const currentVal = val2(value);
if (currentVal && typeof currentVal === "string") {
value(currentVal.split("T")[0] + "T" + String(newHour).padStart(2, "0") + ":00:00");
}
}
}) })
]) : null ]) : null
]);
};
// src/components/Datepicker.js
var Datepicker = (props) => {
const { class: className, value, range, label, placeholder, hour = false, ...rest } = props;
const isOpen = B(false);
const isRangeMode = () => val2(range) === true;
const displayValue = B("");
R(() => {
const v2 = val2(value);
if (!v2) {
displayValue("");
return;
}
let text = "";
if (typeof v2 === "string") {
text = hour && v2.includes("T") ? v2.replace("T", " ") : v2;
} else if (v2.start && v2.end) {
const startStr = hour && v2.startHour !== undefined ? `${v2.start} ${String(v2.startHour).padStart(2, "0")}:00` : v2.start;
const endStr = hour && v2.endHour !== undefined ? `${v2.end} ${String(v2.endHour).padStart(2, "0")}:00` : v2.end;
text = `${startStr} - ${endStr}`;
} else if (v2.start) {
const startStr = hour && v2.startHour !== undefined ? `${v2.start} ${String(v2.startHour).padStart(2, "0")}:00` : v2.start;
text = `${startStr}...`;
}
displayValue(text);
});
const handleCalendarChange = (newValue) => {
if (typeof value === "function") {
value(newValue);
}
if (!isRangeMode() || newValue?.end !== undefined && newValue?.end !== null) {
isOpen(false);
}
};
return S("div", { class: ui("relative w-full", className) }, [
Input({
label,
placeholder: placeholder || (isRangeMode() ? "Seleccionar rango..." : "Seleccionar fecha..."),
value: displayValue,
readonly: true,
icon: getIcon("icon-[lucide--calendar]"),
onclick: (e) => {
e.stopPropagation();
isOpen(!isOpen());
},
...rest
}),
J(isOpen, () => S("div", {
class: "absolute left-0 mt-2 z-[100]",
onclick: (e) => e.stopPropagation()
}, [
Calendar({
value,
range: isRangeMode(),
hour,
onChange: handleCalendarChange
})
])), ])),
J(isOpen, () => S("div", { class: "fixed inset-0 z-[90]", onclick: () => isOpen(false) })) J(isOpen, () => S("div", { class: "fixed inset-0 z-[90]", onclick: () => isOpen(false) }))
]); ]);

File diff suppressed because one or more lines are too long

161
dist/sigpro-ui.js vendored
View File

@@ -856,14 +856,15 @@
__export(exports_Datepicker, { __export(exports_Datepicker, {
Datepicker: () => Datepicker Datepicker: () => Datepicker
}); });
var Datepicker = (props) => {
const { class: className, value, range, label, placeholder, hour = false, ...rest } = props; // src/components/Calendar.js
const isOpen = B(false); var Calendar = (props) => {
const { value, range = false, hour = false, onChange, class: className = "" } = props;
const internalDate = B(new Date); const internalDate = B(new Date);
const hoverDate = B(null); const hoverDate = B(null);
const startHour = B(0); const startHour = B(0);
const endHour = B(0); const endHour = B(0);
const isRangeMode = () => val2(range) === true; const isRangeMode = () => range === true;
const now = new Date; const now = new Date;
const todayStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`; const todayStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
const formatDate = (d) => { const formatDate = (d) => {
@@ -877,52 +878,31 @@
const current = val2(value); const current = val2(value);
if (isRangeMode()) { if (isRangeMode()) {
if (!current?.start || current.start && current.end) { if (!current?.start || current.start && current.end) {
if (typeof value === "function") { const newValue = {
value({
start: dateStr, start: dateStr,
end: null, end: null,
...hour && { startHour: startHour() } ...hour && { startHour: startHour() }
}); };
} onChange?.(newValue);
} else { } else {
const start = current.start; const start = current.start;
if (typeof value === "function") { let newValue;
const newValue = dateStr < start ? { start: dateStr, end: start } : { start, end: dateStr }; if (dateStr < start) {
newValue = { start: dateStr, end: start };
} else {
newValue = { start, end: dateStr };
}
if (hour) { if (hour) {
newValue.startHour = current.startHour || startHour(); newValue.startHour = current.startHour !== undefined ? current.startHour : startHour();
newValue.endHour = current.endHour || endHour(); newValue.endHour = endHour();
} }
value(newValue); onChange?.(newValue);
}
isOpen(false);
} }
} else { } else {
if (typeof value === "function") { const newValue = hour ? `${dateStr}T${String(startHour()).padStart(2, "0")}:00:00` : dateStr;
value(hour ? `${dateStr}T${String(startHour()).padStart(2, "0")}:00:00` : dateStr); onChange?.(newValue);
}
isOpen(false);
} }
}; };
const displayValue = B("");
Watch(() => {
const v2 = val2(value);
if (!v2) {
displayValue("");
return;
}
let text = "";
if (typeof v2 === "string") {
text = hour && v2.includes("T") ? v2.replace("T", " ") : v2;
} else if (v2.start && v2.end) {
const startStr = hour && v2.startHour !== undefined ? `${v2.start} ${String(v2.startHour).padStart(2, "0")}:00` : v2.start;
const endStr = hour && v2.endHour !== undefined ? `${v2.end} ${String(v2.endHour).padStart(2, "0")}:00` : v2.end;
text = `${startStr} - ${endStr}`;
} else if (v2.start) {
const startStr = hour && v2.startHour !== undefined ? `${v2.start} ${String(v2.startHour).padStart(2, "0")}:00` : v2.start;
text = `${startStr}...`;
}
displayValue(text);
});
const move = (m2) => { const move = (m2) => {
const d = internalDate(); const d = internalDate();
internalDate(new Date(d.getFullYear(), d.getMonth() + m2, 1)); internalDate(new Date(d.getFullYear(), d.getMonth() + m2, 1));
@@ -931,7 +911,7 @@
const d = internalDate(); const d = internalDate();
internalDate(new Date(d.getFullYear() + y, d.getMonth(), 1)); internalDate(new Date(d.getFullYear() + y, d.getMonth(), 1));
}; };
const HourSlider = ({ value: hVal, onChange }) => { const HourSlider = ({ value: hVal, onChange: onHourChange }) => {
return S("div", { class: "flex-1" }, [ return S("div", { class: "flex-1" }, [
S("div", { class: "flex gap-2 items-center" }, [ S("div", { class: "flex gap-2 items-center" }, [
S("input", { S("input", {
@@ -942,30 +922,14 @@
class: "range range-xs flex-1", class: "range range-xs flex-1",
oninput: (e) => { oninput: (e) => {
const newHour = parseInt(e.target.value); const newHour = parseInt(e.target.value);
onChange(newHour); onHourChange(newHour);
} }
}), }),
S("span", { class: "text-sm font-mono min-w-[48px] text-center" }, () => String(val2(hVal)).padStart(2, "0") + ":00") S("span", { class: "text-sm font-mono min-w-[48px] text-center" }, () => String(val2(hVal)).padStart(2, "0") + ":00")
]) ])
]); ]);
}; };
return S("div", { class: ui("relative w-full", className) }, [ return S("div", { class: `p-4 bg-base-100 border border-base-300 shadow-2xl rounded-box w-80 select-none ${className}` }, [
Input({
label,
placeholder: placeholder || (isRangeMode() ? "Seleccionar rango..." : "Seleccionar fecha..."),
value: displayValue,
readonly: true,
icon: getIcon("icon-[lucide--calendar]"),
onclick: (e) => {
e.stopPropagation();
isOpen(!isOpen());
},
...rest
}),
J(isOpen, () => S("div", {
class: "absolute left-0 mt-2 p-4 bg-base-100 border border-base-300 shadow-2xl rounded-box z-[100] w-80 select-none",
onclick: (e) => e.stopPropagation()
}, [
S("div", { class: "flex justify-between items-center mb-4 gap-1" }, [ S("div", { class: "flex justify-between items-center mb-4 gap-1" }, [
S("div", { class: "flex gap-0.5" }, [ S("div", { class: "flex gap-0.5" }, [
S("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(-1) }, getIcon("icon-[lucide--chevrons-left]")), S("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(-1) }, getIcon("icon-[lucide--chevrons-left]")),
@@ -1029,33 +993,76 @@
isRangeMode() ? S("div", { class: "flex gap-4" }, [ isRangeMode() ? S("div", { class: "flex gap-4" }, [
HourSlider({ HourSlider({
value: startHour, value: startHour,
onChange: (newHour) => { onChange: (newHour) => startHour(newHour)
startHour(newHour);
const currentVal = val2(value);
if (currentVal?.start)
value({ ...currentVal, startHour: newHour });
}
}), }),
HourSlider({ HourSlider({
value: endHour, value: endHour,
onChange: (newHour) => { onChange: (newHour) => endHour(newHour)
endHour(newHour);
const currentVal = val2(value);
if (currentVal?.end)
value({ ...currentVal, endHour: newHour });
}
}) })
]) : HourSlider({ ]) : HourSlider({
value: startHour, value: startHour,
onChange: (newHour) => { onChange: (newHour) => startHour(newHour)
startHour(newHour);
const currentVal = val2(value);
if (currentVal && typeof currentVal === "string") {
value(currentVal.split("T")[0] + "T" + String(newHour).padStart(2, "0") + ":00:00");
}
}
}) })
]) : null ]) : null
]);
};
// src/components/Datepicker.js
var Datepicker = (props) => {
const { class: className, value, range, label, placeholder, hour = false, ...rest } = props;
const isOpen = B(false);
const isRangeMode = () => val2(range) === true;
const displayValue = B("");
R(() => {
const v2 = val2(value);
if (!v2) {
displayValue("");
return;
}
let text = "";
if (typeof v2 === "string") {
text = hour && v2.includes("T") ? v2.replace("T", " ") : v2;
} else if (v2.start && v2.end) {
const startStr = hour && v2.startHour !== undefined ? `${v2.start} ${String(v2.startHour).padStart(2, "0")}:00` : v2.start;
const endStr = hour && v2.endHour !== undefined ? `${v2.end} ${String(v2.endHour).padStart(2, "0")}:00` : v2.end;
text = `${startStr} - ${endStr}`;
} else if (v2.start) {
const startStr = hour && v2.startHour !== undefined ? `${v2.start} ${String(v2.startHour).padStart(2, "0")}:00` : v2.start;
text = `${startStr}...`;
}
displayValue(text);
});
const handleCalendarChange = (newValue) => {
if (typeof value === "function") {
value(newValue);
}
if (!isRangeMode() || newValue?.end !== undefined && newValue?.end !== null) {
isOpen(false);
}
};
return S("div", { class: ui("relative w-full", className) }, [
Input({
label,
placeholder: placeholder || (isRangeMode() ? "Seleccionar rango..." : "Seleccionar fecha..."),
value: displayValue,
readonly: true,
icon: getIcon("icon-[lucide--calendar]"),
onclick: (e) => {
e.stopPropagation();
isOpen(!isOpen());
},
...rest
}),
J(isOpen, () => S("div", {
class: "absolute left-0 mt-2 z-[100]",
onclick: (e) => e.stopPropagation()
}, [
Calendar({
value,
range: isRangeMode(),
hour,
onChange: handleCalendarChange
})
])), ])),
J(isOpen, () => S("div", { class: "fixed inset-0 z-[90]", onclick: () => isOpen(false) })) J(isOpen, () => S("div", { class: "fixed inset-0 z-[90]", onclick: () => isOpen(false) }))
]); ]);

File diff suppressed because one or more lines are too long

3
dist/sigpro.css vendored
View File

@@ -3339,9 +3339,6 @@
.z-50 { .z-50 {
z-index: 50; z-index: 50;
} }
.z-90 {
z-index: 90;
}
.z-\[1\] { .z-\[1\] {
z-index: 1; z-index: 1;
} }

2
dist/sigpro.min.css vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

181
src/components/Calendar.js Normal file
View File

@@ -0,0 +1,181 @@
import { $, Tag, Watch } from "sigpro";
import { val, getIcon } from "../utils.js";
export const Calendar = (props) => {
const { value, range = false, hour = false, onChange, class: className = "" } = props;
const internalDate = $(new Date());
const hoverDate = $(null);
const startHour = $(0);
const endHour = $(0);
const isRangeMode = () => range === true;
const now = new Date();
const todayStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
const formatDate = (d) => {
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, "0");
const day = String(d.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
};
const selectDate = (date) => {
const dateStr = formatDate(date);
const current = val(value);
if (isRangeMode()) {
if (!current?.start || (current.start && current.end)) {
// primera selección
const newValue = {
start: dateStr,
end: null,
...(hour && { startHour: startHour() }),
};
onChange?.(newValue);
} else {
// segunda selección
const start = current.start;
let newValue;
if (dateStr < start) {
newValue = { start: dateStr, end: start };
} else {
newValue = { start, end: dateStr };
}
if (hour) {
newValue.startHour = current.startHour !== undefined ? current.startHour : startHour();
newValue.endHour = endHour();
}
onChange?.(newValue);
}
} else {
// modo fecha simple
const newValue = hour ? `${dateStr}T${String(startHour()).padStart(2, "0")}:00:00` : dateStr;
onChange?.(newValue);
}
};
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));
};
const HourSlider = ({ value: hVal, onChange: onHourChange }) => {
return Tag("div", { class: "flex-1" }, [
Tag("div", { class: "flex gap-2 items-center" }, [
Tag("input", {
type: "range",
min: 0,
max: 23,
value: hVal,
class: "range range-xs flex-1",
oninput: (e) => {
const newHour = parseInt(e.target.value);
onHourChange(newHour);
},
}),
Tag("span", { class: "text-sm font-mono min-w-[48px] text-center" },
() => String(val(hVal)).padStart(2, "0") + ":00"
),
]),
]);
};
return Tag("div", { class: `p-4 bg-base-100 border border-base-300 shadow-2xl rounded-box w-80 select-none ${className}` }, [
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("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("div", { class: "grid grid-cols-7 gap-1", onmouseleave: () => hoverDate(null) }, [
...["L", "M", "X", "J", "V", "S", "D"].map((d) => Tag("div", { class: "text-[10px] opacity-40 font-bold text-center" }, d)),
() => {
const d = internalDate();
const year = d.getFullYear();
const month = d.getMonth();
const firstDay = new Date(year, month, 1).getDay();
const offset = firstDay === 0 ? 6 : firstDay - 1;
const daysInMonth = new Date(year, month + 1, 0).getDate();
const nodes = [];
for (let i = 0; i < offset; i++) nodes.push(Tag("div"));
for (let i = 1; i <= daysInMonth; i++) {
const date = new Date(year, month, i);
const dStr = formatDate(date);
nodes.push(
Tag("button", {
type: "button",
class: () => {
const v = val(value);
const h = hoverDate();
const isStart = typeof v === "string" ? v.split("T")[0] === dStr : v?.start === dStr;
const isEnd = v?.end === dStr;
let inRange = false;
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;
}
}
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); },
onclick: () => selectDate(date),
}, [i.toString()])
);
}
return nodes;
},
]),
hour ? Tag("div", { class: "mt-3 pt-2 border-t border-base-300" }, [
isRangeMode()
? Tag("div", { class: "flex gap-4" }, [
HourSlider({
value: startHour,
onChange: (newHour) => startHour(newHour),
}),
HourSlider({
value: endHour,
onChange: (newHour) => endHour(newHour),
}),
])
: HourSlider({
value: startHour,
onChange: (newHour) => startHour(newHour),
}),
]) : null,
]);
};

View File

@@ -1,74 +1,15 @@
// components/Datepicker.js import { $, Tag, If, Watch } from "sigpro";
import { $, Tag, If } from "sigpro";
import { val, ui, getIcon } from "../utils.js"; import { val, ui, getIcon } from "../utils.js";
import { Input } from "./Input.js"; import { Input } from "./Input.js";
import { Calendar } from "./Calendar.js";
/**
* Datepicker component
*
* daisyUI classes used:
* - input, input-bordered, input-primary
* - btn, btn-ghost, btn-xs, btn-circle
* - bg-base-100, border, border-base-300, shadow-2xl, rounded-box
* - absolute, left-0, mt-2, p-4, w-80, z-100, z-90
* - grid, grid-cols-7, gap-1, text-center
* - ring, ring-primary, ring-inset, font-black
* - range, range-xs
* - tooltip, tooltip-top, tooltip-bottom
*/
export const Datepicker = (props) => { export const Datepicker = (props) => {
const { class: className, value, range, label, placeholder, hour = false, ...rest } = props; const { class: className, value, range, label, placeholder, hour = false, ...rest } = props;
const isOpen = $(false); const isOpen = $(false);
const internalDate = $(new Date());
const hoverDate = $(null);
const startHour = $(0);
const endHour = $(0);
const isRangeMode = () => val(range) === true; const isRangeMode = () => val(range) === true;
const now = new Date(); // Formatear el valor para mostrarlo en el input
const todayStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
const formatDate = (d) => {
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, "0");
const day = String(d.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
};
const selectDate = (date) => {
const dateStr = formatDate(date);
const current = val(value);
if (isRangeMode()) {
if (!current?.start || (current.start && current.end)) {
if (typeof value === "function") {
value({
start: dateStr,
end: null,
...(hour && { startHour: startHour() }),
});
}
} else {
const start = current.start;
if (typeof value === "function") {
const newValue = dateStr < start ? { start: dateStr, end: start } : { start, end: dateStr };
if (hour) {
newValue.startHour = current.startHour || startHour();
newValue.endHour = current.endHour || endHour();
}
value(newValue);
}
isOpen(false);
}
} else {
if (typeof value === "function") {
value(hour ? `${dateStr}T${String(startHour()).padStart(2, "0")}:00:00` : dateStr);
}
isOpen(false);
}
};
const displayValue = $(""); const displayValue = $("");
Watch(() => { Watch(() => {
@@ -77,7 +18,6 @@ export const Datepicker = (props) => {
displayValue(""); displayValue("");
return; return;
} }
let text = ""; let text = "";
if (typeof v === "string") { if (typeof v === "string") {
text = (hour && v.includes("T")) ? v.replace("T", " ") : v; text = (hour && v.includes("T")) ? v.replace("T", " ") : v;
@@ -92,35 +32,14 @@ export const Datepicker = (props) => {
displayValue(text); displayValue(text);
}); });
const move = (m) => { const handleCalendarChange = (newValue) => {
const d = internalDate(); if (typeof value === "function") {
internalDate(new Date(d.getFullYear(), d.getMonth() + m, 1)); value(newValue);
}; }
// Cerrar el popover si es modo simple o si el rango está completo (end existe)
const moveYear = (y) => { if (!isRangeMode() || (newValue?.end !== undefined && newValue?.end !== null)) {
const d = internalDate(); isOpen(false);
internalDate(new Date(d.getFullYear() + y, d.getMonth(), 1)); }
};
const HourSlider = ({ value: hVal, onChange }) => {
return Tag("div", { class: "flex-1" }, [
Tag("div", { class: "flex gap-2 items-center" }, [
Tag("input", {
type: "range",
min: 0,
max: 23,
value: hVal,
class: "range range-xs flex-1",
oninput: (e) => {
const newHour = parseInt(e.target.value);
onChange(newHour);
},
}),
Tag("span", { class: "text-sm font-mono min-w-[48px] text-center" },
() => String(val(hVal)).padStart(2, "0") + ":00"
),
]),
]);
}; };
return Tag("div", { class: ui('relative w-full', className) }, [ return Tag("div", { class: ui('relative w-full', className) }, [
@@ -138,123 +57,17 @@ export const Datepicker = (props) => {
}), }),
If(isOpen, () => If(isOpen, () =>
Tag( Tag("div", {
"div", class: "absolute left-0 mt-2 z-[100]",
{
class: "absolute left-0 mt-2 p-4 bg-base-100 border border-base-300 shadow-2xl rounded-box z-[100] w-80 select-none",
onclick: (e) => e.stopPropagation(), onclick: (e) => e.stopPropagation(),
}, }, [
[ Calendar({
Tag("div", { class: "flex justify-between items-center mb-4 gap-1" }, [ value,
Tag("div", { class: "flex gap-0.5" }, [ range: isRangeMode(),
Tag("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(-1) }, hour,
getIcon("icon-[lucide--chevrons-left]") onChange: handleCalendarChange,
),
Tag("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(-1) },
getIcon("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("div", { class: "grid grid-cols-7 gap-1", onmouseleave: () => hoverDate(null) }, [
...["L", "M", "X", "J", "V", "S", "D"].map((d) => Tag("div", { class: "text-[10px] opacity-40 font-bold text-center" }, d)),
() => {
const d = internalDate();
const year = d.getFullYear();
const month = d.getMonth();
const firstDay = new Date(year, month, 1).getDay();
const offset = firstDay === 0 ? 6 : firstDay - 1;
const daysInMonth = new Date(year, month + 1, 0).getDate();
const nodes = [];
for (let i = 0; i < offset; i++) nodes.push(Tag("div"));
for (let i = 1; i <= daysInMonth; i++) {
const date = new Date(year, month, i);
const dStr = formatDate(date);
nodes.push(
Tag(
"button",
{
type: "button",
class: () => {
const v = val(value);
const h = hoverDate();
const isStart = typeof v === "string" ? v.split("T")[0] === dStr : v?.start === dStr;
const isEnd = v?.end === dStr;
let inRange = false;
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;
}
}
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); },
onclick: () => selectDate(date),
},
[i.toString()],
),
);
}
return nodes;
},
]),
hour ? Tag("div", { class: "mt-3 pt-2 border-t border-base-300" }, [
isRangeMode()
? Tag("div", { class: "flex gap-4" }, [
HourSlider({
value: startHour,
onChange: (newHour) => {
startHour(newHour);
const currentVal = val(value);
if (currentVal?.start) value({ ...currentVal, startHour: newHour });
},
}),
HourSlider({
value: endHour,
onChange: (newHour) => {
endHour(newHour);
const currentVal = val(value);
if (currentVal?.end) value({ ...currentVal, endHour: newHour });
},
}), }),
]) ])
: HourSlider({
value: startHour,
onChange: (newHour) => {
startHour(newHour);
const currentVal = val(value);
if (currentVal && typeof currentVal === "string") {
value(currentVal.split("T")[0] + "T" + String(newHour).padStart(2, "0") + ":00:00");
}
},
}),
]) : null,
],
),
), ),
If(isOpen, () => Tag("div", { class: "fixed inset-0 z-[90]", onclick: () => isOpen(false) })), If(isOpen, () => Tag("div", { class: "fixed inset-0 z-[90]", onclick: () => isOpen(false) })),

View File

@@ -0,0 +1,44 @@
// components/InputPopover.js
export const InputPopover = (props) => {
const {
signal,
format = (v) => String(v ?? ""),
content,
placeholder,
label,
icon = "icon-[lucide--calendar]",
readonly = false,
class: className = "",
...rest
} = props;
const isOpen = $(false);
const displayValue = $(format(val(signal)));
Watch(signal, () => {
displayValue(format(val(signal)));
});
const close = () => isOpen(false);
const toggle = () => isOpen(!isOpen());
return Tag("div", { class: ui("relative w-full", className) }, [
Input({
label,
placeholder,
value: displayValue,
readonly,
icon: getIcon(icon),
onclick: (e) => { e.stopPropagation(); toggle(); },
onfocus: () => isOpen(true),
...rest,
}),
If(isOpen, () => Tag("div", {
class: "absolute left-0 mt-2 z-[100]",
onclick: (e) => e.stopPropagation(),
}, [
typeof content === "function" ? content({ signal, close }) : content
])),
If(isOpen, () => Tag("div", { class: "fixed inset-0 z-[90]", onclick: close }))
]);
};