59 Commits
1.0.1 ... 1.0.4

Author SHA1 Message Date
70a38f504b Using RollUp 2026-03-31 17:47:14 +02:00
0fc4913209 1.0.4 2026-03-31 17:27:42 +02:00
4b2fb8868a IIFE with SigProCore 2026-03-31 17:27:30 +02:00
0efb288a93 Update to UMD 2026-03-31 17:23:49 +02:00
881b61b69a New Build by components 2026-03-31 13:23:02 +02:00
Natxo
3add19ad5d Update index.js 2026-03-31 13:19:12 +02:00
Natxo
95d7bcd5ad Update index.js 2026-03-31 13:16:35 +02:00
Natxo
5a0cb585a0 Update index.js 2026-03-31 13:15:06 +02:00
Natxo
7600cb2b77 Update Readme.md 2026-03-31 13:01:02 +02:00
Natxo
87aefa3d48 Update Readme.md 2026-03-31 12:59:30 +02:00
7dfafef0e2 Include dist 2026-03-31 12:53:50 +02:00
Natxo
7f609800ed Update index.js 2026-03-31 12:52:33 +02:00
Natxo
b1f7db26ae Update index.js 2026-03-31 12:52:08 +02:00
Natxo
b04ed259da Update Readme.md 2026-03-31 12:50:00 +02:00
Natxo
25054d4817 Update package.json 2026-03-31 12:47:17 +02:00
Natxo
a0bc8a2953 Update index.js 2026-03-31 12:45:58 +02:00
Natxo
21e8c5167a Update index.js 2026-03-31 12:45:18 +02:00
Natxo
b038c8a5f0 Update index.js 2026-03-31 12:41:18 +02:00
Natxo
71d8fb7edf Create index.js 2026-03-31 12:20:59 +02:00
Natxo
40e22d183e Create Loading.js 2026-03-31 12:19:54 +02:00
Natxo
a4283ac66b Implement Toast component for notifications 2026-03-31 12:19:37 +02:00
Natxo
b5263d2ab3 Implement Floating Action Button component 2026-03-31 12:18:59 +02:00
Natxo
5e4650f2df Create Timeline.js 2026-03-31 12:18:44 +02:00
Natxo
8e691c2253 Implement Alert component with customizable types 2026-03-31 12:18:21 +02:00
Natxo
5b4056e3d2 Add Rating component for star rating input 2026-03-31 12:18:07 +02:00
Natxo
d8ede614ac Add Indicator component for displaying status 2026-03-31 12:17:39 +02:00
Natxo
862422ae42 Add Stack component for rendering stacked elements 2026-03-31 12:17:24 +02:00
Natxo
bf3ee40641 Add Swap component for toggle functionality 2026-03-31 12:17:05 +02:00
Natxo
62a3677187 Create Stat.js 2026-03-31 12:16:50 +02:00
Natxo
fd0324135b Add List component for rendering item lists 2026-03-31 12:16:40 +02:00
Natxo
ccbe8e5c4a Add Fieldset component for structured grouping 2026-03-31 12:16:28 +02:00
Natxo
5797e3cea5 Add Navbar component with styling and props 2026-03-31 12:16:09 +02:00
Natxo
c7fb374f38 Add Tooltip component
Implement Tooltip component for displaying tips.
2026-03-31 12:15:52 +02:00
Natxo
a417cb420f Add Badge component for displaying badges 2026-03-31 12:15:35 +02:00
Natxo
e517bbe540 Add Drawer component for UI drawer functionality 2026-03-31 12:15:08 +02:00
Natxo
d4bf359501 Add Menu component for rendering menu items 2026-03-31 12:14:53 +02:00
Natxo
b920a14b5e Add Table component for rendering data in tabular format 2026-03-31 12:14:40 +02:00
Natxo
76641410e7 Implement Tabs component with dynamic content 2026-03-31 12:14:26 +02:00
Natxo
c34fb76a2a Add Accordion component for collapsible sections 2026-03-31 12:14:10 +02:00
Natxo
8fd234ea4c Add Dropdown component for UI 2026-03-31 12:13:58 +02:00
Natxo
7f55732be1 Add Modal component for displaying dialogs 2026-03-31 12:13:46 +02:00
Natxo
3dda7ef1e0 Add Range component for input range selection 2026-03-31 12:13:34 +02:00
Natxo
361101a463 Add Radio component for radio input functionality 2026-03-31 12:13:24 +02:00
Natxo
7c6ee5394c Create Checkbox.js 2026-03-31 12:13:12 +02:00
Natxo
0e849ea03a Add Colorpicker component for color selection 2026-03-31 12:12:54 +02:00
Natxo
0006d1d9a9 Add Datepicker component for date selection 2026-03-31 12:12:30 +02:00
Natxo
cf8bb00a40 Implement Autocomplete component with options and navigation 2026-03-31 12:12:13 +02:00
Natxo
cefeae4a8b Add Select component for dropdown selection
Implement a Select component with label and options.
2026-03-31 12:11:57 +02:00
Natxo
51d1bb7c84 Rename button.js to Button.js 2026-03-31 12:08:04 +02:00
Natxo
0e6adbe44f Add Fileinput component for file selection 2026-03-31 12:07:51 +02:00
Natxo
019213f01c Implement Input component with dynamic features 2026-03-31 12:07:28 +02:00
Natxo
10a3f971d9 Add Button component with badge and tooltip support
Implement a Button component with optional badge and tooltip.
2026-03-31 12:05:22 +02:00
Natxo
ab01398878 Add utility functions for value and class joining 2026-03-31 11:44:40 +02:00
Natxo
e8d8993f01 Rename src/i18n.js to src/core/i18n.js 2026-03-31 11:44:03 +02:00
Natxo
d4b8d8831e Add i18n support for Spanish and English 2026-03-31 11:43:44 +02:00
Natxo
d9d4970324 Add icon exports for various UI elements 2026-03-31 11:43:23 +02:00
Natxo
4597eb2b25 Update package.json for version and entry point changes
Updated version, main entry point, and added peer dependencies.
2026-03-31 11:35:17 +02:00
Natxo
b1b1099f86 Bump version from 1.0.1 to 1.0.2 2026-03-30 23:25:48 +02:00
ea6d7a85f0 Add FileInput & Table 2026-03-30 23:16:32 +02:00
47 changed files with 6680 additions and 1316 deletions

1
.gitignore vendored
View File

@@ -3,7 +3,6 @@ node_modules/
jspm_packages/ jspm_packages/
# Build and Distribution # Build and Distribution
dist/
lib-cov/ lib-cov/
coverage/ coverage/
*.lcov *.lcov

131
Readme.md
View File

