Before repair nav components
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
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")
])
]);
};
-92
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
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)
})
])
])
}
-60
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
])
))
])
]);
};
-58
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;
};
+61
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
])
)
)
]);
};
+46
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
])
))
]),
]);
};
+30
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);
};
@@ -23,6 +23,7 @@ export const filterBy = (items, query, field = 'label') => {
});
};
export const listKey = (items, isOpen) => {
const cursor = $(-1);
@@ -65,4 +65,5 @@ export const Fileinput = (props) => {
])
)
]);
};
};
+4
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);
-34
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
]
);
-7
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);
};
+332 -20
View File
@@ -13,6 +13,7 @@
--spacing: 0.25rem;
--container-xs: 20rem;
--container-md: 28rem;
--container-2xl: 42rem;
--container-3xl: 48rem;
--container-5xl: 64rem;
--container-6xl: 72rem;
@@ -41,6 +42,7 @@
--font-weight-light: 300;
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
--font-weight-black: 900;
--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 {
visibility: collapse;
}
@@ -3101,9 +3133,6 @@
.top-0 {
top: calc(var(--spacing) * 0);
}
.top-1\/2 {
top: calc(1 / 2 * 100%);
}
.top-2 {
top: calc(var(--spacing) * 2);
}
@@ -3125,9 +3154,6 @@
.right-1\/4 {
right: calc(1 / 4 * 100%);
}
.right-2 {
right: calc(var(--spacing) * 2);
}
.right-3 {
right: calc(var(--spacing) * 3);
}
@@ -3879,6 +3905,9 @@
}
}
}
.mx-1 {
margin-inline: calc(var(--spacing) * 1);
}
.mx-auto {
margin-inline: auto;
}
@@ -4133,6 +4162,19 @@
mask-size: 100% 100%;
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='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\] {
display: inline-block;
width: 1em;
@@ -4224,6 +4266,32 @@
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");
}
.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\] {
display: inline-block;
width: 1em;
@@ -4289,6 +4357,19 @@
mask-size: 100% 100%;
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%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\] {
display: inline-block;
width: 1em;
@@ -4302,6 +4383,32 @@
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");
}
.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\] {
display: inline-block;
width: 1em;
@@ -4341,7 +4448,7 @@
mask-size: 100% 100%;
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M13.832 16.568a1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233a14 14 0 0 0 6.392 6.384'/%3E%3C/svg%3E");
}
.icon-\[lucide--search-x\] {
.icon-\[lucide--plus\] {
display: inline-block;
width: 1em;
height: 1em;
@@ -4352,7 +4459,33 @@
mask-repeat: no-repeat;
-webkit-mask-size: 100% 100%;
mask-size: 100% 100%;
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cg fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='m13.5 8.5l-5 5m0-5l5 5'/%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21l-4.3-4.3'/%3E%3C/g%3E%3C/svg%3E");
--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\] {
display: inline-block;
@@ -4367,6 +4500,32 @@
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");
}
.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\] {
display: inline-block;
width: 1em;
@@ -4380,6 +4539,32 @@
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");
}
.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\] {
display: inline-block;
width: 1em;
@@ -4393,6 +4578,19 @@
mask-size: 100% 100%;
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='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\] {
display: inline-block;
width: 1em;
@@ -4687,6 +4885,9 @@
.hidden {
display: none;
}
.inline {
display: inline;
}
.inline-block {
display: inline-block;
}
@@ -4776,6 +4977,9 @@
.h-4 {
height: calc(var(--spacing) * 4);
}
.h-5 {
height: calc(var(--spacing) * 5);
}
.h-8 {
height: calc(var(--spacing) * 8);
}
@@ -4827,6 +5031,9 @@
.min-h-0 {
min-height: calc(var(--spacing) * 0);
}
.min-h-\[10rem\] {
min-height: 10rem;
}
.min-h-\[300px\] {
min-height: 300px;
}
@@ -4850,11 +5057,6 @@
width: 100%;
}
}
.loading-xs {
@layer daisyui.l1.l2 {
width: calc(var(--size-selector, 0.25rem) * 4);
}
}
.w-3\.5 {
width: calc(var(--spacing) * 3.5);
}
@@ -4903,6 +5105,12 @@
.w-full {
width: 100%;
}
.w-px {
width: 1px;
}
.max-w-2xl {
max-width: var(--container-2xl);
}
.max-w-3xl {
max-width: var(--container-3xl);
}
@@ -4949,6 +5157,10 @@
--tw-translate-x: calc(calc(1 / 2 * 100%) * -1);
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 {
--tw-translate-x: calc(var(--spacing) * 2);
translate: var(--tw-translate-x) var(--tw-translate-y);
@@ -4965,10 +5177,6 @@
--tw-translate-x: 100%;
translate: var(--tw-translate-x) var(--tw-translate-y);
}
.-translate-y-1\/2 {
--tw-translate-y: calc(calc(1 / 2 * 100%) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
.translate-y-2 {
--tw-translate-y: calc(var(--spacing) * 2);
translate: var(--tw-translate-x) var(--tw-translate-y);
@@ -5098,6 +5306,9 @@
.flex-col-reverse {
flex-direction: column-reverse;
}
.flex-nowrap {
flex-wrap: nowrap;
}
.flex-wrap {
flex-wrap: wrap;
}
@@ -5107,6 +5318,9 @@
.items-end {
align-items: flex-end;
}
.items-start {
align-items: flex-start;
}
.items-stretch {
align-items: stretch;
}
@@ -5283,6 +5497,10 @@
border-style: var(--tw-border-style);
border-width: 1px;
}
.border-0 {
border-style: var(--tw-border-style);
border-width: 0px;
}
.border-2 {
border-style: var(--tw-border-style);
border-width: 2px;
@@ -5553,6 +5771,17 @@
--tw-gradient-position: to right in oklab;
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 {
@layer daisyui.l1 {
color: var(--alert-color, var(--color-base-content));
@@ -6009,6 +6238,10 @@
--tw-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 {
--tw-tracking: var(--tracking-tight);
letter-spacing: var(--tracking-tight);
@@ -6293,6 +6526,9 @@
.line-through {
text-decoration-line: line-through;
}
.underline {
text-decoration-line: underline;
}
.swap-active {
@layer daisyui.l1.l2 {
.swap-off {
@@ -6327,6 +6563,9 @@
.opacity-80 {
opacity: 80%;
}
.opacity-100 {
opacity: 100%;
}
.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));
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 {
--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,);
@@ -7139,6 +7382,57 @@
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 {
font-size: 14px;
@@ -7170,14 +7464,32 @@
animation: tabFadeIn 0.3s cubic-bezier(0.4, 0, 0.2, 1);
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 {
position: absolute;
top: 100%;
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%;
z-index: 100;
background: white;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
@keyframes tabFadeIn {
from {
+767 -1068
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+767 -1068
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+5 -38
View File
@@ -5,41 +5,8 @@
* **Introduction**
* [Installation](install.md)
* [Quick Reference](quick.md)
* **Forms & Inputs**
* [Autocomplete](components/autocomplete.md)
* [Button](components/button.md)
* [Checkbox](components/checkbox.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)
* [Forms & Inputs](demo_forms.md)
* [Data & Dysplay](demo_display.md)
* [Feedback & Overlays](demo_overlay.md)
* [Navigation & Layout](demo_layout.md)
* [WYSIWYG Editor](demo_editor.md)
+84 -27
View File
@@ -8,51 +8,108 @@
</div>
```js
const paises = [
"España",
"México",
"Argentina",
"Colombia",
"Chile",
"Perú",
"Venezuela",
{ label: "Estados Unidos", value: "US" },
{ label: "Canadá", value: "CA" },
{ label: "Reino Unido", value: "UK" },
];
// const App = () => {
// const selected = $('');
// const filterType = $('all');
// const allItems = {
// fruits: ['Apple', 'Banana', 'Orange', 'Mango'],
// vegetables: ['Carrot', 'Broccoli', 'Spinach', 'Potato'],
// all: ['Apple', 'Banana', 'Orange', 'Mango', 'Carrot', 'Broccoli', 'Spinach', 'Potato']
// };
// 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 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
const requirements = [
{ label: "Mínimo 8 caracteres", check: () => password().length >= 8 },
{ 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" }, [
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({
type: "password",
value: password,
class: "input-warning",
rule: "[0-9]*",
hint: "Debes escribir numeros del 0 al 9",
oninput: (e) => password(e.target.value),
label: "Pass",
float: true,
left: span({ class: "icon-[lucide--lock] mr-2" }),
// 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" }, [
span({ 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
]))
)
])
})
content: div(
{
class:
"mt-2 p-4 bg-base-200 rounded-lg shadow-xl border border-base-300",
},
[
span(
{ 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
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
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
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
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
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'
);
```
+9 -10
View File
@@ -30,6 +30,7 @@
repo: "",
loadSidebar: true,
sidebarDisplayLevel: 1,
subMaxLevel: 3,
executeScript: true,
copyCode: {
buttonText:
@@ -38,26 +39,24 @@
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>',
},
search: {
placeholder: "Type to search",
noData: "No Results!",
depth: 3,
hideOtherSidebarContent: true,
},
plugins: [
function (hook, vm) {
hook.doneEach(function () {
// Seleccionamos solo los bloques marcados con ```js
const codeBlocks = document.querySelectorAll(
'pre[data-lang="js"] code',
);
codeBlocks.forEach((code) => {
try {
// Usamos un bloque anónimo para evitar colisiones de variables const/let
// si el usuario ejecuta el mismo código varias veces.
const scriptContent = `(function() {
${code.innerText}
})();`;
const scriptContent = `(function() { ${code.innerText} })();`;
const runDemo = new Function(scriptContent);
runDemo();
} 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);
}
});
@@ -66,7 +65,7 @@
],
};
</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-copy-code/dist/docsify-copy-code.min.js"></script>
<script src="./sigpro-ui.min.js"></script>
+563 -180
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
```javascript
import "sigpro-ui";
import "sigpro-ui/css";
// All components (Button, Input, Table, Toast, etc.) are now globally available.
```
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `items` | array \| signal | `[]` | Array of items. Each item: `{ title, content?, open? }` or a string. |
| `type` | `'radio'` \| `'details'` | `'radio'` | Collapse mode |
| `variant` | string | `''` | `'arrow'` or `'plus'` for an icon variant |
| `name` | string | autogenerated | Group name for radio inputs |
| `class` | string | - | Extra classes merged with `collapse` |
---
## Core Components
## Alert
`Alert(props, children)`
| Component | Purpose | Basic Example |
| :--- | :--- | :--- |
| **Button** | Styled button with DaisyUI | `button({ class: "btn-primary" }, "Submit")` |
| **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)` |
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `alert` |
---
## Forms & Inputs
## Autocomplete
`Autocomplete(props)`
| Component | Description | Example |
| :--- | :--- | :--- |
| **Input** | Text input with floating label, validation, password toggle | `input({ label: "Email", type: "email", value: $email, validate: validateEmail })` |
| **Select** | Dropdown selector | `Select({ label: "Role", options: ["Admin", "User"], value: $role })` |
| **Autocomplete** | Searchable dropdown with filtering | `autocomplete({ label: "Country", options: countryList, value: $country })` |
| **Datepicker** | Date picker (single or range mode) | `Datepicker({ label: "Date", value: $date, range: false })` |
| **Colorpicker** | Visual color picker with palette | `Colorpicker({ label: "Theme", value: $color })` |
| **Checkbox** | Checkbox or toggle switch | `Checkbox({ label: "Remember me", value: $remember })` |
| **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 })` |
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `items` | array \| signal | `[]` | Autocomplete items |
| `value` | signal | - | Selected value |
| `onselect` | function | - | Called when an item is selected |
| `placeholder` | string | `'Buscar...'` | Input placeholder |
| `class` | string | - | Extra classes |
| `...props` | | | All other props are passed to the internal `Input` |
---
## Input Validation
## Badge
`Badge(props, children)`
The `Input` component supports real-time validation via the `validate` prop:
```javascript
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
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `badge` |
---
## Data Display
## Button
`Button(props, children)`
| Component | Description | Example |
| :--- | :--- | :--- |
| **Table** | Reactive data grid with columns | `Table({ items: $users, columns: [{ label: "Name", key: "name" }] })` |
| **List** | Vertical list with custom rendering | `List({ items: $items, render: (item) => item.name })` |
| **Badge** | Small status indicators | `Badge({ class: "badge-primary" }, "New")` |
| **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(...))` |
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `button` |
| `disabled` | boolean | - | Standard HTML attribute |
| *any* | | | All other standard `<button>` attributes |
---
## Feedback & Overlays
## Calendar
`Calendar(props)`
| Component | Description | Example |
| :--- | :--- | :--- |
| **Alert** | Inline contextual notification | `Alert({ type: "success" }, "Changes saved!")` |
| **Modal** | Dialog overlay | `Modal({ open: $isOpen, title: "Confirm" }, "Are you sure?")` |
| **Toast** | Floating notification (auto-stacking) | `Toast("Action completed", "alert-info", 3000)` |
| **Tooltip** | Hover tooltip wrapper | `Tooltip({ tip: "Help text", ui: "tooltip-top" }, button(...))` |
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `value` | signal \| string \| object | - | Selected date(s) |
| `range` | signal \| boolean | `false` | Enable range mode |
| `hour` | boolean | `false` | Show hour sliders |
| `onChange` | function | - | Called when selection changes |
| `class` | string | - | Extra classes |
---
## Navigation & Layout
## Card
`Card(props, children)`
| Component | Description | Example |
| :--- | :--- | :--- |
| **Navbar** | Top navigation bar | `Navbar({}, [Logo, Menu, Avatar])` |
| **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(...)])` |
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `card` |
---
## Interaction & Utilities
## CardActions
`CardActions(props, children)`
| Component | Description | Example |
| :--- | :--- | :--- |
| **Fab** | Floating Action Button with actions | `Fab({ icon: "+", actions: [{ label: "Add", onclick: add }] })` |
| **Indicator** | Badge indicator wrapper | `Indicator({ value: () => unread() }, button(...))` |
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `card-actions` |
---
## Internationalization (i18n)
## CardBody
`CardBody(props, children)`
Built-in locale system with Spanish and English support.
```javascript
// Set global UI language
Locale("en");
// Get translated string (returns a reactive signal)
const closeText = tt("close"); // "Close" or "Cerrar"
```
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `card-body` |
---
## 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.
2. **DaisyUI v5**: Pass any daisyUI styling via the `class` attribute.
```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.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `card-title` |
---
## Advanced Examples
## Carousel
`Carousel(props, children)`
### Reactive Form with Validation
```javascript
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?");
```
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `carousel` |
---
## Component Props Quick Reference
## CarouselItem
`CarouselItem(props, children)`
| Component | Key Props |
| :--- | :--- |
| `Button` | `class`, `disabled`, `loading`, `icon` |
| `Input` | `value`, `validate`, `type`, `placeholder`, `icon`, `disabled` |
| `Select` | `label`, `options`, `value`, `disabled` |
| `Modal` | `open`, `title`, `buttons` |
| `Table` | `items`, `columns`, `zebra`, `pinRows`, `empty` |
| `Alert` | `type` (info/success/warning/error), `soft`, `actions` |
| `Toast` | `message`, `type`, `duration` |
| `Datepicker` | `value`, `range`, `label`, `placeholder` |
| `Autocomplete` | `options`, `value`, `onselect`, `label` |
| `Indicator` | `value` (function that returns number/string) |
| `Tooltip` | `tip`, `ui` (tooltip-top/bottom/left/right) |
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `carousel-item` |
---
## Chat
`Chat(props, children)`
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `class` | string | - | Extra classes merged with `chat` |
---
## 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` |
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+88 -84
View File
@@ -1,92 +1,96 @@
import * as AccordionModule from './components/accordion.js';
import * as AlertModule from './components/alert.js';
import * as AutocompleteModule from './components/Autocomplete.js';
import * as BadgeModule from './components/badge.js';
import * as ButtonModule from './components/button.js';
import * as CalendarModule from './components/Calendar.js';
import * as CardModule from './components/card.js';
import * as CarouselModule from './components/carousel.js';
import * as ChatModule from './components/chat.js';
import * as CheckboxModule from './components/checkbox.js';
import * as ColorpickerModule from './components/colorpicker.js';
import * as DatepickerModule from './components/Datepicker.js';
import * as DrawerModule from './components/drawer.js';
import * as DropdownModule from './components/dropdown.js';
import * as FabModule from './components/fab.js';
import * as FieldsetModule from './components/fieldset.js';
import * as FileinputModule from './components/fileinput.js';
import * as IconModule from './components/icon.js';
import * as IndicatorModule from './components/indicator.js';
import * as InputModule from './components/Input.js';
import * as KdbModule from './components/kbd.js';
import * as AllModule from './components/All.js';
import * as EditorModule from './components/Editor.js';
// import * as AccordionModule from './components/accordion.js';
// import * as AlertModule from './components/alert.js';
// import * as AutocompleteModule from './components/discarted/Autocomplete.js';
// import * as BadgeModule from './components/badge.js';
// import * as ButtonModule from './components/button.js';
// import * as CalendarModule from './components/Calendar.js';
// import * as CardModule from './components/card.js';
// import * as CarouselModule from './components/carousel.js';
// import * as ChatModule from './components/chat.js';
// import * as CheckboxModule from './components/checkbox.js';
// import * as ColorpickerModule from './components/Colorpicker.js';
// import * as DatepickerModule from './components/Datepicker.js';
// import * as DrawerModule from './components/drawer.js';
// import * as DropdownModule from './components/dropdown.js';
// import * as FabModule from './components/fab.js';
// import * as FieldsetModule from './components/fieldset.js';
// import * as FileinputModule from './components/fileinput.js';
// import * as IconModule from './components/icon.js';
// import * as IndicatorModule from './components/indicator.js';
// import * as InputModule from './components/Input.js';
// import * as KdbModule from './components/kbd.js';
// import * as ListModule from './components/List.js';
import * as LoadingModule from './components/loading.js';
import * as MenuModule from './components/menu.js';
import * as ModalModule from './components/modal.js';
import * as NavbarModule from './components/navbar.js';
import * as RadialModule from './components/radial.js';
import * as RadioModule from './components/radio.js';
import * as RangeModule from './components/range.js';
import * as RatingModule from './components/rating.js';
import * as SkeletonModule from './components/skeleton.js';
import * as SelectModule from './components/select.js';
import * as StackModule from './components/stack.js';
import * as StatModule from './components/stat.js';
import * as StepsModule from './components/stat.js';
import * as SwapModule from './components/swap.js';
import * as TableModule from './components/table.js';
import * as TabsModule from './components/tabs.js';
import * as TextareaModule from './components/textarea.js';
import * as TextrotateModule from './components/textrotate.js';
import * as TimelineModule from './components/timeline.js';
import * as ToastModule from './components/toast.js';
import * as TooltipModule from './components/tooltip.js';
// import * as LoadingModule from './components/loading.js';
// import * as MenuModule from './components/menu.js';
// import * as ModalModule from './components/modal.js';
// import * as NavbarModule from './components/navbar.js';
// import * as RadialModule from './components/radial.js';
// import * as RadioModule from './components/radio.js';
// import * as RangeModule from './components/range.js';
// import * as RatingModule from './components/rating.js';
// import * as SkeletonModule from './components/skeleton.js';
// import * as SelectModule from './components/discarted/Select.js';
// import * as StackModule from './components/stack.js';
// import * as StatModule from './components/stat.js';
// import * as StepsModule from './components/stat.js';
// import * as SwapModule from './components/swap.js';
// import * as TableModule from './components/table.js';
// import * as TabsModule from './components/tabs.js';
// import * as TextareaModule from './components/textarea.js';
// import * as TextrotateModule from './components/textrotate.js';
// import * as TimelineModule from './components/timeline.js';
// import * as ToastModule from './components/toast.js';
// import * as TooltipModule from './components/tooltip.js';
import { Locale, tt } from './utils.js';
export const Components = {
...AccordionModule,
...AlertModule,
...AutocompleteModule,
...BadgeModule,
...ButtonModule,
...CalendarModule,
...CardModule,
...CarouselModule,
...ChatModule,
...CheckboxModule,
...ColorpickerModule,
...DatepickerModule,
...DrawerModule,
...DropdownModule,
...FabModule,
...FieldsetModule,
...FileinputModule,
...IconModule,
...IndicatorModule,
...InputModule,
...KdbModule,
...AllModule,
...EditorModule,
// ...AccordionModule,
// ...AlertModule,
// ...AutocompleteModule,
// ...BadgeModule,
// ...ButtonModule,
// ...CalendarModule,
// ...CardModule,
// ...CarouselModule,
// ...ChatModule,
// ...CheckboxModule,
// ...ColorpickerModule,
// ...DatepickerModule,
// ...DrawerModule,
// ...DropdownModule,
// ...FabModule,
// ...FieldsetModule,
// ...FileinputModule,
// ...IconModule,
// ...IndicatorModule,
// ...InputModule,
// ...KdbModule,
// ...ListModule,
...LoadingModule,
...MenuModule,
...ModalModule,
...NavbarModule,
...RadialModule,
...RadioModule,
...RangeModule,
...RatingModule,
...SkeletonModule,
...SelectModule,
...StackModule,
...StatModule,
...StepsModule,
...SwapModule,
...TableModule,
...TabsModule,
...TextareaModule,
...TextrotateModule,
...TimelineModule,
...ToastModule,
...TooltipModule
// ...LoadingModule,
// ...MenuModule,
// ...ModalModule,
// ...NavbarModule,
// ...RadialModule,
// ...RadioModule,
// ...RangeModule,
// ...RatingModule,
// ...SkeletonModule,
// ...SelectModule,
// ...StackModule,
// ...StatModule,
// ...StepsModule,
// ...SwapModule,
// ...TableModule,
// ...TabsModule,
// ...TextareaModule,
// ...TextrotateModule,
// ...TimelineModule,
// ...ToastModule,
// ...TooltipModule
};
export const Utils = {
+31 -67
View File
@@ -99,14 +99,39 @@
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 {
position: absolute; /* Lo saca del flujo para que no empuje nada */
top: 100%; /* Lo pega justo al borde inferior del input */
position: absolute;
top: 100%; /* Justo debajo */
left: 0;
width: 100%; /* Para que mida lo mismo que el input */
z-index: 100; /* Para que pase por encima de otros botones/inputs */
background: white; /* Para que no sea transparente y se lea bien */
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); /* Opcional: para darle profundidad */
right: 0; /* Esto lo obliga a estirarse de izquierda a derecha */
z-index: 50;
background: oklch(var(--b1)); /* Fondo del tema */
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 {
@@ -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 */