Before repair nav components
All checks were successful
Deploy Docs to Synology / deploy (push) Successful in 4s

This commit is contained in:
2026-04-25 11:24:39 +02:00
parent e842ed8041
commit 910c6ab3c7
71 changed files with 4260 additions and 2819 deletions

735
components/All.js Normal file
View File

@@ -0,0 +1,735 @@
// All base components
import { h, each, watch, when, fx, mount, $ } from "sigpro";
// Helpers
export const get = val => typeof val === "function" ? val() : val;
export const getBy = (item, field = 'label') => (item && typeof item === 'object') ? item[field] : item;
export const cls = (...classes) => classes.filter(Boolean).join(' ').trim();
export const isFn = f => typeof f === "function";
export const filterBy = (items, query, field = 'label') => {
const searchTerm = String(query).toLowerCase();
const list = get(items);
return !searchTerm ? list : list.filter(item => {
const text = (item && typeof item === 'object') ? item[field] : item;
return String(text).toLowerCase().includes(searchTerm);
});
};
export const listKey = (items, isOpen) => {
const cursor = $(-1);
watch(() => { if (!get(isOpen)) cursor(-1) });
const onKey = (e, select) => {
const list = get(items), i = cursor(), len = list.length;
if (!len) return;
const k = e.key;
k === 'ArrowDown' ? (e.preventDefault(), isOpen(true), cursor(Math.min(i + 1, len - 1))) :
k === 'ArrowUp' ? (e.preventDefault(), cursor(Math.max(i - 1, 0))) :
k === 'Enter' ? (i >= 0 && (e.preventDefault(), select(list[i]))) :
k === 'Escape' ? isOpen(false) : null;
};
return { cursor, onKey };
};
export const Alert = (p, c) => h("div", { ...p, class: cls("alert", p.class) }, c);
export const Badge = (p, c) => h("span", { ...p, class: cls("badge", p.class) }, c);
export const Button = (p, c) => h("button", { ...p, class: cls("btn", p.class) }, c);
export const Card = (p, c) => h("div", { ...p, class: cls("card", p.class) }, c);
export const CardTitle = (p, c) => h("div", { ...p, class: cls("card-title", p.class) }, c);
export const CardBody = (p, c) => h("div", { ...p, class: cls("card-body", p.class) }, c);
export const CardActions = (p, c) => h("div", { ...p, class: cls("card-actions", p.class) }, c);
export const Carousel = (p, c) => h("div", { ...p, class: cls("carousel", p.class) }, c);
export const CarouselItem = (p, c) => h("div", { ...p, class: cls("carousel-item", p.class) }, c);
export const Chat = (p, c) => h("div", { ...p, class: cls("chat", p.class) }, c);
export const ChatBubble = (p, c) => h("div", { ...p, class: cls("chat-bubble", p.class) }, c);
export const ChatFooter = (p, c) => h("div", { ...p, class: cls("chat-footer", p.class) }, c);
export const ChatHeader = (p, c) => h("div", { ...p, class: cls("chat-header", p.class) }, c);
export const ChatImage = (p, c) => h("div", { ...p, class: cls("chat-image avatar", p.class) }, h("div", { class: "w-10 rounded-full" }, typeof c === "string" ? h("img", { src: c, alt: "avatar" }) : c));
export const Checkbox = (p) => h("input", { ...p, type: "checkbox", class: cls("checkbox", p.class) });
export const Divider = (p) => h("div", { ...p, class: cls("divider", p.class) });
export const Fab = (p, c) => h("div", { ...p, class: cls("fab", p.class) }, c);
export const Fieldset = (p, c) => h("fieldset", { ...p, class: cls("fieldset", p.class) }, [p.legend && h("legend", { class: "fieldset-legend" }, p.legend), c]);
export const Icon = (p) => h("span", { class: p.startsWith("icon-") ? p : "" }, p.startsWith("icon-") ? null : p);
export const Indicator = (p, c) => h("div", { ...p, class: cls("indicator", p.class) }, [p.value && h("span", { class: cls("indicator-item badge", p.class) }, p.value), c]);
export const Kbd = (p, c) => h("kbd", { ...p, class: cls("kbd", p.class) }, c);
export const Loading = (p, c) => h("span", { ...p, class: cls("loading loading-spinner", p.class) }, c);
export const Navbar = (p, c) => h("div", { ...p, class: cls("navbar", p.class) }, c);
export const Progress = (p) => h("progress", { ...p, class: cls("progress", p.class) });
export const Radial = (p, c) => h("div", { ...p, class: cls("radial-progress", p.class), style: `--value:${p.value ?? 0};${p.style ?? ''}`, role: "progressbar", "aria-valuenow": p.value ?? 0 }, c ?? `${p.value ?? 0}%`)
export const Radio = (p) => h("input", { ...p, type: "radio", class: cls("radio", p.class) });
export const Range = (p) => h("input", { ...p, type: "range", class: cls("range", p.class) });
export const Skeleton = (p) => h("div", { ...p, class: cls("skeleton", p.class) });
export const SkeletonText = (p) => h("span", { ...p, class: cls("skeleton skeleton-text", p.class) });
export const Stack = (p, c) => h("div", { ...p, class: cls("stack", p.class) }, c);
export const Steps = (p, c) => h("ul", { ...p, class: cls("steps", p.class) }, c);
export const Step = (p, c) => h("li", { ...p, class: cls("step", p.class), "data-content": p.dataContent }, c);
export const Swap = (p) => h("label", { ...p, class: cls("swap", p.class) }, [
h("input", { type: "checkbox", checked: () => get(p.value), onchange: (e) => isFn(p.value) && p.value(e.target.checked) }),
h("div", { class: "swap-on" }, p.on),
h("div", { class: "swap-off" }, p.off)
]);
export const Textarea = (p) => h("textarea", { ...p, class: cls("textarea", p.class) });
export const TextRotate = (p) => {
const words = Array.isArray(p.words) ? p.words : (typeof p.words === 'string' ? p.words.split(',') : []);
return h("span", { ...p, class: cls("text-rotate", p.class) }, h("span", {}, words.map(w => h("span", {}, w))));
};
export const Timeline = (p, c) => h("ul", {
...p,
class: cls("timeline",
p.vertical !== false ? 'timeline-vertical' : 'timeline-horizontal',
p.compact ? 'timeline-compact' : '',
p.class
)
}, c);
export const Toggle = (p) => h("input", { ...p, type: "checkbox", class: cls("toggle", p.class) });
export const Tooltip = (p, c) => h("div", { ...p, class: cls("tooltip", p.class), "data-tip": p.tip }, c);
// Complex Components
// Accordion
export const Accordion = (p) => {
const name = p.name || `acc-${Math.random().toString(36).slice(2)}`
const base = cls('collapse', p.variant && `collapse-${p.variant}`, p.class)
const itemFn = (it) => {
const t = getBy(it, 'title')
const c = it.content
return p.type === 'details'
? h('details', { class: base, name, open: it.open || undefined },
h('summary', { class: 'collapse-title font-semibold' }, t),
c ? h('div', { class: 'collapse-content text-sm' }, c) : null)
: h('div', { class: base },
h('input', { type: 'radio', name, checked: it.open || undefined }),
h('div', { class: 'collapse-title font-semibold' }, t),
c ? h('div', { class: 'collapse-content text-sm' }, c) : null)
}
return isFn(p.items) ? each(p.items, itemFn, (it, i) => it?.id ?? i) : (p.items || []).map(itemFn)
}
// Table
export const Table = (p) => {
if (p.children !== undefined) return h('table', { class: cls('table', p.class), ...p }, p.children)
const { items, columns = [], header = true, keyFn, ...rest } = p
const hd = header !== false && columns.some(c => c.label) ? h('thead', {}, h('tr', {}, columns.map(c => h('th', { class: c.class }, c.label)))) : null
const bd = h('tbody', {}, each(
() => get(items) || [],
(it, idx) => h('tr', {}, columns.map(c => {
const v = c.render ? c.render(it, idx) : it[c.key]
return h('td', { class: c.class }, v)
})),
keyFn || ((it, idx) => it?.id ?? idx)
))
return h('table', { class: cls('table', rest.class), ...rest }, [hd, bd])
}
// Tabs
export const Tabs = (p) => {
if (p.children !== undefined) return h('div', { class: cls('tabs', p.class), ...p }, p.children)
const { items, activeIndex, onClose, ...rest } = p
const closeHandler = onClose || (isFn(items) ? (idx) => {
const arr = get(items)
const newArr = arr.filter((_, i) => i !== idx)
items(newArr)
if (activeIndex() >= newArr.length) activeIndex(Math.max(0, newArr.length - 1))
} : null)
return h('div', { class: cls('tabs', p.class), ...rest },
each(
() => get(items) || [],
(it, idx) => {
const act = () => activeIndex() === idx
return [
h('a', {
role: 'tab',
class: () => `tab ${act() ? 'tab-active' : ''} ${it.class || ''}`,
onclick: (e) => { e.preventDefault(); activeIndex(idx); it.onclick?.(e) }
}, getBy(it), it.closable ? h('span', {
class: 'icon-[lucide--x] w-3.5 h-3.5 ml-2 cursor-pointer hover:opacity-70',
onclick: (e) => { e.stopPropagation(); closeHandler?.(idx) }
}) : null),
h('div', {
class: `tab-content ${it.contentClass || ''}`,
style: () => `display: ${act() ? 'block' : 'none'};`
}, isFn(it.content) ? it.content() : it.content)
]
},
(it, idx) => it.id ?? idx
)
)
}
// Rating
export const Rating = (p) => {
const name = `rating-${Math.random().toString(36).slice(2, 7)}`
const stars = p.children ?? Array.from({ length: p.count || 5 }, (_, i) => {
const v = i + 1
return h('input', {
type: 'radio',
name,
class: cls('mask', p.mask || 'mask-star'),
checked: () => get(p.value) === v,
onchange: () => isFn(p.value) ? p.value(v) : p.onchange?.(v)
})
})
return h('div', { class: cls('rating', p.class), ...p }, stars)
}
// Menu
export const Menu = (p) => {
if (p.children !== undefined) return h('ul', { class: cls('menu', p.class), ...p }, p.children)
const { items, keyFn = (it, idx) => it?.id ?? idx, ...rest } = p
const render = (item) => item.children
? h('li', {}, h('details', {}, h('summary', {}, getBy(item)), h('ul', {}, Menu({ items: item.children }))))
: h('li', {}, h('a', {
href: item.href,
onclick: item.onclick ? (e) => { if (!item.href) e.preventDefault(); item.onclick(e) } : null
}, getBy(item)))
return h('ul', { class: cls('menu', rest.class), ...rest },
each(() => get(items) || [], render, keyFn)
)
}
//Drawer
export const Drawer = (p, c) => {
const id = p.id || `drawer-${Math.random().toString(36).slice(2, 9)}`
return h('div', { class: cls('drawer', p.class) }, [
h('input', {
id,
type: 'checkbox',
class: 'drawer-toggle',
checked: () => get(p.open),
onchange: (e) => isFn(p.open) && p.open(e.target.checked)
}),
h('div', { class: 'drawer-content' }, c),
h('div', { class: 'drawer-side' }, [
h('label', {
for: id,
class: 'drawer-overlay',
onclick: () => isFn(p.open) && p.open(false)
}),
h('div', { class: 'min-h-full bg-base-200 w-80 p-4' }, () => get(p.side))
])
])
}
//Dropdown
export const Dropdown = (p, c) => {
const { trigger, items, ...rest } = p
const content = c || (items ? h('ul', {
class: 'menu dropdown-content bg-base-100 rounded-box z-[1] w-52 p-2 shadow'
}, each(
() => get(items) || [],
(item) => h('li', {},
h('a', {
onclick: (e) => {
item.onclick?.(e)
e.currentTarget.closest('details').open = false
}
}, getBy(item))
),
(it, idx) => it.id ?? idx
)) : null)
return h('details', { class: cls('dropdown', rest.class), ...rest }, [
h('summary', { class: 'btn m-1' }, trigger || 'Dropdown'),
content
])
}
//Select
export const Select = (p, c) => {
if (c !== undefined) return h('select', { class: cls('select', p.class), ...p }, c)
const { label, float, placeholder, placeholderDisabled = true, value, left, right, hint, items, keyFn, ...rest } = p
const opts = () => {
const raw = get(items) || []
const ph = placeholder ? [{ disabled: placeholderDisabled, label: placeholder, value: '' }] : []
return [...ph, ...raw]
}
return h('label', { class: float ? 'floating-label' : '' }, [
float && h('span', {}, label),
h('label', { class: cls('select', rest.class) }, [
label && !float && h('span', { class: 'label' }, label),
left ?? null,
h('select', {
value: () => get(value),
onchange: (e) => isFn(value) ? value(e.target.value) : rest.onchange?.(e)
},
each(opts, (item) => {
const val = getBy(item, item.value !== undefined ? 'value' : undefined)
const lab = getBy(item, 'label')
return h('option', { value: val, disabled: item.disabled || undefined }, lab)
}, (it, i) => it?.id ?? (typeof it === 'string' ? it : it.value) ?? i)
),
right ?? null
]),
hint && h('div', { class: 'validator-hint' }, hint)
])
}
// Autocomplete
export const Autocomplete = ({ items, value, onselect, placeholder = 'Buscar...', ...props }) => {
const query = $(get(value) || '');
const isOpen = $(false);
const filtered = $(() => filterBy(items, query()));
const { cursor, onKey } = listKey(filtered, isOpen);
const pick = (item) => {
const display = getBy(item);
const actual = typeof item === 'string' ? item : item.value;
query(display);
if (isFn(value)) value(actual);
onselect?.(item);
isOpen(false);
};
return h('div', { class: 'relative w-full' }, [
Input({
...props,
type: 'text',
placeholder,
value: query,
left: h('span', { class: 'icon-[lucide--search]' }),
oninput: (e) => {
query(e.target.value);
if (isFn(value)) value(e.target.value);
isOpen(true);
},
onfocus: () => isOpen(true),
onblur: () => setTimeout(() => isOpen(false), 150),
onkeydown: (e) => onKey(e, pick)
}),
when(isOpen, () =>
fx({ duration: 200, slide: true },
h('ul', {
class: 'absolute left-0 w-full menu bg-base-100 rounded-box mt-1 p-2 shadow-xl max-h-60 overflow-y-auto border border-base-300 z-50 flex-col flex-nowrap'
}, [
each(filtered, (item, idx) =>
h('li', {}, [
h('a', {
class: () => cursor() === idx ? 'active bg-primary text-primary-content' : '',
onmousedown: (e) => e.preventDefault(), // evita que el blur cierre antes del click
onclick: () => pick(item),
onmouseenter: () => cursor(idx)
}, getBy(item))
]),
(item, idx) => getBy(item) + idx
),
() => filtered().length === 0
? h('li', { class: 'p-4 opacity-50 text-center' }, 'Sin resultados')
: null
])
)
)
]);
};
//Input
export const Input = (p) => {
const { label, icon, float, placeholder, value, left, right, rule, hint, content, ...rest } = p;
const showPassword = $(false);
const isFocused = $(false);
const isPassword = p.type === 'password';
const pattern = rule ?? null;
const inputType = () => isPassword
? (get(showPassword) ? 'text' : 'password')
: (p.type || 'text');
return h("div", {
class: "input-container",
onfocusin: () => isFocused(true),
onfocusout: (e) => {
if (!e.currentTarget.contains(e.relatedTarget)) { isFocused(false); }
}
}, [
h('label', { class: float ? 'floating-label' : '' }, [
float ? h("span", {}, label) : null,
h("label", { pattern: pattern, class: () => cls('input validator', p.class) },
[
label && !float ? h('span', { class: 'label' }, label) : null,
left ?? null,
h('input', { ...rest, type: inputType, class: 'grow', pattern: pattern, placeholder: placeholder || label || ' ', value: value }), right ?? null,
isPassword ? h('label', { class: 'swap swap-rotate ml-2' }, [
h('input', { type: 'checkbox', onchange: (e) => showPassword(e.target.checked) }),
h('span', { class: 'swap-on icon-[lucide--eye]' }),
h('span', { class: 'swap-off icon-[lucide--eye-off]' })
]) : null
]),
hint ? h('div', { class: "validator-hint" }, hint) : null,
when(isFocused, () => fx({ duration: 300, slide: true },
h('div', { class: 'input-content', onmousedown: e => e.preventDefault() },
[
isFn(content) ? content(isFocused) : content
])
))
]),
]);
};
//Colorpicker
export const Colorpicker = (p) => {
const isOpen = $(false)
const current = () => get(p.value) || '#000000'
const palette = [
'#000', '#1A1A1A', '#333', '#4D4D4D', '#666', '#808080', '#B3B3B3', '#FFF',
'#450a0a', '#7f1d1d', '#991b1b', '#b91c1c', '#dc2626', '#ef4444', '#f87171', '#fca5a5',
'#431407', '#7c2d12', '#9a3412', '#c2410c', '#ea580c', '#f97316', '#fb923c', '#ffedd5',
'#713f12', '#a16207', '#ca8a04', '#eab308', '#facc15', '#fde047', '#fef08a', '#fff9c4',
'#064e3b', '#065f46', '#059669', '#10b981', '#34d399', '#4ade80', '#84cc16', '#d9f99d',
'#082f49', '#075985', '#0284c7', '#0ea5e9', '#38bdf8', '#7dd3fc', '#22d3ee', '#cffafe',
'#1e1b4b', '#312e81', '#4338ca', '#4f46e5', '#6366f1', '#818cf8', '#a5b4fc', '#e0e7ff',
'#2e1065', '#4c1d95', '#6d28d9', '#7c3aed', '#8b5cf6', '#a855f7', '#d946ef', '#fae8ff'
]
const pick = (c) => { isFn(p.value) ? p.value(c) : p.onchange?.(c); isOpen(false) }
return h('div', { class: cls('relative w-fit', p.class) }, [
h('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()) }
}, [
h('div', { class: 'size-5 rounded-sm shadow-inner border border-black/10 shrink-0', style: () => `background-color: ${current()}` }),
p.label && h('span', { class: 'opacity-80' }, p.label)
]),
when(isOpen, () => [
h('div', {
class: 'fixed inset-0 z-[100]',
onclick: () => isOpen(false)
}),
h('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' },
h('div', { class: 'grid grid-cols-8 gap-1' },
palette.map(c =>
h('button', {
type: 'button',
style: `background-color: ${c}`,
class: () => {
const act = current().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 ${act ? 'ring-2 ring-offset-1 ring-primary z-10 scale-110' : ''}`
},
onclick: () => pick(c)
})
)
)
)
])
])
}
// Calendar
export const Calendar = (p) => {
const internalDate = $(new Date())
const hoverDate = $(null)
const startHour = $(0)
const endHour = $(0)
const now = new Date()
const todayStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
const fmt = d => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
const rangeMode = () => get(p.range) === true
const current = () => get(p.value)
const selectDate = (date) => {
const s = fmt(date)
const v = current()
if (rangeMode()) {
if (!v?.start || (v.start && v.end)) {
p.onChange?.({ start: s, end: null, ...(p.hour && { startHour: startHour() }) })
} else {
const start = v.start
const nv = s < start ? { start: s, end: start } : { start, end: s }
if (p.hour) { nv.startHour = v.startHour ?? startHour(); nv.endHour = endHour() }
p.onChange?.(nv)
}
} else {
p.onChange?.(p.hour ? `${s}T${String(startHour()).padStart(2, '0')}:00:00` : s)
}
}
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: onH }) => h('div', { class: 'flex-1' }, [
h('div', { class: 'flex gap-2 items-center' }, [
h('input', { type: 'range', min: 0, max: 23, value: hVal, class: 'range range-xs flex-1', oninput: e => onH(+e.target.value) }),
h('span', { class: 'text-sm font-mono min-w-[48px] text-center' }, () => String(get(hVal)).padStart(2, '0') + ':00')
])
])
return h('div', { class: cls('p-4 bg-base-100 border border-base-300 shadow-2xl rounded-box w-80 select-none', p.class) }, [
h('div', { class: 'flex justify-between items-center mb-4 gap-1' }, [
h('div', { class: 'flex gap-0.5' }, [
h('button', { type: 'button', class: 'btn btn-ghost btn-xs px-1', onclick: () => moveYear(-1) }, h('span', { class: 'icon-[lucide--chevrons-left]' })),
h('button', { type: 'button', class: 'btn btn-ghost btn-xs px-1', onclick: () => move(-1) }, h('span', { class: 'icon-[lucide--chevron-left]' }))
]),
h('span', { class: 'font-bold uppercase flex-1 text-center' }, () => internalDate().toLocaleString('es-ES', { month: 'short', year: 'numeric' })),
h('div', { class: 'flex gap-0.5' }, [
h('button', { type: 'button', class: 'btn btn-ghost btn-xs px-1', onclick: () => move(1) }, h('span', { class: 'icon-[lucide--chevron-right]' })),
h('button', { type: 'button', class: 'btn btn-ghost btn-xs px-1', onclick: () => moveYear(1) }, h('span', { class: 'icon-[lucide--chevrons-right]' }))
])
]),
h('div', { class: 'grid grid-cols-7 gap-1', onmouseleave: () => hoverDate(null) }, [
...['L', 'M', 'X', 'J', 'V', 'S', 'D'].map(d => h('div', { class: 'text-[10px] opacity-40 font-bold text-center' }, d)),
() => {
const d = internalDate(), y = d.getFullYear(), m = d.getMonth()
const firstDay = new Date(y, m, 1).getDay()
const offset = firstDay === 0 ? 6 : firstDay - 1
const dim = new Date(y, m + 1, 0).getDate()
const cells = []
for (let i = 0; i < offset; i++) cells.push(h('div'))
for (let i = 1; i <= dim; i++) {
const date = new Date(y, m, i), ds = fmt(date)
cells.push(h('button', {
type: 'button',
class: () => {
const v = current(), h = hoverDate()
const isStart = typeof v === 'string' ? v.split('T')[0] === ds : v?.start === ds
const isEnd = v?.end === ds
let inRange = false
if (rangeMode() && v?.start) {
const start = v.start
if (!v.end && h) inRange = (ds > start && ds <= h) || (ds < start && ds >= h)
else if (v.end) inRange = ds > start && ds < v.end
}
const base = 'btn btn-xs p-0 aspect-square min-h-0 h-auto font-normal relative'
const st = isStart || isEnd ? 'btn-primary z-10' : inRange ? 'bg-primary/20 border-none rounded-none' : 'btn-ghost'
const today = ds === todayStr ? 'ring-1 ring-primary ring-inset font-black text-primary' : ''
return cls(base, st, today)
},
onmouseenter: () => rangeMode() && hoverDate(ds),
onclick: () => selectDate(date)
}, i.toString()))
}
return cells
}
]),
p.hour ? h('div', { class: 'mt-3 pt-2 border-t border-base-300' },
rangeMode()
? h('div', { class: 'flex gap-4' }, [HourSlider({ value: startHour, onChange: h => startHour(h) }), HourSlider({ value: endHour, onChange: h => endHour(h) })])
: HourSlider({ value: startHour, onChange: h => startHour(h) })
) : null
])
}
//Datepicker.js
export const Datepicker = (p) => {
const isOpen = $(false)
const displayValue = $("")
const rangeMode = () => get(p.range) === true
watch(() => {
const v = get(p.value)
if (!v) return displayValue("")
let text = ""
if (typeof v === "string") {
text = p.hour && v.includes("T") ? v.replace("T", " ") : v
} else if (v.start && v.end) {
const startStr = p.hour && v.startHour != null ? `${v.start} ${String(v.startHour).padStart(2, "0")}:00` : v.start
const endStr = p.hour && v.endHour != null ? `${v.end} ${String(v.endHour).padStart(2, "0")}:00` : v.end
text = `${startStr} - ${endStr}`
} else if (v.start) {
const startStr = p.hour && v.startHour != null ? `${v.start} ${String(v.startHour).padStart(2, "0")}:00` : v.start
text = `${startStr}...`
}
displayValue(text)
})
const handleChange = (val) => {
if (isFn(p.value)) p.value(val)
else p.onChange?.(val)
if (!rangeMode() || val?.end != null) isOpen(false)
}
return h('div', { class: cls('relative w-full', p.class) }, [
h('label', { class: 'input input-bordered w-full', onclick: (e) => { e.stopPropagation(); isOpen(!isOpen()) } }, [
h('span', { class: 'icon-[lucide--calendar]' }),
h('input', {
...p,
type: 'text',
class: 'grow',
value: displayValue,
readonly: true,
placeholder: p.placeholder || (rangeMode() ? 'Seleccionar rango...' : 'Seleccionar fecha...')
})
]),
when(isOpen, () => [
h('div', { class: 'fixed inset-0 z-[90]', onclick: () => isOpen(false) }),
h('div', { class: 'absolute left-0 mt-2 z-[100]', onclick: (e) => e.stopPropagation() },
Calendar({ value: p.value, range: rangeMode(), hour: p.hour, onChange: handleChange })
)
])
])
}
//Fileinput
export const Fileinput = (p) => {
const files = $([])
const drag = $(false)
const error = $(null)
const maxBytes = (p.max || 2) * 1024 * 1024
const process = (fileList) => {
const arr = Array.from(fileList)
error(null)
if (arr.some(f => f.size > maxBytes)) {
error(`Máx ${p.max || 2}MB`)
return
}
const updated = [...files(), ...arr]
files(updated)
if (isFn(p.onselect)) p.onselect(updated)
else if (isFn(p.value)) p.value(updated)
}
const remove = (idx) => {
const updated = files().filter((_, i) => i !== idx)
files(updated)
if (isFn(p.onselect)) p.onselect(updated)
else if (isFn(p.value)) p.value(updated)
}
return h('div', { class: cls('fieldset w-full p-0', p.class) }, [
h('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 ${drag() ? 'border-primary bg-primary/10' : 'border-base-content/20 bg-base-100 hover:bg-base-200'}`,
ondragover: (e) => { e.preventDefault(); drag(true) },
ondragleave: () => drag(false),
ondrop: (e) => { e.preventDefault(); drag(false); process(e.dataTransfer.files) }
}, [
h('div', { class: 'flex items-center gap-3 w-full' }, [
h('span', { class: 'icon-[lucide--upload]' }),
h('span', { class: 'text-sm opacity-70 truncate grow text-left' }, 'Arrastra o selecciona archivos...'),
h('span', { class: 'text-[10px] opacity-40 shrink-0' }, `Máx ${p.max || 2}MB`)
]),
h('input', {
type: 'file',
multiple: true,
accept: p.accept || '*',
class: 'hidden',
onchange: (e) => process(e.target.files)
})
]),
() => error() && h('span', { class: 'text-[10px] text-error mt-1 px-1 font-medium' }, error()),
when(() => files().length > 0, () =>
h('ul', { class: 'mt-2 space-y-1' },
each(files, (file, idx) =>
h('li', { class: 'flex items-center justify-between p-1.5 pl-3 text-xs bg-base-200/50 rounded-md border border-base-300' }, [
h('div', { class: 'flex items-center gap-2 truncate' }, [
h('span', { class: 'opacity-50' }, '📄'),
h('span', { class: 'truncate font-medium max-w-[200px]' }, file.name),
h('span', { class: 'text-[9px] opacity-40' }, `(${(file.size / 1024).toFixed(0)} KB)`)
]),
h('button', {
type: 'button',
class: 'btn btn-ghost btn-xs btn-circle',
onclick: (e) => { e.preventDefault(); remove(idx) }
}, h('span', { class: 'icon-[lucide--x]' }))
]),
(file) => file.name + file.lastModified
)
)
)
])
}
//Toast
export const Toast = (message, type = "alert-success", duration = 3500) => {
let container = document.getElementById("sigpro-toast-container");
if (!container) {
container = h("div", {
id: "sigpro-toast-container",
class: "fixed top-0 right-0 z-[9999] p-4 flex flex-col gap-2 pointer-events-none"
});
document.body.appendChild(container);
}
const host = h("div", { style: "display: contents" });
container.appendChild(host);
let closeFn, timer, enterTimer;
const ToastComponent = () => {
const visible = $(false);
const leaving = $(false);
closeFn = () => {
if (leaving()) return;
clearTimeout(timer);
clearTimeout(enterTimer);
leaving(true);
setTimeout(() => {
instance.destroy();
host.remove();
if (!container.hasChildNodes()) container.remove();
}, 300);
};
enterTimer = setTimeout(() => visible(true), 0);
const content = typeof message === 'function' ? get(message) : message;
const msgNode = typeof content === 'string' ? h("span", {}, content) : content;
return h("div", {
class: () => {
if (leaving()) return `alert alert-soft ${type} shadow-lg transition-all duration-300 translate-x-full opacity-0 pointer-events-auto`;
if (visible()) return `alert alert-soft ${type} shadow-lg transition-all duration-300 translate-x-0 opacity-100 pointer-events-auto`;
return `alert alert-soft ${type} shadow-lg transition-all duration-300 translate-x-10 opacity-0 pointer-events-auto`;
}
}, [
msgNode,
h("button", {
class: "btn btn-xs btn-circle btn-ghost",
onclick: closeFn
}, h("span", { class: "icon-[lucide--x]" }))
]);
};
const instance = mount(ToastComponent, host);
if (duration > 0) timer = setTimeout(closeFn, duration);
return closeFn;
};
//Modal
export const Modal = (p) => {
let dialogRef = null;
watch(() => {
const isOpen = get(p.open);
if (!dialogRef) return;
isOpen ? dialogRef.showModal() : dialogRef.close();
});
const close = () => isFn(p.open) && p.open(false);
return h("dialog", {
...p,
ref: el => dialogRef = el,
class: cls('modal', p.class),
onclose: close,
oncancel: close
}, [
h("div", { class: "modal-box" }, [
p.title && h("h3", { class: "text-lg font-bold" }, p.title),
p.children,
h("div", { class: "modal-action" }, [
p.actions || Button({ class: 'btn', onclick: close }, 'Cerrar')
])
]),
h("form", { method: "dialog", class: "modal-backdrop" }, [
h("button", {}, "close")
])
]);
};

View File

@@ -1,92 +0,0 @@
// components/Autocomplete.js
import { $, h, each, watch } from "sigpro";
export const Autocomplete = (props) => {
const query = $("");
const isOpen = $(false);
const cursor = $(-1);
const filteredItems = $([]);
watch(() => {
const v = typeof props.value === "function" ? props.value() : props.value;
return v || "";
}, (newVal) => setTimeout(() => query(newVal), 0));
watch(() => {
const q = String(query()).toLowerCase();
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)
)
: allItems;
filteredItems(filtered);
});
const pick = (item) => {
const display = typeof item === "string" ? item : item.label;
const actual = typeof item === "string" ? item : item.value;
query(display);
if (typeof props.value === "function") props.value(actual);
props.onselect?.(item);
isOpen(false);
cursor(-1);
};
const handleKeyDown = (e) => {
const list = filteredItems();
if (e.key === "ArrowDown") {
e.preventDefault();
isOpen(true);
cursor(Math.min(cursor() + 1, list.length - 1));
} else if (e.key === "ArrowUp") {
e.preventDefault();
cursor(Math.max(cursor() - 1, 0));
} else if (e.key === "Enter" && cursor() >= 0) {
e.preventDefault();
pick(list[cursor()]);
} else if (e.key === "Escape") {
isOpen(false);
}
};
return h("div", { class: `relative w-full ${props.class ?? ''}` }, [
h("label", { class: "input input-bordered w-full" }, [
h("span", { class: "icon-[lucide--search]" }),
h("input", {
...props,
type: "text",
class: "grow",
placeholder: props.placeholder || "Buscar...",
value: query,
onfocus: () => isOpen(true),
onblur: () => setTimeout(() => isOpen(false), 150),
onkeydown: handleKeyDown,
oninput: (e) => {
const newVal = e.target.value;
query(newVal);
if (typeof props.value === "function") props.value(newVal);
isOpen(true);
cursor(-1);
}
})
]),
h("ul", {
class: "absolute left-0 w-full menu bg-base-100 rounded-box mt-1 p-2 shadow-xl max-h-60 overflow-y-auto border border-base-300 z-50",
style: () => `display: ${isOpen() && filteredItems().length ? "block" : "none"};`
}, [
each(filteredItems, (item, idx) =>
h("li", {}, [
h("a", {
class: () => `block w-full ${cursor() === idx ? "active bg-primary text-primary-content" : ""}`,
onclick: () => pick(item),
onmouseenter: () => cursor(idx)
}, typeof item === "string" ? item : item.label)
]),
(item, idx) => (typeof item === "string" ? item : item.value) + idx
),
() => filteredItems().length === 0 ? h("li", { class: "flex justify-center p-4 opacity-50" }, h("span", { class: "icon-[lucide--search-x] text-2xl" })) : null
])
]);
};

145
components/Editor.js Normal file
View File

@@ -0,0 +1,145 @@
import { h, $ } from "sigpro"
import { get, cls, isFn } from "./All.js"
export const Editor = (p) => {
const { value, class: extraClass } = p
let editorRef = null
const isSource = $(false)
const source = $("")
const notify = () => {
if (!editorRef) return
const html = editorRef.innerHTML
if (isFn(value)) value(html)
else p.onchange?.(html)
}
const exec = (cmd, val = null) => {
if (!editorRef) return
editorRef.focus()
document.execCommand(cmd, false, val)
notify()
}
const queryState = (cmd, val = null) => {
if (!editorRef) return false
try {
if (cmd === 'formatBlock') {
const sel = window.getSelection()
if (!sel.rangeCount) return false
let node = sel.getRangeAt(0).commonAncestorContainer
while (node && node !== editorRef) {
if (node.nodeType === 1 && node.tagName === val) return true
node = node.parentNode
}
return false
}
return document.queryCommandState(cmd)
} catch (e) {
return false
}
}
const toolbar = h("div", { class: "flex flex-wrap items-center gap-1 p-2 border-b border-base-300 bg-base-200" }, [
h("div", { class: "flex flex-wrap gap-1 flex-1" }, [
h("button", {
type: "button",
class: () => `btn btn-ghost btn-xs ${queryState('bold') ? 'btn-active' : ''}`,
onclick: () => exec("bold")
}, h("span", { class: "icon-[lucide--bold]" })),
h("button", {
type: "button",
class: () => `btn btn-ghost btn-xs ${queryState('italic') ? 'btn-active' : ''}`,
onclick: () => exec("italic")
}, h("span", { class: "icon-[lucide--italic]" })),
h("button", {
type: "button",
class: () => `btn btn-ghost btn-xs ${queryState('underline') ? 'btn-active' : ''}`,
onclick: () => exec("underline")
}, h("span", { class: "icon-[lucide--underline]" })),
h("button", {
type: "button",
class: () => `btn btn-ghost btn-xs ${queryState('strikeThrough') ? 'btn-active' : ''}`,
onclick: () => exec("strikeThrough")
}, h("span", { class: "icon-[lucide--strikethrough]" })),
h("span", { class: "w-px h-5 bg-base-300 mx-1" }),
h("button", { type: "button", class: "btn btn-ghost btn-xs", onclick: () => exec("insertUnorderedList") }, h("span", { class: "icon-[lucide--list]" })),
h("button", { type: "button", class: "btn btn-ghost btn-xs", onclick: () => exec("insertOrderedList") }, h("span", { class: "icon-[lucide--list-ordered]" })),
h("span", { class: "w-px h-5 bg-base-300 mx-1" }),
h("button", {
type: "button",
class: () => `btn btn-ghost btn-xs ${queryState('formatBlock', 'BLOCKQUOTE') ? 'btn-active' : ''}`,
onclick: () => exec("formatBlock", queryState('formatBlock', 'BLOCKQUOTE') ? 'P' : 'BLOCKQUOTE')
}, h("span", { class: "icon-[lucide--quote]" })),
h("span", { class: "w-px h-5 bg-base-300 mx-1" }),
h("select", { class: "select select-xs w-16", onchange: (e) => exec("fontSize", e.target.value), value: "3" }, [
h("option", { value: "1" }, "1"),
h("option", { value: "2" }, "2"),
h("option", { value: "3" }, "3"),
h("option", { value: "4" }, "4"),
h("option", { value: "5" }, "5"),
h("option", { value: "6" }, "6"),
h("option", { value: "7" }, "7"),
]),
h("span", { class: "w-px h-5 bg-base-300 mx-1" }),
h("button", { type: "button", class: "btn btn-ghost btn-xs", onclick: () => exec("undo") }, h("span", { class: "icon-[lucide--undo-2]" })),
h("button", { type: "button", class: "btn btn-ghost btn-xs", onclick: () => exec("redo") }, h("span", { class: "icon-[lucide--redo-2]" })),
]),
h("div", { class: "flex" }, [
h("button", {
type: "button",
class: () => `btn btn-ghost btn-xs ${isSource() ? 'btn-active' : ''}`,
onclick: () => {
const wasSource = isSource()
if (!wasSource) {
source(editorRef?.innerHTML || "")
} else {
if (editorRef) {
editorRef.innerHTML = source()
notify()
}
}
isSource(!wasSource)
}
}, h("span", { class: "icon-[lucide--code-2]" }))
])
])
return h("div", { class: cls("border border-base-300 rounded-box bg-base-100 overflow-hidden", extraClass) }, [
toolbar,
h("div", { class: "relative" }, [
h("div", {
ref: el => {
if (!editorRef && el) {
editorRef = el
el.innerHTML = get(value) || ""
}
},
style: () => `min-height:10rem;${isSource() ? 'display:none' : ''}`,
class: "p-3 outline-none text-base-content [&_ul]:list-disc [&_ul]:pl-6 [&_ol]:list-decimal [&_ol]:pl-6 [&_li]:list-item [&_p]:m-0 [&_div]:m-0 [&_br]:content-[''] [&_br]:block [&_br]:h-[1em]",
contenteditable: "true",
oninput: notify,
onpaste: () => setTimeout(notify, 0)
}),
h("textarea", {
class: "w-full min-h-[10rem] p-3 outline-none font-mono text-sm bg-base-200 border-0",
style: () => isSource() ? '' : 'display:none',
value: source,
oninput: (e) => source(e.target.value)
})
])
])
}

View File

@@ -1,60 +0,0 @@
import { h, $, when, fx } from 'sigpro';
import { get, cls, isFunc } from './_core.js';
export const Input = (props) => {
const { label, icon, float, placeholder, value, left, right, content, ...rest } = props;
const showPassword = $(false);
const isFocused = $(false);
const isPassword = props.type === 'password';
const inputType = () => isPassword
? (get(showPassword) ? 'text' : 'password')
: (props.type || 'text');
return h("div", {
class: "input-container",
onfocusin: () => isFocused(true),
onfocusout: (e) => {
if (!e.currentTarget.contains(e.relatedTarget)) {
isFocused(false);
}
}
}, [
h('label', { class: "floating-label" }, [
float ? h("span", {}, label) : null,
h("label", {
class: () => cls('input', props.class)
}, [
label && !float ? h('span', { class: 'label' }, label) : null,
left ?? null,
h('input', {
...rest,
type: inputType,
class: 'grow',
placeholder: placeholder || label || ' ',
value: value
}),
right ?? null,
isPassword ? h('label', { class: 'swap swap-rotate ml-2' }, [
h('input', {
type: 'checkbox',
onchange: (e) => showPassword(e.target.checked)
}),
h('span', { class: 'swap-on icon-[lucide--eye]' }),
h('span', { class: 'swap-off icon-[lucide--eye-off]' })
]) : null
]),
when(isFocused, () => fx({ duration: 300, slide: true },
h('div', {
class: 'input-content',
onmousedown: e => e.preventDefault()
}, [
isFunc(content) ? content(isFocused) : content
])
))
])
]);
};

View File

@@ -1,58 +0,0 @@
import { h, $ } from 'sigpro.js';
import { ui, cls, get } from './_core.js';
export const InputBase = ({
loading = false,
ui: uiOptions = '',
class: className = '',
type = 'text',
value,
onInput,
...inputProps
}) => {
const isPassword = type === 'password';
const showPassword = $(false);
const inputClasses = cls(
ui('input', uiOptions),
className,
isPassword && 'pr-10',
loading && 'loading loading-spinner loading-xs absolute right-2 top-1/2 -translate-y-1/2'
);
let inputEl = h('input', {
class: inputClasses + (isPassword ? ' pr-10' : ''),
type: isPassword ? 'text' : 'password',
value,
oninput: onInput,
...inputProps
});
if (isPassword) {
const toggle = h('label', {
class: 'swap swap-rotate absolute right-2 top-1/2 -translate-y-1/2 cursor-pointer'
}, [
h('input', {
type: 'checkbox',
checked: showPassword(),
onchange: (e) => showPassword(e.target.checked)
}),
h('span', { class: 'swap-on icon-[lucide--eye]' }),
h('span', { class: 'swap-off icon-[lucide--eye-off]' })
]);
inputEl = h('div', { class: 'relative w-full' }, [inputEl, toggle]);
}
if (icon) {
const isIconClass = icon.includes('-') || icon.includes('icon-');
const iconElement = isIconClass
? h('span', { class: icon })
: h('span', { class: 'opacity-50' }, icon);
content = h('label', { class: ui('input', uiOptions).replace('input-', '') }, [
iconElement,
h('span', { class: 'opacity-70' })
]);
}
return inputEl;
};

View File

@@ -0,0 +1,61 @@
import { $, h, when, fx } from 'sigpro';
import { Input } from '../Input.js';
import { filterBy, listKey, getBy } from '../All.js';
export const Autocomplete = ({ items, value, onselect, placeholder = 'Buscar...', ...props }) => {
const query = $(value ? (typeof value === 'function' ? value() : value) : '');
const isOpen = $(false);
const filtered = $(() => filterBy(items, query()));
const { cursor, onKey } = listKey(filtered, isOpen);
const pick = (item) => {
const display = getBy(item);
const actual = typeof item === 'string' ? item : item.value;
query(display);
if (typeof value === 'function') value(actual);
onselect?.(item);
isOpen(false);
};
return h('div', { class: 'relative w-full' }, [
Input({
...props,
type: 'text',
placeholder,
value: query,
left: h('span', { class: 'icon-[lucide--search]' }),
oninput: (e) => {
query(e.target.value);
if (typeof value === 'function') value(e.target.value);
isOpen(true);
},
onfocus: () => isOpen(true),
onblur: () => setTimeout(() => isOpen(false), 150),
onkeydown: (e) => onKey(e, pick)
}),
when(isOpen, () =>
fx({ duration: 200, slide: true },
h('ul', {
class: 'absolute left-0 w-full menu bg-base-100 rounded-box mt-1 p-2 shadow-xl max-h-60 overflow-y-auto border border-base-300 z-50 flex-col flex-nowrap'
}, [
each(filtered, (item, idx) =>
h('li', {}, [
h('a', {
class: () => cursor() === idx ? 'active bg-primary text-primary-content' : '',
onmousedown: (e) => e.preventDefault(), // evita que el blur cierre antes del click
onclick: () => pick(item),
onmouseenter: () => cursor(idx)
}, getBy(item))
]),
(item, idx) => getBy(item) + idx
),
() => filtered().length === 0
? h('li', { class: 'p-4 opacity-50 text-center' }, 'Sin resultados')
: null
])
)
)
]);
};

View File

@@ -0,0 +1,46 @@
import { h, $, when, fx } from 'sigpro';
import { get, cls, isFn } from '../All.js';
export const Input = (props) => {
const { label, icon, float, placeholder, value, left, right, rule, hint, content, ...rest } = props;
const showPassword = $(false);
const isFocused = $(false);
const isPassword = props.type === 'password';
const pattern = rule ?? null;
const inputType = () => isPassword
? (get(showPassword) ? 'text' : 'password')
: (props.type || 'text');
return h("div", {
class: "input-container",
onfocusin: () => isFocused(true),
onfocusout: (e) => {
if (!e.currentTarget.contains(e.relatedTarget)) { isFocused(false); }
}
}, [
h('label', { class: "floating-label" }, [
float ? h("span", {}, label) : null,
h("label", { pattern: pattern, class: () => cls('input validator', props.class) },
[
label && !float ? h('span', { class: 'label' }, label) : null,
left ?? null,
h('input', { ...rest, type: inputType, class: 'grow', pattern: pattern, placeholder: placeholder || label || ' ', value: value }), right ?? null,
isPassword ? h('label', { class: 'swap swap-rotate ml-2' }, [
h('input', { type: 'checkbox', onchange: (e) => showPassword(e.target.checked) }),
h('span', { class: 'swap-on icon-[lucide--eye]' }),
h('span', { class: 'swap-off icon-[lucide--eye-off]' })
]) : null
]),
hint ? h('div', { class: "validator-hint" }, hint) : null,
when(isFocused, () => fx({ duration: 300, slide: true },
h('div', { class: 'input-content', onmousedown: e => e.preventDefault() },
[
isFn(content) ? content(isFocused) : content
])
))
]),
]);
};

View File

@@ -0,0 +1,30 @@
// components/Select.js
import { h, each } from "sigpro";
export const Select = (props) => {
const { items, placeholder, placeholderDisabled = true, keyFn, children, ...rest } = props;
if (children !== undefined) {
return h("select", { ...rest, class: `select ${rest.class ?? ''}` }, children);
}
const placeholderOption = placeholder
? h("option", { disabled: placeholderDisabled, selected: true }, placeholder)
: null;
const dynamicOptions = each(
() => [...(typeof items === "function" ? items() : items || [])],
(item) => {
const value = typeof item === "string" ? item : item.value;
const label = typeof item === "string" ? item : item.label;
return h("option", { value }, label);
},
keyFn || ((item) => (typeof item === "string" ? item : item.value))
);
const options = placeholderOption
? [placeholderOption, dynamicOptions]
: dynamicOptions;
return h("select", { ...rest, class: `select ${rest.class ?? ''}` }, options);
};

View File

@@ -23,6 +23,7 @@ export const filterBy = (items, query, field = 'label') => {
}); });
}; };
export const listKey = (items, isOpen) => { export const listKey = (items, isOpen) => {
const cursor = $(-1); const cursor = $(-1);

View File

@@ -66,3 +66,4 @@ export const Fileinput = (props) => {
) )
]); ]);
}; };