@@ -1,8 +1,8 @@
# SigPro UI (WIP) # SigPro UI (W.I.P.)
**SigPro UI** is a lightweight, ultra-fast, and reactive component library built for the **SigPro** reactivity core. It provides a set of high-quality, accessible, and themeable UI components leveraging the power of **Tailwind CSS v4** and **daisyUI v5**. **SigPro UI** is a lightweight, ultra-fast, and reactive component library built for the **SigPro** reactivity core. It provides a set of high-quality, accessible, and themeable UI components leveraging the power of **Tailwind CSS v4** and **daisyUI v5**.
Unlike heavy frameworks, SigPro UI focuses on a **"Zero-Build"** or **"Zero-Import"** philosophy, allowing you to build complex reactive interfaces with a functional, declarative syntax that feels like JSX but runs natively in the browser. Unlike heavy frameworks, SigPro UI focuses on a **"Zero-Build"** philosophy, allowing you to build complex reactive interfaces with a functional, declarative syntax that runs natively in the browser.
--- ---
@@ -10,83 +10,120 @@ Unlike heavy frameworks, SigPro UI focuses on a **"Zero-Build"** or **"Zero-Impo
* **Signals-Based Reactivity**: Powered by SigPro for granular DOM updates. * **Signals-Based Reactivity**: Powered by SigPro for granular DOM updates.
* **DaisyUI v5 Integration**: Beautiful, semantic components out of the box. * **DaisyUI v5 Integration**: Beautiful, semantic components out of the box.
* **No Compilation Needed**: Write standard JavaScript; no Babel or TSC required. * **Tree Shaking Ready**: Import only what you need.
* **Zero-Import Option**: Inject all components into the global scope with one command.
* **Lightweight**: Minimal footprint with a focus on performance. * **Lightweight**: Minimal footprint with a focus on performance.
* **Fully Extensible**: Easy to wrap and create custom reactive components.
--- ---
## Prerequisites ## Prerequisites
To use SigPro UI, your project must include the following dependencies: To use SigPro UI, your project must include:
### 1. SigPro Core 1. **SigPro Core**: `npm install sigpro` (or via CDN).
The underlying reactivity engine. SigPro UI depends on `$`, `$watch`, `$html`, `$if`, and `$for` being available in the global scope or imported. 2. **Tailwind CSS v4**: For utility-first styling.
3. **daisyUI v5**: The component engine for Tailwind.
### 2. Tailwind CSS v4
Used for utility-first styling and the engine behind the component layouts.
### 3. daisyUI v5 ## 1. Prerequisites & Installation
The component plugin for Tailwind that provides the visual styles (buttons, modals, cards, etc.). SigPro UI is built as an extension of the SigPro reactivity system. You need to install the core library first.
### Step 1: Install SigPro Core
```Bash
bun add sigpro
# or
npm install sigpro
```
### Step 2: Install SigPro UI
```Bash
bun add sigpro-ui
# or
npm install sigpro-ui
```
### Via CDN (Browser)
```html
<script src="https://unpkg.com/sigpro"></script>
<script src="https://unpkg.com/sigpro-ui"></script>
```
--- ---
## Installation & Setup ## 2. Styling Setup (Tailwind CSS v4)
SigPro UI components rely on **Tailwind CSS** and **daisyUI** for styling. You don't need a complex tailwind.config.js; simply configure your main CSS entry point.
### 1. Configure Tailwind & daisyUI Create or edit your global CSS file (e.g., style.css):
Ensure your `tailwind.config.js` (or CSS entry point in v4) is set up to include daisyUI v5.
```css ```css
/* In your main CSS file (Tailwind v4 style) */ /* src/style.css */
@import "tailwindcss"; @import "tailwindcss";
@plugin "daisyui"; @plugin "daisyui";
/* Optional: Your custom theme overrides */
:root {
--primary: #570df8;
--secondary: #f000b8;
}
``` ```
### 2. Include SigPro & SigPro UI
You can import the modules directly into your application:
```javascript
import { UI } from "./sigpro-ui.js";
// Initialize the UI library (sets global helpers like Div, Button, Table...)
// Supports 'en' or 'es' for internal i18n
UI("en");
```
--- ---
## Setup & Usage
## Basic Usage You can use SigPro UI in two ways: **Modular** (Recommended for production) or **Global** (Fastest for prototyping).
SigPro UI turns standard HTML tags into PascalCase helpers and provides complex components like `Table`, `Modal`, and `Datepicker`. ### 1. Modular Approach (Tree Shaking)
Import only the components you need. This keeps your bundle small.
```javascript ```javascript
import { $, $mount } from "sigpro"; import { $, $mount } from "sigpro";
import { Button, Modal, Table } from "sigpro-ui";
const App = () => {
const show = $(false);
return Button({ onclick: () => show(true) }, "Open Modal");
};
$mount(App, "#app");
```
### 2. Global Approach (Zero-Import)
Inject all components and utilities into the `window` object. This makes all components available everywhere without manual imports.
```javascript
import SigproUI from "sigpro-ui";
// Injects Button, Table, Input, Icons, Utils, etc. into window
SigproUI.install();
// Now you can use them directly anywhere:
const myApp = () => Table({ items: [], columns: [] });
```
---
## Basic Example
```javascript
import { $ } from "sigpro";
import { Button, Toast, Div, H1 } from "sigpro-ui";
const App = () => { const App = () => {
const count = $(0); const count = $(0);
const showModal = $(false);
return Div({ class: "p-10 flex flex-col gap-4" }, [ return Div({ class: "p-10 flex flex-col gap-4" }, [
H1({ class: "text-2xl font-bold" }, "Welcome to SigPro UI"), H1({ class: "text-2xl font-bold" }, "Welcome to SigPro UI"),
Button({ Button({
class: "btn-primary", class: "btn-primary",
onclick: () => count(c => c + 1) onclick: () => {
}, () => `Clicks: ${count()}`), count(c => c + 1);
Toast(`Count is now ${count()}`, "alert-success");
Button({ }
class: "btn-outline", }, () => `Clicks: ${count()}`)
onclick: () => showModal(true)
}, "Open Modal"),
Modal({
open: showModal,
title: "Hello!"
}, "This is a reactive modal powered by SigPro.")
]); ]);
}; };
$mount(App, "#app");
``` ```
--- ---
@@ -95,10 +132,10 @@ $mount(App, "#app");
| Category | Components | | Category | Components |
| :--- | :--- | | :--- | :--- |
| **Form** | `Input`, `Select`, `Checkbox`, `Radio`, `Range`, `Datepicker`, `Colorpicker`, `Autocomplete` | | **Form** | `Input`, `Select`, `Checkbox`, `Radio`, `Range`, `Datepicker`, `Colorpicker`, `Autocomplete`, `Rating` |
| **Data** | `Table`, `List`, `Stat`, `Badge`, `Indicator`, `Timeline` | | **Data** | `Table`, `List`, `Stat`, `Timeline`, `Badge`, `Indicator` |
| **Layout** | `Navbar`, `Menu`, `Drawer`, `Fieldset`, `Stack`, `Tabs`, `Accordion` | | **Layout** | `Navbar`, `Menu`, `Drawer`, `Fieldset`, `Stack`, `Tabs`, `Accordion` |
| **Feedback** | `Alert`, `Modal`, `Toast`, `Loading`, `Tooltip`, `Rating` | | **Feedback** | `Alert`, `Modal`, `Toast`, `Loading`, `Tooltip` |
| **Interaction** | `Button`, `Dropdown`, `Swap`, `Fab` | | **Interaction** | `Button`, `Dropdown`, `Swap`, `Fab` |
--- ---

1673
dist/sigpro-ui.cjs vendored Normal file

File diff suppressed because it is too large Load Diff

1617
dist/sigpro-ui.esm.js vendored Normal file

File diff suppressed because it is too large Load Diff

1676
dist/sigpro-ui.umd.js vendored Normal file

File diff suppressed because it is too large Load Diff

