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

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

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)

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
docs/demo_display.md Normal file
View File

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

19
docs/demo_editor.md Normal file
View File

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

181
docs/demo_forms.md Normal file
View File

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

201
docs/demo_layout.md Normal file
View File

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

84
docs/demo_overlay.md Normal file
View File

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

View File

@@ -30,6 +30,7 @@
repo: "",
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>

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` |

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long