View File

@@ -0,0 +1,4 @@
// components/Tooltip.js
import { h } from "sigpro";
export const Tooltip = (p, c) => h("div", { ...p, class: `tooltip ${p.class ?? ''}`, "data-tip": p.tip }, c);

View File

@@ -1,34 +0,0 @@
// components/Select.js
import { h, each } from "sigpro";
export const Select = (props, children) => {
children === undefined && (children = props, props = {});
return h("select", { ...props, class: `select ${props.class ?? ''}` }, children);
};
export const SelectItems = (props) => {
const placeholderOption = props.placeholder
? h("option", { disabled: props.placeholderDisabled ?? true, selected: true }, props.placeholder)
: null;
const dynamicOptions = each(
() => [...(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 h("option", { value: val }, label);
},
props.keyFn || ((item) => (typeof item === "string" ? item : item.value))
);
return placeholderOption ? [placeholderOption, dynamicOptions] : dynamicOptions;
};
export const SelectLabel = (props, children) => h("label", { class: `${props.float ? 'floating-label' : 'select'}` },
[
h("span", { class: props.float ? '' : 'label opacity-50' }, props.label),
props.left ?? null,
h("select", { ...props, class: `${props.float ? 'select' : ''} ${props.class ?? ''}` }, children),
props.right ?? null
]
);

View File

@@ -1,7 +0,0 @@
// components/Tooltip.js
import { h } from "sigpro";
export const Tooltip = (props, children) => {
children === undefined && (children = props, props = {});
return h("div", { ...props, class: `tooltip ${props.class ?? ''}`, "data-tip": props.tip }, children);
};

352
dist/sigpro-ui.css vendored
View File

@@ -13,6 +13,7 @@
--spacing: 0.25rem; --spacing: 0.25rem;
--container-xs: 20rem; --container-xs: 20rem;
--container-md: 28rem; --container-md: 28rem;
--container-2xl: 42rem;
--container-3xl: 48rem; --container-3xl: 48rem;
--container-5xl: 64rem; --container-5xl: 64rem;
--container-6xl: 72rem; --container-6xl: 72rem;
@@ -41,6 +42,7 @@
--font-weight-light: 300; --font-weight-light: 300;
--font-weight-normal: 400; --font-weight-normal: 400;
--font-weight-medium: 500; --font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700; --font-weight-bold: 700;
--font-weight-black: 900; --font-weight-black: 900;
--tracking-tighter: -0.05em; --tracking-tighter: -0.05em;
@@ -1392,6 +1394,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 { .collapse {
visibility: collapse; visibility: collapse;
} }
@@ -3101,9 +3133,6 @@
.top-0 { .top-0 {
top: calc(var(--spacing) * 0); top: calc(var(--spacing) * 0);
} }
.top-1\/2 {
top: calc(1 / 2 * 100%);
}
.top-2 { .top-2 {
top: calc(var(--spacing) * 2); top: calc(var(--spacing) * 2);
} }
@@ -3125,9 +3154,6 @@
.right-1\/4 { .right-1\/4 {
right: calc(1 / 4 * 100%); right: calc(1 / 4 * 100%);
} }
.right-2 {
right: calc(var(--spacing) * 2);
}
.right-3 { .right-3 {
right: calc(var(--spacing) * 3); right: calc(var(--spacing) * 3);
} }
@@ -3879,6 +3905,9 @@
} }
} }
} }
.mx-1 {
margin-inline: calc(var(--spacing) * 1);
}
.mx-auto { .mx-auto {
margin-inline: auto; margin-inline: auto;
} }
@@ -4133,6 +4162,19 @@
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%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m21.73 18l-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3M12 9v4m0 4h.01'/%3E%3C/svg%3E"); --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='m21.73 18l-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3M12 9v4m0 4h.01'/%3E%3C/svg%3E");
} }
.icon-\[lucide--bold\] {
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%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8'/%3E%3C/svg%3E");
}
.icon-\[lucide--calendar\] { .icon-\[lucide--calendar\] {
display: inline-block; display: inline-block;
width: 1em; width: 1em;
@@ -4224,6 +4266,32 @@
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%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m6 17l5-5l-5-5m7 10l5-5l-5-5'/%3E%3C/svg%3E"); --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='m6 17l5-5l-5-5m7 10l5-5l-5-5'/%3E%3C/svg%3E");
} }
.icon-\[lucide--clock\] {
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%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M12 6v6l4 2'/%3E%3C/g%3E%3C/svg%3E");
}
.icon-\[lucide--code-2\] {
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%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m18 16l4-4l-4-4M6 8l-4 4l4 4m8.5-12l-5 16'/%3E%3C/svg%3E");
}
.icon-\[lucide--eye-off\] { .icon-\[lucide--eye-off\] {
display: inline-block; display: inline-block;
width: 1em; width: 1em;
@@ -4289,6 +4357,19 @@
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%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M12 16v-4m0-4h.01'/%3E%3C/g%3E%3C/svg%3E"); --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%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M12 16v-4m0-4h.01'/%3E%3C/g%3E%3C/svg%3E");
} }
.icon-\[lucide--italic\] {
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%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 4h-9m4 16H5M15 4L9 20'/%3E%3C/svg%3E");
}
.icon-\[lucide--link\] { .icon-\[lucide--link\] {
display: inline-block; display: inline-block;
width: 1em; width: 1em;
@@ -4302,6 +4383,32 @@
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='M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71'/%3E%3Cpath d='M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71'/%3E%3C/g%3E%3C/svg%3E"); --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='M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71'/%3E%3Cpath d='M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71'/%3E%3C/g%3E%3C/svg%3E");
} }
.icon-\[lucide--list-ordered\] {
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%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M11 5h10m-10 7h10m-10 7h10M4 4h1v5M4 9h2m.5 11H3.4c0-1 2.6-1.925 2.6-3.5a1.5 1.5 0 0 0-2.6-1.02'/%3E%3C/svg%3E");
}
.icon-\[lucide--list\] {
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%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M3 5h.01M3 12h.01M3 19h.01M8 5h13M8 12h13M8 19h13'/%3E%3C/svg%3E");
}
.icon-\[lucide--lock\] { .icon-\[lucide--lock\] {
display: inline-block; display: inline-block;
width: 1em; width: 1em;
@@ -4341,7 +4448,7 @@
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%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"); --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\] { .icon-\[lucide--plus\] {
display: inline-block; display: inline-block;
width: 1em; width: 1em;
height: 1em; height: 1em;
@@ -4352,7 +4459,33 @@
mask-repeat: no-repeat; mask-repeat: no-repeat;
-webkit-mask-size: 100% 100%; -webkit-mask-size: 100% 100%;
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"); --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='M5 12h14m-7-7v14'/%3E%3C/svg%3E");
}
.icon-\[lucide--quote\] {
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%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M16 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2a1 1 0 0 1 1 1v1a2 2 0 0 1-2 2a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1a6 6 0 0 0 6-6V5a2 2 0 0 0-2-2zM5 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2a1 1 0 0 1 1 1v1a2 2 0 0 1-2 2a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1a6 6 0 0 0 6-6V5a2 2 0 0 0-2-2z'/%3E%3C/svg%3E");
}
.icon-\[lucide--redo-2\] {
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='m15 14l5-5l-5-5'/%3E%3Cpath d='M20 9H9.5A5.5 5.5 0 0 0 4 14.5A5.5 5.5 0 0 0 9.5 20H13'/%3E%3C/g%3E%3C/svg%3E");
} }
.icon-\[lucide--search\] { .icon-\[lucide--search\] {
display: inline-block; display: inline-block;
@@ -4367,6 +4500,32 @@
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='m21 21l-4.34-4.34'/%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3C/g%3E%3C/svg%3E"); --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='m21 21l-4.34-4.34'/%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3C/g%3E%3C/svg%3E");
} }
.icon-\[lucide--settings\] {
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='M9.671 4.136a2.34 2.34 0 0 1 4.659 0a2.34 2.34 0 0 0 3.319 1.915a2.34 2.34 0 0 1 2.33 4.033a2.34 2.34 0 0 0 0 3.831a2.34 2.34 0 0 1-2.33 4.033a2.34 2.34 0 0 0-3.319 1.915a2.34 2.34 0 0 1-4.659 0a2.34 2.34 0 0 0-3.32-1.915a2.34 2.34 0 0 1-2.33-4.033a2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915'/%3E%3Ccircle cx='12' cy='12' r='3'/%3E%3C/g%3E%3C/svg%3E");
}
.icon-\[lucide--strikethrough\] {
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%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M16 4H9a3 3 0 0 0-2.83 4M14 12a4 4 0 0 1 0 8H6m-2-8h16'/%3E%3C/svg%3E");
}
.icon-\[lucide--text\] { .icon-\[lucide--text\] {
display: inline-block; display: inline-block;
width: 1em; width: 1em;
@@ -4380,6 +4539,32 @@
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%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M15 18H3M17 6H3m18 6H3'/%3E%3C/svg%3E"); --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='M15 18H3M17 6H3m18 6H3'/%3E%3C/svg%3E");
} }
.icon-\[lucide--underline\] {
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%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M6 4v6a6 6 0 0 0 12 0V4M4 20h16'/%3E%3C/svg%3E");
}
.icon-\[lucide--undo-2\] {
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='M9 14L4 9l5-5'/%3E%3Cpath d='M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11'/%3E%3C/g%3E%3C/svg%3E");
}
.icon-\[lucide--upload\] { .icon-\[lucide--upload\] {
display: inline-block; display: inline-block;
width: 1em; width: 1em;
@@ -4393,6 +4578,19 @@
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%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M12 3v12m5-7l-5-5l-5 5m14 7v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4'/%3E%3C/svg%3E"); --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='M12 3v12m5-7l-5-5l-5 5m14 7v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4'/%3E%3C/svg%3E");
} }
.icon-\[lucide--user\] {
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='M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2'/%3E%3Ccircle cx='12' cy='7' r='4'/%3E%3C/g%3E%3C/svg%3E");
}
.icon-\[lucide--x\] { .icon-\[lucide--x\] {
display: inline-block; display: inline-block;
width: 1em; width: 1em;
@@ -4687,6 +4885,9 @@
.hidden { .hidden {
display: none; display: none;
} }
.inline {
display: inline;
}
.inline-block { .inline-block {
display: inline-block; display: inline-block;
} }
@@ -4776,6 +4977,9 @@
.h-4 { .h-4 {
height: calc(var(--spacing) * 4); height: calc(var(--spacing) * 4);
} }
.h-5 {
height: calc(var(--spacing) * 5);
}
.h-8 { .h-8 {
height: calc(var(--spacing) * 8); height: calc(var(--spacing) * 8);
} }
@@ -4827,6 +5031,9 @@
.min-h-0 { .min-h-0 {
min-height: calc(var(--spacing) * 0); min-height: calc(var(--spacing) * 0);
} }
.min-h-\[10rem\] {
min-height: 10rem;
}
.min-h-\[300px\] { .min-h-\[300px\] {
min-height: 300px; min-height: 300px;
} }
@@ -4850,11 +5057,6 @@
width: 100%; width: 100%;
} }
} }
.loading-xs {
@layer daisyui.l1.l2 {
width: calc(var(--size-selector, 0.25rem) * 4);
}
}
.w-3\.5 { .w-3\.5 {
width: calc(var(--spacing) * 3.5); width: calc(var(--spacing) * 3.5);
} }
@@ -4903,6 +5105,12 @@
.w-full { .w-full {
width: 100%; width: 100%;
} }
.w-px {
width: 1px;
}
.max-w-2xl {
max-width: var(--container-2xl);
}
.max-w-3xl { .max-w-3xl {
max-width: var(--container-3xl); max-width: var(--container-3xl);
} }
@@ -4949,6 +5157,10 @@
--tw-translate-x: calc(calc(1 / 2 * 100%) * -1); --tw-translate-x: calc(calc(1 / 2 * 100%) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y); translate: var(--tw-translate-x) var(--tw-translate-y);
} }
.translate-x-0 {
--tw-translate-x: calc(var(--spacing) * 0);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
.translate-x-2 { .translate-x-2 {
--tw-translate-x: calc(var(--spacing) * 2); --tw-translate-x: calc(var(--spacing) * 2);
translate: var(--tw-translate-x) var(--tw-translate-y); translate: var(--tw-translate-x) var(--tw-translate-y);
@@ -4965,10 +5177,6 @@
--tw-translate-x: 100%; --tw-translate-x: 100%;
translate: var(--tw-translate-x) var(--tw-translate-y); 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 { .translate-y-2 {
--tw-translate-y: calc(var(--spacing) * 2); --tw-translate-y: calc(var(--spacing) * 2);
translate: var(--tw-translate-x) var(--tw-translate-y); translate: var(--tw-translate-x) var(--tw-translate-y);
@@ -5098,6 +5306,9 @@
.flex-col-reverse { .flex-col-reverse {
flex-direction: column-reverse; flex-direction: column-reverse;
} }
.flex-nowrap {
flex-wrap: nowrap;
}
.flex-wrap { .flex-wrap {
flex-wrap: wrap; flex-wrap: wrap;
} }
@@ -5107,6 +5318,9 @@
.items-end { .items-end {
align-items: flex-end; align-items: flex-end;
} }
.items-start {
align-items: flex-start;
}
.items-stretch { .items-stretch {
align-items: stretch; align-items: stretch;
} }
@@ -5283,6 +5497,10 @@
border-style: var(--tw-border-style); border-style: var(--tw-border-style);
border-width: 1px; border-width: 1px;
} }
.border-0 {
border-style: var(--tw-border-style);
border-width: 0px;
}
.border-2 { .border-2 {
border-style: var(--tw-border-style); border-style: var(--tw-border-style);
border-width: 2px; border-width: 2px;
@@ -5553,6 +5771,17 @@
--tw-gradient-position: to right in oklab; --tw-gradient-position: to right in oklab;
background-image: linear-gradient(var(--tw-gradient-stops)); background-image: linear-gradient(var(--tw-gradient-stops));
} }
.skeleton-text {
@layer daisyui.l1.l2 {
background-clip: text;
webkit-background-clip: text;
color: transparent;
background-image: linear-gradient( 105deg, var(--color-base-content) 0% 40%, var(--color-base-content) 50%, var(--color-base-content) 60% 100% );
@supports (color: color-mix(in lab, red, red)) {
background-image: linear-gradient( 105deg, color-mix(in oklab, var(--color-base-content) 20%, transparent) 0% 40%, var(--color-base-content) 50%, color-mix(in oklab, var(--color-base-content) 20%, transparent) 60% 100% );
}
}
}
.alert-soft { .alert-soft {
@layer daisyui.l1 { @layer daisyui.l1 {
color: var(--alert-color, var(--color-base-content)); color: var(--alert-color, var(--color-base-content));
@@ -6009,6 +6238,10 @@
--tw-font-weight: var(--font-weight-normal); --tw-font-weight: var(--font-weight-normal);
font-weight: var(--font-weight-normal); font-weight: var(--font-weight-normal);
} }
.font-semibold {
--tw-font-weight: var(--font-weight-semibold);
font-weight: var(--font-weight-semibold);
}
.tracking-tight { .tracking-tight {
--tw-tracking: var(--tracking-tight); --tw-tracking: var(--tracking-tight);
letter-spacing: var(--tracking-tight); letter-spacing: var(--tracking-tight);
@@ -6293,6 +6526,9 @@
.line-through { .line-through {
text-decoration-line: line-through; text-decoration-line: line-through;
} }
.underline {
text-decoration-line: underline;
}
.swap-active { .swap-active {
@layer daisyui.l1.l2 { @layer daisyui.l1.l2 {
.swap-off { .swap-off {
@@ -6327,6 +6563,9 @@
.opacity-80 { .opacity-80 {
opacity: 80%; opacity: 80%;
} }
.opacity-100 {
opacity: 100%;
}
.shadow { .shadow {
--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
@@ -6400,6 +6639,10 @@
} }
} }
} }
.blur {
--tw-blur: blur(8px);
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
}
.blur-3xl { .blur-3xl {
--tw-blur: blur(var(--blur-3xl)); --tw-blur: blur(var(--blur-3xl));
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
@@ -7139,6 +7382,57 @@
grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-columns: repeat(4, minmax(0, 1fr));
} }
} }
.\[\&_br\]\:block {
& br {
display: block;
}
}
.\[\&_br\]\:h-\[1em\] {
& br {
height: 1em;
}
}
.\[\&_br\]\:content-\[\'\'\] {
& br {
--tw-content: '';
content: var(--tw-content);
}
}
.\[\&_div\]\:m-0 {
& div {
margin: calc(var(--spacing) * 0);
}
}
.\[\&_li\]\:list-item {
& li {
display: list-item;
}
}
.\[\&_ol\]\:list-decimal {
& ol {
list-style-type: decimal;
}
}
.\[\&_ol\]\:pl-6 {
& ol {
padding-left: calc(var(--spacing) * 6);
}
}
.\[\&_p\]\:m-0 {
& p {
margin: calc(var(--spacing) * 0);
}
}
.\[\&_ul\]\:list-disc {
& ul {
list-style-type: disc;
}
}
.\[\&_ul\]\:pl-6 {
& ul {
padding-left: calc(var(--spacing) * 6);
}
}
} }
:root { :root {
font-size: 14px; font-size: 14px;
@@ -7170,14 +7464,32 @@
animation: tabFadeIn 0.3s cubic-bezier(0.4, 0, 0.2, 1); animation: tabFadeIn 0.3s cubic-bezier(0.4, 0, 0.2, 1);
transform-origin: top; transform-origin: top;
} }
.input-container {
position: relative;
width: 100%;
display: flex;
flex-direction: column;
}
.input-container .input {
width: 100%;
display: flex;
align-items: center;
}
.input-content { .input-content {
position: absolute; position: absolute;
top: 100%; top: 100%;
left: 0; left: 0;
right: 0;
z-index: 50;
background: oklch(var(--b1));
border: 1px solid oklch(var(--bc) / 0.2);
border-radius: var(--rounded-box, 1rem);
margin-top: 0.25rem;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.input-content .menu {
width: 100%; width: 100%;
z-index: 100;
background: white;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
} }
@keyframes tabFadeIn { @keyframes tabFadeIn {
from { from {

1803
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

1797
dist/sigpro-ui.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -5,41 +5,8 @@
* **Introduction** * **Introduction**
* [Installation](install.md) * [Installation](install.md)
* [Quick Reference](quick.md) * [Quick Reference](quick.md)
* [Forms & Inputs](demo_forms.md)
* **Forms & Inputs** * [Data & Dysplay](demo_display.md)
* [Autocomplete](components/autocomplete.md) * [Feedback & Overlays](demo_overlay.md)
* [Button](components/button.md) * [Navigation & Layout](demo_layout.md)
* [Checkbox](components/checkbox.md) * [WYSIWYG Editor](demo_editor.md)
* [Colorpicker](components/colorpicker.md)
* [Datepicker](components/datepicker.md)
* [Input](components/input.md)
* [Radio](components/radio.md)
* [Range](components/range.md)
* [Rating](components/rating.md)
* [Select](components/select.md)
* [Swap](components/swap.md)
* **Data Display**
* [Badge](components/badge.md)
* [Indicator](components/indicator.md)
* [List](components/list.md)
* [Stack](components/stack.md)
* [Stat](components/stat.md)
* [Table](components/table.md)
* [Timeline](components/timeline.md)
* **Feedback & Overlays**
* [Alert](components/alert.md)
* [Modal](components/modal.md)
* [Toast](components/toast.md)
* [Tooltip](components/tooltip.md)
* **Navigation & Layout**
* [Accordion](components/accordion.md)
* [Drawer](components/drawer.md)
* [Dropdown](components/dropdown.md)
* [Fab](components/fab.md)
* [Fieldset](components/fieldset.md)
* [Menu](components/menu.md)
* [Navbar](components/navbar.md)
* [Tabs](components/tabs.md)

View File

@@ -8,51 +8,108 @@
</div> </div>
```js ```js
const paises = [ // const App = () => {
"España", // const selected = $('');
"México", // const filterType = $('all');
"Argentina",
"Colombia", // const allItems = {
"Chile", // fruits: ['Apple', 'Banana', 'Orange', 'Mango'],
"Perú", // vegetables: ['Carrot', 'Broccoli', 'Spinach', 'Potato'],
"Venezuela", // all: ['Apple', 'Banana', 'Orange', 'Mango', 'Carrot', 'Broccoli', 'Spinach', 'Potato']
{ label: "Estados Unidos", value: "US" }, // };
{ label: "Canadá", value: "CA" },
{ label: "Reino Unido", value: "UK" }, // 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)
// },
// ),
// Autocomplete({
// items: () => allItems[filterType()],
// value: selected,
// onselect: (value) => selected(value)
// })
// ]);
// };
const App = () => { const App = () => {
const password = $(""); const password = $("");
const selected = $('');
const countries = [
'España', 'México', 'Argentina', 'Colombia', 'Chile',
{ label: 'Estados Unidos', value: 'US' },
{ label: 'Canadá', value: 'CA' },
];
// Lógica de validación sencilla // Lógica de validación sencilla
const requirements = [ const requirements = [
{ label: "Mínimo 8 caracteres", check: () => password().length >= 8 }, { label: "Mínimo 8 caracteres", check: () => password().length >= 8 },
{ label: "Incluye un número", check: () => /\d/.test(password()) }, { label: "Incluye un número", check: () => /\d/.test(password()) },
{ label: "Símbolo especial", check: () => /[^A-Za-z0-9]/.test(password()) } { label: "Símbolo especial", check: () => /[^A-Za-z0-9]/.test(password()) },
]; ];
const paisSelected = $("");
return div({ class: "p-10 max-w-md" }, [ return div({ class: "p-10 max-w-md" }, [
Autocomplete({
label: 'País',
float: true,
placeholder: 'Escribe para buscar...',
items: countries,
value: selected,
onselect: (item) => console.log('Seleccionado:', item),
}),
h('p', { class: 'text-sm opacity-70' }, () =>
`Valor actual: ${selected() || 'ninguno'}`
),
Input({ Input({
type: "password", type: "password",
value: password, value: password,
class: "input-warning", rule: "[0-9]*",
hint: "Debes escribir numeros del 0 al 9",
oninput: (e) => password(e.target.value), oninput: (e) => password(e.target.value),
label: "Pass", label: "Pass",
float: true, float: true,
left: span({ class: "icon-[lucide--lock] mr-2" }), left: span({ class: "icon-[lucide--lock] mr-2" }),
// El contenido que se animará con fx() dentro del Input // El contenido que se animará con fx() dentro del Input
content: div({ class: "mt-2 p-4 bg-base-200 rounded-lg shadow-xl border border-base-300" }, [ content: div(
span({ class: "text-xs font-bold uppercase opacity-50" }, "Seguridad:"), {
ul({ class: "mt-2 space-y-1" }, class:
requirements.map(req => h('li', { "mt-2 p-4 bg-base-200 rounded-lg shadow-xl border border-base-300",
class: () => `flex items-center text-sm ${req.check() ? 'text-success' : 'text-error opacity-50'}` },
}, [ [
span({ class: () => `mr-2 ${req.check() ? 'icon-[lucide--check]' : 'icon-[lucide--x]'}` }), span(
req.label { class: "text-xs font-bold uppercase opacity-50" },
])) "Seguridad:",
) ),
]) ul(
}) { class: "mt-2 space-y-1" },
requirements.map((req) =>
h(
"li",
{
class: () =>
`flex items-center text-sm ${req.check() ? "text-success" : "text-error opacity-50"}`,
},
[
span({
class: () =>
`mr-2 ${req.check() ? "icon-[lucide--check]" : "icon-[lucide--x]"}`,
}),
req.label,
],
),
),
),
],
),
}),
]); ]);
}; };

100
docs/demo_display.md Normal file
View File

@@ -0,0 +1,100 @@
# Display
## Badge
<div id="demo-badge"></div>
```js
mount(
Badge({ class: 'badge-primary' }, 'New'),
'#demo-badge'
);
```
## Indicator
<div id="demo-indicator"></div>
```js
mount(
Indicator({ value: '5' },
Button({ class: 'btn btn-sm' }, 'Notifications')
),
'#demo-indicator'
);
```
## Stack
<div id="demo-stack"></div>
```js
mount(
Stack({ class: 'w-32 h-20' }, [
div({ class: 'bg-primary text-primary-content p-2' }, 'Top'),
div({ class: 'bg-secondary text-secondary-content p-2' }, 'Bottom')
]),
'#demo-stack'
);
```
## Stat
<div id="demo-stat"></div>
```js
// Stat is a simple wrapper for the DaisyUI stat component
const Stat = (p, c) => div({ ...p, class: cls('stat', p.class) }, c)
mount(
div({ class: 'stats shadow' },
Stat({ class: 'stat' }, [
div({ class: 'stat-title' }, 'Total Downloads'),
div({ class: 'stat-value' }, '12.5K'),
div({ class: 'stat-desc' }, '21% more than last month')
])
),
'#demo-stat'
)
```
## Table
<div id="demo-table"></div>
```js
const users = [
{ id: 1, name: 'Alice', role: 'Admin' },
{ id: 2, name: 'Bob', role: 'Editor' },
{ id: 3, name: 'Charlie', role: 'Viewer' }
];
mount(
Table({
items: users,
columns: [
{ key: 'name', label: 'Name' },
{ key: 'role', label: 'Role' },
{ render: (it) => Button({ class: 'btn-xs' }, 'Edit') }
],
class: 'table-zebra'
}),
'#demo-table'
);
```
## Timeline
<div id="demo-timeline"></div>
```js
mount(
Timeline({ vertical: true }, [
li({}, [
div({ class: 'timeline-start' }, '2024'),
div({ class: 'timeline-middle' }, span({ class: 'icon-[lucide--check]' })),
div({ class: 'timeline-end timeline-box' }, 'Project started')
]),
li({}, [
div({ class: 'timeline-start' }, '2025'),
div({ class: 'timeline-middle' }, span({ class: 'icon-[lucide--clock]' })),
div({ class: 'timeline-end timeline-box' }, 'First prototype')
])
]),
'#demo-timeline'
);
```

19
docs/demo_editor.md Normal file
View File

@@ -0,0 +1,19 @@
# Editor
<div id="demo-editor"></div>
<div id="demo-editor-output" class="mt-4 p-4 border border-base-300 rounded-box bg-base-200"></div>
```js
const content = $('<p><strong>Hello</strong> world!</p>');
mount(
div({ class: 'flex flex-col gap-4' }, [
Editor({
value: content,
placeholder: 'Escribe tu historia…',
class: 'max-w-2xl'
})
]),
'#demo-editor'
);
```

181
docs/demo_forms.md Normal file
View File

@@ -0,0 +1,181 @@
# Forms
## Autocomplete
<div id="demo-autocomplete"></div>
```js
const selected = $('');
const items = ['Apple', 'Banana', 'Orange', 'Mango', 'Carrot', 'Broccoli', 'Spinach', 'Potato'];
mount(
Autocomplete({
items,
value: selected,
placeholder: 'Search...',
onselect: (val) => console.log('Selected:', val)
}),
'#demo-autocomplete'
);
```
## Button
<div id="demo-button"></div>
```js
const count = $(0);
mount(
Button({
class: 'btn-primary',
onclick: () => count(count() + 1)
}, () => `Clicked ${count()} times`),
'#demo-button'
);
```
## Checkbox
<div id="demo-checkbox"></div>
```js
const checked = $(false);
mount(
div({ class: 'flex items-center gap-2' }, [
Checkbox({
checked,
onchange: (e) => checked(e.target.checked),
}),
span({}, 'Accept terms')
]),
'#demo-checkbox'
);
```
## Colorpicker
<div id="demo-colorpicker"></div>
```js
const color = $('');
mount(
Colorpicker({
value: color,
label: 'Pick a color',
onchange: (c) => console.log('Color:', c)
}),
'#demo-colorpicker'
);
```
## Datepicker
<div id="demo-datepicker"></div>
```js
const date = $('');
mount(
Datepicker({
value: date,
placeholder: 'Select date',
onChange: (val) => console.log('Date:', val)
}),
'#demo-datepicker'
);
```
## Input
<div id="demo-input"></div>
```js
const text = $('');
mount(
Input({
type: 'text',
label: 'Username',
float: true,
value: text,
left: Icon('icon-[lucide--user]')
}),
'#demo-input'
);
```
## Radio
<div id="demo-radio"></div>
```js
const option = $('');
mount(
div({ class: 'flex gap-2' }, [
Radio({ name: 'option', value: 'a', checked: () => option() === 'a', onchange: () => option('a') }),
Radio({ name: 'option', value: 'b', checked: () => option() === 'b', onchange: () => option('b') }),
]),
'#demo-radio'
);
```
## Range
<div id="demo-range"></div>
```js
const rangeValue = $(50);
mount(
div({ class: 'flex flex-col gap-2' }, [
Range({ min: 0, max: 100, value: rangeValue, oninput: (e) => rangeValue(+e.target.value) }),
span({}, () => `Value: ${rangeValue()}`)
]),
'#demo-range'
);
```
## Rating
<div id="demo-rating"></div>
```js
const stars = $('');
mount(
Rating({
value: stars,
count: 5,
mask: 'mask-star',
onchange: (v) => console.log('Rated:', v)
}),
'#demo-rating'
);
```
## Select
<div id="demo-select"></div>
```js
const choice = $('');
mount(
Select({
items: ['Option 1', 'Option 2', 'Option 3'],
placeholder: 'Choose...',
value: choice,
}),
'#demo-select'
);
```
## Swap
<div id="demo-swap"></div>
```js
const swapOn = $(false);
mount(
Swap({
value: swapOn,
on: span({ class: 'text-success' }, 'ON'),
off: span({ class: 'text-error' }, 'OFF'),
}),
'#demo-swap'
);
```

201
docs/demo_layout.md Normal file
View File

@@ -0,0 +1,201 @@
# Layout
## Accordion
<div id="demo-accordion"></div>
```js
const accItems = $([
{ title: 'What is SigPro?', content: 'A lightweight UI library built on DaisyUI and a finegrained reactivity system.' },
{ title: 'Why use it?', content: 'No build step, minimal boilerplate, and all components are just JavaScript functions.' },
{ title: 'Browser support?', content: 'All modern browsers that support ES2020+' }
]);
mount(
div({ class: 'flex flex-col gap-8' }, [
// Example 1: radio type with arrow variant
Accordion({
variant: 'arrow',
items: [
{ title: 'Radio with arrow', content: 'This uses collapsearrow' },
{ title: 'Open by default', content: 'This one is open', open: true },
'Simple string item (no content)'
]
}),
// Example 2: details type with plus variant
Accordion({
type: 'details',
variant: 'plus',
items: [
{ title: 'Details with plus', content: 'Uses the native <details> element' },
{ title: 'Another detail', content: 'Accordion style but with plus icon' }
]
}),
// Example 3: reactive items (signal)
Accordion({ items: accItems })
]),
'#demo-accordion'
);
```
## Drawer
<div id="demo-drawer"></div>
```js
const drawerOpen = $(false);
mount(
div({}, [
Button({ class: 'btn', onclick: () => drawerOpen(true) }, 'Open Drawer'),
Drawer({ open: drawerOpen, side: Menu({ items: [
{ label: 'Dashboard', onclick: () => drawerOpen(false) },
{ label: 'Settings' },
{ label: 'Help' }
]}) }, [
div({ class: 'p-4' }, [
h3({ class: 'text-lg font-bold' }, 'Main Content'),
p({}, 'This is the main page. Click the button above to open the drawer.')
])
])
]),
'#demo-drawer'
);
```
## Dropdown
<div id="demo-dropdown"></div>
```js
mount(
div({ class: 'flex gap-4' }, [
// Example 1: automatic items
Dropdown({
trigger: 'Options',
items: [
{ label: 'Edit', onclick: () => console.log('Edit') },
{ label: 'Delete', onclick: () => console.log('Delete') },
{ label: 'Archive' }
]
}),
// Example 2: custom children (manual)
Dropdown({ trigger: 'More' }, [
h('ul', { class: 'menu dropdown-content bg-base-100 rounded-box w-40 p-2 shadow' }, [
li({}, a({}, 'Profile')),
li({}, a({}, 'Logout'))
])
])
]),
'#demo-dropdown'
);
```
## Fab
<div id="demo-fab"></div>
```js
mount(
div({ class: 'flex gap-4' }, [
Fab({ class: 'fab-bottom-left' }, span({ class: 'icon-[lucide--plus]' })),
Fab({ class: 'fab-top-right', style: 'position:relative' }, span({ class: 'icon-[lucide--settings]' }))
]),
'#demo-fab'
);
```
## Fieldset
<div id="demo-fieldset"></div>
```js
mount(
div({ class: 'flex gap-4' }, [
Fieldset({ legend: 'Personal Info' }, [
Input({ label: 'Name', float: true, value: $('') }),
Select({ label: 'Country', float: true, items: ['Spain', 'France', 'Italy'], value: $('') })
]),
Fieldset({ class: 'bg-base-200 p-4 rounded-box' }, [
div({}, 'Any content without legend')
])
]),
'#demo-fieldset'
);
```
## Menu
<div id="demo-menu"></div>
```js
const menuItems = $([
{ label: 'Home' },
{ label: 'Products', children: [
{ label: 'Laptops' },
{ label: 'Phones' }
]},
{ label: 'About', onclick: () => console.log('About clicked') }
]);
mount(
div({ class: 'flex gap-4' }, [
// Example 1: automatic from items (signal)
Menu({ items: menuItems, class: 'menu-vertical' }),
// Example 2: manual children
Menu({ class: 'menu-horizontal bg-base-200 rounded-box' }, [
li({}, a({}, 'One')),
li({}, a({}, 'Two'))
])
]),
'#demo-menu'
);
```
## Navbar
<div id="demo-navbar"></div>
```js
mount(
Navbar({ class: 'bg-base-200 rounded-box' }, [
div({ class: 'flex-1' },
a({ class: 'btn btn-ghost text-xl' }, 'SigPro')
),
div({ class: 'flex-none gap-2' }, [
Button({ class: 'btn btn-ghost' }, 'Login'),
Button({ class: 'btn btn-primary' }, 'Sign up')
])
]),
'#demo-navbar'
);
```
## Tabs
<div id="demo-tabs"></div>
```js
const activeTab = $(0);
const tabsData = $([
{ label: 'Tab A', content: 'Content of tab A' },
{ label: 'Tab B', content: 'Content of tab B', closable: true },
{ label: 'Tab C', content: 'Content of tab C' }
]);
mount(
div({ class: 'flex flex-col gap-4' }, [
// Example 1: reactive tabs with closable
Tabs({
items: tabsData,
activeIndex: activeTab,
class: "tabs-box",
onClose: (idx) => {
const newTabs = tabsData().filter((_, i) => i !== idx);
tabsData(newTabs);
if (activeTab() >= newTabs.length) activeTab(Math.max(0, newTabs.length - 1));
}
}),
// Example 2: manual tabs with custom content
Tabs({}, [
a({ class: () => `tab ${activeTab() === 0 ? 'tab-active' : ''}`, onclick: () => activeTab(0) }, 'Manual A'),
a({ class: () => `tab ${activeTab() === 1 ? 'tab-active' : ''}`, onclick: () => activeTab(1) }, 'Manual B'),
div({ class: 'tab-content', style: () => `display:${activeTab() === 0 ? 'block' : 'none'}` }, 'Content for manual A'),
div({ class: 'tab-content', style: () => `display:${activeTab() === 1 ? 'block' : 'none'}` }, 'Content for manual B')
])
]),
'#demo-tabs'
);
```

84
docs/demo_overlay.md Normal file
View File

@@ -0,0 +1,84 @@
# Overlays
## Alert
<div id="demo-alert"></div>
```js
mount(
Alert({ class: 'alert-success' }, [
span({}, 'Operation completed successfully!'),
Button({ class: 'btn-sm' }, 'Undo')
]),
'#demo-alert'
);
```
## Modal
<div id="demo-modal"></div>
```js
const modalOpen = $(false);
mount(
div({ class: 'flex flex-col items-start gap-2' }, [
Button({
class: 'btn',
onclick: () => modalOpen(true)
}, 'Abrir modal'),
Modal({
open: modalOpen,
title: 'Congratulations!',
backdrop: true,
actions: [
form({ method: 'dialog' },
Button({ class: 'btn', onclick: () => modalOpen(false) }, 'Close')
)
]
}, [
p({}, 'You have successfully created a reactive DaisyUI modal with SigPro.')
])
]),
'#demo-modal'
);
```
## Toast
<div id="demo-toast"></div>
```js
mount(
div({ class: 'flex flex-wrap gap-2' }, [
Button({ class: 'btn', onclick: () => Toast('File saved!') }, 'Simple'),
Button({ class: 'btn', onclick: () => Toast('Error', 'alert-error', 5000) }, 'Error (5s)'),
Button({ class: 'btn', onclick: () => Toast(
div({ class: 'flex items-center gap-2' }, [
span({ class: 'icon-[lucide--check] text-lg' }),
span({}, 'Report generated successfully')
]),
'alert-success'
) }, 'With icon'),
Button({ class: 'btn', onclick: () => Toast(
h('div', { class: 'flex flex-col' }, [
h('strong', {}, '¡ATTENTION!'),
h('span', {}, 'Error saving!'),
h('button', { class: 'btn btn-xs mt-1', onclick: () => console.log('retry') }, 'Retry')
]),
'alert-warning',
7000
) }, 'Complex')
]),
'#demo-toast'
);
```
## Tooltip
<div id="demo-tooltip"></div>
```js
mount(
Tooltip({ tip: 'Click to save', class: 'tooltip-right' },
Button({ class: 'btn' }, 'Save')
),
'#demo-tooltip'
);
```

View File

@@ -30,6 +30,7 @@
repo: "", repo: "",
loadSidebar: true, loadSidebar: true,
sidebarDisplayLevel: 1, sidebarDisplayLevel: 1,
subMaxLevel: 3,
executeScript: true, executeScript: true,
copyCode: { copyCode: {
buttonText: buttonText:
@@ -38,26 +39,24 @@
successText: successText:
'<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><polyline points="20 6 9 17 4 12"></polyline></svg>', '<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><polyline points="20 6 9 17 4 12"></polyline></svg>',
}, },
search: {
placeholder: "Type to search",
noData: "No Results!",
depth: 3,
hideOtherSidebarContent: true,
},
plugins: [ plugins: [
function (hook, vm) { function (hook, vm) {
hook.doneEach(function () { hook.doneEach(function () {
// Seleccionamos solo los bloques marcados con ```js
const codeBlocks = document.querySelectorAll( const codeBlocks = document.querySelectorAll(
'pre[data-lang="js"] code', 'pre[data-lang="js"] code',
); );
codeBlocks.forEach((code) => { codeBlocks.forEach((code) => {
try { try {
// Usamos un bloque anónimo para evitar colisiones de variables const/let const scriptContent = `(function() { ${code.innerText} })();`;
// si el usuario ejecuta el mismo código varias veces.
const scriptContent = `(function() {
${code.innerText}
})();`;
const runDemo = new Function(scriptContent); const runDemo = new Function(scriptContent);
runDemo(); runDemo();
} catch (err) { } catch (err) {
// Un error común es que el bloque de código esté vacío o mal formado
console.error("Error ejecutando demo de SigPro:", err); console.error("Error ejecutando demo de SigPro:", err);
} }
}); });
@@ -66,7 +65,7 @@
], ],
}; };
</script> </script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify@4.13.0/lib/docsify.min.js"></script> <script src="//cdn.jsdelivr.net/npm/docsify@4.13.0/lib/docsify.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify-copy-code/dist/docsify-copy-code.min.js"></script> <script src="//cdn.jsdelivr.net/npm/docsify-copy-code/dist/docsify-copy-code.min.js"></script>
<script src="./sigpro-ui.min.js"></script> <script src="./sigpro-ui.min.js"></script>

View File

@@ -1,227 +1,610 @@
# SigPro-UI Quick Reference # SigPro Components Quick Reference
**Status:** Active / WIP All simple components use the pattern `ComponentName(props, children?)` and accept any additional HTML attributes.
## Accordion
`Accordion(props)`
## Global Initialization | Prop | Type | Default | Description |
|------|------|---------|-------------|
```javascript | `items` | array \| signal | `[]` | Array of items. Each item: `{ title, content?, open? }` or a string. |
import "sigpro-ui"; | `type` | `'radio'` \| `'details'` | `'radio'` | Collapse mode |
import "sigpro-ui/css"; | `variant` | string | `''` | `'arrow'` or `'plus'` for an icon variant |
| `name` | string | autogenerated | Group name for radio inputs |
// All components (Button, Input, Table, Toast, etc.) are now globally available. | `class` | string | - | Extra classes merged with `collapse` |
```
--- ---
## Core Components ## Alert
`Alert(props, children)`
| Component | Purpose | Basic Example | | Prop | Type | Default | Description |
| :--- | :--- | :--- | |------|------|---------|-------------|
| **Button** | Styled button with DaisyUI | `button({ class: "btn-primary" }, "Submit")` | | `class` | string | - | Extra classes merged with `alert` |
| **Input** | Reactive text field with validation | `input({ value: $name, validate: (v) => !v ? "Required" : "" })` |
| **Select** | Dropdown selection menu | `Select({ options: ["Admin", "User"], value: $role })` |
| **Checkbox** | Binary toggle (boolean) | `Checkbox({ label: "Active", checked: $isActive })` |
| **Table** | Data grid with column rendering | `Table({ items: $data, columns: [...] })` |
| **Modal** | Overlay dialog controlled by a Signal | `Modal({ open: $show, title: "Alert" }, "Message")` |
| **Badge** | Small status indicator or tag | `Badge({ class: "badge-outline" }, "Beta")` |
| **Alert** | Contextual notification | `Alert({ type: "info" }, "Update available")` |
| **Dropdown** | Contextual overlay menu | `Dropdown({ label: "Menu" }, [Link1, Link2])` |
| **Tabs** | Reactive tab-based navigation | `Tabs({ items: ["Home", "Settings"], active: $index })` |
| **Stat** | Statistical data block (KPIs) | `Stat({ label: "Sales", value: "$400" })` |
| **Toast** | Temporary floating notification | `Toast("Done!", "alert-success", 3000)` |
--- ---
## Forms & Inputs ## Autocomplete
`Autocomplete(props)`
| Component | Description | Example | | Prop | Type | Default | Description |
| :--- | :--- | :--- | |------|------|---------|-------------|
| **Input** | Text input with floating label, validation, password toggle | `input({ label: "Email", type: "email", value: $email, validate: validateEmail })` | | `items` | array \| signal | `[]` | Autocomplete items |
| **Select** | Dropdown selector | `Select({ label: "Role", options: ["Admin", "User"], value: $role })` | | `value` | signal | - | Selected value |
| **Autocomplete** | Searchable dropdown with filtering | `autocomplete({ label: "Country", options: countryList, value: $country })` | | `onselect` | function | - | Called when an item is selected |
| **Datepicker** | Date picker (single or range mode) | `Datepicker({ label: "Date", value: $date, range: false })` | | `placeholder` | string | `'Buscar...'` | Input placeholder |
| **Colorpicker** | Visual color picker with palette | `Colorpicker({ label: "Theme", value: $color })` | | `class` | string | - | Extra classes |
| **Checkbox** | Checkbox or toggle switch | `Checkbox({ label: "Remember me", value: $remember })` | | `...props` | | | All other props are passed to the internal `Input` |
| **Radio** | Radio button | `Radio({ label: "Option 1", value: $selected, name: "group" })` |
| **Range** | Slider control | `Range({ label: "Volume", min: 0, max: 100, value: $volume })` |
| **Rating** | Star rating component | `Rating({ value: $stars, count: 5 })` |
| **Swap** | Toggle between two states (sun/moon) | `Swap({ on: "🌞", off: "🌙", value: $isDark })` |
--- ---
## Input Validation ## Badge
`Badge(props, children)`
The `Input` component supports real-time validation via the `validate` prop: | Prop | Type | Default | Description |
|------|------|---------|-------------|
```javascript | `class` | string | - | Extra classes merged with `badge` |
const email = $('');
input({
type: 'email',
value: email,
placeholder: 'Enter your 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)
})
```
**How it works:**
- Returns `''` or `null` → no error
- Returns a string → shows error message and adds `input-error` class
- Validates on every keystroke
- No external state needed for error messages
--- ---
## Data Display ## Button
`Button(props, children)`
| Component | Description | Example | | Prop | Type | Default | Description |
| :--- | :--- | :--- | |------|------|---------|-------------|
| **Table** | Reactive data grid with columns | `Table({ items: $users, columns: [{ label: "Name", key: "name" }] })` | | `class` | string | - | Extra classes merged with `button` |
| **List** | Vertical list with custom rendering | `List({ items: $items, render: (item) => item.name })` | | `disabled` | boolean | - | Standard HTML attribute |
| **Badge** | Small status indicators | `Badge({ class: "badge-primary" }, "New")` | | *any* | | | All other standard `<button>` attributes |
| **Stat** | Statistical data blocks (KPIs) | `Stat({ label: "Total", value: "1.2k", desc: "Monthly" })` |
| **Timeline** | Vertical/horizontal timeline | `Timeline({ items: [{ title: "Step 1", detail: "Completed" }] })` |
| **Stack** | Stacked elements | `Stack({}, [Card1, Card2, Card3])` |
| **Indicator** | Badge on corner of element | `Indicator({ value: () => count() }, button(...))` |
--- ---
## Feedback & Overlays ## Calendar
`Calendar(props)`
| Component | Description | Example | | Prop | Type | Default | Description |
| :--- | :--- | :--- | |------|------|---------|-------------|
| **Alert** | Inline contextual notification | `Alert({ type: "success" }, "Changes saved!")` | | `value` | signal \| string \| object | - | Selected date(s) |
| **Modal** | Dialog overlay | `Modal({ open: $isOpen, title: "Confirm" }, "Are you sure?")` | | `range` | signal \| boolean | `false` | Enable range mode |
| **Toast** | Floating notification (auto-stacking) | `Toast("Action completed", "alert-info", 3000)` | | `hour` | boolean | `false` | Show hour sliders |
| **Tooltip** | Hover tooltip wrapper | `Tooltip({ tip: "Help text", ui: "tooltip-top" }, button(...))` | | `onChange` | function | - | Called when selection changes |
| `class` | string | - | Extra classes |
--- ---
## Navigation & Layout ## Card
`Card(props, children)`
| Component | Description | Example | | Prop | Type | Default | Description |
| :--- | :--- | :--- | |------|------|---------|-------------|
| **Navbar** | Top navigation bar | `Navbar({}, [Logo, Menu, Avatar])` | | `class` | string | - | Extra classes merged with `card` |
| **Menu** | Vertical navigation menu | `Menu({ items: [{ label: "Home", onclick: goHome }] })` |
| **Drawer** | Side drawer (off-canvas) | `Drawer({ id: "my-drawer", open: $isOpen, content: Main, side: SideMenu })` |
| **Tabs** | Content switching | `Tabs({ items: [{ label: "Tab1", content: Panel1 }] })` |
| **Accordion** | Collapsible sections | `Accordion({ title: "Details" }, "Hidden content")` |
| **Dropdown** | Contextual menus | `Dropdown({ label: "Options" }, [MenuLink("Edit")])` |
| **Fieldset** | Form grouping with legend | `Fieldset({ legend: "Personal Info" }, [input(...)])` |
--- ---
## Interaction & Utilities ## CardActions
`CardActions(props, children)`
| Component | Description | Example | | Prop | Type | Default | Description |
| :--- | :--- | :--- | |------|------|---------|-------------|
| **Fab** | Floating Action Button with actions | `Fab({ icon: "+", actions: [{ label: "Add", onclick: add }] })` | | `class` | string | - | Extra classes merged with `card-actions` |
| **Indicator** | Badge indicator wrapper | `Indicator({ value: () => unread() }, button(...))` |
--- ---
## Internationalization (i18n) ## CardBody
`CardBody(props, children)`
Built-in locale system with Spanish and English support. | Prop | Type | Default | Description |
|------|------|---------|-------------|
```javascript | `class` | string | - | Extra classes merged with `card-body` |
// Set global UI language
Locale("en");
// Get translated string (returns a reactive signal)
const closeText = tt("close"); // "Close" or "Cerrar"
```
--- ---
## Implementation Notes ## CardTitle
`CardTitle(props, children)`
1. **Atomic Reactivity**: Components accepting `value` or `checked` bind directly to **SigPro Signals**. Updates happen instantly without full page re-renders. | Prop | Type | Default | Description |
|------|------|---------|-------------|
2. **DaisyUI v5**: Pass any daisyUI styling via the `class` attribute. | `class` | string | - | Extra classes merged with `card-title` |
```javascript
button({ class: "btn-primary btn-sm rounded-full shadow-lg" }, "Click")
```
3. **Zero Imports (Global Mode)**: After calling `UI()`, all components are attached to `window`. No manual imports needed.
4. **Self-Cleaning**: Components internally manage `_cleanups` to destroy observers and event listeners when removed from the DOM.
--- ---
## Advanced Examples ## Carousel
`Carousel(props, children)`
### Reactive Form with Validation | Prop | Type | Default | Description |
|------|------|---------|-------------|
```javascript | `class` | string | - | Extra classes merged with `carousel` |
const name = $("");
input({
value: name,
placeholder: "Name",
validate: (value) => {
if (!value) return "Name is required";
if (value.length < 3) return "Name too short";
return "";
},
oninput: (e) => name(e.target.value)
})
```
### API Request Pattern
```javascript
const userId = $("123");
const userData = $(null);
watch(userId, async (id) => {
loading(true);
userData(await fetch(`/api/user/${id}`).then(r => r.json()));
loading(false);
});
// In template
when(() => userData(), () => Alert({ type: "success" }, userData()?.name))
```
### Modal with Confirm Action
```javascript
const showModal = $(false);
Modal({
open: showModal,
title: "Delete Item",
buttons: [
button({ class: "btn-error", onclick: () => { deleteItem(); showModal(false); } }, "Delete")
]
}, "Are you sure you want to delete this item?");
```
--- ---
## Component Props Quick Reference ## CarouselItem
`CarouselItem(props, children)`
| Component | Key Props | | Prop | Type | Default | Description |
| :--- | :--- | |------|------|---------|-------------|
| `Button` | `class`, `disabled`, `loading`, `icon` | | `class` | string | - | Extra classes merged with `carousel-item` |
| `Input` | `value`, `validate`, `type`, `placeholder`, `icon`, `disabled` |
| `Select` | `label`, `options`, `value`, `disabled` | ---
| `Modal` | `open`, `title`, `buttons` |
| `Table` | `items`, `columns`, `zebra`, `pinRows`, `empty` | ## Chat
| `Alert` | `type` (info/success/warning/error), `soft`, `actions` | `Chat(props, children)`
| `Toast` | `message`, `type`, `duration` |
| `Datepicker` | `value`, `range`, `label`, `placeholder` | | Prop | Type | Default | Description |
| `Autocomplete` | `options`, `value`, `onselect`, `label` | |------|------|---------|-------------|
| `Indicator` | `value` (function that returns number/string) | | `class` | string | - | Extra classes merged with `chat` |
| `Tooltip` | `tip`, `ui` (tooltip-top/bottom/left/right) |
---
## ChatBubble
`ChatBubble(props, children)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `chat-bubble` |
---
## ChatFooter
`ChatFooter(props, children)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `chat-footer` |
---
## ChatHeader
`ChatHeader(props, children)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `chat-header` |
---
## ChatImage
`ChatImage(props, children)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `chat-image avatar` |
| *(children)* | string \| node | - | If a string, used as `src` of an `<img>` inside a rounded `div`; otherwise passed as content. |
---
## Checkbox
`Checkbox(props)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `checkbox` |
| `checked` | boolean \| signal | - | Checked state (can be a signal) |
| `onchange` | function | - | Change handler |
| *any* | | | All standard `<input>` attributes |
---
## Colorpicker
`Colorpicker(props)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `value` | signal \| string | `'#000000'` | Selected color |
| `label` | string | - | Optional label inside the trigger |
| `onchange` | function | - | Called when a color is picked (if `value` is not a signal) |
| `class` | string | - | Extra classes |
---
## Datepicker
`Datepicker(props)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `value` | signal | - | Selected date(s) |
| `range` | signal \| boolean | `false` | Range mode |
| `hour` | boolean | `false` | Enable hour selection |
| `onChange` | function | - | Change handler (if `value` is not a signal) |
| `placeholder` | string | auto | Placeholder text |
| `class` | string | - | Extra classes |
---
## Divider
`Divider(props)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `divider` |
---
## Drawer
`Drawer(props, children)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `open` | signal \| boolean | - | Drawer open state |
| `side` | node \| signal | - | Sidebar content (can be a signal) |
| `id` | string | autogenerated | Drawer ID |
| `class` | string | - | Extra classes |
**children** Main content (`.drawer-content`).
---
## Dropdown
`Dropdown(props, children?)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `trigger` | node | `'Dropdown'` | Summary content |
| `items` | array \| signal | - | Dropdown items: `{ label, onclick? }` |
| `class` | string | - | Extra classes merged with `dropdown` |
If `children` is provided, it replaces the autogenerated menu.
---
## Fab
`Fab(props, children)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `fab` |
---
## Fieldset
`Fieldset(props, children)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `legend` | string | - | Legend text (rendered as `<legend>`) |
| `class` | string | - | Extra classes merged with `fieldset` |
---
## Fileinput
`Fileinput(props)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `max` | number | `2` | Max file size in MB |
| `accept` | string | `'*'` | Accepted file types |
| `onselect` | function | - | Called when files change (receives array) |
| `value` | signal | - | Alternative to `onselect` for signal binding |
| `class` | string | - | Extra classes |
---
## Icon
`Icon(props)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | If the value starts with `icon-`, used as class; otherwise becomes content |
---
## Indicator
`Indicator(props, children)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `value` | node | - | Content of the indicator badge |
| `class` | string | - | Extra classes merged with `indicator` |
---
## Input
`Input(props)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `label` | string | - | Label text |
| `float` | boolean | `false` | Floating label |
| `left` | node | - | Element on the left |
| `right` | node | - | Element on the right |
| `hint` | string | - | Validation hint |
| `content` | node \| function | - | Content shown when focused (can be a function receiving `isFocused`) |
| `rule` | string | - | Validation pattern |
| `type` | string | `'text'` | Input type (`text`, `password`, etc.) |
| `value` | signal \| string | - | Input value |
| `class` | string | - | Extra classes |
| *any* | | | All other props are passed to the `<input>` |
---
## Kbd
`Kbd(props, children)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `kbd` |
---
## LabelFloat
`LabelFloat(props, children)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `floating-label` |
---
## LabelInput
`LabelInput(props, children)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `input` |
---
## LabelSelect
`LabelSelect(props, children)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `select` |
---
## Loading
`Loading(props)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `loading loading-spinner` |
---
## Menu
`Menu(props)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `items` | array \| signal | `[]` | Menu items: `{ label, href?, onclick?, children? }` |
| `keyFn` | function | `(it, idx) => it?.id ?? idx` | Key function for reactivity |
| `class` | string | - | Extra classes |
If `children` is provided, manual mode.
---
## Navbar
`Navbar(props, children)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `navbar` |
---
## Progress
`Progress(props)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `progress` |
---
## Radial
`Radial(props, children)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `value` | number | `0` | Progress value (sets CSS `--value` and default text) |
| `style` | string | - | Additional inline styles appended to the defaults |
| `class` | string | - | Extra classes merged with `radial-progress` |
---
## Radio
`Radio(props)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `radio` |
| `checked` | boolean | - | Checked state |
| `onchange` | function | - | Change handler |
---
## Range
`Range(props)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `range` |
| `min` | number | - | Standard range attribute |
| `max` | number | - | Standard range attribute |
| `value` | number | - | Standard range attribute |
---
## Rating
`Rating(props)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `value` | number \| signal | - | Selected rating |
| `count` | number | `5` | Number of stars |
| `mask` | string | `'mask-star'` | Shape class (e.g., `'mask-heart'`) |
| `onchange` | function | - | Called when a star is clicked |
| `class` | string | - | Extra classes |
---
## Select
`Select(props, children?)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `items` | array \| signal | - | Options list |
| `placeholder` | string | - | Placeholder option text |
| `placeholderDisabled` | boolean | `true` | Whether the placeholder is disabled |
| `label` | string | - | Label text |
| `float` | boolean | `false` | Floating label style |
| `left` | node | - | Element left of select |
| `right` | node | - | Element right of select |
| `hint` | string | - | Validation hint |
| `value` | signal \| string | - | Selected value |
| `keyFn` | function | auto | Key function for reactivity |
| `class` | string | - | Extra classes |
If `children` is provided, manual mode.
---
## Skeleton
`Skeleton(props)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `skeleton` |
---
## SkeletonText
`SkeletonText(props)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `skeleton skeleton-text` |
---
## Stack
`Stack(props, children)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `stack` |
---
## Step
`Step(props, children)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `dataContent` | string | - | Value for the `data-content` attribute |
| `class` | string | - | Extra classes merged with `step` |
---
## Steps
`Steps(props, children)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `steps` |
---
## Swap
`Swap(props)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `value` | signal \| boolean | - | Checked state (can be a signal) |
| `on` | node | - | Content for the “on” state |
| `off` | node | - | Content for the “off” state |
| `class` | string | - | Extra classes merged with `swap` |
---
## Table
`Table(props)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `items` | array \| signal | `[]` | Data rows |
| `columns` | array | `[]` | Column definitions: `{ key?, label?, class?, render? }` |
| `header` | boolean | `true` | Show header row |
| `keyFn` | function | `(item, idx) => item?.id ?? idx` | Key function for reactivity |
| `class` | string | - | Extra classes |
If `children` is provided, manual mode (ignores `items`).
---
## Tabs
`Tabs(props)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `items` | array \| signal | `[]` | Tab items: `{ label, content, closable?, class?, contentClass?, onclick? }` |
| `activeIndex` | signal | - | Index of the active tab |
| `onClose` | function | - | Called when a closable tab is closed: `(idx, item)` |
| `class` | string | - | Extra classes |
If `children` is provided, manual mode.
---
## TextRotate
`TextRotate(props)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `words` | array \| string | - | Array of words or commaseparated string |
| `class` | string | - | Extra classes merged with `text-rotate` |
---
## Textarea
`Textarea(props)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `textarea` |
| *any* | | | All standard `<textarea>` attributes |
---
## Timeline
`Timeline(props, children)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `vertical` | boolean | `true` | If `false`, horizontal layout |
| `compact` | boolean | `false` | Compact mode |
| `class` | string | - | Extra classes merged with `timeline` |
---
## Toast
`Toast(message, type?, duration?)`
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `message` | string \| signal | - | Toast text |
| `type` | string | `'alert-success'` | Alert type class |
| `duration` | number | `3500` | Autoclose time in ms (0 for manual close) |
Returns a `close` function.
---
## Toggle
`Toggle(props)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `toggle` |
| `checked` | boolean \| signal | - | Checked state |
| `onchange` | function | - | Change handler |
---
## Tooltip
`Tooltip(props, children)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `tip` | string | - | Tooltip text (sets `data-tip`) |
| `class` | string | - | Extra classes merged with `tooltip` |

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

172
index.js
View File

@@ -1,92 +1,96 @@
import * as AccordionModule from './components/accordion.js'; import * as AllModule from './components/All.js';
import * as AlertModule from './components/alert.js'; import * as EditorModule from './components/Editor.js';
import * as AutocompleteModule from './components/Autocomplete.js'; // import * as AccordionModule from './components/accordion.js';
import * as BadgeModule from './components/badge.js'; // import * as AlertModule from './components/alert.js';
import * as ButtonModule from './components/button.js'; // import * as AutocompleteModule from './components/discarted/Autocomplete.js';
import * as CalendarModule from './components/Calendar.js'; // import * as BadgeModule from './components/badge.js';
import * as CardModule from './components/card.js'; // import * as ButtonModule from './components/button.js';
import * as CarouselModule from './components/carousel.js'; // import * as CalendarModule from './components/Calendar.js';
import * as ChatModule from './components/chat.js'; // import * as CardModule from './components/card.js';
import * as CheckboxModule from './components/checkbox.js'; // import * as CarouselModule from './components/carousel.js';
import * as ColorpickerModule from './components/colorpicker.js'; // import * as ChatModule from './components/chat.js';
import * as DatepickerModule from './components/Datepicker.js'; // import * as CheckboxModule from './components/checkbox.js';
import * as DrawerModule from './components/drawer.js'; // import * as ColorpickerModule from './components/Colorpicker.js';
import * as DropdownModule from './components/dropdown.js'; // import * as DatepickerModule from './components/Datepicker.js';
import * as FabModule from './components/fab.js'; // import * as DrawerModule from './components/drawer.js';
import * as FieldsetModule from './components/fieldset.js'; // import * as DropdownModule from './components/dropdown.js';
import * as FileinputModule from './components/fileinput.js'; // import * as FabModule from './components/fab.js';
import * as IconModule from './components/icon.js'; // import * as FieldsetModule from './components/fieldset.js';
import * as IndicatorModule from './components/indicator.js'; // import * as FileinputModule from './components/fileinput.js';
import * as InputModule from './components/Input.js'; // import * as IconModule from './components/icon.js';
import * as KdbModule from './components/kbd.js'; // import * as IndicatorModule from './components/indicator.js';
// import * as InputModule from './components/Input.js';
// import * as KdbModule from './components/kbd.js';
// import * as ListModule from './components/List.js'; // import * as ListModule from './components/List.js';
import * as LoadingModule from './components/loading.js'; // import * as LoadingModule from './components/loading.js';
import * as MenuModule from './components/menu.js'; // import * as MenuModule from './components/menu.js';
import * as ModalModule from './components/modal.js'; // import * as ModalModule from './components/modal.js';
import * as NavbarModule from './components/navbar.js'; // import * as NavbarModule from './components/navbar.js';
import * as RadialModule from './components/radial.js'; // import * as RadialModule from './components/radial.js';
import * as RadioModule from './components/radio.js'; // import * as RadioModule from './components/radio.js';
import * as RangeModule from './components/range.js'; // import * as RangeModule from './components/range.js';
import * as RatingModule from './components/rating.js'; // import * as RatingModule from './components/rating.js';
import * as SkeletonModule from './components/skeleton.js'; // import * as SkeletonModule from './components/skeleton.js';
import * as SelectModule from './components/select.js'; // import * as SelectModule from './components/discarted/Select.js';
import * as StackModule from './components/stack.js'; // import * as StackModule from './components/stack.js';
import * as StatModule from './components/stat.js'; // import * as StatModule from './components/stat.js';
import * as StepsModule from './components/stat.js'; // import * as StepsModule from './components/stat.js';
import * as SwapModule from './components/swap.js'; // import * as SwapModule from './components/swap.js';
import * as TableModule from './components/table.js'; // import * as TableModule from './components/table.js';
import * as TabsModule from './components/tabs.js'; // import * as TabsModule from './components/tabs.js';
import * as TextareaModule from './components/textarea.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 TimelineModule from './components/timeline.js';
import * as ToastModule from './components/toast.js'; // import * as ToastModule from './components/toast.js';
import * as TooltipModule from './components/tooltip.js'; // import * as TooltipModule from './components/tooltip.js';
import { Locale, tt } from './utils.js'; import { Locale, tt } from './utils.js';
export const Components = { export const Components = {
...AccordionModule, ...AllModule,
...AlertModule, ...EditorModule,
...AutocompleteModule, // ...AccordionModule,
...BadgeModule, // ...AlertModule,
...ButtonModule, // ...AutocompleteModule,
...CalendarModule, // ...BadgeModule,
...CardModule, // ...ButtonModule,
...CarouselModule, // ...CalendarModule,
...ChatModule, // ...CardModule,
...CheckboxModule, // ...CarouselModule,
...ColorpickerModule, // ...ChatModule,
...DatepickerModule, // ...CheckboxModule,
...DrawerModule, // ...ColorpickerModule,
...DropdownModule, // ...DatepickerModule,
...FabModule, // ...DrawerModule,
...FieldsetModule, // ...DropdownModule,
...FileinputModule, // ...FabModule,
...IconModule, // ...FieldsetModule,
...IndicatorModule, // ...FileinputModule,
...InputModule, // ...IconModule,
...KdbModule, // ...IndicatorModule,
// ...InputModule,
// ...KdbModule,
// ...ListModule, // ...ListModule,
...LoadingModule, // ...LoadingModule,
...MenuModule, // ...MenuModule,
...ModalModule, // ...ModalModule,
...NavbarModule, // ...NavbarModule,
...RadialModule, // ...RadialModule,
...RadioModule, // ...RadioModule,
...RangeModule, // ...RangeModule,
...RatingModule, // ...RatingModule,
...SkeletonModule, // ...SkeletonModule,
...SelectModule, // ...SelectModule,
...StackModule, // ...StackModule,
...StatModule, // ...StatModule,
...StepsModule, // ...StepsModule,
...SwapModule, // ...SwapModule,
...TableModule, // ...TableModule,
...TabsModule, // ...TabsModule,
...TextareaModule, // ...TextareaModule,
...TextrotateModule, // ...TextrotateModule,
...TimelineModule, // ...TimelineModule,
...ToastModule, // ...ToastModule,
...TooltipModule // ...TooltipModule
}; };
export const Utils = { export const Utils = {

View File

@@ -99,14 +99,39 @@
transform-origin: top; transform-origin: top;
} }
/* 1. El contenedor manda */
.input-container {
position: relative;
width: 100%;
display: flex;
flex-direction: column;
}
/* 2. El label de DaisyUI debe ocupar todo el ancho */
.input-container .input {
width: 100%;
display: flex;
align-items: center;
}
/* 3. El panel flotante */
.input-content { .input-content {
position: absolute; /* Lo saca del flujo para que no empuje nada */ position: absolute;
top: 100%; /* Lo pega justo al borde inferior del input */ top: 100%; /* Justo debajo */
left: 0; left: 0;
width: 100%; /* Para que mida lo mismo que el input */ right: 0; /* Esto lo obliga a estirarse de izquierda a derecha */
z-index: 100; /* Para que pase por encima de otros botones/inputs */ z-index: 50;
background: white; /* Para que no sea transparente y se lea bien */ background: oklch(var(--b1)); /* Fondo del tema */
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); /* Opcional: para darle profundidad */ border: 1px solid oklch(var(--bc) / 0.2);
border-radius: var(--rounded-box, 1rem);
margin-top: 0.25rem;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
overflow: hidden; /* Para que el 'menu' no se salga de los bordes redondeados */
}
/* 4. La lista interna */
.input-content .menu {
width: 100%;
} }
@keyframes tabFadeIn { @keyframes tabFadeIn {
@@ -148,67 +173,6 @@
} }
} }
/* @layer utilities {
button {
@apply btn;
}
input:not([type="radio"]):not([type="checkbox"]):not([type="range"]):not(
[type="color"]
),
select,
textarea {
@apply input;
}
input[type="radio"] {
@apply radio;
}
input[type="checkbox"] {
@apply checkbox;
}
input[type="range"] {
@apply range;
}
select {
@apply select;
}
textarea {
@apply textarea;
}
hr {
@apply divider;
}
progress {
@apply progress;
}
table {
@apply table;
}
dialog {
@apply modal;
}
[data-tip] {
@apply tooltip;
}
nav {
@apply navbar;
}
[role="alert"] {
@apply alert;
}
} */
/* sigpro-ui daisyUI classes - extracted from components */ /* sigpro-ui daisyUI classes - extracted from components */