Rebuild all components
All checks were successful
Deploy Docs to Synology / deploy (push) Successful in 4s

This commit is contained in:
2026-04-21 18:00:17 +02:00
parent d900659d88
commit 16afea2768
67 changed files with 1820 additions and 2132 deletions

View File

@@ -1,22 +1,15 @@
// components/Collapse.js
// components/Accordion.js
import { Tag } from "sigpro";
import { Collapse } from "./Collapse.js";
export const Collapse = (props, children) => {
const { class: className, title, name, open, ...rest } = props;
return Tag("div", {
...rest,
class: `collapse collapse-arrow bg-base-200 ${className || ''}`.trim()
}, [
Tag("input", {
type: name ? "radio" : "checkbox",
name: name,
checked: () => typeof open === "function" ? open() : open,
onchange: (e) => {
if (typeof open === "function") open(e.target.checked);
}
}),
Tag("div", { class: "collapse-title text-xl font-medium" }, title),
Tag("div", { class: "collapse-content" }, children)
]);
export const Accordion = (props) => {
const name = props.name || `accordion-${Math.random().toString(36).slice(2, 9)}`;
return Tag("div", { class: `space-y-2 ${props.class ?? ''}` },
props.items.map(item => Collapse({
...item,
name,
type: "radio",
class: item.class
}, item.children))
);
};

View File

@@ -2,11 +2,6 @@
import { Tag } from "sigpro";
export const Alert = (props, children) => {
const { class: className, ...rest } = props;
return Tag("div", {
...rest,
role: "alert",
class: className || undefined
}, children);
children === undefined && (children = props, props = {});
return Tag("div", { ...props, class: `alert ${props.class ?? ''}` }, children);
};

View File

@@ -2,20 +2,19 @@
import { $, Tag, For, Watch } from "sigpro";
export const Autocomplete = (props) => {
const { class: className, items = [], value, onselect, placeholder, ...rest } = props;
const query = $(() => {
const v = typeof value === "function" ? value() : value;
return v || "";
});
const query = $("");
const isOpen = $(false);
const cursor = $(-1);
const filteredItems = $([]);
Watch(() => {
const v = typeof props.value === "function" ? props.value() : props.value;
return v || "";
}, (newVal) => query(newVal));
Watch(() => {
const q = String(query()).toLowerCase();
const allItems = typeof items === "function" ? items() : items;
const allItems = typeof props.items === "function" ? props.items() : props.items;
const filtered = q
? allItems.filter((item) =>
(typeof item === "string" ? item : item.label).toLowerCase().includes(q)
@@ -28,8 +27,8 @@ export const Autocomplete = (props) => {
const display = typeof item === "string" ? item : item.label;
const actual = typeof item === "string" ? item : item.value;
query(display);
if (typeof value === "function") value(actual);
onselect?.(item);
if (typeof props.value === "function") props.value(actual);
props.onselect?.(item);
isOpen(false);
cursor(-1);
};
@@ -51,22 +50,22 @@ export const Autocomplete = (props) => {
}
};
return Tag("div", { class: `relative w-full ${className || ''}`.trim() }, [
return Tag("div", { class: `relative w-full ${props.class ?? ''}` }, [
Tag("label", { class: "input input-bordered w-full" }, [
Tag("span", { class: "icon-[lucide--search]" }),
Tag("input", {
...rest,
...props,
type: "text",
class: "grow",
value: query,
placeholder: placeholder || "Buscar...",
placeholder: props.placeholder || "Buscar...",
onfocus: () => isOpen(true),
onblur: () => setTimeout(() => isOpen(false), 150),
onkeydown: handleKeyDown,
oninput: (e) => {
const newVal = e.target.value;
query(newVal);
if (typeof value === "function") value(newVal);
if (typeof props.value === "function") props.value(newVal);
isOpen(true);
cursor(-1);
}
@@ -87,7 +86,7 @@ export const Autocomplete = (props) => {
]),
(item, idx) => (typeof item === "string" ? item : item.value) + idx
),
() => filteredItems().length === 0 && Tag("li", { class: "p-2 text-center opacity-50" }, "Sin resultados")
() => filteredItems().length === 0 ? Tag("li", { class: "flex justify-center p-4 opacity-50" }, Tag("span", { class: "icon-[lucide--search-x] text-2xl" })) : null
])
]);
};

View File

@@ -2,10 +2,6 @@
import { Tag } from "sigpro";
export const Badge = (props, children) => {
const { class: className, ...rest } = props;
return Tag("span", {
...rest,
class: className || undefined
}, children);
children === undefined && (children = props, props = {});
return Tag("span", { ...props, class: `badge ${props.class ?? ''}` }, children);
};

View File

@@ -1,9 +1,6 @@
import { Tag } from "sigpro";
export const Button = (props, children) => {
const { class: className, ...rest } = props;
return Tag("button", {
...rest,
class: `btn ${className || ''}`.trim()
}, children);
children === undefined && (children = props, props = {});
return Tag("button", { ...props, class: `btn ${props.class ?? ''}` }, children);
};

View File

@@ -1,15 +1,14 @@
// components/Calendar.js
import { $, Tag, Watch } from "sigpro";
import { $, Tag } from "sigpro";
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 = () => {
const r = typeof range === "function" ? range() : range;
const r = typeof props.range === "function" ? props.range() : props.range;
return r === true;
};
@@ -24,8 +23,7 @@ export const Calendar = (props) => {
};
const getCurrentValue = () => {
const v = value;
return typeof v === "function" ? v() : v;
return typeof props.value === "function" ? props.value() : props.value;
};
const selectDate = (date) => {
@@ -37,9 +35,9 @@ export const Calendar = (props) => {
const newValue = {
start: dateStr,
end: null,
...(hour && { startHour: startHour() }),
...(props.hour && { startHour: startHour() }),
};
onChange?.(newValue);
props.onChange?.(newValue);
} else {
const start = current.start;
let newValue;
@@ -48,15 +46,15 @@ export const Calendar = (props) => {
} else {
newValue = { start, end: dateStr };
}
if (hour) {
if (props.hour) {
newValue.startHour = current.startHour !== undefined ? current.startHour : startHour();
newValue.endHour = endHour();
}
onChange?.(newValue);
props.onChange?.(newValue);
}
} else {
const newValue = hour ? `${dateStr}T${String(startHour()).padStart(2, "0")}:00:00` : dateStr;
onChange?.(newValue);
const newValue = props.hour ? `${dateStr}T${String(startHour()).padStart(2, "0")}:00:00` : dateStr;
props.onChange?.(newValue);
}
};
@@ -88,7 +86,7 @@ export const Calendar = (props) => {
]);
};
return Tag("div", { class: `p-4 bg-base-100 border border-base-300 shadow-2xl rounded-box w-80 select-none ${className}`.trim() }, [
return Tag("div", { class: `p-4 bg-base-100 border border-base-300 shadow-2xl rounded-box w-80 select-none ${props.class ?? ''}`.trim() }, [
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) },
@@ -162,7 +160,7 @@ export const Calendar = (props) => {
}
]),
hour ? Tag("div", { class: "mt-3 pt-2 border-t border-base-300" }, [
props.hour ? Tag("div", { class: "mt-3 pt-2 border-t border-base-300" }, [
isRangeMode()
? Tag("div", { class: "flex gap-4" }, [
HourSlider({ value: startHour, onChange: (h) => startHour(h) }),

View File

@@ -2,33 +2,21 @@
import { Tag } from "sigpro";
export const Card = (props, children) => {
const { class: className, ...rest } = props;
return Tag("div", {
...rest,
class: `card ${className || ''}`.trim()
}, children);
children === undefined && (children = props, props = {});
return Tag("div", { ...props, class: `card ${props.class ?? ''}` }, children);
};
export const CardTitle = (props, children) => {
const { class: className, ...rest } = props;
return Tag("div", {
...rest,
class: `card-title ${className || ''}`.trim()
}, children);
children === undefined && (children = props, props = {});
return Tag("div", { ...props, class: `card-title ${props.class ?? ''}` }, children);
};
export const CardBody = (props, children) => {
const { class: className, ...rest } = props;
return Tag("div", {
...rest,
class: `card-body ${className || ''}`.trim()
}, children);
children === undefined && (children = props, props = {});
return Tag("div", { ...props, class: `card-body ${props.class ?? ''}` }, children);
};
export const CardActions = (props, children) => {
const { class: className, ...rest } = props;
return Tag("div", {
...rest,
class: `card-actions ${className || ''}`.trim()
}, children);
children === undefined && (children = props, props = {});
return Tag("div", { ...props, class: `card-actions ${props.class ?? ''}` }, children);
};

View File

@@ -2,17 +2,11 @@
import { Tag } from "sigpro";
export const Carousel = (props, children) => {
const { class: className, ...rest } = props;
return Tag("div", {
...rest,
class: `carousel ${className || ''}`.trim()
}, children);
children === undefined && (children = props, props = {});
return Tag("div", { ...props, class: `carousel ${props.class ?? ''}` }, children);
};
export const CarouselItem = (props, children) => {
const { class: className, ...rest } = props;
return Tag("div", {
...rest,
class: `carousel-item ${className || ''}`.trim()
}, children);
children === undefined && (children = props, props = {});
return Tag("div", { ...props, class: `carousel-item ${props.class ?? ''}` }, children);
};

View File

@@ -2,41 +2,41 @@
import { Tag } from "sigpro";
export const Chat = (props, children) => {
const { class: className, ...rest } = props;
return Tag("div", {
...rest,
class: `chat ${className || ''}`.trim()
}, children);
children === undefined && (children = props, props = {});
return Tag("div", { ...props, class: `chat ${props.class ?? ''}` }, children);
};
export const ChatImage = (props, children) => {
const { class: className, ...rest } = props;
return Tag("div", {
...rest,
class: `chat-image ${className || ''}`.trim()
}, children);
children === undefined && (children = props, props = {});
return Tag("div", { ...props, class: `chat-image avatar ${props.class ?? ''}` },
Tag("div", { class: "w-10 rounded-full" },
typeof children === "string" ? Tag("img", { src: children, alt: "avatar" }) : children
)
);
};
export const ChatHeader = (props, children) => {
const { class: className, ...rest } = props;
return Tag("div", {
...rest,
class: `chat-header ${className || ''}`.trim()
}, children);
children === undefined && (children = props, props = {});
return Tag("div", { ...props, class: `chat-header ${props.class ?? ''}` }, children);
};
export const ChatFooter = (props, children) => {
const { class: className, ...rest } = props;
return Tag("div", {
...rest,
class: `chat-footer ${className || ''}`.trim()
}, children);
children === undefined && (children = props, props = {});
return Tag("div", { ...props, class: `chat-footer ${props.class ?? ''}` }, children);
};
export const ChatBubble = (props, children) => {
const { class: className, ...rest } = props;
return Tag("div", {
...rest,
class: `chat-bubble ${className || ''}`.trim()
}, children);
children === undefined && (children = props, props = {});
return Tag("div", { ...props, class: `chat-bubble ${props.class ?? ''}` }, children);
};
export const ChatMessage = (props) => {
const { position = "start", avatar, header, message, footer, bubbleClass, ...rest } = props;
return Chat({ ...rest, class: `chat-${position} ${props.class ?? ''}` }, [
avatar && ChatImage(avatar),
header && ChatHeader(header),
ChatBubble({ class: bubbleClass }, message),
footer && ChatFooter(footer)
]);
};

View File

@@ -1,19 +1,4 @@
// components/Checkbox.js
import { Tag } from "sigpro";
export const Checkbox = (props) => {
const { class: className, label, ...rest } = props;
const inputEl = Tag("input", {
...rest,
type: "checkbox",
class: className || undefined
});
if (!label) return inputEl;
return Tag("label", { class: "label cursor-pointer justify-start gap-3" }, [
inputEl,
Tag("span", { class: "label-text" }, label)
]);
};
export const Checkbox = (props) => Tag("input", { ...props, type: "checkbox", class: `checkbox ${props.class ?? ''}` });

View File

@@ -2,20 +2,6 @@
import { Tag } from "sigpro";
export const Collapse = (props, children) => {
const { class: className, open, ...rest } = props;
return Tag("div", {
...rest,
class: `collapse ${className || ''}`.trim(),
tabindex: 0
}, [
Tag("input", {
type: "checkbox",
checked: () => typeof open === "function" ? open() : open,
onchange: (e) => {
if (typeof open === "function") open(e.target.checked);
}
}),
...(Array.isArray(children) ? children : [children])
]);
children === undefined && (children = props, props = {});
return Tag("div", { ...props, class: `collapse ${props.class ?? ''}` }, children);
};

View File

@@ -2,7 +2,6 @@
import { $, Tag, If } from "sigpro";
export const Colorpicker = (props) => {
const { class: className, value, label, ...rest } = props;
const isOpen = $(false);
const palette = [
@@ -17,67 +16,53 @@ export const Colorpicker = (props) => {
];
const getColor = () => {
const v = value;
const v = props.value;
return (typeof v === "function" ? v() : v) || "#000000";
};
return Tag("div", { class: `relative w-fit ${className || ''}`.trim() }, [
Tag(
"button",
{
type: "button",
class: "btn px-3 bg-base-100 border-base-300 hover:border-primary/50 flex items-center gap-2 shadow-sm font-normal normal-case",
onclick: (e) => {
e.stopPropagation();
isOpen(!isOpen());
},
...rest,
},
[
Tag("div", {
class: "size-5 rounded-sm shadow-inner border border-black/10 shrink-0",
style: () => `background-color: ${getColor()}`,
}),
label ? Tag("span", { class: "opacity-80" }, label) : null,
],
),
return Tag("div", { class: `relative w-fit ${props.class ?? ''}` }, [
Tag("button", {
type: "button",
class: "btn px-3 bg-base-100 border-base-300 hover:border-primary/50 flex items-center gap-2 shadow-sm font-normal normal-case",
onclick: (e) => { e.stopPropagation(); isOpen(!isOpen()); },
...props
}, [
Tag("div", {
class: "size-5 rounded-sm shadow-inner border border-black/10 shrink-0",
style: () => `background-color: ${getColor()}`
}),
props.label ? Tag("span", { class: "opacity-80" }, props.label) : null
]),
If(isOpen, () =>
Tag(
"div",
{
class: "absolute left-0 mt-2 p-3 bg-base-100 border border-base-300 shadow-2xl rounded-box z-[110] w-64 select-none",
onclick: (e) => e.stopPropagation(),
},
[
Tag(
"div",
{ class: "grid grid-cols-8 gap-1" },
palette.map((c) =>
Tag("button", {
type: "button",
style: `background-color: ${c}`,
class: () => {
const active = getColor().toLowerCase() === c.toLowerCase();
return `size-6 rounded-sm cursor-pointer transition-all hover:scale-125 hover:z-10 active:scale-95 outline-none border border-black/5 p-0 min-h-0
${active ? "ring-2 ring-offset-1 ring-primary z-10 scale-110" : ""}`;
},
onclick: () => {
if (typeof value === "function") value(c);
isOpen(false);
},
}),
),
),
],
),
Tag("div", {
class: "absolute left-0 mt-2 p-3 bg-base-100 border border-base-300 shadow-2xl rounded-box z-[110] w-64 select-none",
onclick: (e) => e.stopPropagation()
}, [
Tag("div", { class: "grid grid-cols-8 gap-1" },
palette.map(c =>
Tag("button", {
type: "button",
style: `background-color: ${c}`,
class: () => {
const active = getColor().toLowerCase() === c.toLowerCase();
return `size-6 rounded-sm cursor-pointer transition-all hover:scale-125 hover:z-10 active:scale-95 outline-none border border-black/5 p-0 min-h-0 ${active ? "ring-2 ring-offset-1 ring-primary z-10 scale-110" : ""}`;
},
onclick: () => {
if (typeof props.value === "function") props.value(c);
isOpen(false);
}
})
)
)
])
),
If(isOpen, () =>
Tag("div", {
class: "fixed inset-0 z-[100]",
onclick: () => isOpen(false),
}),
),
onclick: () => isOpen(false)
})
)
]);
};

View File

@@ -3,35 +3,33 @@ import { $, Tag, If, Watch } from "sigpro";
import { Calendar } from "./Calendar.js";
export const Datepicker = (props) => {
const { class: className, value, range, placeholder, hour = false, ...rest } = props;
const isOpen = $(false);
const isRangeMode = () => {
const r = typeof range === "function" ? range() : range;
const r = typeof props.range === "function" ? props.range() : props.range;
return r === true;
};
const displayValue = $("");
Watch(() => {
const v = typeof value === "function" ? value() : value;
const v = typeof props.value === "function" ? props.value() : props.value;
if (!v) {
displayValue("");
return;
}
let text = "";
if (typeof v === "string") {
text = (hour && v.includes("T")) ? v.replace("T", " ") : v;
text = (props.hour && v.includes("T")) ? v.replace("T", " ") : v;
} else if (v.start && v.end) {
const startStr = hour && v.startHour !== undefined
const startStr = props.hour && v.startHour !== undefined
? `${v.start} ${String(v.startHour).padStart(2, "0")}:00`
: v.start;
const endStr = hour && v.endHour !== undefined
const endStr = props.hour && v.endHour !== undefined
? `${v.end} ${String(v.endHour).padStart(2, "0")}:00`
: v.end;
text = `${startStr} - ${endStr}`;
} else if (v.start) {
const startStr = hour && v.startHour !== undefined
const startStr = props.hour && v.startHour !== undefined
? `${v.start} ${String(v.startHour).padStart(2, "0")}:00`
: v.start;
text = `${startStr}...`;
@@ -40,7 +38,7 @@ export const Datepicker = (props) => {
});
const handleCalendarChange = (newValue) => {
if (typeof value === "function") value(newValue);
if (typeof props.value === "function") props.value(newValue);
if (!isRangeMode() || (newValue?.end !== undefined && newValue?.end !== null)) {
isOpen(false);
}
@@ -51,16 +49,16 @@ export const Datepicker = (props) => {
isOpen(!isOpen());
};
return Tag("div", { class: `relative w-full ${className || ''}`.trim() }, [
return Tag("div", { class: `relative w-full ${props.class ?? ''}` }, [
Tag("label", { class: "input input-bordered w-full", onclick: toggleOpen }, [
Tag("span", { class: "icon-[lucide--calendar]" }),
Tag("input", {
...rest,
...props,
type: "text",
class: "grow",
value: displayValue,
readonly: true,
placeholder: placeholder || (isRangeMode() ? "Seleccionar rango..." : "Seleccionar fecha...")
placeholder: props.placeholder || (isRangeMode() ? "Seleccionar rango..." : "Seleccionar fecha...")
})
]),
@@ -70,9 +68,9 @@ export const Datepicker = (props) => {
onclick: (e) => e.stopPropagation()
}, [
Calendar({
value,
value: props.value,
range: isRangeMode(),
hour,
hour: props.hour,
onChange: handleCalendarChange
})
])

4
components/Divider.js Normal file
View File

@@ -0,0 +1,4 @@
// components/Collapse.js
import { Tag } from "sigpro";
export const Divider = (props) => Tag("div", { ...props, class: `divider ${props.class ?? ''}` });

View File

@@ -1,36 +1,31 @@
// components/Drawer.js
import { Tag } from "sigpro";
export const Drawer = (props) => {
const { class: className, id, open, content, children, ...rest } = props;
const drawerId = id || `drawer-${Math.random().toString(36).slice(2, 9)}`;
return Tag("div", {
...rest,
class: `drawer ${className || ''}`.trim()
}, [
export const Drawer = (props, children) => {
children === undefined && (children = props, props = {});
return Tag("div", { ...props, class: `drawer ${props.class ?? ''}` }, children);
};
export const Sidebar = (props) => {
const id = props.id || `drawer-${Math.random().toString(36).slice(2, 9)}`;
return Tag("div", { ...props, class: `drawer ${props.class ?? ''}` }, [
Tag("input", {
id: drawerId,
id,
type: "checkbox",
class: "drawer-toggle",
checked: () => typeof open === "function" ? open() : open,
onchange: (e) => {
if (typeof open === "function") open(e.target.checked);
}
checked: () => (typeof props.open === "function" ? props.open() : props.open),
onchange: (e) => typeof props.open === "function" && props.open(e.target.checked)
}),
Tag("div", { class: "drawer-content" }, children),
Tag("div", { class: "drawer-content" }, props.children),
Tag("div", { class: "drawer-side" }, [
Tag("label", {
for: drawerId,
Tag("label", {
for: id,
class: "drawer-overlay",
onclick: () => {
if (typeof open === "function") open(false);
}
onclick: () => typeof props.open === "function" && props.open(false)
}),
Tag("div", { class: "min-h-full bg-base-200 w-80 p-4" }, [
typeof content === "function" ? content() : content
])
Tag("div", { class: "min-h-full bg-base-200 w-80 p-4" },
typeof props.content === "function" ? props.content() : props.content
)
])
]);
};

View File

@@ -13,20 +13,12 @@ if (typeof window !== 'undefined' && !window.__dropdownHandlerRegistered) {
window.__dropdownHandlerRegistered = true;
}
export const Dropdown = (props) => {
const { class: className, children, ...rest } = props;
return Tag("details", {
...rest,
class: `dropdown ${className || ''}`.trim(),
onclick: (e) => {
const details = e.currentTarget;
if (currentOpen && currentOpen !== details) {
currentOpen.open = false;
}
setTimeout(() => {
currentOpen = details.open ? details : null;
}, 0);
}
}, children);
};
export const Dropdown = (props) => Tag("details", {
...props,
class: `dropdown ${props.class ?? ''}`,
onclick: (e) => {
const details = e.currentTarget;
if (currentOpen && currentOpen !== details) currentOpen.open = false;
setTimeout(() => { currentOpen = details.open ? details : null; }, 0);
}
}, props.children);

View File

@@ -2,10 +2,6 @@
import { Tag } from "sigpro";
export const Fab = (props, children) => {
const { class: className, position = "bottom-6 right-6", ...rest } = props;
return Tag("div", {
...rest,
class: `absolute ${position} flex flex-col-reverse items-end gap-3 z-[100] ${className || ''}`.trim()
}, children);
children === undefined && (children = props, props = {});
return Tag("div", { ...props, class: `fab ${props.class ?? ''}` }, children);
};

View File

@@ -1,14 +1,10 @@
// components/Fieldset.js
import { Tag } from "sigpro";
export const Fieldset = (props, children) => {
const { class: className, legend, ...rest } = props;
return Tag("fieldset", {
...rest,
class: `fieldset ${className || ''}`.trim()
}, [
legend ? Tag("legend", { class: "fieldset-legend" }, legend) : null,
children
]);
};
export const Fieldset = (props, children) => Tag("fieldset", {
...props,
class: `fieldset ${props.class ?? ''}`
}, [
props.legend ? Tag("legend", { class: "fieldset-legend" }, props.legend) : null,
children
]);

View File

@@ -2,42 +2,31 @@
import { $, Tag, If, For } from "sigpro";
export const Fileinput = (props) => {
const { class: className, max = 2, accept = "*", onselect, ...rest } = props;
const selectedFiles = $([]);
const isDragging = $(false);
const error = $(null);
const MAX_BYTES = max * 1024 * 1024;
const maxBytes = (props.max || 2) * 1024 * 1024;
const handleFiles = (files) => {
const fileList = Array.from(files);
error(null);
const oversized = fileList.find((f) => f.size > MAX_BYTES);
if (oversized) {
error(`Máx ${max}MB`);
if (fileList.find(f => f.size > maxBytes)) {
error(`Máx ${props.max || 2}MB`);
return;
}
selectedFiles([...selectedFiles(), ...fileList]);
onselect?.(selectedFiles());
props.onselect?.(selectedFiles());
};
const removeFile = (index) => {
const updated = selectedFiles().filter((_, i) => i !== index);
const removeFile = (idx) => {
const updated = selectedFiles().filter((_, i) => i !== idx);
selectedFiles(updated);
onselect?.(updated);
props.onselect?.(updated);
};
return Tag("div", {
...rest,
class: `fieldset w-full p-0 ${className || ''}`.trim()
}, [
return Tag("div", { ...props, class: `fieldset w-full p-0 ${props.class ?? ''}` }, [
Tag("label", {
class: () => `
relative flex items-center justify-between w-full h-12 px-4
border-2 border-dashed rounded-lg cursor-pointer
transition-all duration-200
${isDragging() ? "border-primary bg-primary/10" : "border-base-content/20 bg-base-100 hover:bg-base-200"}
`,
class: () => `relative flex items-center justify-between w-full h-12 px-4 border-2 border-dashed rounded-lg cursor-pointer transition-all duration-200 ${isDragging() ? "border-primary bg-primary/10" : "border-base-content/20 bg-base-100 hover:bg-base-200"}`,
ondragover: (e) => { e.preventDefault(); isDragging(true); },
ondragleave: () => isDragging(false),
ondrop: (e) => { e.preventDefault(); isDragging(false); handleFiles(e.dataTransfer.files); }
@@ -45,19 +34,17 @@ export const Fileinput = (props) => {
Tag("div", { class: "flex items-center gap-3 w-full" }, [
Tag("span", { class: "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`)
Tag("span", { class: "text-[10px] opacity-40 shrink-0" }, `Máx ${props.max || 2}MB`)
]),
Tag("input", {
type: "file",
multiple: true,
accept,
accept: props.accept || "*",
class: "hidden",
onchange: (e) => handleFiles(e.target.files)
})
]),
() => error() && Tag("span", { class: "text-[10px] text-error mt-1 px-1 font-medium" }, error()),
If(() => selectedFiles().length > 0, () =>
Tag("ul", { class: "mt-2 space-y-1" }, [
For(selectedFiles, (file, idx) =>

55
components/FilterList.js Normal file
View File

@@ -0,0 +1,55 @@
// components/FilterList.js
import { $, Tag, For, Watch } from "sigpro";
export const FilterList = (props) => {
const { items, filterKeys = ["label"], placeholder = "Filtrar...", renderItem, onSelect, class: className } = props;
const filterText = $("");
const filteredItems = $([]);
const updateFiltered = () => {
const q = String(filterText()).toLowerCase();
// items puede ser función, señal o array plano
const allItems = typeof items === "function" ? items() : items || [];
if (!q) {
filteredItems([...allItems]);
return;
}
const filtered = allItems.filter(item =>
filterKeys.some(key => {
const val = typeof item === "string" ? item : item[key];
return String(val || "").toLowerCase().includes(q);
})
);
filteredItems(filtered);
};
// Ejecutar inmediatamente al montar y luego reactivamente cuando cambie items o filterText
updateFiltered();
Watch(() => {
// Dependencias: items (evaluado) y filterText
const it = typeof items === "function" ? items() : items;
return { it, ft: filterText() };
}, () => updateFiltered());
return Tag("div", { class: `filter-list flex flex-col ${className ?? ''}` }, [
Tag("div", { class: "p-2 border-b border-base-300" }, [
Tag("label", { class: "input input-sm input-bordered flex items-center gap-2 w-full" }, [
Tag("span", { class: "icon-[lucide--search] opacity-50" }),
Tag("input", {
type: "text",
class: "grow",
placeholder,
value: filterText,
oninput: (e) => filterText(e.target.value),
ref: (el) => { if (el && props.autoFocus) el.focus(); }
})
])
]),
Tag("div", { class: "max-h-60 overflow-y-auto" }, [
For(filteredItems, (item, idx) =>
renderItem(item, idx, () => onSelect?.(item))
)
])
]);
};

View File

@@ -1,7 +1,14 @@
// components/Icon.js
import { Tag } from "sigpro";
export const Icon = (iconClass) => {
if (!iconClass) return null;
return Tag("span", { class: iconClass });
};
export const Icon = (props, children) => {
if (typeof props === "string") {
if (props.includes("icon-") || props.startsWith("lucide-")) {
return Tag("span", { class: props }, children);
}
return Tag("span", { class: "icon" }, props);
}
if (!props) return null;
const { class: className, ...rest } = props;
return Tag("span", { ...rest, class: className }, children);
};

View File

@@ -2,13 +2,9 @@
import { Tag } from "sigpro";
export const Indicator = (props, children) => {
const { value, class: className, ...rest } = props;
return Tag("div", {
...rest,
class: "indicator"
}, [
value ? Tag("span", { class: `indicator-item badge ${className || ''}`.trim() }, value) : null,
children === undefined && (children = props, props = {});
return Tag("div", { ...props, class: `indicator ${props.class ?? ''}` }, [
props.value ? Tag("span", { class: `indicator-item badge ${props.class ?? ''}` }, props.value) : null,
children
]);
};

View File

@@ -1,10 +1,4 @@
// components/Input.js
import { Tag } from "sigpro";
export const Input = (props) => {
const { class: className, type = "text", ...rest } = props;
return Tag("input", {
...rest,
type,
class: `input ${className || ''}`.trim()
});
};
export const Input = (props) => Tag("input", { ...props, class: `input ${props.class ?? ''}` });

View File

@@ -2,13 +2,6 @@
import { Tag } from "sigpro";
export const Kbd = (props, children) => {
if (typeof props === "string" || typeof props === "number") {
children = props;
props = {};
}
const { class: className, ...rest } = props;
return Tag("kbd", {
...rest,
class: `kbd ${className || ''}`.trim()
}, children);
children === undefined && (children = props, props = {});
return Tag("kbd", { ...props, class: `kbd ${props.class ?? ''}` }, children);
};

View File

@@ -1,11 +0,0 @@
// components/List.js
import { Tag } from "sigpro";
export const List = (props, children) => {
const { class: className, ...rest } = props;
return Tag("ul", {
...rest,
class: `list ${className || ''}`.trim()
}, children);
};

View File

@@ -1,10 +1,7 @@
// components/Loading.js
import { Tag } from "sigpro";
export const Spinner = (props) => {
const { class: className, ...rest } = props;
return Tag("span", {
...rest,
class: `loading loading-spinner ${className || ''}`.trim()
});
export const Loading = (props, children) => {
children === undefined && (children = props, props = {});
return Tag("span", { ...props, class: `loading loading-spinner ${props.class ?? ''}` }, children);
};

View File

@@ -1,11 +1,32 @@
// components/Menu.js
import { Tag } from "sigpro";
import { Tag, For } from "sigpro";
export const Menu = (props, children) => {
const { class: className, ...rest } = props;
children === undefined && (children = props, props = {});
return Tag("ul", { ...props, class: `menu ${props.class ?? ''}` }, children);
};
return Tag("ul", {
...rest,
class: `menu ${className || ''}`.trim()
}, children);
export const MenuItems = (props) => {
const { items, keyFn = (item, idx) => item.id ?? idx } = props;
const itemsSignal = typeof items === "function" ? items : () => items || [];
const renderItem = (item) => {
if (item.children) {
return Tag("li", {}, [
Tag("details", {}, [
Tag("summary", {}, item.label),
Tag("ul", {}, MenuItems({ items: item.children }))
])
]);
}
return Tag("li", {}, Tag("a", {
href: item.href,
onclick: item.onclick ? (e) => {
if (!item.href) e.preventDefault();
item.onclick(e);
} : null
}, item.label));
};
return For(itemsSignal, renderItem, keyFn);
};

View File

@@ -2,31 +2,28 @@
import { Tag, Watch } from "sigpro";
export const Modal = (props) => {
const { class: className, open, title, children, ...rest } = props;
let dialogRef = null;
Watch(() => {
const isOpen = typeof open === "function" ? open() : open;
const isOpen = typeof props.open === "function" ? props.open() : props.open;
if (!dialogRef) return;
isOpen ? dialogRef.showModal() : dialogRef.close();
});
const close = () => {
if (typeof open === "function") open(false);
};
const close = () => typeof props.open === "function" && props.open(false);
return Tag("dialog", {
...rest,
...props,
ref: el => dialogRef = el,
class: `modal ${className || ''}`.trim(),
class: `modal ${props.class ?? ''}`,
onclose: close,
oncancel: close
}, [
Tag("div", { class: "modal-box" }, [
title && Tag("h3", { class: "text-lg font-bold" }, title),
children,
props.title && Tag("h3", { class: "text-lg font-bold" }, props.title),
props.children,
Tag("div", { class: "modal-action" }, [
props.actions || Button({ onclick: close }, "Cerrar")
props.actions || Tag("button", { class: "btn", onclick: close }, "Cerrar")
])
]),
Tag("form", { method: "dialog", class: "modal-backdrop" }, [

View File

@@ -2,10 +2,6 @@
import { Tag } from "sigpro";
export const Navbar = (props, children) => {
const { class: className, ...rest } = props;
return Tag("div", {
...rest,
class: `navbar ${className || ''}`.trim()
}, children);
children === undefined && (children = props, props = {});
return Tag("div", { ...props, class: `navbar ${props.class ?? ''}` }, children);
};

View File

@@ -1,18 +1,18 @@
// components/RadialProgress.js
// components/Radial.js
import { Tag } from "sigpro";
export const RadialProgress = (props) => {
const { class: className, value, max = 100, children, ...rest } = props;
const percentage = value != null ? (value / max) * 100 : 0;
export const Radial = (props, children) => {
children === undefined && (children = props, props = {});
const percentage = props.value != null ? (props.value / (props.max || 100)) * 100 : 0;
const style = `--value: ${percentage}; --max: 100;`;
return Tag("div", {
...rest,
class: `radial-progress ${className || ''}`.trim(),
...props,
class: `radial-progress ${props.class ?? ''}`,
style: style,
role: "progressbar",
"aria-valuenow": value,
"aria-valuenow": props.value,
"aria-valuemin": 0,
"aria-valuemax": max
"aria-valuemax": props.max || 100
}, children || `${Math.round(percentage)}%`);
};

View File

@@ -1,19 +1,4 @@
// components/Radio.js
import { Tag } from "sigpro";
export const Radio = (props) => {
const { class: className, label, ...rest } = props;
const radioEl = Tag("input", {
...rest,
type: "radio",
class: `radio ${className || ''}`.trim()
});
if (!label) return radioEl;
return Tag("label", { class: "label cursor-pointer justify-start gap-3" }, [
radioEl,
Tag("span", { class: "label-text" }, label)
]);
};
export const Radio = (props) => Tag("input", { ...props, type: "radio", class: `radio ${props.class ?? ''}` });

View File

@@ -1,12 +1,4 @@
// components/Range.js
import { Tag } from "sigpro";
export const Range = (props) => {
const { class: className, ...rest } = props;
return Tag("input", {
...rest,
type: "range",
class: `range ${className || ''}`.trim()
});
};
export const Range = (props) => Tag("input", { ...props, type: "range", class: `range ${props.class ?? ''}` });

View File

@@ -2,23 +2,19 @@
import { Tag } from "sigpro";
export const Rating = (props, children) => {
const { class: className, count, mask = "mask-star", value, onchange, ...rest } = props;
children === undefined && (children = props, props = {});
const name = `rating-${Math.random().toString(36).slice(2, 7)}`;
return Tag("div", {
...rest,
class: `rating ${className || ''}`.trim()
}, children || Array.from({ length: count || 5 }, (_, i) => {
return Tag("div", { ...props, class: `rating ${props.class ?? ''}` }, children || Array.from({ length: props.count || 5 }, (_, i) => {
const starValue = i + 1;
return Tag("input", {
type: "radio",
name,
class: `mask ${mask}`,
checked: () => typeof value === "function" ? value() === starValue : value === starValue,
class: `mask ${props.mask || "mask-star"}`,
checked: () => typeof props.value === "function" ? props.value() === starValue : props.value === starValue,
onchange: () => {
if (onchange) onchange(starValue);
else if (typeof value === "function") value(starValue);
if (props.onchange) props.onchange(starValue);
else if (typeof props.value === "function") props.value(starValue);
}
});
}));

View File

@@ -2,24 +2,24 @@
import { Tag, For } from "sigpro";
export const Select = (props, children) => {
const { class: className, ...rest } = props;
return Tag("select", {
...rest,
class: `select ${className || ''}`.trim()
}, children);
children === undefined && (children = props, props = {});
return Tag("select", { ...props, class: `select ${props.class ?? ''}` }, children);
};
export const Options = (props) => {
const { items, placeholder, placeholderDisabled = true, ...rest } = props;
const itemArray = typeof items === "function" ? items() : (items || []);
return [
placeholder && Tag("option", { disabled: placeholderDisabled, selected: true }, placeholder),
For(itemArray, (item) => {
const placeholderOption = props.placeholder
? Tag("option", { disabled: props.placeholderDisabled ?? true, selected: true }, props.placeholder)
: null;
const dynamicOptions = For(
() => [...(typeof props.items === "function" ? props.items() : props.items || [])],
(item) => {
const val = typeof item === "string" ? item : item.value;
const label = typeof item === "string" ? item : item.label;
return Tag("option", { value: val, ...rest }, label);
})
];
return Tag("option", { value: val }, label);
},
props.keyFn || ((item) => (typeof item === "string" ? item : item.value))
);
return placeholderOption ? [placeholderOption, dynamicOptions] : dynamicOptions;
};

View File

@@ -1,19 +1,12 @@
// components/Skeleton.js
import { Tag } from "sigpro";
export const Skeleton = (props) => {
const { class: className, ...rest } = props;
return Tag("div", {
...rest,
class: `skeleton ${className || ''}`.trim()
});
};
export const Skeleton = (props) => Tag("div", { ...props, class: `skeleton ${props.class ?? ''}` });
export const SkeletonText = (props) => {
const { class: className, lines = 3, ...rest } = props;
return Tag("div", { ...rest, class: "space-y-2" },
Array.from({ length: lines }, (_, i) =>
Tag("div", { class: `skeleton h-4 w-full ${className || ''}`.trim() })
return Tag("div", { ...props, class: "space-y-2" },
Array.from({ length: props.lines || 3 }, () =>
Tag("div", { class: `skeleton h-4 w-full ${props.class ?? ''}` })
)
);
};

View File

@@ -2,9 +2,6 @@
import { Tag } from "sigpro";
export const Stack = (props, children) => {
const { class: className, ...rest } = props;
return Tag("div", {
...rest,
class: `stack ${className || ''}`.trim()
}, children);
children === undefined && (children = props, props = {});
return Tag("div", { ...props, class: `stack ${props.class ?? ''}` }, children);
};

View File

@@ -2,26 +2,19 @@
import { Tag } from "sigpro";
export const Stats = (props, children) => {
const { class: className, vertical = false, ...rest } = props;
const direction = vertical ? "stats-vertical" : "stats-horizontal";
return Tag("div", {
...rest,
class: `stats ${direction} ${className || ''}`.trim()
}, children);
children === undefined && (children = props, props = {});
const direction = props.vertical ? "stats-vertical" : "stats-horizontal";
return Tag("div", { ...props, class: `stats ${direction} ${props.class ?? ''}`.trim() }, children);
};
export const Stat = (props) => {
const { class: className, label, value, desc, icon, actions, children, ...rest } = props;
if (children !== undefined) {
return Tag("div", { ...rest, class: `stat ${className || ''}`.trim() }, children);
}
return Tag("div", { ...rest, class: `stat ${className || ''}`.trim() }, [
icon && Tag("div", { class: "stat-figure" }, icon),
label && Tag("div", { class: "stat-title" }, label),
value && Tag("div", { class: "stat-value" }, value),
desc && Tag("div", { class: "stat-desc" }, desc),
actions && Tag("div", { class: "stat-actions" }, actions)
export const Stat = (props, children) => {
children === undefined && (children = props, props = {});
return Tag("div", { ...props, class: `stat ${props.class ?? ''}` }, [
props.icon && Tag("div", { class: "stat-figure" }, props.icon),
props.label && Tag("div", { class: "stat-title" }, props.label),
props.value && Tag("div", { class: "stat-value" }, props.value),
props.desc && Tag("div", { class: "stat-desc" }, props.desc),
props.actions && Tag("div", { class: "stat-actions" }, props.actions),
children
]);
};

View File

@@ -2,18 +2,11 @@
import { Tag } from "sigpro";
export const Steps = (props, children) => {
const { class: className, ...rest } = props;
return Tag("ul", {
...rest,
class: `steps ${className || ''}`.trim()
}, children);
children === undefined && (children = props, props = {});
return Tag("ul", { ...props, class: `steps ${props.class ?? ''}` }, children);
};
export const Step = (props, children) => {
const { class: className, dataContent, ...rest } = props;
return Tag("li", {
...rest,
class: `step ${className || ''}`.trim(),
"data-content": dataContent
}, children);
children === undefined && (children = props, props = {});
return Tag("li", { ...props, class: `step ${props.class ?? ''}`, "data-content": props.dataContent }, children);
};

View File

@@ -2,20 +2,13 @@
import { Tag } from "sigpro";
export const Swap = (props) => {
const { class: className, value, on, off, ...rest } = props;
return Tag("label", {
...rest,
class: `swap ${className || ''}`.trim()
}, [
return Tag("label", { ...props, class: `swap ${props.class ?? ''}` }, [
Tag("input", {
type: "checkbox",
checked: () => typeof value === "function" ? value() : value,
onchange: (e) => {
if (typeof value === "function") value(e.target.checked);
}
checked: () => typeof props.value === "function" ? props.value() : props.value,
onchange: (e) => typeof props.value === "function" && props.value(e.target.checked)
}),
Tag("div", { class: "swap-on" }, on),
Tag("div", { class: "swap-off" }, off)
Tag("div", { class: "swap-on" }, props.on),
Tag("div", { class: "swap-off" }, props.off)
]);
};

View File

@@ -1,10 +1,31 @@
// components/Table.js
import { Tag } from "sigpro";
import { Tag, For } from "sigpro";
export const Table = (props, children) => {
const { class: className, ...rest } = props;
return Tag("table", {
...rest,
class: `table ${className || ''}`.trim()
}, children);
children === undefined && (children = props, props = {});
return Tag("table", { ...props, class: `table ${props.class ?? ''}` }, children);
};
export const Rows = (props) => {
const itemArray = typeof props.items === "function" ? props.items() : (props.items || []);
const thead = props.header !== false && props.columns?.some(col => col.label) ?
Tag("thead", {},
Tag("tr", {},
props.columns.map(col => Tag("th", { class: col.class }, col.label))
)
) : null;
const tbody = Tag("tbody", {}, [
For(itemArray, (item, idx) =>
Tag("tr", {},
props.columns.map(col => {
const content = col.render ? col.render(item, idx) : item[col.key];
return Tag("td", { class: col.class }, content);
})
)
, props.keyFn || ((item, idx) => item.id ?? idx))
]);
return [thead, tbody];
};

View File

@@ -1,75 +1,49 @@
// components/Tabs.js
import { Tag, $, Watch } from "sigpro";
import { Tag, For } from "sigpro";
export const Tabs = (props) => {
const { items, class: className, onTabClose, ...rest } = props;
const itemsSignal = typeof items === "function" ? items : () => items || [];
const activeIndex = $(0);
export const Tabs = (props, children) => {
children === undefined && (children = props, props = {});
return Tag("div", { ...props, class: `tabs ${props.class ?? ''}` }, children);
};
Watch(() => {
const list = itemsSignal();
const idx = list.findIndex(it => {
const active = it.active;
return typeof active === "function" ? active() : active;
});
if (idx !== -1 && activeIndex() !== idx) activeIndex(idx);
});
export const Tab = (props, children) => {
children === undefined && (children = props, props = {});
return Tag("a", { ...props, role: "tab", class: `tab ${props.class ?? ''}` }, children);
};
const removeTab = (idx, item) => {
item.onClose?.();
onTabClose?.(item, idx);
const current = itemsSignal();
if (typeof items !== "function" || items._isComputed) return;
const newItems = current.filter((_, i) => i !== idx);
items(newItems);
if (newItems.length) {
let newIdx = activeIndex();
if (idx < newIdx) newIdx--;
else if (idx === newIdx) newIdx = Math.min(newIdx, newItems.length - 1);
activeIndex(newIdx);
}
};
export const TabContent = (props, children) => {
children === undefined && (children = props, props = {});
return Tag("div", { ...props, class: `tab-content ${props.class ?? ''}` }, children);
};
return Tag("div", { ...rest, class: `tabs ${className || ''}`.trim() }, () => {
const list = itemsSignal();
const elements = [];
for (let i = 0; i < list.length; i++) {
const item = list[i];
const label = typeof item.label === "function" ? item.label() : item.label;
const closable = typeof item.closable === "function" ? item.closable() : item.closable;
export const TabClose = (props) => Tag("a", { ...props, role: "tab", class: `tab ${props.class ?? ''}` }, [
Tag("span", { class: "flex items-center" }, [
props.label,
Tag("span", {
class: "icon-[lucide--x] w-3.5 h-3.5 ml-2 cursor-pointer hover:opacity-70",
onclick: (e) => { e.stopPropagation(); props.onClose?.(e); }
})
])
]);
const btnContent = closable
? Tag("span", { class: "flex items-center" }, [
label,
Tag("span", {
class: "icon-[lucide--x] w-3.5 h-3.5 ml-2 cursor-pointer hover:opacity-70",
onclick: (e) => { e.stopPropagation(); removeTab(i, item); }
})
])
: label;
const tabBtn = Tag("button", {
class: () => `tab ${activeIndex() === i ? 'tab-active' : ''}`,
onclick: (e) => {
e.preventDefault();
const disabled = typeof item.disabled === "function" ? item.disabled() : item.disabled;
if (!disabled) {
item.onclick?.();
activeIndex(i);
}
}
}, btnContent);
elements.push(item.tip ? Tag("div", { class: "tooltip", "data-tip": item.tip }, tabBtn) : tabBtn);
const content = typeof item.content === "function" ? item.content() : item.content;
elements.push(
Tag("div", {
class: "tab-content bg-base-100 border-base-300 p-6",
style: () => `display: ${activeIndex() === i ? 'block' : 'none'}`
}, content)
);
}
return elements;
});
export const TabItems = (props) => {
const items = typeof props.items === "function" ? props.items : () => props.items || [];
return For(
items,
(item, idx) => {
const TabComp = item.closable ? TabClose : Tab;
return [
TabComp({
...item,
class: () => props.activeIndex() === idx ? `tab-active ${item.class ?? ''}` : item.class,
onclick: (e) => { e.preventDefault(); props.activeIndex(idx); item.onclick?.(e); },
onClose: () => props.onClose?.(idx, item)
}),
TabContent({
style: () => `display: ${props.activeIndex() === idx ? "block" : "none"};`
}, typeof item.content === "function" ? item.content() : item.content)
];
},
(item, idx) => item.id ?? idx
);
};

View File

@@ -1,19 +0,0 @@
// components/TextRotate.js
import { Tag } from "sigpro";
export const TextRotate = (props) => {
const { class: className, words, ...rest } = props;
const wordsArray = Array.isArray(words)
? words
: (typeof words === 'string' ? words.split(',') : []);
return Tag("span", {
...rest,
class: `text-rotate ${className || ''}`.trim()
}, [
Tag("span", {},
wordsArray.map(word => Tag("span", {}, word))
)
]);
};

View File

@@ -1,9 +1,4 @@
// components/Textarea.js
import { Tag } from "sigpro";
export const Textarea = (props) => {
const { class: className, ...rest } = props;
return Tag("textarea", {
...rest,
class: `textarea ${className || ''}`.trim()
});
};
export const Textarea = (props) => Tag("textarea", { ...props, class: `textarea ${props.class ?? ''}` });

12
components/Textrotate.js Normal file
View File

@@ -0,0 +1,12 @@
// components/Textrotate.js
import { Tag } from "sigpro";
export const TextRotate = (props) => {
const wordsArray = Array.isArray(props.words)
? props.words
: (typeof props.words === 'string' ? props.words.split(',') : []);
return Tag("span", { ...props, class: `text-rotate ${props.class ?? ''}` }, [
Tag("span", {}, wordsArray.map(word => Tag("span", {}, word)))
]);
};

View File

@@ -2,9 +2,11 @@
import { Tag } from "sigpro";
export const Timeline = (props, children) => {
const { class: className, vertical = true, compact = false, ...rest } = props;
children === undefined && (children = props, props = {});
const vertical = props.vertical !== false;
const compact = props.compact === true;
return Tag("ul", {
...rest,
class: `timeline ${vertical ? 'timeline-vertical' : 'timeline-horizontal'} ${compact ? 'timeline-compact' : ''} ${className || ''}`.trim()
...props,
class: `timeline ${vertical ? 'timeline-vertical' : 'timeline-horizontal'} ${compact ? 'timeline-compact' : ''} ${props.class ?? ''}`.trim()
}, children);
};

View File

@@ -3,11 +3,10 @@ import { Tag, Mount } from "sigpro";
export const Toast = (message, type = "alert-success", duration = 3500) => {
let container = document.getElementById("sigpro-toast-container");
if (!container) {
container = Tag("div", {
id: "sigpro-toast-container",
class: "fixed top-0 right-0 z-[9999] p-4 flex flex-col gap-2 pointer-events-none",
class: "fixed top-0 right-0 z-[9999] p-4 flex flex-col gap-2 pointer-events-none"
});
document.body.appendChild(container);
}
@@ -16,7 +15,6 @@ export const Toast = (message, type = "alert-success", duration = 3500) => {
container.appendChild(toastHost);
let timeoutId;
const close = () => {
clearTimeout(timeoutId);
const el = toastHost.firstElementChild;
@@ -52,10 +50,6 @@ export const Toast = (message, type = "alert-success", duration = 3500) => {
};
const instance = Mount(ToastComponent, toastHost);
if (duration > 0) {
timeoutId = setTimeout(close, duration);
}
if (duration > 0) timeoutId = setTimeout(close, duration);
return close;
};

View File

@@ -1,19 +1,4 @@
// components/Toggle.js
import { Tag } from "sigpro";
export const Toggle = (props) => {
const { class: className, label, ...rest } = props;
const inputEl = Tag("input", {
...rest,
type: "checkbox",
class: `toggle ${className || ''}`.trim()
});
if (!label) return inputEl;
return Tag("label", { class: "label cursor-pointer justify-start gap-3" }, [
inputEl,
Tag("span", { class: "label-text" }, label)
]);
};
export const Toggle = (p) => Tag("input", { ...p, type: "checkbox", class: `toggle ${p.class ?? ''}` });

View File

@@ -2,10 +2,6 @@
import { Tag } from "sigpro";
export const Tooltip = (props, children) => {
const { class: className, tip, ...rest } = props;
return Tag("div", {
...rest,
class: `tooltip ${className || ''}`.trim(),
"data-tip": tip
}, children);
children === undefined && (children = props, props = {});
return Tag("div", { ...props, class: `tooltip ${props.class ?? ''}`, "data-tip": props.tip }, children);
};

84
components/_Base.js Normal file
View File

@@ -0,0 +1,84 @@
import { $, Tag } from "sigpro";
import { Tag } from "sigpro";
export const Alert = (props, children) => Tag("div", { ...props, class: `alert ${props.class ?? ''}` }, children);
export const Badge = (props, children) => Tag("span", { ...props, class: `badge ${props.class ?? ''}` }, children);
export const Button = (props, children) => Tag("button", { ...props, class: `btn ${props.class ?? ''}` }, children);
export const Card = (props, children) => Tag("div", { ...props, class: `card ${props.class ?? ''}` }, children);
export const CardTitle = (props, children) => Tag("div", { ...props, class: `card-title ${props.class ?? ''}` }, children);
export const CardBody = (props, children) => Tag("div", { ...props, class: `card-body ${props.class ?? ''}` }, children);
export const CardActions = (props, children) => Tag("div", { ...props, class: `card-actions ${props.class ?? ''}` }, children);
export const Carousel = (props, children) => Tag("div", { ...props, class: `carousel ${props.class ?? ''}` }, children);
export const CarouselItem = (props, children) => Tag("div", { ...props, class: `carousel-item ${props.class ?? ''}` }, children);
export const Chat = (props, children) => Tag("div", { ...props, class: `chat ${props.class ?? ''}` }, children);
export const ChatBubble = (props, children) => Tag("div", { ...props, class: `chat-bubble ${props.class ?? ''}` }, children);
export const Checkbox = (props) => Tag("input", { ...props, type: "checkbox", class: `checkbox ${props.class ?? ''}` });
export const Collapse = (props, children) => Tag("div", { ...props, class: `collapse ${props.class ?? ''}` }, children);
export const Divider = (props) => Tag("div", { ...props, class: `divider ${props.class ?? ''}` });
export const Drawer = (props, children) => Tag("div", { ...props, class: `drawer ${props.class ?? ''}` }, children);
export const Dropdown = (props, children) => Tag("details", { ...props, class: `dropdown ${props.class ?? ''}` }, children);
export const Fab = (props, children) => Tag("div", { ...props, class: `fab ${props.class ?? ''}` }, children);
export const Fieldset = (props, children) => Tag("fieldset", { ...props, class: `fieldset ${props.class ?? ''}` }, children);
export const Indicator = (props, children) => Tag("div", { ...props, class: `indicator ${props.class ?? ''}` }, children);
export const Input = (props) => Tag("input", { ...props, class: `input ${props.class ?? ''}` });
export const Kbd = (props, children) => Tag("kbd", { ...props, class: `kbd ${props.class ?? ''}` }, children);
export const Label = (props, children) => Tag("label", { ...props, class: `label ${props.class ?? ''}` }, children);
export const List = (props, children) => Tag("ul", { ...props, class: `list ${props.class ?? ''}` }, children);
export const Menu = (props, children) => Tag("ul", { ...props, class: `menu ${props.class ?? ''}` }, children);
export const Modal = (props, children) => Tag("dialog", { ...props, class: `modal ${props.class ?? ''}` }, children);
export const Navbar = (props, children) => Tag("div", { ...props, class: `navbar ${props.class ?? ''}` }, children);
export const Radio = (props) => Tag("input", { ...props, type: "radio", class: `radio ${props.class ?? ''}` });
export const Range = (props) => Tag("input", { ...props, type: "range", class: `range ${props.class ?? ''}` });
export const Rating = (props, children) => Tag("div", { ...props, class: `rating ${props.class ?? ''}` }, children);
export const Select = (props, children) => Tag("select", { ...props, class: `select ${props.class ?? ''}` }, children);
export const Skeleton = (props) => Tag("div", { ...props, class: `skeleton ${props.class ?? ''}` });
export const Stack = (props, children) => Tag("div", { ...props, class: `stack ${props.class ?? ''}` }, children);
export const Stat = (props, children) => Tag("div", { ...props, class: `stat ${props.class ?? ''}` }, children);
export const Stats = (props, children) => Tag("div", { ...props, class: `stats ${props.class ?? ''}` }, children);
export const Steps = (props, children) => Tag("ul", { ...props, class: `steps ${props.class ?? ''}` }, children);
export const Step = (props, children) => Tag("li", { ...props, class: `step ${props.class ?? ''}` }, children);
export const Swap = (props, children) => Tag("label", { ...props, class: `swap ${props.class ?? ''}` }, children);
export const Table = (props, children) => Tag("table", { ...props, class: `table ${props.class ?? ''}` }, children);
export const Tab = (props, children) => Tag("a", { ...props, role: "tab", class: `tab ${props.class ?? ''}` }, children);
export const Tabs = (props, children) => Tag("div", { ...props, class: `tabs ${props.class ?? ''}` }, children);
export const TabContent = (props, children) => Tag("div", { ...props, class: `tab-content ${props.class ?? ''}` }, children);
export const Textarea = (props) => Tag("textarea", { ...props, class: `textarea ${props.class ?? ''}` });
export const Timeline = (props, children) => Tag("ul", { ...props, class: `timeline ${props.class ?? ''}` }, children);
export const Toast = (props, children) => Tag("div", { ...props, class: `toast ${props.class ?? ''}` }, children);
export const Toggle = (props) => Tag("input", { ...props, type: "checkbox", class: `toggle ${props.class ?? ''}` });
export const Tooltip = (props, children) => Tag("div", { ...props, class: `tooltip ${props.class ?? ''}` }, children);
// Complex
export const TabClose = (props) => Tag("a", { ...props, role: "tab", class: `tab ${props.class ?? ''}` }, [
Tag("span", { class: "flex items-center" }, [
p.label,
Tag("span", {
class: "icon-[lucide--x] w-3.5 h-3.5 ml-2 cursor-pointer hover:opacity-70",
onclick: (e) => { e.stopPropagation(); p.onClose?.(e); }
})
])
]);
export const TabItems = (props) => {
const items = typeof p.items === "function" ? p.items : () => p.items || [];
return For(
items,
(item, idx) => {
const TabComp = item.closable ? TabClose : Tab;
return [
TabComp({
label: item.label,
class: () => p.activeIndex() === idx ? "tab-active" : "",
onclick: (e) => { e.preventDefault(); p.activeIndex(idx); },
onClose: () => p.onClose?.(idx, item)
}),
TabContent({
style: () => `display: ${p.activeIndex() === idx ? "block" : "none"};`
}, typeof item.content === "function" ? item.content() : item.content)
];
},
(item, idx) => item.id ?? idx
);
};

View File

@@ -11,6 +11,7 @@
--color-black: #000;
--color-white: #fff;
--spacing: 0.25rem;
--container-xs: 20rem;
--container-md: 28rem;
--container-3xl: 48rem;
--container-5xl: 64rem;
@@ -1411,6 +1412,36 @@
}
}
}
.validator-hint {
@layer daisyui.l1.l2.l3 {
visibility: hidden;
margin-top: calc(0.25rem * 2);
font-size: 0.75rem;
}
}
.validator {
@layer daisyui.l1.l2.l3 {
&:user-valid, &:has(:user-valid) {
&, &:focus, &:checked, &[aria-checked="true"], &:focus-within {
--input-color: var(--color-success);
}
}
&:user-invalid, &:has(:user-invalid), &[aria-invalid]:not([aria-invalid="false"]), &:has([aria-invalid]:not([aria-invalid="false"])) {
&, &:focus, &:checked, &[aria-checked="true"], &:focus-within {
--input-color: var(--color-error);
}
& ~ .validator-hint {
visibility: visible;
color: var(--color-error);
}
}
}
&:user-invalid, &:has(:user-invalid), &[aria-invalid]:not([aria-invalid="false"]), &:has([aria-invalid]:not([aria-invalid="false"])) {
& ~ .validator-hint {
display: revert-layer;
}
}
}
.collapse {
visibility: collapse;
}
@@ -1595,6 +1626,27 @@
}
}
}
.toast {
@layer daisyui.l1.l2.l3 {
position: fixed;
inset-inline-start: auto;
inset-inline-end: calc(0.25rem * 4);
top: auto;
bottom: calc(0.25rem * 4);
display: flex;
flex-direction: column;
gap: calc(0.25rem * 2);
background-color: transparent;
translate: var(--toast-x, 0) var(--toast-y, 0);
width: max-content;
max-width: calc(100vw - 2rem);
& > * {
@media (prefers-reduced-motion: no-preference) {
animation: toast 0.25s ease-out;
}
}
}
}
.toggle {
@layer daisyui.l1.l2.l3 {
border: var(--border) solid currentColor;
@@ -3120,6 +3172,9 @@
.top-0 {
top: calc(var(--spacing) * 0);
}
.top-1\/2 {
top: calc(1 / 2 * 100%);
}
.top-2 {
top: calc(var(--spacing) * 2);
}
@@ -4341,6 +4396,19 @@
mask-size: 100% 100%;
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M13.832 16.568a1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233a14 14 0 0 0 6.392 6.384'/%3E%3C/svg%3E");
}
.icon-\[lucide--search-x\] {
display: inline-block;
width: 1em;
height: 1em;
background-color: currentColor;
-webkit-mask-image: var(--svg);
mask-image: var(--svg);
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: 100% 100%;
mask-size: 100% 100%;
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cg fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='m13.5 8.5l-5 5m0-5l5 5'/%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21l-4.3-4.3'/%3E%3C/g%3E%3C/svg%3E");
}
.icon-\[lucide--search\] {
display: inline-block;
width: 1em;
@@ -4900,6 +4968,9 @@
.max-w-md {
max-width: var(--container-md);
}
.max-w-xs {
max-width: var(--container-xs);
}
.min-w-\[4rem\] {
min-width: 4rem;
}
@@ -4944,6 +5015,10 @@
--tw-translate-x: 100%;
translate: var(--tw-translate-x) var(--tw-translate-y);
}
.-translate-y-1\/2 {
--tw-translate-y: calc(calc(1 / 2 * 100%) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
.translate-y-2 {
--tw-translate-y: calc(var(--spacing) * 2);
translate: var(--tw-translate-x) var(--tw-translate-y);

1096
dist/sigpro-ui.esm.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

1096
dist/sigpro-ui.js vendored

File diff suppressed because it is too large Load Diff

2
dist/sigpro-ui.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
dist/sigpro.min.css vendored

File diff suppressed because one or more lines are too long

View File

@@ -149,15 +149,20 @@ const DynamicDemo = () => {
};
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Select({
items: [
{ value: 'all', label: 'All items' },
{ value: 'fruits', label: 'Fruits' },
{ value: 'vegetables', label: 'Vegetables' }
],
value: filterType,
onchange: (e) => filterType(e.target.value)
}),
Select({
class: 'select-bordered w-full',
value: filterType,
onchange: (e) => filterType(e.target.value)
}, [
Options({
items: [
{ value: 'all', label: 'All items' },
{ value: 'fruits', label: 'Fruits' },
{ value: 'vegetables', label: 'Vegetables' }
]
})
]),
Autocomplete({
items: () => allItems[filterType()],
value: selected,
@@ -165,6 +170,7 @@ const DynamicDemo = () => {
})
]);
};
Mount(DynamicDemo, '#demo-dynamic');
```

View File

@@ -69,7 +69,7 @@ const LoadingDemo = () => {
isSaving(false);
},
},
[Spinner({ value: isSaving }), "Save Changes"],
[If(isSaving, ()=>Loading()), "Save Changes"],
);
};
Mount(LoadingDemo, "#demo-loading");

View File

@@ -8,26 +8,26 @@ Form input component with icons, password toggle, and validation. Use `Label()`
## Props
| Prop | Type | Default | Description |
| :----------- | :--------------------------- | :--------- | :----------------------------------------------- |
| `type` | `string` | `'text'` | Input type (text, password, email, number, date) |
| `value` | `string \| Signal<string>` | `''` | Input value |
| `placeholder`| `string` | `' '` | Placeholder text |
| `icon` | `string \| VNode \| Signal` | `-` | Icon displayed inside input |
| `disabled` | `boolean \| Signal<boolean>` | `false` | Disabled state |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `oninput` | `function` | `-` | Input event handler |
| `validate` | `function` | `-` | Validation function returning error message |
| Prop | Type | Default | Description |
| :------------ | :--------------------------- | :------- | :----------------------------------------------- |
| `type` | `string` | `'text'` | Input type (text, password, email, number, date) |
| `value` | `string \| Signal<string>` | `''` | Input value |
| `placeholder` | `string` | `' '` | Placeholder text |
| `icon` | `string \| VNode \| Signal` | `-` | Icon displayed inside input |
| `disabled` | `boolean \| Signal<boolean>` | `false` | Disabled state |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `oninput` | `function` | `-` | Input event handler |
| `validate` | `function` | `-` | Validation function returning error message |
## Styling
Input supports all **daisyUI Input classes**:
| Category | Keywords | Description |
| :--- | :--- | :--- |
| Style | `input-bordered`, `input-ghost` | Input style variants |
| Color | `input-primary`, `input-secondary`, `input-accent`, `input-info`, `input-success`, `input-warning`, `input-error` | Input color variants |
| Size | `input-xs`, `input-sm`, `input-md`, `input-lg` | Input size variants |
| Category | Keywords | Description |
| :------- | :---------------------------------------------------------------------------------------------------------------- | :------------------- |
| Style | `input-bordered`, `input-ghost` | Input style variants |
| Color | `input-primary`, `input-secondary`, `input-accent`, `input-info`, `input-success`, `input-warning`, `input-error` | Input color variants |
| Size | `input-xs`, `input-sm`, `input-md`, `input-lg` | Input size variants |
## Live Examples
@@ -42,15 +42,15 @@ Input supports all **daisyUI Input classes**:
```javascript
const BasicDemo = () => {
const name = $('');
const name = $("");
return Input({
placeholder: 'Enter your name',
placeholder: "Enter your name",
value: name,
oninput: (e) => name(e.target.value)
oninput: (e) => name(e.target.value),
});
};
Mount(BasicDemo, '#demo-basic');
Mount(BasicDemo, "#demo-basic");
```
### With Icon
@@ -64,16 +64,19 @@ Mount(BasicDemo, '#demo-basic');
```javascript
const IconDemo = () => {
const email = $('');
return Input({
type: 'email',
icon: "✉️",
value: email,
oninput: (e) => email(e.target.value)
});
const email = $("");
return Label({ class: "input" }, [
Icon("✉️"),
Input({
class: "grow",
type: "email",
value: email,
oninput: (e) => email(e.target.value),
}),
]);
};
Mount(IconDemo, '#demo-icon');
Mount(IconDemo, "#demo-icon");
```
### Password with Toggle
@@ -87,15 +90,27 @@ Mount(IconDemo, '#demo-icon');
```javascript
const PasswordDemo = () => {
const password = $('');
return Input({
type: 'password',
value: password,
oninput: (e) => password(e.target.value)
});
const password = $("");
const visible = $(false);
return Label({ class: "input input-bordered w-full max-w-xs" }, [
Icon("icon-[lucide--lock]"),
Input({
type: () => (visible() ? "text" : "password"),
value: password,
placeholder: "Contraseña",
class: "grow",
oninput: (e) => password(e.target.value),
}),
Swap({
value: visible,
class: "swap-rotate",
on: Icon("icon-[lucide--eye]"),
off: Icon("icon-[lucide--eye-off]"),
}),
]);
};
Mount(PasswordDemo, '#demo-password');
Mount(PasswordDemo, "#demo-password");
```
### With Floating Label
@@ -111,18 +126,20 @@ Wrap the input with `Label()` component:
```javascript
const LabelDemo = () => {
const email = $('');
return Input({
type: 'email',
const email = $("");
return Label({ class: "floating-label" }, [
Span("Text floating"),
Input({
type: "email",
label: "Email",
floating: true,
value: email,
placeholder: ' ',
oninput: (e) => email(e.target.value)
});
placeholder: "Clic here",
oninput: (e) => email(e.target.value),
}),
]);
};
Mount(LabelDemo, '#demo-label');
Mount(LabelDemo, "#demo-label");
```
### With Tooltip & label
@@ -138,18 +155,22 @@ Wrap the input with `Tooltip()` component:
```javascript
const TooltipDemo = () => {
const username = $('');
return Tooltip({ tip: 'Must be at least 3 characters' },
Input({
value: username,
label: "Username",
placeholder: 'Username',
oninput: (e) => username(e.target.value)
})
const username = $("");
return Tooltip(
{ tip: "Must be at least 3 characters" },
Label({ class: "input" }, [
Span({ class: "label" }, "User"),
Input({
value: username,
label: "Username",
placeholder: "Username",
oninput: (e) => username(e.target.value),
}),
]),
);
};
Mount(TooltipDemo, '#demo-tooltip');
Mount(TooltipDemo, "#demo-tooltip");
```
### Error State
@@ -163,26 +184,27 @@ Mount(TooltipDemo, '#demo-tooltip');
```javascript
const ErrorDemo = () => {
const email = $('');
return Div({ class: 'w-full' }, [
Input({
type: 'email',
value: email,
placeholder: 'Enter your email',
label: 'Email',
icon: 'icon-[lucide--mail]',
validate: (value) => {
if (!value) return '';
if (!value.includes('@')) return 'Email must contain @';
if (!value.includes('.')) return 'Email must contain .';
return '';
},
oninput: (e) => email(e.target.value)
})
const email = $("");
return Div({ class: "form-control w-full max-w-xs" }, [
Label({ class: "label" }, Span({ class: "label-text" }, "Email")),
Div({ class: "relative w-full" }, [
Input({
type: "email",
value: email,
placeholder: "mail@site.com",
class: "input input-bordered w-full pl-10 validator",
required: true,
oninput: (e) => email(e.target.value)
}),
Span({ class: "absolute left-3 top-1/2 -translate-y-1/2 text-base-content/60" },
Icon("icon-[lucide--mail]")
)
]),
Div({ class: "validator-hint hidden" }, "Enter a valid email address")
]);
};
Mount(ErrorDemo, '#demo-error');
Mount(ErrorDemo, "#demo-error");
```
### Disabled State
@@ -197,11 +219,11 @@ Mount(ErrorDemo, '#demo-error');
```javascript
const DisabledDemo = () => {
return Input({
value: 'john.doe',
disabled: true
value: "john.doe",
disabled: true,
});
};
Mount(DisabledDemo, '#demo-disabled');
Mount(DisabledDemo, "#demo-disabled");
```
### All Variants
@@ -215,33 +237,33 @@ Mount(DisabledDemo, '#demo-disabled');
```javascript
const VariantsDemo = () => {
const text = $('');
const text = $("");
const number = $(0);
return Div({ class: 'flex flex-col gap-4' }, [
return Div({ class: "flex flex-col gap-4" }, [
Input({
placeholder: 'Type something...',
placeholder: "Type something...",
value: text,
oninput: (e) => text(e.target.value)
oninput: (e) => text(e.target.value),
}),
Input({
type: 'number',
type: "number",
value: number,
oninput: (e) => number(parseInt(e.target.value) || 0)
oninput: (e) => number(parseInt(e.target.value) || 0),
}),
Input({
type: 'date',
value: $('2024-01-01')
type: "date",
value: $("2024-01-01"),
}),
Input({class: 'input-primary', value: "Primary"}),
Input({class: 'input-secondary', value: "Secondary"}),
Input({class: 'input-accent', value: "Accent"}),
Input({class: 'input-ghost', value: "Ghost"}),
Input({class: 'input-info', value: "Info"}),
Input({class: 'input-success', value: "Success"}),
Input({class: 'input-warning', value: "Warning"}),
Input({class: 'input-error', value: "Error"}),
Input({ class: "input-primary", value: "Primary" }),
Input({ class: "input-secondary", value: "Secondary" }),
Input({ class: "input-accent", value: "Accent" }),
Input({ class: "input-ghost", value: "Ghost" }),
Input({ class: "input-info", value: "Info" }),
Input({ class: "input-success", value: "Success" }),
Input({ class: "input-warning", value: "Warning" }),
Input({ class: "input-error", value: "Error" }),
]);
};
Mount(VariantsDemo, '#demo-variants');
```
Mount(VariantsDemo, "#demo-variants");
```

View File

@@ -1,22 +1,27 @@
# Select
Dropdown select component with full DaisyUI styling, reactive items, and form integration.
Dropdown select component with full DaisyUI styling and reactive options.
## Tag
`Select`
`Select`, `Options`
## Props
## Select Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `label` | `string` | `-` | Label text above select |
| `items` | `Array<{value: string, label: string}> \| Signal<Array>` | `[]` | Array of items with value and label |
| `value` | `string \| Signal<string>` | `''` | Selected value |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `disabled` | `boolean \| Signal<boolean>` | `false` | Disabled state |
| `onchange` | `function` | `-` | Change event handler |
## Options Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `items` | `Array<string \| {value, label}> \| Signal<Array>` | `[]` | Array of items (strings or objects) |
| `placeholder` | `string` | `-` | Optional placeholder option (disabled, selected) |
## Styling
Select supports all **daisyUI Select classes**:
@@ -44,17 +49,23 @@ Select supports all **daisyUI Select classes**:
const BasicDemo = () => {
const selected = $('apple');
return Select({
label: 'Choose a fruit',
items: [
{ value: 'apple', label: '🍎 Apple' },
{ value: 'banana', label: '🍌 Banana' },
{ value: 'orange', label: '🍊 Orange' },
{ value: 'grape', label: '🍇 Grape' }
],
value: selected,
onchange: (e) => selected(e.target.value)
});
return Div({ class: 'form-control w-full max-w-xs' }, [
Label({ class: 'label' }, Span({ class: 'label-text' }, 'Choose a fruit')),
Select({
class: 'select select-bordered w-full',
value: selected,
onchange: (e) => selected(e.target.value)
}, [
Options({
items: [
{ value: 'apple', label: '🍎 Apple' },
{ value: 'banana', label: '🍌 Banana' },
{ value: 'orange', label: '🍊 Orange' },
{ value: 'grape', label: '🍇 Grape' }
]
})
])
]);
};
Mount(BasicDemo, '#demo-basic');
```
@@ -72,16 +83,22 @@ Mount(BasicDemo, '#demo-basic');
const ReactiveDemo = () => {
const selected = $('small');
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Select({
label: 'Select size',
items: [
{ value: 'small', label: 'Small' },
{ value: 'medium', label: 'Medium' },
{ value: 'large', label: 'Large' }
],
value: selected,
onchange: (e) => selected(e.target.value)
}),
Div({ class: 'form-control w-full max-w-xs' }, [
Label({ class: 'label' }, Span({ class: 'label-text' }, 'Select size')),
Select({
class: 'select select-bordered w-full',
value: selected,
onchange: (e) => selected(e.target.value)
}, [
Options({
items: [
{ value: 'small', label: 'Small' },
{ value: 'medium', label: 'Medium' },
{ value: 'large', label: 'Large' }
]
})
])
]),
Div({ class: 'alert alert-info' }, () => `You selected: ${selected()}`)
]);
};
@@ -99,16 +116,22 @@ Mount(ReactiveDemo, '#demo-reactive');
```javascript
const DisabledDemo = () => {
return Select({
label: 'Country (disabled)',
items: [
{ value: 'mx', label: 'Mexico' },
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' }
],
value: 'mx',
disabled: true
});
return Div({ class: 'form-control w-full max-w-xs' }, [
Label({ class: 'label' }, Span({ class: 'label-text' }, 'Country (disabled)')),
Select({
class: 'select select-bordered w-full',
value: 'mx',
disabled: true
}, [
Options({
items: [
{ value: 'mx', label: 'Mexico' },
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' }
]
})
])
]);
};
Mount(DisabledDemo, '#demo-disabled');
```
@@ -139,24 +162,36 @@ const DynamicDemo = () => {
};
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Select({
label: 'Category',
items: [
{ value: 'fruits', label: 'Fruits' },
{ value: 'vegetables', label: 'Vegetables' }
],
value: category,
onchange: (e) => {
category(e.target.value);
selectedItem('');
}
}),
Select({
label: 'Item',
items: () => items[category()] || [],
value: selectedItem,
onchange: (e) => selectedItem(e.target.value)
}),
Div({ class: 'form-control w-full max-w-xs' }, [
Label({ class: 'label' }, Span({ class: 'label-text' }, 'Category')),
Select({
class: 'select select-bordered w-full',
value: category,
onchange: (e) => {
category(e.target.value);
selectedItem('');
}
}, [
Options({
items: [
{ value: 'fruits', label: 'Fruits' },
{ value: 'vegetables', label: 'Vegetables' }
]
})
])
]),
Div({ class: 'form-control w-full max-w-xs' }, [
Label({ class: 'label' }, Span({ class: 'label-text' }, 'Item')),
Select({
class: 'select select-bordered w-full',
value: selectedItem,
onchange: (e) => selectedItem(e.target.value)
}, [
Options({
items: () => items[category()] || []
})
])
]),
() => selectedItem() ? Div({ class: 'alert alert-success' }, `Selected: ${selectedItem()}`) : null
]);
};
@@ -179,38 +214,53 @@ const VariantsDemo = () => {
const ghost = $('');
return Div({ class: 'flex flex-col gap-4' }, [
Select({
label: 'Primary Select',
class: 'select-primary',
items: [
{ value: 'option1', label: 'Option 1' },
{ value: 'option2', label: 'Option 2' },
{ value: 'option3', label: 'Option 3' }
],
value: primary,
onchange: (e) => primary(e.target.value)
}),
Select({
label: 'Secondary Select',
class: 'select-secondary',
items: [
{ value: 'option1', label: 'Option 1' },
{ value: 'option2', label: 'Option 2' }
],
value: secondary,
onchange: (e) => secondary(e.target.value)
}),
Select({
label: 'Ghost Select',
class: 'select-ghost',
items: [
{ value: '', label: 'Select an option' },
{ value: 'opt1', label: 'Option 1' },
{ value: 'opt2', label: 'Option 2' }
],
value: ghost,
onchange: (e) => ghost(e.target.value)
})
Div({ class: 'form-control w-full max-w-xs' }, [
Label({ class: 'label' }, Span({ class: 'label-text' }, 'Primary Select')),
Select({
class: 'select select-primary w-full',
value: primary,
onchange: (e) => primary(e.target.value)
}, [
Options({
items: [
{ value: 'option1', label: 'Option 1' },
{ value: 'option2', label: 'Option 2' },
{ value: 'option3', label: 'Option 3' }
]
})
])
]),
Div({ class: 'form-control w-full max-w-xs' }, [
Label({ class: 'label' }, Span({ class: 'label-text' }, 'Secondary Select')),
Select({
class: 'select select-secondary w-full',
value: secondary,
onchange: (e) => secondary(e.target.value)
}, [
Options({
items: [
{ value: 'option1', label: 'Option 1' },
{ value: 'option2', label: 'Option 2' }
]
})
])
]),
Div({ class: 'form-control w-full max-w-xs' }, [
Label({ class: 'label' }, Span({ class: 'label-text' }, 'Ghost Select')),
Select({
class: 'select select-ghost w-full',
value: ghost,
onchange: (e) => ghost(e.target.value)
}, [
Options({
placeholder: 'Select an option',
items: [
{ value: 'opt1', label: 'Option 1' },
{ value: 'opt2', label: 'Option 2' }
]
})
])
])
]);
};
Mount(VariantsDemo, '#demo-variants');

View File

@@ -10,7 +10,7 @@
href="//cdn.jsdelivr.net/npm/docsify/lib/themes/vue.css"
/>
<link href="./sigpro.css" rel="stylesheet" type="text/css" />
<link href="./sigpro-ui.min.css" rel="stylesheet" type="text/css" />
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>

2
docs/sigpro-ui.min.css vendored Normal file

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

@@ -3,10 +3,12 @@ import * as AlertModule from './components/Alert.js';
import * as AutocompleteModule from './components/Autocomplete.js';
import * as BadgeModule from './components/Badge.js';
import * as ButtonModule from './components/Button.js';
import * as CalendarModule from './components/Calendar.js';
import * as CardModule from './components/Card.js';
import * as CarouselModule from './components/Carousel.js';
import * as ChatModule from './components/Chat.js';
import * as CheckboxModule from './components/Checkbox.js';
import * as CollapseModule from './components/Collapse.js';
import * as ColorpickerModule from './components/Colorpicker.js';
import * as DatepickerModule from './components/Datepicker.js';
import * as DrawerModule from './components/Drawer.js';
@@ -16,9 +18,9 @@ import * as FieldsetModule from './components/Fieldset.js';
import * as FileinputModule from './components/Fileinput.js';
import * as IconModule from './components/Icon.js';
import * as IndicatorModule from './components/Indicator.js';
import * as KdbModule from './components/Kdb.js';
import * as InputModule from './components/Input.js';
import * as ListModule from './components/List.js';
import * as KdbModule from './components/Kdb.js';
// import * as ListModule from './components/List.js';
import * as LoadingModule from './components/Loading.js';
import * as MenuModule from './components/Menu.js';
import * as ModalModule from './components/Modal.js';
@@ -36,10 +38,9 @@ import * as SwapModule from './components/Swap.js';
import * as TableModule from './components/Table.js';
import * as TabsModule from './components/Tabs.js';
import * as TextareaModule from './components/Textarea.js';
import * as TextRotateModule from './components/TextRotate.js';
import * as TextrotateModule from './components/Textrotate.js';
import * as TimelineModule from './components/Timeline.js';
import * as ToastModule from './components/Toast.js';
import * as ToggleModule from './components/Toggle.js';
import * as TooltipModule from './components/Tooltip.js';
import { Locale, tt } from './utils.js';
@@ -49,10 +50,12 @@ export const Components = {
...AutocompleteModule,
...BadgeModule,
...ButtonModule,
...CalendarModule,
...CardModule,
...CarouselModule,
...ChatModule,
...CheckboxModule,
...CollapseModule,
...ColorpickerModule,
...DatepickerModule,
...DrawerModule,
@@ -62,9 +65,9 @@ export const Components = {
...FileinputModule,
...IconModule,
...IndicatorModule,
...KdbModule,
...InputModule,
...ListModule,
...KdbModule,
// ...ListModule,
...LoadingModule,
...MenuModule,
...ModalModule,
@@ -82,10 +85,9 @@ export const Components = {
...TableModule,
...TabsModule,
...TextareaModule,
...TextRotateModule,
...TextrotateModule,
...TimelineModule,
...ToastModule,
...ToggleModule,
...TooltipModule
};

View File

@@ -44,17 +44,18 @@
},
"scripts": {
"clean": "rm -rf ./dist ./css/*.css ./docs/*.js ./docs/*.css",
"build:cssmin": "tailwindcss -i ./sigpro.css -o ./dist/sigpro.min.css --content './src/**/*.js' --minify",
"build:css": "tailwindcss -i ./sigpro.css -o ./dist/sigpro.css --content './src/**/*.js'",
"build:cssdocs": "tailwindcss -i ./sigpro.css -o ./docs/sigpro.css --content './src/**/*.js' --minify",
"build:css": "tailwindcss -i ./sigpro.css -o ./dist/sigpro-ui.css --content './src/**/*.js'",
"build:cssmin": "tailwindcss -i ./sigpro.css -o ./dist/sigpro-ui.min.css --content './src/**/*.js' --minify",
"build:js": "bun run build:js:iife && bun run build:js:esm",
"build:js:iife": "bun build ./index.js --bundle --outfile=./dist/sigpro-ui.js --format=iife --global-name=SigProUI && bun build ./index.js --bundle --outfile=./dist/sigpro-ui.min.js --format=iife --global-name=SigProUI --minify",
"build:js:esm": "bun build ./index.js --bundle --outfile=./dist/sigpro-ui.esm.js --format=esm && bun build ./index.js --bundle --outfile=./dist/sigpro-ui.esm.min.js --format=esm --minify",
"build:jsdocs": "bun build ./index.js --bundle --outfile=./docs/sigpro-ui.min.js --format=iife --global-name=SigProUI --minify",
"build": "bun run clean && bun run build:css && bun run build:js && bun run build:jsdocs && bun run build:cssdocs && bun run build:cssmin",
"build:js:iife": "bun build ./index.js --bundle --outfile=./dist/sigpro-ui.js --format=iife --global-name=SigProUI",
"build:js:iife:min": "bun build ./index.js --bundle --outfile=./dist/sigpro-ui.min.js --format=iife --global-name=SigProUI --minify",
"build:js:esm": "bun build ./index.js --bundle --outfile=./dist/sigpro-ui.esm.js --format=esm",
"build:js:esm:min": "bun build ./index.js --bundle --outfile=./dist/sigpro-ui.esm.min.js --format=esm --minify",
"copy:docs": "cp dist/sigpro-ui.min.css dist/sigpro-ui.min.js docs/",
"build": "bun run clean && bun run build:css && bun run build:cssmin && bun run build:js:iife && bun run build:js:iife:min && bun run build:js:esm && bun run build:js:esm:min && bun run copy:docs",
"prepublishOnly": "bun run build",
"docs": "bun x serve docs"
},
"type": "module",
"unpkg": "./dist/sigpro-ui.min.js"
}
}

View File

@@ -60,7 +60,7 @@
font-size: 14px;
}
/* Agrupamos los selectores normales de CSS */
.input,
.select,
.textarea {
@@ -79,7 +79,6 @@
&:focus {
--focus-color: var(--color-primary);
/* Selectores que detectan la variante de color sin importar el prefijo */
&[class*="-secondary"] {
--focus-color: var(--color-secondary);
}