Next Preview Work Final
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
</div>
|
||||
<h1 class="text-7xl md:text-9xl font-black tracking-tighter mb-4 bg-clip-text text-transparent bg-linear-to-r from-secondary via-accent to-primary !text-center w-full">SigPro UI beta (W.I.P.)</h1>
|
||||
<div class="text-2xl md:text-3xl font-bold text-base-content/90 mb-4 !text-center w-full">Reactive Design System for SigPro</div>
|
||||
<div class="text-xl text-base-content/60 max-w-3xl mx-auto mb-10 leading-relaxed italic text-balance font-light !text-center w-full">"Atomic components for high-performance interfaces. Zero-boilerplate, pure DaisyUI v5 elegance."</div>
|
||||
<div class="text-xl text-base-content/60 max-w-3xl mx-auto mb-10 leading-relaxed italic text-balance font-light !text-center w-full">"Atomic components for high-performance interfaces. Zero-boilerplate, pure reactivity."</div>
|
||||
<div class="flex flex-wrap justify-center gap-4 w-full">
|
||||
<a href="#/install" class="btn btn-secondary btn-lg shadow-xl shadow-secondary/20 group px-10 border-none text-secondary-content">View Components <span class="group-hover:translate-x-1 transition-transform inline-block">→</span></a>
|
||||
<button onclick="window.open('https://github.com/natxocc/sigpro-ui')" class="btn btn-outline btn-lg border-base-300 hover:bg-base-300 hover:text-base-content">GitHub</button>
|
||||
@@ -25,26 +25,26 @@
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 items-stretch">
|
||||
<div class="card bg-base-200/30 border border-base-300 hover:border-secondary/40 transition-all p-1">
|
||||
<div class="card-body p-6">
|
||||
<h3 class="card-title text-xl font-black text-secondary italic">TAILWIND V4</h3>
|
||||
<p class="text-sm opacity-70">Built on the latest CSS engine. Lightning fast styles with zero legacy overhead.</p>
|
||||
<h3 class="card-title text-xl font-black text-secondary italic">SIGPRO NATIVE</h3>
|
||||
<p class="text-sm opacity-70">Direct integration with SigPro signals. No wrappers, no context, just pure atomic reactivity.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card bg-base-200/30 border border-base-300 hover:border-accent/40 transition-all p-1">
|
||||
<div class="card-body p-6">
|
||||
<h3 class="card-title text-xl font-black text-accent italic">DAISYUI V5</h3>
|
||||
<p class="text-sm opacity-70">Semantic, beautiful and accessible. Professional components out of the box.</p>
|
||||
<h3 class="card-title text-xl font-black text-accent italic">ZERO CONFIG</h3>
|
||||
<p class="text-sm opacity-70">Import and build immediately. Designed for developers who hate configuration files.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card bg-base-200/30 border border-base-300 hover:border-primary/40 transition-all p-1">
|
||||
<div class="card-body p-6">
|
||||
<h3 class="card-title text-xl font-black text-primary italic">NATIVE REACTION</h3>
|
||||
<p class="text-sm opacity-70">Direct integration with SigPro signals. No wrappers, no context, just speed.</p>
|
||||
<h3 class="card-title text-xl font-black text-primary italic">REACTIVE BY DESIGN</h3>
|
||||
<p class="text-sm opacity-70">Every component is a high-order function optimized for SigPro's fine-grained updates.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card bg-base-200/30 border border-base-300 hover:border-base-content/20 transition-all p-1">
|
||||
<div class="card-body p-6">
|
||||
<h3 class="card-title text-xl font-black italic text-base-content">READY-TO-GO</h3>
|
||||
<p class="text-sm opacity-70">Import and build. Designed for developers who hate configuration files.</p>
|
||||
<p class="text-sm opacity-70">60+ atomic components. Semantic, accessible, and production-ready.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -54,13 +54,13 @@
|
||||
|
||||
SigPro-UI isn't just a library; it's a **Functional Design System**.
|
||||
|
||||
It eliminates the gap between your data (Signals) and your layout (DaisyUI). Each component is a high-order function optimized for the SigPro core, ensuring that your UI only updates where it matters.
|
||||
It eliminates the gap between your data (Signals) and your UI components. Each component is a high-order function optimized for the SigPro core, ensuring that your interface only updates where it matters.
|
||||
|
||||
| Requirement | Value | Why? |
|
||||
| :--- | :--- | :--- |
|
||||
| **Engine** | **SigPro** | Atomic reactivity without V-DOM. |
|
||||
| **Styling** | **Tailwind CSS v4** | Pure CSS performance. |
|
||||
| **Components** | **daisyUI v5** | Semantic and clean layouts. |
|
||||
| **Components** | **SigPro-UI** | 60+ semantic, reactive components. |
|
||||
| **Styling** | **daisyUI v5** | Beautiful, accessible, themeable. |
|
||||
| **Learning Curve** | **Zero** | If you know JS and HTML, you know SigPro-UI. |
|
||||
|
||||
### Semantic Functionalism
|
||||
@@ -73,43 +73,43 @@ Modal({
|
||||
title: "Precision Engineering"
|
||||
}, () =>
|
||||
Div({ class: "space-y-4" }, [
|
||||
P("SigPro-UI leverages Tailwind v4 for instant styling."),
|
||||
P("SigPro-UI provides instant reactivity out of the box."),
|
||||
Button({
|
||||
class: "btn-primary",
|
||||
onclick: () => isVisible(false)
|
||||
}, "Confirm")
|
||||
])
|
||||
)
|
||||
````
|
||||
```
|
||||
|
||||
-----
|
||||
---
|
||||
|
||||
## Technical Stack Requirements
|
||||
|
||||
To achieve the performance promised by SigPro-UI, your environment must be equipped with:
|
||||
|
||||
### 1\. SigPro Core
|
||||
### 1. SigPro Core
|
||||
|
||||
The atomic heart. SigPro-UI requires the SigPro runtime (`$`, `$watch`, `$html`, etc.) to be present in the global scope or provided as a module.
|
||||
|
||||
### 2\. Tailwind CSS v4 Engine
|
||||
|
||||
SigPro-UI uses the modern `@theme` and utility engine of Tailwind v4. It is designed to work with the ultra-fast compiler of the new generation.
|
||||
|
||||
### 3\. daisyUI v5
|
||||
### 2. daisyUI v5
|
||||
|
||||
The visual DNA. All components are mapped to daisyUI v5 semantic classes, providing access to dozens of themes and accessible UI patterns without writing a single line of custom CSS.
|
||||
|
||||
-----
|
||||
### 3. Modern Browser
|
||||
|
||||
SigPro-UI uses modern Web APIs and requires no polyfills for evergreen browsers.
|
||||
|
||||
---
|
||||
|
||||
<div class="bg-base-200/50 rounded-3xl p-10 my-16 border border-base-300 shadow-inner">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8 items-center">
|
||||
<div class="lg:col-span-2">
|
||||
<h2 class="text-4xl font-black mb-4 mt-0 tracking-tight italic text-secondary">Design at Runtime.</h2>
|
||||
<h2 class="text-4xl font-black mb-4 mt-0 tracking-tight italic text-secondary">Reactive at Runtime.</h2>
|
||||
<p class="text-xl opacity-80 leading-relaxed">
|
||||
Combine the best of three worlds: <strong>SigPro</strong> for logic,
|
||||
<strong>Tailwind v4</strong> for speed, and <strong>daisyUI v5</strong> for beauty.
|
||||
Build interfaces that feel as fast as they look.
|
||||
Combine the best of both worlds: <strong>SigPro</strong> for logic and
|
||||
<strong>daisyUI v5</strong> for beauty. Build interfaces that feel as fast as they look,
|
||||
with components that react instantly to your data changes.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -42,9 +42,4 @@
|
||||
* [Fieldset](components/fieldset.md)
|
||||
* [Menu](components/menu.md)
|
||||
* [Navbar](components/navbar.md)
|
||||
* [Tabs](components/tabs.md)
|
||||
|
||||
* **Advanced**
|
||||
* [Reactivity Guide](advanced/reactivity.md)
|
||||
* [i18n Guide](advanced/i18n.md)
|
||||
* [Theming](advanced/theming.md)
|
||||
* [Tabs](components/tabs.md)
|
||||
@@ -171,7 +171,7 @@ const CartDemo = () => {
|
||||
),
|
||||
Span({ class: 'text-lg font-bold' }, () => `Total: $${total()}`)
|
||||
]),
|
||||
cart().length === 0
|
||||
() => cart().length === 0
|
||||
? Div({ class: 'alert alert-soft text-center' }, 'Cart is empty')
|
||||
: Div({ class: 'flex flex-col gap-2' }, cart().map(item =>
|
||||
Div({ class: 'flex justify-between items-center p-2 bg-base-200 rounded-lg' }, [
|
||||
@@ -187,6 +187,7 @@ const CartDemo = () => {
|
||||
))
|
||||
]);
|
||||
};
|
||||
|
||||
$mount(CartDemo, '#demo-cart');
|
||||
```
|
||||
|
||||
@@ -235,7 +236,7 @@ const InboxDemo = () => {
|
||||
}
|
||||
}, 'Mark all read')
|
||||
]),
|
||||
Div({ class: 'flex flex-col gap-2' }, messages().map(msg =>
|
||||
Div({ class: 'flex flex-col gap-2' }, () => messages().map(msg =>
|
||||
Div({
|
||||
class: `p-3 rounded-lg cursor-pointer transition-all ${msg.read ? 'bg-base-200 opacity-60' : 'bg-primary/10 border-l-4 border-primary'}`,
|
||||
onclick: () => markAsRead(msg.id)
|
||||
@@ -247,6 +248,7 @@ const InboxDemo = () => {
|
||||
))
|
||||
]);
|
||||
};
|
||||
|
||||
$mount(InboxDemo, '#demo-inbox');
|
||||
```
|
||||
|
||||
|
||||
@@ -125,20 +125,22 @@ $mount(TooltipDemo, '#demo-tooltip');
|
||||
```javascript
|
||||
const ErrorDemo = () => {
|
||||
const email = $('');
|
||||
const isValid = $(true);
|
||||
|
||||
const validate = (value) => {
|
||||
const valid = value.includes('@') && value.includes('.');
|
||||
isValid(valid);
|
||||
email(value);
|
||||
};
|
||||
|
||||
return Input({
|
||||
type: 'email',
|
||||
value: email,
|
||||
error: () => !isValid() && email() ? 'Invalid email address' : '',
|
||||
oninput: (e) => validate(e.target.value)
|
||||
});
|
||||
return Div({ class: 'w-full max-w-md' }, [
|
||||
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)
|
||||
})
|
||||
]);
|
||||
};
|
||||
$mount(ErrorDemo, '#demo-error');
|
||||
```
|
||||
|
||||
@@ -8,23 +8,23 @@ List component with custom item rendering, headers, and reactive data binding.
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `items` | `Array \| Signal<Array>` | `[]` | Data array to display |
|
||||
| `header` | `string \| VNode \| Signal` | `-` | Optional header content |
|
||||
| `render` | `function(item, index)` | Required | Custom render function for each item |
|
||||
| `keyFn` | `function(item, index)` | `(item, idx) => idx` | Unique key function for items |
|
||||
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
|
||||
| Prop | Type | Default | Description |
|
||||
| :------- | :-------------------------- | :------------------- | :------------------------------------------ |
|
||||
| `items` | `Array \| Signal<Array>` | `[]` | Data array to display |
|
||||
| `header` | `string \| VNode \| Signal` | `-` | Optional header content |
|
||||
| `render` | `function(item, index)` | Required | Custom render function for each item |
|
||||
| `keyFn` | `function(item, index)` | `(item, idx) => idx` | Unique key function for items |
|
||||
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
|
||||
|
||||
## Styling
|
||||
|
||||
List supports all **daisyUI List classes**:
|
||||
|
||||
| Category | Keywords | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| Base | `list` | Base list styling |
|
||||
| Variant | `list-row` | Row styling for list items |
|
||||
| Background | `bg-base-100` | Background color |
|
||||
| Category | Keywords | Description |
|
||||
| :--------- | :------------ | :------------------------- |
|
||||
| Base | `list` | Base list styling |
|
||||
| Variant | `list-row` | Row styling for list items |
|
||||
| Background | `bg-base-100` | Background color |
|
||||
|
||||
> For further details, check the [daisyUI List Documentation](https://daisyui.com/components/list) – Full reference for CSS classes.
|
||||
|
||||
@@ -41,16 +41,17 @@ List supports all **daisyUI List classes**:
|
||||
|
||||
```javascript
|
||||
const BasicDemo = () => {
|
||||
const items = ['Apple', 'Banana', 'Orange', 'Grape', 'Mango'];
|
||||
|
||||
const items = ["Apple", "Banana", "Orange", "Grape", "Mango"];
|
||||
|
||||
return List({
|
||||
items: items,
|
||||
render: (item) => Div({ class: 'p-3 hover:bg-base-200 transition-colors' }, [
|
||||
Span({ class: 'font-medium' }, item)
|
||||
])
|
||||
render: (item) =>
|
||||
Div({ class: "p-3 hover:bg-base-200 transition-colors" }, [
|
||||
Span({ class: "font-medium" }, item),
|
||||
]),
|
||||
});
|
||||
};
|
||||
$mount(BasicDemo, '#demo-basic');
|
||||
$mount(BasicDemo, "#demo-basic");
|
||||
```
|
||||
|
||||
### With Header
|
||||
@@ -65,22 +66,31 @@ $mount(BasicDemo, '#demo-basic');
|
||||
```javascript
|
||||
const HeaderDemo = () => {
|
||||
const users = [
|
||||
{ name: 'John Doe', email: 'john@example.com', status: 'active' },
|
||||
{ name: 'Jane Smith', email: 'jane@example.com', status: 'inactive' },
|
||||
{ name: 'Bob Johnson', email: 'bob@example.com', status: 'active' }
|
||||
{ name: "John Doe", email: "john@example.com", status: "active" },
|
||||
{ name: "Jane Smith", email: "jane@example.com", status: "inactive" },
|
||||
{ name: "Bob Johnson", email: "bob@example.com", status: "active" },
|
||||
];
|
||||
|
||||
|
||||
return List({
|
||||
items: users,
|
||||
header: Div({ class: 'p-3 bg-primary/10 font-bold border-b border-base-300' }, 'Active Users'),
|
||||
render: (user) => Div({ class: 'p-3 border-b border-base-300 hover:bg-base-200' }, [
|
||||
Div({ class: 'font-medium' }, user.name),
|
||||
Div({ class: 'text-sm opacity-70' }, user.email),
|
||||
Span({ class: `badge badge-sm ${user.status === 'active' ? 'badge-success' : 'badge-ghost'} mt-1` }, user.status)
|
||||
])
|
||||
header: Div(
|
||||
{ class: "p-3 bg-primary/10 font-bold border-b border-base-300" },
|
||||
"Active Users",
|
||||
),
|
||||
render: (user) =>
|
||||
Div({ class: "p-3 border-b border-base-300 hover:bg-base-200" }, [
|
||||
Div({ class: "font-medium" }, user.name),
|
||||
Div({ class: "text-sm opacity-70" }, user.email),
|
||||
Span(
|
||||
{
|
||||
class: `badge badge-sm ${user.status === "active" ? "badge-success" : "badge-ghost"} mt-1`,
|
||||
},
|
||||
user.status,
|
||||
),
|
||||
]),
|
||||
});
|
||||
};
|
||||
$mount(HeaderDemo, '#demo-header');
|
||||
$mount(HeaderDemo, "#demo-header");
|
||||
```
|
||||
|
||||
### With Icons
|
||||
@@ -95,25 +105,36 @@ $mount(HeaderDemo, '#demo-header');
|
||||
```javascript
|
||||
const IconsDemo = () => {
|
||||
const settings = [
|
||||
{ icon: '🔊', label: 'Sound', description: 'Adjust volume and notifications' },
|
||||
{ icon: '🌙', label: 'Display', description: 'Brightness and dark mode' },
|
||||
{ icon: '🔒', label: 'Privacy', description: 'Security settings' },
|
||||
{ icon: '🌐', label: 'Network', description: 'WiFi and connections' }
|
||||
{
|
||||
icon: "🔊",
|
||||
label: "Sound",
|
||||
description: "Adjust volume and notifications",
|
||||
},
|
||||
{ icon: "🌙", label: "Display", description: "Brightness and dark mode" },
|
||||
{ icon: "🔒", label: "Privacy", description: "Security settings" },
|
||||
{ icon: "🌐", label: "Network", description: "WiFi and connections" },
|
||||
];
|
||||
|
||||
|
||||
return List({
|
||||
items: settings,
|
||||
render: (item) => Div({ class: 'flex gap-3 p-3 hover:bg-base-200 transition-colors cursor-pointer' }, [
|
||||
Div({ class: 'text-2xl' }, item.icon),
|
||||
Div({ class: 'flex-1' }, [
|
||||
Div({ class: 'font-medium' }, item.label),
|
||||
Div({ class: 'text-sm opacity-60' }, item.description)
|
||||
]),
|
||||
Span({ class: 'opacity-40' }, '→')
|
||||
])
|
||||
render: (item) =>
|
||||
Div(
|
||||
{
|
||||
class:
|
||||
"flex gap-3 p-3 hover:bg-base-200 transition-colors cursor-pointer",
|
||||
},
|
||||
[
|
||||
Div({ class: "text-2xl" }, item.icon),
|
||||
Div({ class: "flex-1" }, [
|
||||
Div({ class: "font-medium" }, item.label),
|
||||
Div({ class: "text-sm opacity-60" }, item.description),
|
||||
]),
|
||||
Span({ class: "opacity-40" }, "→"),
|
||||
],
|
||||
),
|
||||
});
|
||||
};
|
||||
$mount(IconsDemo, '#demo-icons');
|
||||
$mount(IconsDemo, "#demo-icons");
|
||||
```
|
||||
|
||||
### With Badges
|
||||
@@ -128,24 +149,52 @@ $mount(IconsDemo, '#demo-icons');
|
||||
```javascript
|
||||
const BadgesDemo = () => {
|
||||
const notifications = [
|
||||
{ id: 1, message: 'New comment on your post', time: '5 min ago', unread: true },
|
||||
{ id: 2, message: 'Your order has been shipped', time: '1 hour ago', unread: true },
|
||||
{ id: 3, message: 'Welcome to the platform!', time: '2 days ago', unread: false },
|
||||
{ id: 4, message: 'Weekly digest available', time: '3 days ago', unread: false }
|
||||
{
|
||||
id: 1,
|
||||
message: "New comment on your post",
|
||||
time: "5 min ago",
|
||||
unread: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
message: "Your order has been shipped",
|
||||
time: "1 hour ago",
|
||||
unread: true,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
message: "Welcome to the platform!",
|
||||
time: "2 days ago",
|
||||
unread: false,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
message: "Weekly digest available",
|
||||
time: "3 days ago",
|
||||
unread: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
return List({
|
||||
items: notifications,
|
||||
render: (item) => Div({ class: `flex justify-between items-center p-3 border-b border-base-300 hover:bg-base-200 ${item.unread ? 'bg-primary/5' : ''}` }, [
|
||||
Div({ class: 'flex-1' }, [
|
||||
Div({ class: 'font-medium' }, item.message),
|
||||
Div({ class: 'text-xs opacity-50' }, item.time)
|
||||
]),
|
||||
item.unread ? Span({ class: 'badge badge-primary badge-sm' }, 'New') : null
|
||||
])
|
||||
render: (item) =>
|
||||
Div(
|
||||
{
|
||||
class: `flex justify-between items-center p-3 border-b border-base-300 hover:bg-base-200 ${item.unread ? "bg-primary/5" : ""}`,
|
||||
},
|
||||
[
|
||||
Div({ class: "flex-1" }, [
|
||||
Div({ class: "font-medium" }, item.message),
|
||||
Div({ class: "text-xs opacity-50" }, item.time),
|
||||
]),
|
||||
item.unread
|
||||
? Span({ class: "badge badge-primary badge-sm" }, "New")
|
||||
: null,
|
||||
],
|
||||
),
|
||||
});
|
||||
};
|
||||
$mount(BadgesDemo, '#demo-badges');
|
||||
$mount(BadgesDemo, "#demo-badges");
|
||||
```
|
||||
|
||||
### Interactive List
|
||||
@@ -161,38 +210,49 @@ $mount(BadgesDemo, '#demo-badges');
|
||||
const InteractiveDemo = () => {
|
||||
const selected = $(null);
|
||||
const items = [
|
||||
{ id: 1, name: 'Project Alpha', status: 'In Progress' },
|
||||
{ id: 2, name: 'Project Beta', status: 'Planning' },
|
||||
{ id: 3, name: 'Project Gamma', status: 'Completed' },
|
||||
{ id: 4, name: 'Project Delta', status: 'Review' }
|
||||
{ id: 1, name: "Project Alpha", status: "In Progress" },
|
||||
{ id: 2, name: "Project Beta", status: "Planning" },
|
||||
{ id: 3, name: "Project Gamma", status: "Completed" },
|
||||
{ id: 4, name: "Project Delta", status: "Review" },
|
||||
];
|
||||
|
||||
|
||||
const statusColors = {
|
||||
'In Progress': 'badge-warning',
|
||||
'Planning': 'badge-info',
|
||||
'Completed': 'badge-success',
|
||||
'Review': 'badge-accent'
|
||||
"In Progress": "badge-warning",
|
||||
Planning: "badge-info",
|
||||
Completed: "badge-success",
|
||||
Review: "badge-accent",
|
||||
};
|
||||
|
||||
return Div({ class: 'flex flex-col gap-4' }, [
|
||||
|
||||
return Div({ class: "flex flex-col gap-4" }, [
|
||||
List({
|
||||
items: items,
|
||||
render: (item) => Div({
|
||||
class: `p-3 cursor-pointer transition-all hover:bg-base-200 ${selected() === item.id ? 'bg-primary/10 border-l-4 border-primary' : 'border-l-4 border-transparent'}`,
|
||||
onclick: () => selected(item.id)
|
||||
}, [
|
||||
Div({ class: 'flex justify-between items-center' }, [
|
||||
Div({ class: 'font-medium' }, item.name),
|
||||
Span({ class: `badge ${statusColors[item.status]}` }, item.status)
|
||||
])
|
||||
])
|
||||
render: (item) =>
|
||||
Div(
|
||||
{
|
||||
class: `p-3 cursor-pointer transition-all hover:bg-base-200 ${selected() === item.id ? "bg-primary/10 border-l-4 border-primary" : "border-l-4 border-transparent"}`,
|
||||
onclick: () => selected(item.id),
|
||||
},
|
||||
[
|
||||
Div({ class: "flex justify-between items-center" }, [
|
||||
Div({ class: "font-medium" }, item.name),
|
||||
Span(
|
||||
{ class: `badge ${statusColors[item.status]}` },
|
||||
item.status,
|
||||
),
|
||||
]),
|
||||
],
|
||||
),
|
||||
}),
|
||||
() => selected()
|
||||
? Div({ class: 'alert alert-info' }, `Selected: ${items.find(i => i.id === selected()).name}`)
|
||||
: Div({ class: 'alert alert-soft' }, 'Select a project to see details')
|
||||
() =>
|
||||
selected()
|
||||
? Div(
|
||||
{ class: "alert alert-info" },
|
||||
`Selected: ${items.find((i) => i.id === selected()).name}`,
|
||||
)
|
||||
: Div({ class: "alert alert-soft" }, "Select a project to see details"),
|
||||
]);
|
||||
};
|
||||
$mount(InteractiveDemo, '#demo-interactive');
|
||||
$mount(InteractiveDemo, "#demo-interactive");
|
||||
```
|
||||
|
||||
### Reactive List (Todo App)
|
||||
@@ -223,9 +283,7 @@ const ReactiveDemo = () => {
|
||||
};
|
||||
|
||||
const toggleTodo = (id) => {
|
||||
todos(todos().map(t =>
|
||||
t.id === id ? { ...t, done: !t.done } : t
|
||||
));
|
||||
todos(todos().map(t => t.id === id ? { ...t, done: !t.done } : t));
|
||||
};
|
||||
|
||||
const deleteTodo = (id) => {
|
||||
@@ -233,13 +291,12 @@ const ReactiveDemo = () => {
|
||||
};
|
||||
|
||||
const pendingCount = () => todos().filter(t => !t.done).length;
|
||||
|
||||
$watch(()=> console.log(pendingCount()));
|
||||
return Div({ class: 'flex flex-col gap-4' }, [
|
||||
Div({ class: 'flex gap-2' }, [
|
||||
Input({
|
||||
placeholder: 'Add new task...',
|
||||
value: newTodo,
|
||||
class: 'flex-1',
|
||||
oninput: (e) => newTodo(e.target.value),
|
||||
onkeypress: (e) => e.key === 'Enter' && addTodo()
|
||||
}),
|
||||
@@ -247,24 +304,32 @@ const ReactiveDemo = () => {
|
||||
]),
|
||||
List({
|
||||
items: todos,
|
||||
render: (todo) => Div({ class: `flex items-center gap-3 p-2 border-b border-base-300 hover:bg-base-200 ${todo.done ? 'opacity-60' : ''}` }, [
|
||||
Checkbox({
|
||||
value: todo.done,
|
||||
onclick: () => toggleTodo(todo.id)
|
||||
}),
|
||||
Span({
|
||||
class: `flex-1 ${todo.done ? 'line-through' : ''}`,
|
||||
onclick: () => toggleTodo(todo.id)
|
||||
}, todo.text),
|
||||
Button({
|
||||
class: 'btn btn-ghost btn-xs btn-circle',
|
||||
onclick: () => deleteTodo(todo.id)
|
||||
}, '✕')
|
||||
])
|
||||
render: (item) => {
|
||||
// Esta función busca siempre el estado actual del item dentro del signal
|
||||
const it = () => todos().find(t => t.id === item.id) || item;
|
||||
|
||||
return Div({
|
||||
class: () => `flex items-center gap-3 p-2 border-b border-base-300 ${it().done ? 'opacity-60' : ''}`
|
||||
}, [
|
||||
Checkbox({
|
||||
value: () => it().done,
|
||||
onclick: () => toggleTodo(item.id)
|
||||
}),
|
||||
Span({
|
||||
class: () => `flex-1 ${it().done ? 'line-through' : ''}`,
|
||||
onclick: () => toggleTodo(item.id)
|
||||
}, () => it().text),
|
||||
Button({
|
||||
class: 'btn btn-ghost btn-xs btn-circle',
|
||||
onclick: () => deleteTodo(item.id)
|
||||
}, '✕')
|
||||
]);
|
||||
}
|
||||
}),
|
||||
Div({ class: 'text-sm opacity-70 mt-2' }, () => `${pendingCount()} tasks remaining`)
|
||||
]);
|
||||
};
|
||||
|
||||
$mount(ReactiveDemo, '#demo-reactive');
|
||||
```
|
||||
|
||||
@@ -280,27 +345,45 @@ $mount(ReactiveDemo, '#demo-reactive');
|
||||
```javascript
|
||||
const AvatarDemo = () => {
|
||||
const contacts = [
|
||||
{ name: 'Alice Johnson', role: 'Developer', avatar: 'A', online: true },
|
||||
{ name: 'Bob Smith', role: 'Designer', avatar: 'B', online: false },
|
||||
{ name: 'Charlie Brown', role: 'Manager', avatar: 'C', online: true },
|
||||
{ name: 'Diana Prince', role: 'QA Engineer', avatar: 'D', online: false }
|
||||
{ name: "Alice Johnson", role: "Developer", avatar: "A", online: true },
|
||||
{ name: "Bob Smith", role: "Designer", avatar: "B", online: false },
|
||||
{ name: "Charlie Brown", role: "Manager", avatar: "C", online: true },
|
||||
{ name: "Diana Prince", role: "QA Engineer", avatar: "D", online: false },
|
||||
];
|
||||
|
||||
|
||||
return List({
|
||||
items: contacts,
|
||||
render: (contact) => Div({ class: 'flex gap-3 p-3 hover:bg-base-200 transition-colors' }, [
|
||||
Div({ class: `avatar ${contact.online ? 'online' : 'offline'}`, style: 'width: 48px' }, [
|
||||
Div({ class: 'rounded-full bg-primary text-primary-content flex items-center justify-center w-12 h-12 font-bold' }, contact.avatar)
|
||||
render: (contact) =>
|
||||
Div({ class: "flex gap-3 p-3 hover:bg-base-200 transition-colors" }, [
|
||||
Div(
|
||||
{
|
||||
class: `avatar ${contact.online ? "online" : "offline"}`,
|
||||
style: "width: 48px",
|
||||
},
|
||||
[
|
||||
Div(
|
||||
{
|
||||
class:
|
||||
"rounded-full bg-primary text-primary-content flex items-center justify-center w-12 h-12 font-bold",
|
||||
},
|
||||
contact.avatar,
|
||||
),
|
||||
],
|
||||
),
|
||||
Div({ class: "flex-1" }, [
|
||||
Div({ class: "font-medium" }, contact.name),
|
||||
Div({ class: "text-sm opacity-60" }, contact.role),
|
||||
]),
|
||||
Div(
|
||||
{
|
||||
class: `badge badge-sm ${contact.online ? "badge-success" : "badge-ghost"}`,
|
||||
},
|
||||
contact.online ? "Online" : "Offline",
|
||||
),
|
||||
]),
|
||||
Div({ class: 'flex-1' }, [
|
||||
Div({ class: 'font-medium' }, contact.name),
|
||||
Div({ class: 'text-sm opacity-60' }, contact.role)
|
||||
]),
|
||||
Div({ class: `badge badge-sm ${contact.online ? 'badge-success' : 'badge-ghost'}` }, contact.online ? 'Online' : 'Offline')
|
||||
])
|
||||
});
|
||||
};
|
||||
$mount(AvatarDemo, '#demo-avatar');
|
||||
$mount(AvatarDemo, "#demo-avatar");
|
||||
```
|
||||
|
||||
### All Variants
|
||||
@@ -314,29 +397,29 @@ $mount(AvatarDemo, '#demo-avatar');
|
||||
|
||||
```javascript
|
||||
const VariantsDemo = () => {
|
||||
const items = ['Item 1', 'Item 2', 'Item 3'];
|
||||
|
||||
return Div({ class: 'flex flex-col gap-6' }, [
|
||||
Div({ class: 'text-sm font-bold' }, 'Default List'),
|
||||
const items = ["Item 1", "Item 2", "Item 3"];
|
||||
|
||||
return Div({ class: "flex flex-col gap-6" }, [
|
||||
Div({ class: "text-sm font-bold" }, "Default List"),
|
||||
List({
|
||||
items: items,
|
||||
render: (item) => Div({ class: 'p-2' }, item)
|
||||
render: (item) => Div({ class: "p-2" }, item),
|
||||
}),
|
||||
|
||||
Div({ class: 'text-sm font-bold mt-2' }, 'With Shadow'),
|
||||
|
||||
Div({ class: "text-sm font-bold mt-2" }, "With Shadow"),
|
||||
List({
|
||||
items: items,
|
||||
render: (item) => Div({ class: 'p-2' }, item),
|
||||
class: 'shadow-lg'
|
||||
render: (item) => Div({ class: "p-2" }, item),
|
||||
class: "shadow-lg",
|
||||
}),
|
||||
|
||||
Div({ class: 'text-sm font-bold mt-2' }, 'Rounded Corners'),
|
||||
|
||||
Div({ class: "text-sm font-bold mt-2" }, "Rounded Corners"),
|
||||
List({
|
||||
items: items,
|
||||
render: (item) => Div({ class: 'p-2' }, item),
|
||||
class: 'rounded-box overflow-hidden'
|
||||
})
|
||||
render: (item) => Div({ class: "p-2" }, item),
|
||||
class: "rounded-box overflow-hidden",
|
||||
}),
|
||||
]);
|
||||
};
|
||||
$mount(VariantsDemo, '#demo-variants');
|
||||
```
|
||||
$mount(VariantsDemo, "#demo-variants");
|
||||
```
|
||||
|
||||
@@ -140,41 +140,6 @@ const ReactiveDemo = () => {
|
||||
$mount(ReactiveDemo, '#demo-reactive');
|
||||
```
|
||||
|
||||
### With Trend Indicators
|
||||
|
||||
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||||
<div class="card-body">
|
||||
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||||
<div id="demo-trends" class="bg-base-100 p-6 rounded-xl border border-base-300 grid grid-cols-1 md:grid-cols-3 gap-4"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
```javascript
|
||||
const TrendsDemo = () => {
|
||||
return Div({ class: 'grid grid-cols-1 md:grid-cols-3 gap-4' }, [
|
||||
Stat({
|
||||
label: 'Weekly Sales',
|
||||
value: '$12,345',
|
||||
desc: Div({ class: 'text-success' }, '↗︎ 15% increase'),
|
||||
icon: Span({ class: 'text-2xl' }, '📈')
|
||||
}),
|
||||
Stat({
|
||||
label: 'Bounce Rate',
|
||||
value: '42%',
|
||||
desc: Div({ class: 'text-error' }, '↘︎ 3% from last week'),
|
||||
icon: Span({ class: 'text-2xl' }, '📉')
|
||||
}),
|
||||
Stat({
|
||||
label: 'Avg. Session',
|
||||
value: '4m 32s',
|
||||
desc: Div({ class: 'text-warning' }, '↗︎ 12 seconds'),
|
||||
icon: Span({ class: 'text-2xl' }, '⏱️')
|
||||
})
|
||||
]);
|
||||
};
|
||||
$mount(TrendsDemo, '#demo-trends');
|
||||
```
|
||||
|
||||
### Multiple Stats in Row
|
||||
|
||||
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||||
|
||||
@@ -294,27 +294,22 @@ $mount(ColorsDemo, '#demo-colors');
|
||||
const AllPositionsDemo = () => {
|
||||
return Div({ class: 'grid grid-cols-3 gap-4 justify-items-center' }, [
|
||||
Div({ class: 'col-start-2' }, [
|
||||
Tooltip({ tip: 'Top tooltip', class: 'tooltip-top' }, [
|
||||
Tooltip({ tip: 'Top tooltip', ui: 'tooltip-top' }, [
|
||||
Button({ class: 'btn btn-sm w-24' }, 'Top')
|
||||
])
|
||||
]),
|
||||
Div({ class: 'col-start-1 row-start-2' }, [
|
||||
Tooltip({ tip: 'Left tooltip', class: 'tooltip-left' }, [
|
||||
Tooltip({ tip: 'Left tooltip', ui: 'tooltip-left' }, [
|
||||
Button({ class: 'btn btn-sm w-24' }, 'Left')
|
||||
])
|
||||
]),
|
||||
Div({ class: 'col-start-2 row-start-2' }, [
|
||||
Tooltip({ tip: 'Center tooltip', class: 'tooltip' }, [
|
||||
Button({ class: 'btn btn-sm w-24' }, 'Center')
|
||||
])
|
||||
]),
|
||||
Div({ class: 'col-start-3 row-start-2' }, [
|
||||
Tooltip({ tip: 'Right tooltip', class: 'tooltip-right' }, [
|
||||
Tooltip({ tip: 'Right tooltip', ui: 'tooltip-right' }, [
|
||||
Button({ class: 'btn btn-sm w-24' }, 'Right')
|
||||
])
|
||||
]),
|
||||
Div({ class: 'col-start-2 row-start-3' }, [
|
||||
Tooltip({ tip: 'Bottom tooltip', class: 'tooltip-bottom' }, [
|
||||
Tooltip({ tip: 'Bottom tooltip', ui: 'tooltip-bottom' }, [
|
||||
Button({ class: 'btn btn-sm w-24' }, 'Bottom')
|
||||
])
|
||||
])
|
||||
|
||||
@@ -6,7 +6,7 @@ Follow these steps to integrate **SigPro-UI** into your project.
|
||||
|
||||
|
||||
!> **📘 Core Concepts**
|
||||
**Note:** SigPro-UI now includes SigPro core internally. You no longer need to install SigPro separately.
|
||||
**Note:** SigPro-UI now includes SigPro core internally. No need to install SigPro separately.
|
||||
SigProUI is built on top of the [SigPro](https://natxocc.github.io/sigpro/#/) reactive core. To learn how to create signals, manage reactivity, and structure your application logic, check out the [SigPro documentation](https://natxocc.github.io/sigpro/#/). It covers everything you need to build reactive applications with signals, computed values, and effects.
|
||||
---
|
||||
|
||||
@@ -135,7 +135,6 @@ When you install SigProUI, you get:
|
||||
- And 30+ more components!
|
||||
|
||||
### Utilities
|
||||
- `Utils` - Helper functions (ui, val)
|
||||
- `tt()` - i18n translation function
|
||||
|
||||
## Language Support
|
||||
@@ -143,7 +142,7 @@ When you install SigProUI, you get:
|
||||
SigProUI includes built-in i18n with Spanish and English:
|
||||
|
||||
```javascript
|
||||
import { tt } from 'sigpro-ui';
|
||||
import { tt, Locale } from 'sigpro-ui';
|
||||
|
||||
// Change locale (default is 'es')
|
||||
Locale('en');
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# SigPro-UI Quick Reference
|
||||
|
||||
**Version:** daisyUI v5 + Tailwind v4 Plugin
|
||||
**Status:** Active / WIP
|
||||
|
||||
|
||||
@@ -8,9 +7,7 @@
|
||||
|
||||
```javascript
|
||||
import "sigpro-ui";
|
||||
|
||||
// Injects all components into window and sets default language
|
||||
Locale('en'); // 'es' or 'en'
|
||||
import "sigpro-ui/css";
|
||||
|
||||
// All components (Button, Input, Table, Toast, etc.) are now globally available.
|
||||
```
|
||||
@@ -22,7 +19,7 @@ Locale('en'); // 'es' or 'en'
|
||||
| Component | Purpose | Basic Example |
|
||||
| :--- | :--- | :--- |
|
||||
| **Button** | Styled button with DaisyUI | `Button({ class: "btn-primary" }, "Submit")` |
|
||||
| **Input** | Reactive text field with floating label | `Input({ label: "Name", value: $name })` |
|
||||
| **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: [...] })` |
|
||||
@@ -40,7 +37,7 @@ Locale('en'); // 'es' or 'en'
|
||||
|
||||
| Component | Description | Example |
|
||||
| :--- | :--- | :--- |
|
||||
| **Input** | Text input with floating label, validation, password toggle | `Input({ label: "Email", type: "email", value: $email })` |
|
||||
| **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 })` |
|
||||
@@ -53,6 +50,36 @@ Locale('en'); // 'es' or 'en'
|
||||
|
||||
---
|
||||
|
||||
## Input Validation
|
||||
|
||||
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
|
||||
|
||||
---
|
||||
|
||||
## Data Display
|
||||
|
||||
| Component | Description | Example |
|
||||
@@ -63,7 +90,7 @@ Locale('en'); // 'es' or 'en'
|
||||
| **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({ badge: "3" }, Button(...))` |
|
||||
| **Indicator** | Badge on corner of element | `Indicator({ value: () => count() }, Button(...))` |
|
||||
|
||||
---
|
||||
|
||||
@@ -74,7 +101,7 @@ Locale('en'); // 'es' or 'en'
|
||||
| **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" }, Button(...))` |
|
||||
| **Tooltip** | Hover tooltip wrapper | `Tooltip({ tip: "Help text", ui: "tooltip-top" }, Button(...))` |
|
||||
|
||||
---
|
||||
|
||||
@@ -97,7 +124,7 @@ Locale('en'); // 'es' or 'en'
|
||||
| Component | Description | Example |
|
||||
| :--- | :--- | :--- |
|
||||
| **Fab** | Floating Action Button with actions | `Fab({ icon: "+", actions: [{ label: "Add", onclick: add }] })` |
|
||||
| **Indicator** | Badge indicator wrapper | `Indicator({ badge: "99+" }, Button(...))` |
|
||||
| **Indicator** | Badge indicator wrapper | `Indicator({ value: () => unread() }, Button(...))` |
|
||||
|
||||
---
|
||||
|
||||
@@ -138,15 +165,16 @@ const closeText = tt("close"); // "Close" or "Cerrar"
|
||||
|
||||
```javascript
|
||||
const name = $("");
|
||||
const error = $(null);
|
||||
|
||||
Input({
|
||||
value: name,
|
||||
error: error,
|
||||
oninput: (e) => {
|
||||
name(e.target.value);
|
||||
error(e.target.value.length < 3 ? "Name too short" : null);
|
||||
}
|
||||
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)
|
||||
})
|
||||
```
|
||||
|
||||
@@ -186,14 +214,14 @@ Modal({
|
||||
|
||||
| Component | Key Props |
|
||||
| :--- | :--- |
|
||||
| `Button` | `class`, `disabled`, `loading`, `badge`, `tooltip`, `icon` |
|
||||
| `Input` | `label`, `value`, `error`, `type`, `placeholder`, `disabled`, `tip` |
|
||||
| `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` |
|
||||
| `Loading` | `show` |
|
||||
| `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) |
|
||||
|
||||
8
docs/sigpro-ui.min.js
vendored
8
docs/sigpro-ui.min.js
vendored
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user