1
dist/sigpro-ui.umd.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -26,17 +26,6 @@ Styled button with full DaisyUI support and reactive states.
<div id="demo-basic"></div> <div id="demo-basic"></div>
<script type="module">
import { $mount } from 'https://cdn.jsdelivr.net/npm/sigpro@latest/+esm';
import { Button } from 'https://cdn.jsdelivr.net/npm/sigpro-ui@latest/+esm';
const Demo = () => {
return Button({ class: "btn-primary" }, "Click Me");
};
$mount(Demo, "#demo-basic");
</script>
```javascript ```javascript
Button({ class: "btn-primary" }, "Click Me") Button({ class: "btn-primary" }, "Click Me")
``` ```
@@ -45,27 +34,6 @@ Button({ class: "btn-primary" }, "Click Me")
<div id="demo-loading"></div> <div id="demo-loading"></div>
<script type="module">
import { $, $mount } from 'https://cdn.jsdelivr.net/npm/sigpro@latest/+esm';
import { Button } from '../../sigpro-ui/sigpro-ui.js';
const isSaving = $(false);
const Demo = () => {
return Button({
class: "btn-success",
loading: isSaving,
onclick: async () => {
isSaving(true);
await new Promise(resolve => setTimeout(resolve, 2000));
isSaving(false);
}
}, "Save Changes");
};
$mount(Demo, "#demo-loading");
</script>
```javascript ```javascript
const isSaving = $(false); const isSaving = $(false);
@@ -84,21 +52,6 @@ Button({
<div id="demo-badge"></div> <div id="demo-badge"></div>
<script type="module">
import { $mount } from 'https://cdn.jsdelivr.net/npm/sigpro@latest/+esm';
import { Button } from 'https://cdn.jsdelivr.net/npm/sigpro-ui@latest/+esm';
const Demo = () => {
return Button({
class: "btn-outline",
badge: "3",
badgeClass: "badge-accent"
}, "Notifications");
};
$mount(Demo, "#demo-badge");
</script>
```javascript ```javascript
Button({ Button({
class: "btn-outline", class: "btn-outline",
@@ -111,20 +64,6 @@ Button({
<div id="demo-tooltip"></div> <div id="demo-tooltip"></div>
<script type="module">
import { $mount } from 'https://cdn.jsdelivr.net/npm/sigpro@latest/+esm';
import { Button } from 'https://cdn.jsdelivr.net/npm/sigpro-ui@latest/+esm';
const Demo = () => {
return Button({
class: "btn-ghost",
tooltip: "Delete item"
}, "Delete");
};
$mount(Demo, "#demo-tooltip");
</script>
```javascript ```javascript
Button({ Button({
class: "btn-ghost", class: "btn-ghost",
@@ -136,22 +75,6 @@ Button({
<div id="demo-disabled"></div> <div id="demo-disabled"></div>
<script type="module">
import { $, $mount } from 'https://cdn.jsdelivr.net/npm/sigpro@latest/+esm';
import { Button } from 'https://cdn.jsdelivr.net/npm/sigpro-ui@latest/+esm';
const isDisabled = $(true);
const Demo = () => {
return Button({
class: "btn-primary",
disabled: isDisabled
}, "Submit");
};
$mount(Demo, "#demo-disabled");
</script>
```javascript ```javascript
const isDisabled = $(true); const isDisabled = $(true);
@@ -165,29 +88,6 @@ Button({
<div id="demo-variants"></div> <div id="demo-variants"></div>
<script type="module">
import { $, $mount } from 'https://cdn.jsdelivr.net/npm/sigpro@latest/+esm';
import { Button, Div } from 'https://cdn.jsdelivr.net/npm/sigpro-ui@latest/+esm';
const isLoading = $(false);
const Demo = () => {
return Div({ class: "flex flex-wrap gap-2" }, [
Button({ class: "btn-primary" }, "Primary"),
Button({ class: "btn-secondary" }, "Secondary"),
Button({ class: "btn-accent" }, "Accent"),
Button({ class: "btn-ghost" }, "Ghost"),
Button({ class: "btn-outline" }, "Outline"),
Button({ class: "btn-success", loading: isLoading, onclick: () => {
isLoading(true);
setTimeout(() => isLoading(false), 1500);
} }, "Loading")
]);
};
$mount(Demo, "#demo-variants");
</script>
```javascript ```javascript
Div({ class: "flex flex-wrap gap-2" }, [ Div({ class: "flex flex-wrap gap-2" }, [
Button({ class: "btn-primary" }, "Primary"), Button({ class: "btn-primary" }, "Primary"),
@@ -198,24 +98,3 @@ Div({ class: "flex flex-wrap gap-2" }, [
]) ])
``` ```
``` ```
## Ventajas de este enfoque:
1. **Ejecución real** - Los ejemplos son interactivos y funcionales
2. **Código visible** - Debajo de cada ejemplo se muestra el código fuente
3. **Aprendizaje visual** - Los usuarios pueden ver y probar los componentes
4. **Reactivo** - Los ejemplos con signals demuestran la reactividad en acción
## Consejos adicionales:
Para mejorar la experiencia, podrías agregar estilos a los contenedores de los ejemplos:
```css
/* En tu HTML, dentro de <style> */
.demo-container {
margin: 1rem 0;
padding: 1rem;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
background: #f8fafc;
}

View File

@@ -39,13 +39,8 @@
}; };
</script> </script>
<script type="module"> <script src="https://unpkg.com/sigpro"> </script>
import * as sigpro from "https://cdn.jsdelivr.net/npm/sigpro@latest/+esm"; <script src="https://unpkg.com/sigpro-ui"> </script>
import { UI } from "https://cdn.jsdelivr.net/npm/sigpro-ui@latest/+esm";
window.sigpro = sigpro;
const ui = UI("en");
window.sigproui = ui;
</script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script> <script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify-copy-code/dist/docsify-copy-code.min.js"></script> <script src="//cdn.jsdelivr.net/npm/docsify-copy-code/dist/docsify-copy-code.min.js"></script>
</body> </body>

1140
index.js

File diff suppressed because it is too large Load Diff

View File

@@ -1,39 +1,52 @@
{ {
"name": "sigpro-ui", "name": "sigpro-ui",
"version": "1.0.1", "version": "1.0.4",
"type": "module",
"license": "MIT",
"main": "./index.js",
"module": "./index.js",
"exports": {
".": "./index.js"
},
"files": [
"index.js",
"README.md",
"LICENSE"
],
"homepage": "https://natxocc.github.io/sigpro-ui/",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/natxocc/sigpro-ui.git" "url": "https://github.com/natxocc/sigpro-ui.git"
}, },
"main": "./dist/sigpro-ui.cjs",
"module": "./dist/sigpro-ui.esm.js",
"unpkg": "./dist/sigpro-ui.umd.min.js",
"jsdelivr": "./dist/sigpro-ui.umd.min.js",
"exports": {
".": {
"import": "./dist/sigpro-ui.esm.js",
"require": "./dist/sigpro-ui.cjs"
}
},
"bugs": { "bugs": {
"url": "https://github.com/natxocc/sigpro-ui/issues" "url": "https://github.com/natxocc/sigpro-ui/issues"
}, },
"scripts": { "files": [
"docs": "bun x serve docs" "index.js",
}, "src",
"dist",
"README.md",
"LICENSE"
],
"homepage": "https://natxocc.github.io/sigpro-ui/",
"keywords": [ "keywords": [
"signals", "signals",
"reactive", "reactive",
"sigpro", "sigpro",
"sigpro components", "sigpro components",
"UI", "UI",
"web-components",
"vanilla-js", "vanilla-js",
"reactive-programming", "reactive-programming"
"signals-library", ],
"fine-grained-reactivity" "license": "MIT",
] "scripts": {
} "docs": "bun x serve docs",
"build": "rollup -c",
"prepublishOnly": "npm run build"
},
"peerDependencies": {
"sigpro": ">=1.1.16"
},
"type": "module",
"devDependencies": {
"@rollup/plugin-terser": "^0.4.4",
"rollup": "^4.34.8"
}
}

49
rollup.config.js Normal file
View File

@@ -0,0 +1,49 @@
import terser from '@rollup/plugin-terser';
export default [
// ESM
{
input: './index.js',
external: ['sigpro'],
output: {
file: './dist/sigpro-ui.esm.js',
format: 'esm'
}
},
// CommonJS
{
input: './index.js',
external: ['sigpro'],
output: {
file: './dist/sigpro-ui.cjs',
format: 'cjs'
}
},
// UMD (IIFE para navegador)
{
input: './index.js',
external: ['sigpro'],
output: {
file: './dist/sigpro-ui.umd.js',
format: 'iife',
name: 'SigProUI',
globals: {
sigpro: 'SigProCore'
}
}
},
// UMD minificado
{
input: './index.js',
external: ['sigpro'],
output: {
file: './dist/sigpro-ui.umd.min.js',
format: 'iife',
name: 'SigProUI',
globals: {
sigpro: 'SigProCore'
},
plugins: [terser()]
}
}
];

View File

@@ -0,0 +1,24 @@
import { $html } from "sigpro";
import { joinClass } from "../core/utils.js";
/** ACCORDION */
export const Accordion = (props, children) => {
const { title, name, open, ...rest } = props;
return $html(
"div",
{
...rest,
class: joinClass("collapse collapse-arrow bg-base-200 mb-2", props.class),
},
[
$html("input", {
type: name ? "radio" : "checkbox",
name: name,
checked: open
}),
$html("div", { class: "collapse-title text-xl font-medium" }, title),
$html("div", { class: "collapse-content" }, children),
],
);
};

50
src/components/Alert.js Normal file
View File

@@ -0,0 +1,50 @@
import { $html } from "sigpro";
import { val } from "../core/utils.js";
import { iconInfo, iconSuccess, iconWarning, iconError } from "../core/icons.js";
/** ALERT */
export const Alert = (props, children) => {
const { type = "info", soft = true, ...rest } = props;
const icons = {
info: iconInfo,
success: iconSuccess,
warning: iconWarning,
error: iconError,
};
const typeClass = () => {
const t = val(type);
const map = {
info: "alert-info",
success: "alert-success",
warning: "alert-warning",
error: "alert-error",
};
return map[t] || t;
};
const content = children || props.message;
return $html(
"div",
{
...rest,
role: "alert",
class: () => `alert ${typeClass()} ${val(soft) ? "alert-soft" : ""} ${props.class || ""}`,
},
[
$html("img", {
src: icons[val(type)] || icons.info,
class: "w-4 h-4 object-contain",
alt: val(type),
}),
$html("div", { class: "flex-1" }, [
$html("span", {}, [typeof content === "function" ? content() : content])
]),
props.actions ? $html("div", { class: "flex-none" }, [
typeof props.actions === "function" ? props.actions() : props.actions
]) : null,
],
);
};

View File

@@ -0,0 +1,95 @@
import { $, $html, $for } from "sigpro";
import { val } from "../core/utils.js";
import { tt } from "../core/i18n.js";
import { Input } from "./Input.js"; // Importamos el componente hermano
/** AUTOCOMPLETE */
export const Autocomplete = (props) => {
const { options = [], value, onSelect, label, placeholder, ...rest } = props;
const query = $(val(value) || "");
const isOpen = $(false);
const cursor = $(-1);
const list = $(() => {
const q = query().toLowerCase();
const data = val(options) || [];
return q
? data.filter((o) => (typeof o === "string" ? o : o.label).toLowerCase().includes(q))
: data;
});
const pick = (opt) => {
const valStr = typeof opt === "string" ? opt : opt.value;
const labelStr = typeof opt === "string" ? opt : opt.label;
query(labelStr);
if (typeof value === "function") value(valStr);
onSelect?.(opt);
isOpen(false);
cursor(-1);
};
const nav = (e) => {
const items = list();
if (e.key === "ArrowDown") {
e.preventDefault();
isOpen(true);
cursor(Math.min(cursor() + 1, items.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(items[cursor()]);
} else if (e.key === "Escape") {
isOpen(false);
}
};
return $html("div", { class: "relative w-full" }, [
Input({
label,
placeholder: placeholder || tt("search")(),
value: query,
onfocus: () => isOpen(true),
onblur: () => setTimeout(() => isOpen(false), 150),
onkeydown: nav,
oninput: (e) => {
const v = e.target.value;
query(v);
if (typeof value === "function") value(v);
isOpen(true);
cursor(-1);
},
...rest,
}),
$html(
"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: () => (isOpen() && list().length ? "display:block" : "display:none"),
},
[
$for(
list,
(opt, i) =>
$html("li", {}, [
$html(
"a",
{
class: () => `block w-full ${cursor() === i ? "active bg-primary text-primary-content" : ""}`,
onclick: () => pick(opt),
onmouseenter: () => cursor(i),
},
typeof opt === "string" ? opt : opt.label,
),
]),
(opt, i) => (typeof opt === "string" ? opt : opt.value) + i,
),
() => (list().length ? null : $html("li", { class: "p-2 text-center opacity-50" }, "No results")),
],
),
]);
};

6
src/components/Badge.js Normal file
View File

@@ -0,0 +1,6 @@
import { $html } from "sigpro";
import { joinClass } from "../core/utils.js";
/** BADGE */
export const Badge = (props, children) =>
$html("span", { ...props, class: joinClass("badge", props.class) }, children);

39
src/components/Button.js Normal file
View File

@@ -0,0 +1,39 @@
import { $html } from "sigpro";
import { val, joinClass } from "../core/utils.js";
/** BUTTON */
export const Button = (props, children) => {
const { badge, badgeClass, tooltip, icon, loading, ...rest } = props;
const btn = $html(
"button",
{
...rest,
// Usamos props.class directamente
class: joinClass("btn", props.class),
disabled: () => val(loading) || val(props.disabled),
},
[
() => (val(loading) ? $html("span", { class: "loading loading-spinner" }) : null),
icon ? $html("span", { class: "mr-1" }, icon) : null,
children,
]
);
let out = btn;
if (badge) {
out = $html("div", { class: "indicator" }, [
$html(
"span",
{ class: joinClass("indicator-item badge", badgeClass || "badge-secondary") },
badge
),
out,
]);
}
return tooltip
? $html("div", { class: "tooltip", "data-tip": tooltip }, out)
: out;
};

View File

@@ -0,0 +1,21 @@
import { $html } from "sigpro";
import { val } from "../core/utils.js";
/** CHECKBOX */
export const Checkbox = (props) => {
const { value, tooltip, toggle, label, ...rest } = props;
const checkEl = $html("input", {
...rest,
type: "checkbox",
class: () => (val(toggle) ? "toggle" : "checkbox"),
checked: value
});
const layout = $html("label", { class: "label cursor-pointer justify-start gap-3" }, [
checkEl,
label ? $html("span", { class: "label-text" }, label) : null,
]);
return tooltip ? $html("div", { class: "tooltip", "data-tip": tooltip }, layout) : layout;
};

View File

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

View File

@@ -0,0 +1,252 @@
import { $, $html, $if } from "sigpro";
import { val } from "../core/utils.js";
import {
iconCalendar,
iconLeft,
iconRight,
iconLLeft,
iconRRight
} from "../core/icons.js";
import { Input } from "./Input.js";
/** DATEPICKER */
export const Datepicker = (props) => {
const { value, range, label, placeholder, hour = false, ...rest } = props;
const isOpen = $(false);
const internalDate = $(new Date());
const hoverDate = $(null);
const startHour = $(0);
const endHour = $(0);
const isRangeMode = () => val(range) === true;
const now = new Date();
const todayStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
const formatDate = (d) => {
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, "0");
const day = String(d.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
};
const selectDate = (date) => {
const dateStr = formatDate(date);
const current = val(value);
if (isRangeMode()) {
if (!current?.start || (current.start && current.end)) {
if (typeof value === "function") {
value({
start: dateStr,
end: null,
...(hour && { startHour: startHour() }),
});
}
} else {
const start = current.start;
if (typeof value === "function") {
const newValue = dateStr < start ? { start: dateStr, end: start } : { start, end: dateStr };
if (hour) {
newValue.startHour = current.startHour || startHour();
newValue.endHour = current.endHour || endHour();
}
value(newValue);
}
isOpen(false);
}
} else {
if (typeof value === "function") {
value(hour ? `${dateStr}T${String(startHour()).padStart(2, "0")}:00:00` : dateStr);
}
isOpen(false);
}
};
const displayValue = $(() => {
const v = val(value);
if (!v) return "";
if (typeof v === "string") {
if (hour && v.includes("T")) return v.replace("T", " ");
return v;
}
if (v.start && v.end) {
const startStr = hour && v.startHour ? `${v.start} ${String(v.startHour).padStart(2, "0")}:00` : v.start;
const endStr = hour && v.endHour ? `${v.end} ${String(v.endHour).padStart(2, "0")}:00` : v.end;
return `${startStr} - ${endStr}`;
}
if (v.start) {
const startStr = hour && v.startHour ? `${v.start} ${String(v.startHour).padStart(2, "0")}:00` : v.start;
return `${startStr}...`;
}
return "";
});
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 }) => {
return $html("div", { class: "flex-1" }, [
$html("div", { class: "flex gap-2 items-center" }, [
$html("input", {
type: "range",
min: 0,
max: 23,
value: hVal,
class: "range range-xs flex-1",
oninput: (e) => {
const newHour = parseInt(e.target.value);
onChange(newHour);
},
}),
$html("span", { class: "text-sm font-mono min-w-[48px] text-center" },
() => String(val(hVal)).padStart(2, "0") + ":00"
),
]),
]);
};
return $html("div", { class: "relative w-full" }, [
Input({
label,
placeholder: placeholder || (isRangeMode() ? "Seleccionar rango..." : "Seleccionar fecha..."),
value: displayValue,
readonly: true,
icon: $html("img", { src: iconCalendar, class: "opacity-40" }),
onclick: (e) => {
e.stopPropagation();
isOpen(!isOpen());
},
...rest,
}),
$if(isOpen, () =>
$html(
"div",
{
class: "absolute left-0 mt-2 p-4 bg-base-100 border border-base-300 shadow-2xl rounded-box z-[100] w-80 select-none",
onclick: (e) => e.stopPropagation(),
},
[
$html("div", { class: "flex justify-between items-center mb-4 gap-1" }, [
$html("div", { class: "flex gap-0.5" }, [
$html("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(-1) },
$html("img", { src: iconLLeft, class: "opacity-40" })
),
$html("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(-1) },
$html("img", { src: iconLeft, class: "opacity-40" })
),
]),
$html("span", { class: "font-bold uppercase flex-1 text-center" }, [
() => internalDate().toLocaleString("es-ES", { month: "short", year: "numeric" }),
]),
$html("div", { class: "flex gap-0.5" }, [
$html("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(1) },
$html("img", { src: iconRight, class: "opacity-40" })
),
$html("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(1) },
$html("img", { src: iconRRight, class: "opacity-40" })
),
]),
]),
$html("div", { class: "grid grid-cols-7 gap-1", onmouseleave: () => hoverDate(null) }, [
...["L", "M", "X", "J", "V", "S", "D"].map((d) => $html("div", { class: "text-[10px] opacity-40 font-bold text-center" }, d)),
() => {
const d = internalDate();
const year = d.getFullYear();
const month = d.getMonth();
const firstDay = new Date(year, month, 1).getDay();
const offset = firstDay === 0 ? 6 : firstDay - 1;
const daysInMonth = new Date(year, month + 1, 0).getDate();
const nodes = [];
for (let i = 0; i < offset; i++) nodes.push($html("div"));
for (let i = 1; i <= daysInMonth; i++) {
const date = new Date(year, month, i);
const dStr = formatDate(date);
nodes.push(
$html(
"button",
{
type: "button",
class: () => {
const v = val(value);
const h = hoverDate();
const isStart = typeof v === "string" ? v.split("T")[0] === dStr : v?.start === dStr;
const isEnd = v?.end === dStr;
let inRange = false;
if (isRangeMode() && v?.start) {
const start = v.start;
if (!v.end && h) {
inRange = (dStr > start && dStr <= h) || (dStr < start && dStr >= h);
} else if (v.end) {
inRange = dStr > start && dStr < v.end;
}
}
const base = "btn btn-xs p-0 aspect-square min-h-0 h-auto font-normal relative";
const state = isStart || isEnd ? "btn-primary z-10" : inRange ? "bg-primary/20 border-none rounded-none" : "btn-ghost";
const today = dStr === todayStr ? "ring-1 ring-primary ring-inset font-black text-primary" : "";
return `${base} ${state} ${today}`;
},
onmouseenter: () => { if (isRangeMode()) hoverDate(dStr); },
onclick: () => selectDate(date),
},
[i.toString()],
),
);
}
return nodes;
},
]),
hour ? $html("div", { class: "mt-3 pt-2 border-t border-base-300" }, [
isRangeMode()
? $html("div", { class: "flex gap-4" }, [
HourSlider({
value: startHour,
onChange: (newHour) => {
startHour(newHour);
const currentVal = val(value);
if (currentVal?.start) value({ ...currentVal, startHour: newHour });
},
}),
HourSlider({
value: endHour,
onChange: (newHour) => {
endHour(newHour);
const currentVal = val(value);
if (currentVal?.end) value({ ...currentVal, endHour: newHour });
},
}),
])
: HourSlider({
value: startHour,
onChange: (newHour) => {
startHour(newHour);
const currentVal = val(value);
if (currentVal && typeof currentVal === "string" && currentVal.includes("-")) {
value(currentVal.split("T")[0] + "T" + String(newHour).padStart(2, "0") + ":00:00");
}
},
}),
]) : null,
],
),
),
$if(isOpen, () => $html("div", { class: "fixed inset-0 z-[90]", onclick: () => isOpen(false) })),
]);
};

18
src/components/Drawer.js Normal file
View File

@@ -0,0 +1,18 @@
import { $html } from "sigpro";
import { joinClass } from "../core/utils.js";
/** DRAWER */
export const Drawer = (props) =>
$html("div", { class: joinClass("drawer", props.class) }, [
$html("input", {
id: props.id,
type: "checkbox",
class: "drawer-toggle",
checked: props.open,
}),
$html("div", { class: "drawer-content" }, props.content),
$html("div", { class: "drawer-side" }, [
$html("label", { for: props.id, class: "drawer-overlay", onclick: () => props.open?.(false) }),
$html("div", { class: "min-h-full bg-base-200 w-80" }, props.side),
]),
]);

View File

@@ -0,0 +1,37 @@
import { $html } from "sigpro";
import { val } from "../core/utils.js";
/** DROPDOWN */
export const Dropdown = (props, children) => {
const { label, icon, ...rest } = props;
return $html(
"div",
{
...rest,
class: () => `dropdown ${val(props.class) || props.class || ""}`,
},
[
$html(
"div",
{
tabindex: 0,
role: "button",
class: "btn m-1 flex items-center gap-2",
},
[
icon ? (typeof icon === "function" ? icon() : icon) : null,
label ? (typeof label === "function" ? label() : label) : null
],
),
$html(
"ul",
{
tabindex: 0,
class: "dropdown-content z-[50] menu p-2 shadow bg-base-100 rounded-box min-w-max border border-base-300",
},
[typeof children === "function" ? children() : children],
),
],
);
};

51
src/components/Fab.js Normal file
View File

@@ -0,0 +1,51 @@
import { $html } from "sigpro";
import { val } from "../core/utils.js";
/** FAB (Floating Action Button) */
export const Fab = (props) => {
const { icon, label, actions = [], position = "bottom-6 right-6", ...rest } = props;
return $html(
"div",
{
...rest,
class: () => `fab fixed ${val(position)} flex flex-col-reverse items-end gap-3 z-[100] ${
props.class || ""
}`,
},
[
// Botón principal
$html(
"div",
{
tabindex: 0,
role: "button",
class: "btn btn-lg btn-circle btn-primary shadow-2xl",
},
[
icon ? (typeof icon === "function" ? icon() : icon) : null,
!icon && label ? label : null
],
),
// Acciones secundarias (se despliegan hacia arriba)
...val(actions).map((act) =>
$html("div", { class: "flex items-center gap-3 transition-all duration-300" }, [
act.label ? $html("span", { class: "badge badge-ghost shadow-sm whitespace-nowrap" }, act.label) : null,
$html(
"button",
{
type: "button",
class: `btn btn-circle shadow-lg ${act.class || ""}`,
onclick: (e) => {
e.stopPropagation();
act.onclick?.(e);
},
},
[act.icon ? (typeof act.icon === "function" ? act.icon() : act.icon) : act.text || ""],
),
]),
),
],
);
};

View File

@@ -0,0 +1,19 @@
import { $html } from "sigpro";
import { val, joinClass } from "../core/utils.js";
/** FIELDSET */
export const Fieldset = (props, children) =>
$html(
"fieldset",
{
...props,
class: joinClass("fieldset bg-base-200 border border-base-300 p-4 rounded-lg", props.class),
},
[
() => {
const legendText = val(props.legend);
return legendText ? $html("legend", { class: "fieldset-legend font-bold" }, [legendText]) : null;
},
children,
],
);

113
src/components/Fileinput.js Normal file
View File

@@ -0,0 +1,113 @@
import { $, $html, $if, $for } from "sigpro";
import { iconUpload, iconClose } from "../core/icons.js";
/** FILEINPUT */
export const Fileinput = (props) => {
const { tooltip, max = 2, accept = "*", onSelect } = props;
const selectedFiles = $([]);
const isDragging = $(false);
const error = $(null);
const MAX_BYTES = max * 1024 * 1024;
const handleFiles = (files) => {
const fileList = Array.from(files);
error(null);
const oversized = fileList.find((f) => f.size > MAX_BYTES);
if (oversized) {
error(`Máx ${max}MB`);
return;
}
selectedFiles([...selectedFiles(), ...fileList]);
onSelect?.(selectedFiles());
};
const removeFile = (index) => {
const updated = selectedFiles().filter((_, i) => i !== index);
selectedFiles(updated);
onSelect?.(updated);
};
return $html("fieldset", { class: "fieldset w-full p-0" }, [
$html(
"div",
{
class: () => `w-full ${tooltip ? "tooltip tooltip-top before:z-50 after:z-50" : ""}`,
"data-tip": tooltip,
},
[
$html(
"label",
{
class: () => `
relative flex items-center justify-between w-full h-12 px-4
border-2 border-dashed rounded-lg cursor-pointer
transition-all duration-200
${isDragging() ? "border-primary bg-primary/10" : "border-base-content/20 bg-base-100 hover:bg-base-200"}
`,
ondragover: (e) => {
e.preventDefault();
isDragging(true);
},
ondragleave: () => isDragging(false),
ondrop: (e) => {
e.preventDefault();
isDragging(false);
handleFiles(e.dataTransfer.files);
},
},
[
$html("div", { class: "flex items-center gap-3 w-full" }, [
$html("img", { src: iconUpload, class: "w-5 h-5 opacity-50 shrink-0" }),
$html("span", { class: "text-sm opacity-70 truncate grow text-left" }, "Arrastra o selecciona archivos..."),
$html("span", { class: "text-[10px] opacity-40 shrink-0" }, `Máx ${max}MB`),
]),
$html("input", {
type: "file",
multiple: true,
accept: accept,
class: "hidden",
onchange: (e) => handleFiles(e.target.files),
}),
],
),
],
),
() => (error() ? $html("span", { class: "text-[10px] text-error mt-1 px-1 font-medium" }, error()) : null),
$if(
() => selectedFiles().length > 0,
() =>
$html("ul", { class: "mt-2 space-y-1" }, [
$for(
selectedFiles,
(file, index) =>
$html("li", { class: "flex items-center justify-between p-1.5 pl-3 text-xs bg-base-200/50 rounded-md border border-base-300" }, [
$html("div", { class: "flex items-center gap-2 truncate" }, [
$html("span", { class: "opacity-50" }, "📄"),
$html("span", { class: "truncate font-medium max-w-[200px]" }, file.name),
$html("span", { class: "text-[9px] opacity-40" }, `(${(file.size / 1024).toFixed(0)} KB)`),
]),
$html(
"button",
{
type: "button",
class: "btn btn-ghost btn-xs btn-circle",
onclick: (e) => {
e.preventDefault();
e.stopPropagation();
removeFile(index);
},
},
[$html("img", { src: iconClose, class: "w-3 h-3 opacity-70" })],
),
]),
(file) => file.name + file.lastModified,
),
]),
),
]);
};

View File

@@ -0,0 +1,9 @@
import { $html } from "sigpro";
import { joinClass } from "../core/utils.js";
/** INDICATOR */
export const Indicator = (props, children) =>
$html("div", { class: joinClass("indicator", props.class) }, [
children,
$html("span", { class: joinClass("indicator-item badge", props.badgeClass) }, props.badge),
]);

77
src/components/Input.js Normal file
View File

@@ -0,0 +1,77 @@
import { $, $html } from "sigpro";
import { val, joinClass } from "../core/utils.js";
import { tt } from "../core/i18n.js";
import {
iconAbc,
iconLock,
iconCalendar,
icon123,
iconMail,
iconShow,
iconHide
} from "../core/icons.js";
/** INPUT */
export const Input = (props) => {
const { label, tip, value, error, isSearch, icon, type = "text", ...rest } = props;
const isPassword = type === "password";
const visible = $(false);
const iconsByType = {
text: iconAbc,
password: iconLock,
date: iconCalendar,
number: icon123,
email: iconMail,
};
const inputEl = $html("input", {
...rest,
type: () => (isPassword ? (visible() ? "text" : "password") : type),
placeholder: props.placeholder || label || (isSearch ? tt("search")() : " "),
class: joinClass("grow order-2 focus:outline-none", props.class),
value: value,
oninput: (e) => props.oninput?.(e),
disabled: () => val(props.disabled),
});
const leftIcon = icon ? icon : iconsByType[type] ? $html("img", { src: iconsByType[type], class: "opacity-50", alt: type }) : null;
return $html(
"label",
{
class: () => joinClass("input input-bordered floating-label flex items-center gap-2 w-full relative", val(error) ? "input-error" : ""),
},
[
leftIcon ? $html("div", { class: "order-1 shrink-0" }, leftIcon) : null,
label ? $html("span", { class: "text-base-content/60 order-0" }, label) : null,
inputEl,
isPassword
? $html(
"button",
{
type: "button",
class: "order-3 btn btn-ghost btn-xs btn-circle opacity-50 hover:opacity-100",
onclick: (e) => {
e.preventDefault();
visible(!visible());
},
},
() =>
$html("img", {
class: "w-5 h-5",
src: visible() ? iconShow : iconHide,
}),
)
: null,
tip
? $html(
"div",
{ class: "tooltip tooltip-left order-4", "data-tip": tip },
$html("span", { class: "badge badge-ghost badge-xs cursor-help" }, "?"),
)
: null,
() => (val(error) ? $html("span", { class: "text-error text-[10px] absolute -bottom-5 left-2" }, val(error)) : null),
],
);
};

18
src/components/List.js Normal file
View File

@@ -0,0 +1,18 @@
import { $html, $if, $for } from "sigpro";
import { joinClass, val } from "../core/utils.js";
/** LIST */
export const List = (props) => {
const { items, header, render, keyFn, class: className } = props;
return $html(
"ul",
{
class: joinClass("list bg-base-100 rounded-box shadow-md", className),
},
[
$if(header, () => $html("li", { class: "p-4 pb-2 text-xs opacity-60 tracking-wide" }, [val(header)])),
$for(items, (item, index) => $html("li", { class: "list-row" }, [render(item, index)]), keyFn),
],
);
};

13
src/components/Loading.js Normal file
View File

@@ -0,0 +1,13 @@
import { $html, $if } from "sigpro";
/** LOADING (Overlay Component) */
export const Loading = (props) => {
// Se espera un signal props.$show para controlar la visibilidad
return $if(props.$show, () =>
$html("div", {
class: "fixed inset-0 z-[100] flex items-center justify-center backdrop-blur-sm bg-base-100/30"
}, [
$html("span", { class: "loading loading-spinner loading-lg text-primary" }),
]),
);
};

25
src/components/Menu.js Normal file
View File

@@ -0,0 +1,25 @@
import { $html, $for } from "sigpro";
import { val, joinClass } from "../core/utils.js";
/** MENU */
export const Menu = (props) => {
const renderItems = (items) =>
$for(
() => items || [],
(it) =>
$html("li", {}, [
it.children
? $html("details", { open: it.open }, [
$html("summary", {}, [it.icon && $html("span", { class: "mr-2" }, it.icon), it.label]),
$html("ul", {}, renderItems(it.children)),
])
: $html("a", { class: () => (val(it.active) ? "active" : ""), onclick: it.onclick }, [
it.icon && $html("span", { class: "mr-2" }, it.icon),
it.label,
]),
]),
(it, i) => it.label || i,
);
return $html("ul", { ...props, class: joinClass("menu bg-base-200 rounded-box", props.class) }, renderItems(props.items));
};

31
src/components/Modal.js Normal file
View File

@@ -0,0 +1,31 @@
import { $html, $if } from "sigpro";
import { tt } from "../core/i18n.js";
import { Button } from "./Button.js";
/** MODAL */
export const Modal = (props, children) => {
const { title, buttons, open, ...rest } = props;
const close = () => open(false);
return $if(open, () =>
$html("dialog", { ...rest, class: "modal modal-open" }, [
$html("div", { class: "modal-box" }, [
title ? $html("h3", { class: "text-lg font-bold mb-4" }, title) : null,
typeof children === "function" ? children() : children,
$html("div", { class: "modal-action flex gap-2" }, [
...(Array.isArray(buttons) ? buttons : [buttons]).filter(Boolean),
Button({ onclick: close }, tt("close")()),
]),
]),
$html(
"form",
{
method: "dialog",
class: "modal-backdrop",
onclick: (e) => (e.preventDefault(), close()),
},
[$html("button", {}, "close")],
),
]),
);
};

6
src/components/Navbar.js Normal file
View File

@@ -0,0 +1,6 @@
import { $html } from "sigpro";
import { joinClass } from "../core/utils.js";
/** NAVBAR */
export const Navbar = (props, children) =>
$html("div", { ...props, class: joinClass("navbar bg-base-100 shadow-sm px-4", props.class) }, children);

25
src/components/Radio.js Normal file
View File

@@ -0,0 +1,25 @@
import { $html } from "sigpro";
import { val, joinClass } from "../core/utils.js";
/** RADIO */
export const Radio = (props) => {
const { label, tooltip, value, ...rest } = props;
const radioEl = $html("input", {
...rest,
type: "radio",
class: joinClass("radio", props.class),
checked: () => val(value) === props.value,
disabled: () => val(props.disabled),
onclick: () => typeof value === "function" && value(props.value),
});
if (!label && !tooltip) return radioEl;
const layout = $html("label", { class: "label cursor-pointer justify-start gap-3" }, [
radioEl,
label ? $html("span", { class: "label-text" }, label) : null,
]);
return tooltip ? $html("div", { class: "tooltip", "data-tip": tooltip }, layout) : layout;
};

24
src/components/Range.js Normal file
View File

@@ -0,0 +1,24 @@
import { $html } from "sigpro";
import { val, joinClass } from "../core/utils.js";
/** RANGE */
export const Range = (props) => {
const { label, tooltip, value, ...rest } = props;
const rangeEl = $html("input", {
...rest,
type: "range",
class: joinClass("range", props.class),
value: value,
disabled: () => val(props.disabled)
});
if (!label && !tooltip) return rangeEl;
const layout = $html("div", { class: "flex flex-col gap-2" }, [
label ? $html("span", { class: "label-text" }, label) : null,
rangeEl
]);
return tooltip ? $html("div", { class: "tooltip", "data-tip": tooltip }, layout) : layout;
};

34
src/components/Rating.js Normal file
View File

@@ -0,0 +1,34 @@
import { $html } from "sigpro";
import { val } from "../core/utils.js";
/** RATING */
export const Rating = (props) => {
const { value, count = 5, mask = "mask-star", readonly = false, ...rest } = props;
// Generamos un ID único para el grupo de radio buttons
const ratingGroup = `rating-${Math.random().toString(36).slice(2, 7)}`;
return $html(
"div",
{
...rest,
class: () => `rating ${val(readonly) ? "pointer-events-none" : ""} ${props.class || ""}`,
},
Array.from({ length: val(count) }, (_, i) => {
const starValue = i + 1;
return $html("input", {
type: "radio",
name: ratingGroup,
class: `mask ${mask}`,
"aria-label": `${starValue} star`,
checked: () => Math.round(val(value)) === starValue,
onchange: () => {
if (!val(readonly) && typeof value === "function") {
value(starValue);
}
},
});
})
);
};

36
src/components/Select.js Normal file
View File

@@ -0,0 +1,36 @@
import { $html, $for } from "sigpro";
import { val, joinClass } from "../core/utils.js";
/** SELECT */
export const Select = (props) => {
const { label, options, value, ...rest } = props;
const selectEl = $html(
"select",
{
...rest,
class: joinClass("select select-bordered w-full", props.class),
value: value
},
$for(
() => val(options) || [],
(opt) =>
$html(
"option",
{
value: opt.value,
$selected: () => String(val(value)) === String(opt.value),
},
opt.label,
),
(opt) => opt.value,
),
);
if (!label) return selectEl;
return $html("label", { class: "fieldset-label flex flex-col gap-1" }, [
$html("span", {}, label),
selectEl
]);
};

6
src/components/Stack.js Normal file
View File

@@ -0,0 +1,6 @@
import { $html } from "sigpro";
import { joinClass } from "../core/utils.js";
/** STACK */
export const Stack = (props, children) =>
$html("div", { ...props, class: joinClass("stack", props.class) }, children);

11
src/components/Stat.js Normal file
View File

@@ -0,0 +1,11 @@
import { $html } from "sigpro";
import { val, joinClass } from "../core/utils.js";
/** STAT */
export const Stat = (props) =>
$html("div", { ...props, class: joinClass("stat", props.class) }, [
props.icon && $html("div", { class: "stat-figure text-secondary" }, props.icon),
props.label && $html("div", { class: "stat-title" }, props.label),
$html("div", { class: "stat-value" }, () => val(props.value) ?? props.value),
props.desc && $html("div", { class: "stat-desc" }, props.desc),
]);

13
src/components/Swap.js Normal file
View File

@@ -0,0 +1,13 @@
import { $html } from "sigpro";
import { joinClass } from "../core/utils.js";
/** SWAP */
export const Swap = (props) =>
$html("label", { class: joinClass("swap", props.class) }, [
$html("input", {
type: "checkbox",
checked: props.value
}),
$html("div", { class: "swap-on" }, props.on),
$html("div", { class: "swap-off" }, props.off),
]);

60
src/components/Table.js Normal file
View File

@@ -0,0 +1,60 @@
import { $html, $for, $if } from "sigpro";
import { val, joinClass } from "../core/utils.js";
import { tt } from "../core/i18n.js";
/** TABLE */
export const Table = (props) => {
const {
items = [],
columns = [],
keyFn,
zebra = false,
pinRows = false,
empty = tt("nodata")(),
...rest
} = props;
const tableClass = () => joinClass(
"table",
`${val(zebra) ? "table-zebra" : ""} ${val(pinRows) ? "table-pin-rows" : ""} ${props.class || ""}`
);
return $html("div", { class: "overflow-x-auto w-full bg-base-100 rounded-box border border-base-300" }, [
$html("table", { ...rest, class: tableClass }, [
$html("thead", {}, [
$html("tr", {},
columns.map(col => $html("th", { class: col.class || "" }, col.label))
)
]),
$html("tbody", {}, [
$for(items, (item, index) => {
return $html("tr", { class: "hover" },
columns.map(col => {
const cellContent = () => {
if (col.render) return col.render(item, index);
const value = item[col.key];
return val(value);
};
return $html("td", { class: col.class || "" }, [cellContent]);
})
);
}, keyFn || ((item, idx) => item.id || idx)),
$if(() => val(items).length === 0, () =>
$html("tr", {}, [
$html("td", { colspan: columns.length, class: "text-center p-10 opacity-50" }, [
val(empty)
])
])
)
]),
$if(() => columns.some(c => c.footer), () =>
$html("tfoot", {}, [
$html("tr", {},
columns.map(col => $html("th", {}, col.footer || ""))
)
])
)
])
]);
};

46
src/components/Tabs.js Normal file
View File

@@ -0,0 +1,46 @@
import { $html, $for } from "sigpro";
import { val, joinClass } from "../core/utils.js";
/** TABS */
export const Tabs = (props) => {
const { items, ...rest } = props;
const itemsSignal = typeof items === "function" ? items : () => items || [];
return $html("div", { ...rest, class: "flex flex-col gap-4 w-full" }, [
$html(
"div",
{
role: "tablist",
class: joinClass("tabs tabs-box", props.class),
},
$for(
itemsSignal,
(it) =>
$html(
"a",
{
role: "tab",
class: () => joinClass(
"tab",
val(it.active) && "tab-active",
val(it.disabled) && "tab-disabled",
it.tip && "tooltip"
),
"data-tip": it.tip,
onclick: (e) => !val(it.disabled) && it.onclick?.(e),
},
it.label,
),
(t) => t.label,
),
),
() => {
const active = itemsSignal().find((it) => val(it.active));
if (!active) return null;
const content = val(active.content);
return $html("div", { class: "p-4" }, [
typeof content === "function" ? content() : content
]);
},
]);
};

View File

@@ -0,0 +1,52 @@
import { $html, $for } from "sigpro";
import { val } from "../core/utils.js";
import { iconInfo, iconSuccess, iconWarning, iconError } from "../core/icons.js";
/** TIMELINE */
export const Timeline = (props) => {
const { items = [], vertical = true, compact = false, ...rest } = props;
const icons = {
info: iconInfo,
success: iconSuccess,
warning: iconWarning,
error: iconError,
};
return $html(
"ul",
{
...rest,
class: () =>
`timeline ${val(vertical) ? "timeline-vertical" : "timeline-horizontal"} ${
val(compact) ? "timeline-compact" : ""
} ${props.class || ""}`,
},
[
$for(
items,
(item, i) => {
const isFirst = i === 0;
const isLast = i === val(items).length - 1;
const itemType = item.type || "success";
const renderSlot = (content) => (typeof content === "function" ? content() : content);
return $html("li", { class: "flex-1" }, [
!isFirst ? $html("hr", { class: item.completed ? "bg-primary" : "" }) : null,
$html("div", { class: "timeline-start" }, [renderSlot(item.title)]),
$html("div", { class: "timeline-middle" }, [
$html("img", {
src: icons[itemType] || item.icon || icons.success,
class: "w-4 h-4 object-contain mx-1",
alt: itemType,
}),
]),
$html("div", { class: "timeline-end timeline-box shadow-sm" }, [renderSlot(item.detail)]),
!isLast ? $html("hr", { class: item.completed ? "bg-primary" : "" }) : null,
]);
},
(item, i) => item.id || i,
),
],
);
};

63
src/components/Toast.js Normal file
View File

@@ -0,0 +1,63 @@
import { $html, $mount } from "sigpro";
import { Button } from "./Button.js";
/** TOAST (Imperative Function) */
export const Toast = (message, type = "alert-success", duration = 3500) => {
let container = document.getElementById("sigpro-toast-container");
// Crear el contenedor global si no existe
if (!container) {
container = $html("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 toastHost = $html("div", { style: "display: contents" });
container.appendChild(toastHost);
let timeoutId;
const close = () => {
clearTimeout(timeoutId);
const el = toastHost.firstElementChild;
if (el && !el.classList.contains("opacity-0")) {
el.classList.add("translate-x-full", "opacity-0");
setTimeout(() => {
instance.destroy();
toastHost.remove();
// Limpiar el contenedor si ya no hay más toasts
if (!container.hasChildNodes()) container.remove();
}, 300);
} else {
instance.destroy();
toastHost.remove();
}
};
const ToastComponent = () => {
const el = $html(
"div",
{
class: `alert alert-soft ${type} shadow-lg transition-all duration-300 translate-x-10 opacity-0 pointer-events-auto`,
},
[
$html("span", {}, [typeof message === "function" ? message() : message]),
Button({ class: "btn-xs btn-circle btn-ghost", onclick: close }, "✕")
],
);
// Animación de entrada
requestAnimationFrame(() => el.classList.remove("translate-x-10", "opacity-0"));
return el;
};
const instance = $mount(ToastComponent, toastHost);
if (duration > 0) {
timeoutId = setTimeout(close, duration);
}
return close;
};

View File

@@ -0,0 +1,6 @@
import { $html } from "sigpro";
import { joinClass } from "../core/utils.js";
/** TOOLTIP */
export const Tooltip = (props, children) =>
$html("div", { ...props, class: joinClass("tooltip", props.class), "data-tip": props.tip }, children);

110
src/components/index.js Normal file
View File

@@ -0,0 +1,110 @@
import * as AccordionModule from './Accordion.js';
import * as AlertModule from './Alert.js';
import * as AutocompleteModule from './Autocomplete.js';
import * as BadgeModule from './Badge.js';
import * as ButtonModule from './Button.js';
import * as CheckboxModule from './Checkbox.js';
import * as ColorpickerModule from './Colorpicker.js';
import * as DatepickerModule from './Datepicker.js';
import * as DrawerModule from './Drawer.js';
import * as DropdownModule from './Dropdown.js';
import * as FabModule from './Fab.js';
import * as FieldsetModule from './Fieldset.js';
import * as FileinputModule from './Fileinput.js';
import * as IndicatorModule from './Indicator.js';
import * as InputModule from './Input.js';
import * as ListModule from './List.js';
import * as LoadingModule from './Loading.js';
import * as MenuModule from './Menu.js';
import * as ModalModule from './Modal.js';
import * as NavbarModule from './Navbar.js';
import * as RadioModule from './Radio.js';
import * as RangeModule from './Range.js';
import * as RatingModule from './Rating.js';
import * as SelectModule from './Select.js';
import * as StackModule from './Stack.js';
import * as StatModule from './Stat.js';
import * as SwapModule from './Swap.js';
import * as TableModule from './Table.js';
import * as TabsModule from './Tabs.js';
import * as TimelineModule from './Timeline.js';
import * as ToastModule from './Toast.js';
import * as TooltipModule from './Tooltip.js';
export * from './Accordion.js';
export * from './Alert.js';
export * from './Autocomplete.js';
export * from './Badge.js';
export * from './Button.js';
export * from './Checkbox.js';
export * from './Colorpicker.js';
export * from './Datepicker.js';
export * from './Drawer.js';
export * from './Dropdown.js';
export * from "./Fab.js";
export * from './Fieldset.js';
export * from './Fileinput.js';
export * from './Indicator.js';
export * from './Input.js';
export * from './List.js';
export * from './Loading.js';
export * from './Menu.js';
export * from './Modal.js';
export * from './Navbar.js';
export * from './Radio.js';
export * from './Range.js';
export * from './Rating.js';
export * from './Select.js';
export * from './Stack.js';
export * from './Stat.js';
export * from './Swap.js';
export * from './Table.js';
export * from './Tabs.js';
export * from './Timeline.js';
export * from './Toast.js';
export * from './Tooltip.js';
const Components = {
...AccordionModule,
...AlertModule,
...AutocompleteModule,
...BadgeModule,
...ButtonModule,
...CheckboxModule,
...ColorpickerModule,
...DatepickerModule,
...DrawerModule,
...DropdownModule,
...FabModule,
...FieldsetModule,
...FileinputModule,
...IndicatorModule,
...InputModule,
...ListModule,
...LoadingModule,
...MenuModule,
...ModalModule,
...NavbarModule,
...RadioModule,
...RangeModule,
...RatingModule,
...SelectModule,
...StackModule,
...StatModule,
...SwapModule,
...TableModule,
...TabsModule,
...TimelineModule,
...ToastModule,
...TooltipModule
};
export default {
...Components,
install: (target = window) => {
Object.entries(Components).forEach(([name, component]) => {
target[name] = component;
});
console.log("🚀 SigproUI");
}
};

26
src/core/i18n.js Normal file
View File

@@ -0,0 +1,26 @@
import { $ } from "sigpro";
export const i18n = {
es: {
close: "Cerrar",
confirm: "Confirmar",
cancel: "Cancelar",
search: "Buscar...",
loading: "Cargando...",
nodata: "Sin datos"
},
en: {
close: "Close",
confirm: "Confirm",
cancel: "Cancel",
search: "Search...",
loading: "Loading...",
nodata: "No data"
}
};
export const currentLocale = $("es");
export const Locale = t => currentLocale(t);
export const tt = t => () => i18n[currentLocale()][t] || t;

17
src/core/icons.js Normal file
View File

@@ -0,0 +1,17 @@
export const iconShow = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAADjSURBVDiN3dJNSgNBEAXgz4DZeAAVJ9tko2St3kaIFxAVt4KZeAD1GKKi7vQSydI/yHgALxAXU02GxniAFBR0v1ev+3V1sZSxjxtM8BM5wTX2/hNu4gFvOMI21iJ3cIwP3GMjF/dQ4RyraOMS34GPAmvjIrBeEnfwjoPGgSM8ooh8QtngB6Ep4BWnmaMqkY1LqqzmDC8tzNDK3/RHzLL9SloUYWfQIMuw3Yl8xrDBH6qbvZWALqbqBqVmlWF7GuKEDwPr5hbXcYdPnKBv/o39wL5wG7ULY1c9NGPzQRrjKrhli1/02zEjWyWMBwAAAABJRU5ErkJggg==";
export const iconHide = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAEDSURBVDiN1dK/K8VhFAbwD+VLGSxKcu9guSQ/Zils/gNkuaX4BxRZDTdklYU/QAaDlEVGGwu2Kz/uVbKJzWDwfuv1+jHz1Km3c85znuf0Hv4jxnD2W8MItnCJ5xAX2MQcHsOQL+jEAapYQD9aQwxiDy+B3JKSe1DHCpqQYQ0PeMJOpDyAmyAAirjGbDRwFYcoYCZSzjGP+8B1gqXEUT2QxyPlqaRnGceNeENzUswwil1MBocbSU9DCAXUUI6K25HtIo5QSVaooitP9OEO65iIbE+HXSvBVRbeNZQSR9pxGil3o83HNw5hEbfYR0dKFki5ci+u8OrzIQ1/R8xx7ocL+9t4B0HPOVXjoptxAAAAAElFTkSuQmCC";
export const iconClose = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAABcSURBVDiN3dIxDoAwCIXhL563g3bSm+hlq4O6GFNbO+k/EV54QIDfsSBk9IA5ZxCQEG+0eGi5BqDHivEhV2xSXXwy2EdOR3xLV+ta0/26wvSm+KTYpPmMzY/0QTZeZR2f+FxhRQAAAABJRU5ErkJggg==";
export const iconCalendar = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAACLSURBVDiN7dO9CQJBFEXhb38K0FwQrMNEVpuwB0NjrcYabECsQk0sQ1mTF4zIjrgmBh54MMx998AEwzOrmC5e8gJjbDHCJO7PHYI0v2JT4Ig9DljGwq5DkOZTLOCOMoIhBpknpHmFWx3ldaaUo6oTc2/ab7rl+508f8GvCC5oenTn4tM1cWg/nBNmD4fBH/Kfvt2TAAAAAElFTkSuQmCC";
export const iconLock = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAWQAAAFkBqp2phgAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAACQSURBVDiN7dKxDcJQDATQJ0YgXQQ1bAgDEIZBETPQwjakIjRQ8CMSyR8SiZKTrvHZd/r+JsYSNZrEI1ZR4ywzfElcJ55xwiITOECNTVDf4jDGoEEZ1Etcxxg8pmjRDiahb7BH20uKKPVUkVmL+YjQArdI+PT2bO9Pd/A34O71Rd9QeN/LAFUSckfUscWuG3oCgP8nrDH6T5AAAAAASUVORK5CYII=";
export const iconAbc = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAADFSURBVDiN7dCxSoIBFAXgr1BbgmgSB5ubxKAHaAkcgnBpySVaDET3WhzcpQfoHZojawgX0ZZcfAWDSDdBoeUKP/8ojZ7tnnPv4dzDFv+KZzwl5jf84B354C4wwjdeUV4vl7DCEsXgxmhigDpOMcMVjoKr7cTyI/ZxiE90wmCB4zi+RRatZOxd7OEavxHtBmvjIV5wH2a59N8ZXIZQisMCzkL/wgGq6EYffXzgHHNo4y5h+oBGlLjEBJVUiVP0cJJOtMUG+APtfyYzbH7eVgAAAABJRU5ErkJggg==";
export const icon123 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAB2AAAAdgFOeyYIAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAMxJREFUOI3t0bFKwlEUBvBfmmBEr1APIDZJ9AJJQyAIvkGP0C4uQruza+DUmuIc9AC9gBG4Nmpkw/8IB3Vw1w8u95zvnvPde77LEeUUV9HAF67QRA2nmMf5A+o4x3cWOsMYy8j7WMX6jaYbLBL/mAWe8RcHm1ihs8G94gVKQQzwlAouMcQo8p/Y28HdYpYFZmsi0MVdxD1MdrxsC500wijdvgtbI1AYtDbxMwkuFAZmE1uYwkkSqOIaHyHcxEU0vUXNPSqKr37fZ6xDwD9DPS0OyHjQHQAAAABJRU5ErkJggg==";
export const iconMail = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAC4SURBVDiNxdIxagJRFIXhLzLFBNJYaJslSEylWOhq3IorMGQ16SyjYCFiZWU5pTaDFvOUyTAZ8RHID69555577oXLf/OEGaY4R3g/4IhORHg3eOXYYvSAeRQ8OWQYYoNPvDQYnxUr7zBB1grCAv3QbIlxjXmAb7Txhq+rkFUKq9NUU8vcJiizwDtOWGEdmvTKqT+61H0GXsP7jSxpEGF/R1e3wkO0FBeVRnhTSBTneBB3yvOI4D/mAnvrIwKM5s4AAAAAAElFTkSuQmCC";
export const iconInfo = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAnXAAAJ1wGxbhe3AAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAASVJREFUOI190r0uhFEQBuBnVxaF2PUTCkFchV0SV6BQi0rEbShFlCqNktJP0Iqf3i3YVSlXVEQozojP8e2+ySSTed+ZMzNnKnpjCFPhv+C9j/YPlnCBV3TCujhHq19iFftoYxOjBa4esTb2QvsP+7jFWJ9HxnEXRf5gGU9Z8gKucBl+sUgHTahE8AJnOCoIT/AcmhmsF7gtrGINBqWFFWcmLXMUhzjIuEbk1GA+2i/DNh4wUsK1MVfFV2GUHJO4xlsPHr8j1Eu44bAcDek2agP4lDZaxWMm3MEKbrL4hjT/8U+gJc00nglnw4qYkL5xMW9rTzqSvEiefI/dMrIaRTrSPzcKXCNinUguPeUfNKWj6kqH9Bz+aVnbvb6PtKTp8F/wUSb6Bu5YN5n7ff0kAAAAAElFTkSuQmCC";
export const iconSuccess = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAnXAAAJ1wGxbhe3AAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAQtJREFUOI2F0jFOAlEQBuAPImoFqyTa6TEEbfUihruYDYfwCAg3UDsTY20na0VjgqUWWuxgHsuy/skk82bmn/fPm9eyHXs4Cn+Br4baNZxjhk8UYUtMMWwitjHGHNfoJrlexObIo3YDY9zjoOGSQzxEkzVc4O0fctqkwCANzkJiE9LmI9ytDrvKB+tWGQnylIAsOB04VcrfdluO55CeYo6THfygVUne4jX8S1zho1LTDu7fCL2KxCe8oF8zUqb8G51VYGrzEffD6jDCJA0MY6bqnHXoK9d4Vk3kyk/S1KSPR9zUJdvRpAiJWZLLIlYEufYrrzBQ7nyJ97ClcuYN2dX1pejgOPwFvuuKfgHXiDR+HL1j1AAAAABJRU5ErkJggg==";
export const iconError = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAnXAAAJ1wGxbhe3AAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAARZJREFUOI2V0j1KQ1EQBeDPp4lWRiMoKVyAK9AoiLgJGytxD9oJNhKyDyvBnw2IugC3YGKVRk1KRbR48yC5vjzwwIHL3DPnzp2ZGdMxj9U4D/BZoZ3ANu4wQj84xC3aVYkZuujhCItjd42I9dAJ7R908YDlikeaeAyTCezgpST5IJia9LFVlA0nOMd7It4IjuMttKeFQR17uKooPcUV9lHL0ArX0T8MPqLa1hx+MDNFWDX7LHLV4/VGiWghmGJJvhu1WXzLO5rhORGeYRf3SfwQNVwWgbZ8SZqJcD04jhX5GDfTsjryJUlN0uQnXJRdZmHSx7H8nwWWItaP5NJVLrCFG3mTXoNDXJeVPW185E1ai/MAX2WiX9S3NSPYbj+uAAAAAElFTkSuQmCC";
export const iconWarning = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAnXAAAJ1wGxbhe3AAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAARJJREFUOI2l0r8uRFEQBvAfu9glwUYiUaxHUEl0VDpKeq+wpZBINAqFRHgTKg0tCSqVhmKDEM1u/Esodm725rq7iC+ZzMnM982ZmXP4JwpdchWsYBrXeMkj9XQQV3GEi+BMYR63v+mqiDPUUrEaTiP3I1ZxEOcySnE+jFxXVPEQPimWiCYzOdCbKbCFPe1Z+8PgBvvBycVMCIdSsY2wBEPBmcnrYBtraKRib2EJGljHjswLLuI8Z6SS9hLTl15iIR08wZLv2AzLYjk0YATP8n9lVWbrgUJohosYxCdG8Zghdvp5ldCUi6hrPd0VjvGEVzTxEYLkogGMYQ67uEtvcgKzGA8y9IV/D9/Evdb89Q7d/Q1fB8U0mpUmzV0AAAAASUVORK5CYII=";
export const iconLeft = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAABfSURBVDiNY2AY8oCZSHWxDAwMEgwMDHfJsaSAgYHhH9QQsjT/Z2BgKKe75gQGiLMLCSlkwiHOSI6t6ADmhYoBN6SIARIeidgkiUlIxxkYGB4xMDB8YmBguE6JSwYpAACvLRHTKwPjZgAAAABJRU5ErkJggg==";
export const iconRight = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAABNSURBVDiN3dAxCoAwFATRh3fU2oAHiDbi5Y1F2jT+gKLbzyy7/DYjUo8g4cTWI8koOF6XrOqc5ifDDVGJthfsj8OLujtHYJgwR+GP5QKMxA9/SolDQgAAAABJRU5ErkJggg==";
export const iconLLeft = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAABlSURBVDiN3ZLBDUBAEEUfmtCchA5woUMlOO1FCQrAwbqwf8eFhHd7mfzJn2Tg82TGvABywAmPUgOLD4XcDK9AJ/y5cOlrNsIvpCdPDL/FUbkX/t6Slv3+SjgQf6QBmIAZGAP+FzZJViOd89x8pAAAAABJRU5ErkJggg==";
export const iconRRight = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAABmSURBVDiN3dGxCoAgEMbxfz1dL1BTREJzmUv08trgDYcg6VCD3/YD7zvkoLmMgFEegLmmwAAecOJVvNeUWCAAt7IHjt9LThkyiRf9qC8oCom70u0BuDL+bngj/tNm/JqJePucW8wDvGYdzT0nMUkAAAAASUVORK5CYII=";
export const iconUpload = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAADNSURBVDiNndOxTgJRFIThz41ZDMFKqH0DLSRSq4lQ0RifUcMzUJlYQKjtLcHVSimBggPRNSzs/sk0kzPnTHEvxZyHKnGJD3yhXSWcYRnKwvvH0Y7wEG/4wQI1XOEek6LLF3FtiDoGoXp4WcxsSXILHjFCH/Nf/jy8ER6KGuTZNNhJvkFpEpygUyHbRi1BFy8VFryilyANlSVFerxn6N36IRVyG0PNEtdbkbmBU8zwdOCSJp4xRWNj3sWS5YGaRvM/f6GBa5ztafCJMb5hBQQ/MMwXLnnZAAAAAElFTkSuQmCC";

5
src/core/utils.js Normal file
View File

@@ -0,0 +1,5 @@
export const val = t => typeof t === "function" ? t() : t;
export const joinClass = (t, l) => typeof l === "function"
? () => `${t} ${l() || ""}`.trim()
: `${t} ${l || ""}`.trim();