84 Commits
1.0.0 ... 1.1.1

Author SHA1 Message Date
ddcc960e1f add pages 2026-04-04 00:00:58 +02:00
a6705621d8 Next Preview Work Final 2026-04-03 23:54:11 +02:00
257107e198 Docs Updated 2026-04-03 01:41:07 +02:00
b0629ef3d0 dropdown ok 2026-04-02 21:40:07 +02:00
f0c710f8c2 Updateing Docs 2026-04-02 19:31:39 +02:00
5a77deb442 BUILD BEFORE CHANGE NEW COMPONENTS WITH UI FUNCTION 2026-04-01 20:53:41 +02:00
c9411be600 Complete 1 fase docs components 2026-04-01 09:26:52 +02:00
cacdbc26ad Docs 2026-04-01 00:24:49 +02:00
d41bfc8599 docs 2026-04-01 00:19:51 +02:00
83aa69014c Modal 2026-04-01 00:13:26 +02:00
091bd13062 Modal 2026-04-01 00:11:13 +02:00
2765b76593 Stat 2026-04-01 00:08:17 +02:00
c451c3a1f6 Docs 2026-04-01 00:07:13 +02:00
a05216b992 Docs 2026-04-01 00:01:35 +02:00
39e17bd1e4 Docs 2026-03-31 23:44:09 +02:00
f3393ee0db Docs 2026-03-31 23:41:51 +02:00
dcfd7b67b6 Radio correction 2026-03-31 19:38:10 +02:00
3de48fe222 Update CDN 2026-03-31 19:28:49 +02:00
80f1be6f07 Update Docs 2026-03-31 19:28:26 +02:00
3619239b9d Problem Object Object in Rating 2026-03-31 19:28:22 +02:00
e2f5932810 Include name in Radio 2026-03-31 19:20:13 +02:00
0bdbd70ab3 New docs 2026-03-31 19:15:18 +02:00
d62e99b17e Include AutoInstall 2026-03-31 18:52:28 +02:00
7c142eb677 Error Global Name 2026-03-31 17:54:19 +02:00
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
a2c2e762c6 Change Location 2026-03-30 16:38:41 +02:00
149 changed files with 40202 additions and 1576 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` |
--- ---

3
css/index.js Normal file
View File

@@ -0,0 +1,3 @@
// css/index.js
import './sigpro.min.css';
export default { version: '1.0.0' };

7563
css/sigpro.css Normal file

File diff suppressed because it is too large Load Diff

2
css/sigpro.min.css vendored Normal file

File diff suppressed because one or more lines are too long

1907
dist/sigpro-ui.js vendored Normal file

File diff suppressed because it is too large Load Diff

7
dist/sigpro-ui.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -8,7 +8,7 @@
</div> </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> <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-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"> <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> <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> <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="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 bg-base-200/30 border border-base-300 hover:border-secondary/40 transition-all p-1">
<div class="card-body p-6"> <div class="card-body p-6">
<h3 class="card-title text-xl font-black text-secondary italic">TAILWIND V4</h3> <h3 class="card-title text-xl font-black text-secondary italic">SIGPRO NATIVE</h3>
<p class="text-sm opacity-70">Built on the latest CSS engine. Lightning fast styles with zero legacy overhead.</p> <p class="text-sm opacity-70">Direct integration with SigPro signals. No wrappers, no context, just pure atomic reactivity.</p>
</div> </div>
</div> </div>
<div class="card bg-base-200/30 border border-base-300 hover:border-accent/40 transition-all p-1"> <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"> <div class="card-body p-6">
<h3 class="card-title text-xl font-black text-accent italic">DAISYUI V5</h3> <h3 class="card-title text-xl font-black text-accent italic">ZERO CONFIG</h3>
<p class="text-sm opacity-70">Semantic, beautiful and accessible. Professional components out of the box.</p> <p class="text-sm opacity-70">Import and build immediately. Designed for developers who hate configuration files.</p>
</div> </div>
</div> </div>
<div class="card bg-base-200/30 border border-base-300 hover:border-primary/40 transition-all p-1"> <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"> <div class="card-body p-6">
<h3 class="card-title text-xl font-black text-primary italic">NATIVE REACTION</h3> <h3 class="card-title text-xl font-black text-primary italic">REACTIVE BY DESIGN</h3>
<p class="text-sm opacity-70">Direct integration with SigPro signals. No wrappers, no context, just speed.</p> <p class="text-sm opacity-70">Every component is a high-order function optimized for SigPro's fine-grained updates.</p>
</div> </div>
</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 bg-base-200/30 border border-base-300 hover:border-base-content/20 transition-all p-1">
<div class="card-body p-6"> <div class="card-body p-6">
<h3 class="card-title text-xl font-black italic text-base-content">READY-TO-GO</h3> <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> </div>
</div> </div>
@@ -54,13 +54,13 @@
SigPro-UI isn't just a library; it's a **Functional Design System**. 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? | | Requirement | Value | Why? |
| :--- | :--- | :--- | | :--- | :--- | :--- |
| **Engine** | **SigPro** | Atomic reactivity without V-DOM. | | **Engine** | **SigPro** | Atomic reactivity without V-DOM. |
| **Styling** | **Tailwind CSS v4** | Pure CSS performance. | | **Components** | **SigPro-UI** | 60+ semantic, reactive components. |
| **Components** | **daisyUI v5** | Semantic and clean layouts. | | **Styling** | **daisyUI v5** | Beautiful, accessible, themeable. |
| **Learning Curve** | **Zero** | If you know JS and HTML, you know SigPro-UI. | | **Learning Curve** | **Zero** | If you know JS and HTML, you know SigPro-UI. |
### Semantic Functionalism ### Semantic Functionalism
@@ -73,43 +73,43 @@ Modal({
title: "Precision Engineering" title: "Precision Engineering"
}, () => }, () =>
Div({ class: "space-y-4" }, [ 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({ Button({
class: "btn-primary", class: "btn-primary",
onclick: () => isVisible(false) onclick: () => isVisible(false)
}, "Confirm") }, "Confirm")
]) ])
) )
```` ```
----- ---
## Technical Stack Requirements ## Technical Stack Requirements
To achieve the performance promised by SigPro-UI, your environment must be equipped with: 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. 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 ### 2. daisyUI v5
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
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. 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="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="grid grid-cols-1 lg:grid-cols-3 gap-8 items-center">
<div class="lg:col-span-2"> <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"> <p class="text-xl opacity-80 leading-relaxed">
Combine the best of three worlds: <strong>SigPro</strong> for logic, Combine the best of both worlds: <strong>SigPro</strong> for logic and
<strong>Tailwind v4</strong> for speed, and <strong>daisyUI v5</strong> for beauty. <strong>daisyUI v5</strong> for beauty. Build interfaces that feel as fast as they look,
Build interfaces that feel as fast as they look. with components that react instantly to your data changes.
</p> </p>
</div> </div>
</div> </div>

View File

@@ -7,48 +7,39 @@
* [Quick Reference](quick.md) * [Quick Reference](quick.md)
* **Forms & Inputs** * **Forms & Inputs**
* [Button](components/button.md)
* [Input](components/input.md)
* [Select](components/select.md)
* [Autocomplete](components/autocomplete.md) * [Autocomplete](components/autocomplete.md)
* [Datepicker](components/datepicker.md) * [Button](components/button.md)
* [Colorpicker](components/colorpicker.md)
* [Checkbox](components/checkbox.md) * [Checkbox](components/checkbox.md)
* [Colorpicker](components/colorpicker.md)
* [Datepicker](components/datepicker.md)
* [Input](components/input.md)
* [Radio](components/radio.md) * [Radio](components/radio.md)
* [Range](components/range.md) * [Range](components/range.md)
* [Rating](components/rating.md) * [Rating](components/rating.md)
* [Select](components/select.md)
* [Swap](components/swap.md) * [Swap](components/swap.md)
* **Data Display** * **Data Display**
* [Table](components/table.md)
* [List](components/list.md)
* [Badge](components/badge.md) * [Badge](components/badge.md)
* [Stat](components/stat.md)
* [Timeline](components/timeline.md)
* [Stack](components/stack.md)
* [Indicator](components/indicator.md) * [Indicator](components/indicator.md)
* [List](components/list.md)
* [Stack](components/stack.md)
* [Stat](components/stat.md)
* [Table](components/table.md)
* [Timeline](components/timeline.md)
* **Feedback & Overlays** * **Feedback & Overlays**
* [Alert](components/alert.md) * [Alert](components/alert.md)
* [Modal](components/modal.md) * [Modal](components/modal.md)
* [Toast](components/toast.md) * [Toast](components/toast.md)
* [Loading](components/loading.md)
* [Tooltip](components/tooltip.md) * [Tooltip](components/tooltip.md)
* **Navigation & Layout** * **Navigation & Layout**
* [Navbar](components/navbar.md)
* [Menu](components/menu.md)
* [Drawer](components/drawer.md)
* [Tabs](components/tabs.md)
* [Accordion](components/accordion.md) * [Accordion](components/accordion.md)
* [Drawer](components/drawer.md)
* [Dropdown](components/dropdown.md) * [Dropdown](components/dropdown.md)
* [Fieldset](components/fieldset.md)
* **Utilities**
* [Fab](components/fab.md) * [Fab](components/fab.md)
* [Toast](components/toast.md) * [Fieldset](components/fieldset.md)
* [Menu](components/menu.md)
* **Advanced** * [Navbar](components/navbar.md)
* [Reactivity Guide](advanced/reactivity.md) * [Tabs](components/tabs.md)
* [i18n Guide](advanced/i18n.md)
* [Theming](advanced/theming.md)

View File

@@ -0,0 +1,400 @@
# Accordion
Collapsible accordion component for organizing content into expandable sections.
## Tag
`Accordion`
## Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `title` | `string \| VNode \| Signal` | Required | Accordion section title |
| `open` | `boolean \| Signal<boolean>` | `false` | Whether the accordion is expanded |
| `name` | `string` | `-` | Group name for radio-style accordions (only one open at a time) |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `children` | `VNode \| Array<VNode>` | Required | Content to display when expanded |
## Styling
Accordion supports all **daisyUI Collapse classes**:
| Category | Keywords | Description |
| :--- | :--- | :--- |
| Type | `collapse-arrow`, `collapse-plus`, `collapse-minus` | Expand indicator style |
| Color | `collapse-primary`, `collapse-secondary`, `collapse-accent` | Color variants |
| Background | `bg-base-100`, `bg-base-200` | Background colors |
> For further details, check the [daisyUI Collapse Documentation](https://daisyui.com/components/collapse) Full reference for CSS classes.
## Live Examples
### Basic Accordion
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const open1 = $(false);
const open2 = $(false);
const open3 = $(false);
return Div({ class: 'flex flex-col gap-2' }, [
Accordion({
title: 'Section 1',
open: open1,
onclick: () => open1(!open1())
}, [
Div({ class: 'p-2' }, 'Content for section 1. This is a basic accordion section.')
]),
Accordion({
title: 'Section 2',
open: open2,
onclick: () => open2(!open2())
}, [
Div({ class: 'p-2' }, 'Content for section 2. You can put any content here.')
]),
Accordion({
title: 'Section 3',
open: open3,
onclick: () => open3(!open3())
}, [
Div({ class: 'p-2' }, 'Content for section 3. Accordions are great for FAQs.')
])
]);
};
$mount(BasicDemo, '#demo-basic');
```
### Group Accordion (Radio Style)
<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-group" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const GroupDemo = () => {
const openSection = $('section1');
return Div({ class: 'flex flex-col gap-2' }, [
Accordion({
title: 'Section 1',
name: 'group',
open: () => openSection() === 'section1',
onclick: () => openSection('section1')
}, [
Div({ class: 'p-2' }, 'Content for section 1. Only one section can be open at a time.')
]),
Accordion({
title: 'Section 2',
name: 'group',
open: () => openSection() === 'section2',
onclick: () => openSection('section2')
}, [
Div({ class: 'p-2' }, 'Content for section 2. Opening this will close section 1.')
]),
Accordion({
title: 'Section 3',
name: 'group',
open: () => openSection() === 'section3',
onclick: () => openSection('section3')
}, [
Div({ class: 'p-2' }, 'Content for section 3. This is useful for FAQ sections.')
])
]);
};
$mount(GroupDemo, '#demo-group');
```
### FAQ Accordion
<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-faq" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const FaqDemo = () => {
const openFaq = $('faq1');
const faqs = [
{ id: 'faq1', question: 'What is this component?', answer: 'This is an accordion component built with DaisyUI and Tailwind CSS for creating collapsible content sections.' },
{ id: 'faq2', question: 'How do I use it?', answer: 'Simply import the Accordion component and pass title and children props. Use the open prop to control expansion.' },
{ id: 'faq3', question: 'Can I have multiple open?', answer: 'Yes! By default, accordions can be opened independently. Use the name prop to create groups where only one can be open.' },
{ id: 'faq4', question: 'Is it accessible?', answer: 'Yes, the accordion uses proper ARIA attributes and keyboard navigation support.' }
];
return Div({ class: 'flex flex-col gap-2' }, faqs.map(faq =>
Accordion({
title: faq.question,
name: 'faq-group',
open: () => openFaq() === faq.id,
onclick: () => openFaq(openFaq() === faq.id ? '' : faq.id)
}, [
Div({ class: 'p-2 text-sm' }, faq.answer)
])
));
};
$mount(FaqDemo, '#demo-faq');
```
### With Rich Content
<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-rich" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const RichDemo = () => {
const open1 = $(true);
const open2 = $(false);
return Div({ class: 'flex flex-col gap-2' }, [
Accordion({
title: Span({ class: 'flex items-center gap-2' }, ['📊', 'Statistics']),
open: open1,
onclick: () => open1(!open1())
}, [
Div({ class: 'p-2' }, [
Div({ class: 'grid grid-cols-2 gap-4' }, [
Div({ class: 'stat bg-base-100 rounded-lg p-3' }, [
Div({ class: 'stat-title' }, 'Users'),
Div({ class: 'stat-value text-lg' }, '1,234')
]),
Div({ class: 'stat bg-base-100 rounded-lg p-3' }, [
Div({ class: 'stat-title' }, 'Revenue'),
Div({ class: 'stat-value text-lg' }, '$45,678')
]),
Div({ class: 'stat bg-base-100 rounded-lg p-3' }, [
Div({ class: 'stat-title' }, 'Growth'),
Div({ class: 'stat-value text-lg text-success' }, '+23%')
]),
Div({ class: 'stat bg-base-100 rounded-lg p-3' }, [
Div({ class: 'stat-title' }, 'Active'),
Div({ class: 'stat-value text-lg' }, '89%')
])
])
])
]),
Accordion({
title: Span({ class: 'flex items-center gap-2' }, ['👥', 'Team Members']),
open: open2,
onclick: () => open2(!open2())
}, [
Div({ class: 'p-2 space-y-2' }, [
Div({ class: 'flex items-center gap-3 p-2 hover:bg-base-100 rounded-lg' }, [
Div({ class: 'avatar placeholder' }, [
Div({ class: 'bg-primary text-primary-content rounded-full w-10 h-10 flex items-center justify-center' }, 'JD')
]),
Div({ class: 'flex-1' }, [
Div({ class: 'font-medium' }, 'John Doe'),
Div({ class: 'text-sm opacity-70' }, 'Developer')
])
]),
Div({ class: 'flex items-center gap-3 p-2 hover:bg-base-100 rounded-lg' }, [
Div({ class: 'avatar placeholder' }, [
Div({ class: 'bg-secondary text-secondary-content rounded-full w-10 h-10 flex items-center justify-center' }, 'JS')
]),
Div({ class: 'flex-1' }, [
Div({ class: 'font-medium' }, 'Jane Smith'),
Div({ class: 'text-sm opacity-70' }, 'Designer')
])
])
])
])
]);
};
$mount(RichDemo, '#demo-rich');
```
### Form Accordion
<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-form" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const FormAccordion = () => {
const openStep = $('step1');
const formData = $({
name: '',
email: '',
address: '',
payment: 'credit'
});
const updateField = (field, value) => {
formData({ ...formData(), [field]: value });
};
const nextStep = () => {
if (openStep() === 'step1') openStep('step2');
else if (openStep() === 'step2') openStep('step3');
};
const prevStep = () => {
if (openStep() === 'step2') openStep('step1');
else if (openStep() === 'step3') openStep('step2');
};
const handleSubmit = () => {
Toast('Form submitted!', 'alert-success', 2000);
console.log(formData());
};
return Div({ class: 'flex flex-col gap-2' }, [
Accordion({
title: Span({ class: 'flex items-center gap-2' }, ['1⃣', 'Personal Information']),
name: 'form-steps',
open: () => openStep() === 'step1',
onclick: () => openStep('step1')
}, [
Div({ class: 'p-4 space-y-4' }, [
Input({
label: 'Full Name',
value: () => formData().name,
placeholder: 'Enter your name',
oninput: (e) => updateField('name', e.target.value)
}),
Input({
label: 'Email',
type: 'email',
value: () => formData().email,
placeholder: 'email@example.com',
oninput: (e) => updateField('email', e.target.value)
}),
Div({ class: 'flex justify-end mt-2' }, [
Button({
class: 'btn btn-primary btn-sm',
onclick: nextStep,
disabled: () => !formData().name || !formData().email
}, 'Next →')
])
])
]),
Accordion({
title: Span({ class: 'flex items-center gap-2' }, ['2⃣', 'Address']),
name: 'form-steps',
open: () => openStep() === 'step2',
onclick: () => openStep('step2')
}, [
Div({ class: 'p-4 space-y-4' }, [
Input({
label: 'Address',
value: () => formData().address,
placeholder: 'Street address',
oninput: (e) => updateField('address', e.target.value)
}),
Div({ class: 'flex justify-between mt-2' }, [
Button({ class: 'btn btn-ghost btn-sm', onclick: prevStep }, '← Back'),
Button({
class: 'btn btn-primary btn-sm',
onclick: nextStep
}, 'Next →')
])
])
]),
Accordion({
title: Span({ class: 'flex items-center gap-2' }, ['3⃣', 'Payment']),
name: 'form-steps',
open: () => openStep() === 'step3',
onclick: () => openStep('step3')
}, [
Div({ class: 'p-4 space-y-4' }, [
Div({ class: 'flex flex-col gap-2' }, [
Radio({
label: 'Credit Card',
value: () => formData().payment,
radioValue: 'credit',
onclick: () => updateField('payment', 'credit')
}),
Radio({
label: 'PayPal',
value: () => formData().payment,
radioValue: 'paypal',
onclick: () => updateField('payment', 'paypal')
}),
Radio({
label: 'Bank Transfer',
value: () => formData().payment,
radioValue: 'bank',
onclick: () => updateField('payment', 'bank')
})
]),
Div({ class: 'flex justify-between mt-2' }, [
Button({ class: 'btn btn-ghost btn-sm', onclick: prevStep }, '← Back'),
Button({ class: 'btn btn-success btn-sm', onclick: handleSubmit }, 'Submit')
])
])
])
]);
};
$mount(FormAccordion, '#demo-form');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-6"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
const open1 = $(true);
const open2 = $(false);
const open3 = $(false);
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'text-sm font-bold' }, 'Default Accordion'),
Div({ class: 'flex flex-col gap-2' }, [
Accordion({ title: 'Default style', open: open1, onclick: () => open1(!open1()) }, [
Div({ class: 'p-2' }, 'Default accordion with standard styling.')
])
]),
Div({ class: 'text-sm font-bold mt-2' }, 'Custom Styling'),
Div({ class: 'flex flex-col gap-2' }, [
Accordion({
title: Span({ class: 'text-primary font-bold' }, 'Primary Title'),
open: open2,
onclick: () => open2(!open2()),
class: 'bg-primary/5 border-primary/20'
}, [
Div({ class: 'p-2' }, 'Accordion with custom styling and primary color.')
])
]),
Div({ class: 'text-sm font-bold mt-2' }, 'With Icons'),
Div({ class: 'flex flex-col gap-2' }, [
Accordion({
title: Span({ class: 'flex items-center gap-2' }, ['✨', 'Featured Content']),
open: open3,
onclick: () => open3(!open3())
}, [
Div({ class: 'p-2' }, 'Accordion with emoji icons in the title.')
])
])
]);
};
$mount(VariantsDemo, '#demo-variants');
```

314
docs/components/alert.md Normal file
View File

@@ -0,0 +1,314 @@
# Alert
Alert component for displaying contextual messages, notifications, and feedback with different severity levels. Supports soft and solid variants.
## Tag
`Alert`
## Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `type` | `string` | `'info'` | Alert type: `'info'`, `'success'`, `'warning'`, `'error'` |
| `soft` | `boolean \| Signal<boolean>` | `true` | Use soft variant (subtle background) |
| `actions` | `VNode \| function` | `-` | Optional action buttons or content |
| `message` | `string \| VNode \| Signal` | `-` | Alert message content |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `children` | `string \| VNode` | `-` | Alert content (alternative to `message`) |
## Styling
Alert supports all **daisyUI Alert classes**:
| Category | Keywords | Description |
| :--- | :--- | :--- |
| Color | `alert-info`, `alert-success`, `alert-warning`, `alert-error` | Alert type variants |
| Style | `alert-soft` (default), `alert-solid` | Visual style variants |
> For further details, check the [daisyUI Alert Documentation](https://daisyui.com/components/alert) Full reference for CSS classes.
### Example
```javascript
Alert({
type: "success",
soft: false,
class: "shadow-lg",
message: "Operation completed!"
});
// Applies: solid success alert with shadow
```
## Live Examples
### Basic Alerts
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-3"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
return Div({ class: 'flex flex-col gap-3' }, [
Alert({ type: 'info', message: 'This is an informational message.' }),
Alert({ type: 'success', message: 'Operation completed successfully!' }),
Alert({ type: 'warning', message: 'Please review your input before proceeding.' }),
Alert({ type: 'error', message: 'An error occurred while processing your request.' })
]);
};
$mount(BasicDemo, '#demo-basic');
```
### Soft vs Solid Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-3"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
return Div({ class: 'flex flex-col gap-3' }, [
Alert({ type: 'info', soft: true, message: 'Soft info alert (default)' }),
Alert({ type: 'info', soft: false, message: 'Solid info alert' }),
Alert({ type: 'success', soft: true, message: 'Soft success alert' }),
Alert({ type: 'success', soft: false, message: 'Solid success alert' })
]);
};
$mount(VariantsDemo, '#demo-variants');
```
### With Actions
<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-actions" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-3"></div>
</div>
</div>
```javascript
const ActionsDemo = () => {
const showUndo = $(false);
const deletedItem = $(null);
const deleteItem = (item) => {
deletedItem(item);
showUndo(true);
setTimeout(() => {
if (showUndo()) {
showUndo(false);
Toast('Item permanently deleted', 'alert-info', 2000);
}
}, 5000);
};
const undoDelete = () => {
showUndo(false);
Toast(`Restored: ${deletedItem()}`, 'alert-success', 2000);
};
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex gap-2' }, [
Button({ class: 'btn btn-sm', onclick: () => deleteItem('Document A') }, 'Delete Document A'),
Button({ class: 'btn btn-sm', onclick: () => deleteItem('Document B') }, 'Delete Document B')
]),
() => showUndo() ? Alert({
type: 'warning',
soft: true,
message: `Deleted: ${deletedItem()}`,
actions: Button({
class: 'btn btn-sm btn-primary',
onclick: undoDelete
}, 'Undo')
}) : null
]);
};
$mount(ActionsDemo, '#demo-actions');
```
### Dismissible Alert
<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-dismissible" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-3"></div>
</div>
</div>
```javascript
const DismissibleDemo = () => {
const visible = $(true);
return Div({ class: 'flex flex-col gap-3' }, [
() => visible() ? Alert({
type: 'info',
message: 'This alert can be dismissed. Click the X button to close.',
actions: Button({
class: 'btn btn-xs btn-circle btn-ghost',
onclick: () => visible(false)
}, '✕')
}) : null,
() => !visible() ? Button({
class: 'btn btn-sm btn-ghost',
onclick: () => visible(true)
}, 'Show Alert') : null
]);
};
$mount(DismissibleDemo, '#demo-dismissible');
```
### Reactive Alert
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ReactiveDemo = () => {
const email = $('');
const error = $('');
const validateEmail = (value) => {
email(value);
if (value && !value.includes('@')) {
error('Please enter a valid email address');
} else {
error('');
}
};
return Div({ class: 'flex flex-col gap-4' }, [
Input({
label: 'Email Address',
placeholder: 'Enter your email',
value: email,
oninput: (e) => validateEmail(e.target.value)
}),
() => error() ? Alert({ type: 'error', message: error() }) : null,
() => email() && !error() ? Alert({
type: 'success',
message: `Valid email: ${email()}`
}) : null
]);
};
$mount(ReactiveDemo, '#demo-reactive');
```
### Form Validation
<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-form" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const FormDemo = () => {
const name = $('');
const email = $('');
const submitted = $(false);
const errors = $({ name: '', email: '' });
const validate = () => {
const newErrors = {
name: name().trim() ? '' : 'Name is required',
email: email().trim() ? (email().includes('@') ? '' : 'Invalid email') : 'Email is required'
};
errors(newErrors);
return !newErrors.name && !newErrors.email;
};
const handleSubmit = () => {
if (validate()) {
submitted(true);
setTimeout(() => submitted(false), 3000);
Toast('Form submitted successfully!', 'alert-success', 2000);
}
};
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'text-lg font-bold' }, 'Contact Form'),
Input({
label: 'Name',
value: name,
error: () => errors().name,
oninput: (e) => {
name(e.target.value);
validate();
}
}),
Input({
label: 'Email',
value: email,
error: () => errors().email,
oninput: (e) => {
email(e.target.value);
validate();
}
}),
Button({ class: 'btn btn-primary', onclick: handleSubmit }, 'Submit'),
() => submitted() ? Alert({
type: 'success',
message: 'Thank you! We will contact you soon.'
}) : null,
() => (errors().name || errors().email) ? Alert({
type: 'error',
message: 'Please fix the errors above before submitting.'
}) : null
]);
};
$mount(FormDemo, '#demo-form');
```
### Icon Alerts
<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-icons" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-3"></div>
</div>
</div>
```javascript
const IconsDemo = () => {
return Div({ class: 'flex flex-col gap-3' }, [
Alert({ type: 'info', message: 'Information alert with custom icon' }),
Alert({ type: 'success', message: 'Success alert with custom icon' }),
Alert({ type: 'warning', message: 'Warning alert with custom icon' }),
Alert({ type: 'error', message: 'Error alert with custom icon' })
]);
};
$mount(IconsDemo, '#demo-icons');
```
### All Types
<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-all" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-3"></div>
</div>
</div>
```javascript
const AllTypesDemo = () => {
return Div({ class: 'flex flex-col gap-3' }, [
Alert({ type: 'info', message: ' This is an info alert' }),
Alert({ type: 'success', message: '✅ This is a success alert' }),
Alert({ type: 'warning', message: '⚠️ This is a warning alert' }),
Alert({ type: 'error', message: '❌ This is an error alert' })
]);
};
$mount(AllTypesDemo, '#demo-all');
```

View File

@@ -0,0 +1,212 @@
# Autocomplete
Searchable dropdown with autocomplete functionality, keyboard navigation, and reactive items.
## Tag
`Autocomplete`
## Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `class` | `string` | `''` | Additional CSS classes for the container |
| `items` | `Array<string \| {value: string, label: string}> \| Signal` | `[]` | Items to search from |
| `value` | `string \| Signal<string>` | `''` | Selected value (reactive) |
| `onSelect` | `function(item)` | `-` | Called when an option is selected |
| `label` | `string` | `-` | Label text for the input |
| `placeholder` | `string` | `'Search...'` | Placeholder text |
## Styling
Autocomplete wraps a **daisyUI Input component** internally. All Input styling classes work:
| Category | Keywords | Description |
| :--- | :--- | :--- |
| Color | `input-primary`, `input-secondary`, `input-accent`, `input-ghost`, `input-info`, `input-success`, `input-warning`, `input-error` | Input color variants |
| Size | `input-xs`, `input-sm`, `input-md`, `input-lg` | Input scale |
| Style | `input-bordered` (default), `input-ghost` | Visual style variants |
> For further details, check the [daisyUI Input Documentation](https://daisyui.com/components/input) Full reference for CSS classes.
## Live Examples
### Basic Autocomplete
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const selected = $('');
const fruits = ['Apple', 'Banana', 'Orange', 'Grape', 'Strawberry', 'Mango', 'Pineapple', 'Watermelon'];
return Autocomplete({
items: fruits,
value: selected,
onSelect: (value) => selected(value)
});
};
$mount(BasicDemo, '#demo-basic');
```
### With Objects
<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-objects" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const ObjectsDemo = () => {
const selected = $('');
const selectedLabel = $('');
const countries = [
{ value: 'mx', label: 'Mexico' },
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' },
{ value: 'br', label: 'Brazil' },
{ value: 'ar', label: 'Argentina' },
{ value: 'es', label: 'Spain' }
];
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Autocomplete({
items: countries,
value: selectedLabel,
onSelect: (item) => {
const selectedItem = typeof item === 'string'
? countries.find(c => c.label === item)
: item;
selected(selectedItem?.value || '');
selectedLabel(selectedItem?.label || '');
}
}),
Div({ class: 'alert alert-success' }, [
() => `Selected: ${selected()} - ${selectedLabel()}`
])
]);
};
$mount(ObjectsDemo, '#demo-objects');
```
### With Reactive Display
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const ReactiveDemo = () => {
const selected = $('');
const programmingLanguages = [
'JavaScript', 'Python', 'Java', 'C++', 'Ruby', 'Go', 'Rust', 'TypeScript', 'Swift', 'Kotlin'
];
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Autocomplete({
items: programmingLanguages,
value: selected,
onSelect: (value) => selected(value)
}),
() => selected() ? Div({ class: 'alert alert-info' }, [
`You selected: ${selected()}`
]) : null
]);
};
$mount(ReactiveDemo, '#demo-reactive');
```
### Dynamic Items
<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-dynamic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const DynamicDemo = () => {
const selected = $('');
const filterType = $('all');
const allItems = {
fruits: ['Apple', 'Banana', 'Orange', 'Mango'],
vegetables: ['Carrot', 'Broccoli', 'Spinach', 'Potato'],
all: ['Apple', 'Banana', 'Orange', 'Mango', 'Carrot', 'Broccoli', 'Spinach', 'Potato']
};
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Select({
items: [
{ value: 'all', label: 'All items' },
{ value: 'fruits', label: 'Fruits' },
{ value: 'vegetables', label: 'Vegetables' }
],
value: filterType,
onchange: (e) => filterType(e.target.value)
}),
Autocomplete({
items: () => allItems[filterType()],
value: selected,
onSelect: (value) => selected(value)
})
]);
};
$mount(DynamicDemo, '#demo-dynamic');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
const colors = ['Red', 'Blue', 'Green', 'Yellow', 'Purple', 'Orange', 'Pink', 'Brown', 'Black', 'White'];
return Div({ class: 'flex flex-col gap-4' }, [
Div({}, [
Autocomplete({
class: 'input-primary',
items: colors,
value: $(''),
placeholder: 'Search colors...'
})
]),
Div({}, [
Autocomplete({
class: 'input-secondary',
items: colors,
value: $(''),
placeholder: 'Search colors...'
})
]),
Div({}, [
Autocomplete({
class: 'input-ghost',
items: colors,
value: $(''),
placeholder: 'Search colors...'
})
])
]);
};
$mount(VariantsDemo, '#demo-variants');
```

323
docs/components/badge.md Normal file
View File

@@ -0,0 +1,323 @@
# Badge
Badge component for displaying counts, labels, and status indicators.
## Tag
`Badge`
## Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI badge variants) |
| `children` | `string \| VNode` | `-` | Badge content |
## Styling
Badge supports all **daisyUI Badge classes**:
| Category | Keywords | Description |
| :--- | :--- | :--- |
| Color | `badge-primary`, `badge-secondary`, `badge-accent`, `badge-info`, `badge-success`, `badge-warning`, `badge-error` | Visual color variants |
| Size | `badge-xs`, `badge-sm`, `badge-md`, `badge-lg` | Badge scale |
| Style | `badge-outline`, `badge-ghost`, `badge-dash` | Visual style variants |
> For further details, check the [daisyUI Badge Documentation](https://daisyui.com/components/badge) Full reference for CSS classes.
### Example
```javascript
Badge({ class: "badge-primary badge-lg" }, "New");
// Applies: primary color, large size
```
## Live Examples
### Basic Badge
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-2"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
return Div({ class: 'flex flex-wrap gap-2' }, [
Badge({}, 'Default'),
Badge({ class: 'badge-primary' }, 'Primary'),
Badge({ class: 'badge-secondary' }, 'Secondary'),
Badge({ class: 'badge-accent' }, 'Accent'),
Badge({ class: 'badge-info' }, 'Info'),
Badge({ class: 'badge-success' }, 'Success'),
Badge({ class: 'badge-warning' }, 'Warning'),
Badge({ class: 'badge-error' }, 'Error')
]);
};
$mount(BasicDemo, '#demo-basic');
```
### Badge Sizes
<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-sizes" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-2 items-center"></div>
</div>
</div>
```javascript
const SizesDemo = () => {
return Div({ class: 'flex flex-wrap gap-2 items-center' }, [
Badge({ class: 'badge-xs' }, 'Extra Small'),
Badge({ class: 'badge-sm' }, 'Small'),
Badge({}, 'Default'),
Badge({ class: 'badge-md' }, 'Medium'),
Badge({ class: 'badge-lg' }, 'Large')
]);
};
$mount(SizesDemo, '#demo-sizes');
```
### Outline Badges
<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-outline" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-2"></div>
</div>
</div>
```javascript
const OutlineDemo = () => {
return Div({ class: 'flex flex-wrap gap-2' }, [
Badge({ class: 'badge-outline' }, 'Default'),
Badge({ class: 'badge-outline badge-primary' }, 'Primary'),
Badge({ class: 'badge-outline badge-secondary' }, 'Secondary'),
Badge({ class: 'badge-outline badge-accent' }, 'Accent'),
Badge({ class: 'badge-outline badge-info' }, 'Info'),
Badge({ class: 'badge-outline badge-success' }, 'Success'),
Badge({ class: 'badge-outline badge-warning' }, 'Warning'),
Badge({ class: 'badge-outline badge-error' }, 'Error')
]);
};
$mount(OutlineDemo, '#demo-outline');
```
### Ghost Badges
<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-ghost" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-2"></div>
</div>
</div>
```javascript
const GhostDemo = () => {
return Div({ class: 'flex flex-wrap gap-2' }, [
Badge({ class: 'badge-ghost' }, 'Default'),
Badge({ class: 'badge-ghost badge-primary' }, 'Primary'),
Badge({ class: 'badge-ghost badge-secondary' }, 'Secondary'),
Badge({ class: 'badge-ghost badge-accent' }, 'Accent'),
Badge({ class: 'badge-ghost badge-info' }, 'Info'),
Badge({ class: 'badge-ghost badge-success' }, 'Success'),
Badge({ class: 'badge-ghost badge-warning' }, 'Warning'),
Badge({ class: 'badge-ghost badge-error' }, 'Error')
]);
};
$mount(GhostDemo, '#demo-ghost');
```
### With Icons
<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-icons" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-2"></div>
</div>
</div>
```javascript
const IconsDemo = () => {
return Div({ class: 'flex flex-wrap gap-2' }, [
Badge({ class: 'gap-1' }, [
Span({}, '✓'),
Span({}, 'Success')
]),
Badge({ class: 'gap-1 badge-warning' }, [
Span({}, '⚠'),
Span({}, 'Warning')
]),
Badge({ class: 'gap-1 badge-error' }, [
Span({}, '✗'),
Span({}, 'Error')
]),
Badge({ class: 'gap-1 badge-info' }, [
Span({}, ''),
Span({}, 'Info')
]),
Badge({ class: 'gap-1' }, [
Span({}, '★'),
Span({}, '4.5')
])
]);
};
$mount(IconsDemo, '#demo-icons');
```
### Status Badges
<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-status" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const StatusDemo = () => {
const statuses = [
{ label: 'Active', class: 'badge-success' },
{ label: 'Pending', class: 'badge-warning' },
{ label: 'Completed', class: 'badge-info' },
{ label: 'Failed', class: 'badge-error' },
{ label: 'Archived', class: 'badge-ghost' }
];
return Div({ class: 'flex flex-col gap-2' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Order Status'),
Div({ class: 'flex flex-wrap gap-2' }, statuses.map(status =>
Badge({ class: status.class }, status.label)
))
]);
};
$mount(StatusDemo, '#demo-status');
```
### Count Badges
<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-count" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-4 items-center"></div>
</div>
</div>
```javascript
const CountDemo = () => {
const notifications = $(3);
const messages = $(5);
const updates = $(0);
return Div({ class: 'flex flex-wrap gap-6' }, [
Div({ class: 'flex items-center gap-2' }, [
Span({}, 'Notifications'),
Badge({ class: 'badge-primary' }, () => notifications())
]),
Div({ class: 'flex items-center gap-2' }, [
Span({}, 'Messages'),
Badge({ class: 'badge-secondary' }, () => messages())
]),
Div({ class: 'flex items-center gap-2' }, [
Span({}, 'Updates'),
Badge({ class: 'badge-ghost' }, () => updates() || '0')
])
]);
};
$mount(CountDemo, '#demo-count');
```
### Interactive Badge
<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-interactive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const InteractiveDemo = () => {
const count = $(0);
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Div({ class: 'flex items-center gap-4' }, [
Button({ class: 'btn btn-sm', onclick: () => count(count() - 1) }, '-'),
Badge({ class: 'badge-primary text-lg min-w-[4rem] justify-center' }, () => count()),
Button({ class: 'btn btn-sm', onclick: () => count(count() + 1) }, '+')
]),
Button({
class: 'btn btn-ghost btn-sm',
onclick: () => count(0)
}, 'Reset')
]);
};
$mount(InteractiveDemo, '#demo-interactive');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
return Div({ class: 'flex flex-col gap-6' }, [
Div({ class: 'flex flex-wrap gap-2' }, [
Badge({ class: 'badge-xs' }, 'XS'),
Badge({ class: 'badge-sm' }, 'SM'),
Badge({}, 'MD'),
Badge({ class: 'badge-lg' }, 'LG')
]),
Div({ class: 'flex flex-wrap gap-2' }, [
Badge({ class: 'badge-primary badge-sm' }, 'Primary'),
Badge({ class: 'badge-secondary badge-sm' }, 'Secondary'),
Badge({ class: 'badge-accent badge-sm' }, 'Accent')
]),
Div({ class: 'flex flex-wrap gap-2' }, [
Badge({ class: 'badge-outline badge-primary' }, 'Outline'),
Badge({ class: 'badge-ghost badge-primary' }, 'Ghost')
])
]);
};
$mount(VariantsDemo, '#demo-variants');
```
### Inline with Text
<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-inline" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const InlineDemo = () => {
return Div({ class: 'space-y-2' }, [
Div({ class: 'text-sm' }, [
'Your order is ',
Badge({ class: 'badge-success badge-sm' }, 'Confirmed'),
' and will be shipped soon.'
]),
Div({ class: 'text-sm' }, [
'This feature is ',
Badge({ class: 'badge-warning badge-sm' }, 'Beta'),
' and may change.'
]),
Div({ class: 'text-sm' }, [
'Version ',
Badge({ class: 'badge-info badge-xs' }, 'v2.1.0'),
' released on March 2026'
])
]);
};
$mount(InlineDemo, '#demo-inline');
```

View File

@@ -1,221 +1,160 @@
# Button # Button
Styled button with full DaisyUI support and reactive states. ---
## Tag
`Button`
## Props ## Props
| Prop | Type | Default | Description | | Prop | Type | Description |
| :--- | :--- | :--- | :--- | | :--------- | :--------------------------- | :----------------------------------------------------- |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) | | `class` | `string` | Additional CSS classes |
| `disabled` | `boolean \| Signal<boolean>` | `false` | Disabled state | | `loading` | `boolean \| Signal<boolean>` | Shows a spinner and disables the button |
| `loading` | `boolean \| Signal<boolean>` | `false` | Shows loading spinner | | `disabled` | `boolean \| Signal<boolean>` | Disabled state |
| `badge` | `string \| Signal<string>` | `-` | Badge text displayed on corner | | `icon` | `string \| VNode \| Signal` | Icon displayed before the text |
| `badgeClass` | `string` | `'badge-secondary'` | Badge styling classes | | `onclick` | `function` | Click event handler |
| `tooltip` | `string \| Signal<string>` | `-` | Tooltip text on hover | | `type` | `string` | Native button type (`'button'`, `'submit'`, `'reset'`) |
| `icon` | `string \| VNode \| Signal` | `-` | Icon displayed before text |
| `onclick` | `function` | `-` | Click event handler |
| `type` | `string` | `'button'` | Native button type |
## Live Examples ---
## Classes (daisyUI)
| Category | Keywords | Description |
| :--- | :--- | :--- |
| Color | `btn-primary`, `btn-secondary`, `btn-accent`, `btn-ghost`, `btn-info`, `btn-success`, `btn-warning`, `btn-error` | Visual color variants |
| Size | `btn-xs`, `btn-sm`, `btn-md`, `btn-lg`, `btn-xl` | Button scale |
| Style | `btn-outline`, `btn-soft`, `btn-dash`, `btn-link` | Visual style variants |
| Shape | `btn-circle`, `btn-square`, `btn-wide`, `btn-block` | Button shape |
| State | `btn-active`, `btn-disabled` | Forced visual states |
> SigProUI supports styling via daisyUI independently or combined with the `ui` prop.
> For further details, check the [daisyUI Button Documentation](https://daisyui.com/components/button) Full reference for CSS classes.
### Example
```javascript
Button({ class: "btn-primary btn-lg btn-circle gap-4"}, "Click Me");
// Applies: primary color, large size, circular shape
// class is any css class from pure css or favorite framework
```
---
## Examples
### Basic Button ### Basic Button
<div id="demo-basic"></div> <div id="demo-basic" class="card bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></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") const BasicDemo = () => {
return Button({ class: "btn-primary" }, "Click Me");
};
$mount(BasicDemo, "#demo-basic");
``` ```
### With Loading State ### With Loading State
<div id="demo-loading"></div> <div id="demo-loading" class="card bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></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 LoadingDemo = () => {
const isSaving = $(false); const isSaving = $(false);
Button({ return Button(
{
class: "btn-success", class: "btn-success",
loading: isSaving, loading: isSaving,
onclick: async () => { onclick: async () => {
isSaving(true); isSaving(true);
await saveData(); await new Promise((resolve) => setTimeout(resolve, 2000));
isSaving(false); isSaving(false);
} },
}, "Save Changes") },
"Save Changes",
);
};
$mount(LoadingDemo, "#demo-loading");
``` ```
### With Badge ### With Icon
<div id="demo-badge"></div> <div id="demo-icon" class="card bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></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({ const IconDemo = () => {
class: "btn-outline", return Button(
badge: "3", {
badgeClass: "badge-accent" class: "btn-primary",
}, "Notifications") icon: "⭐",
},
"Favorite",
);
};
$mount(IconDemo, "#demo-icon");
```
### With Badge (using Indicator)
<div id="demo-badge" class="card bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
```javascript
const BadgeDemo = () => {
return Indicator(
{ value: "3", class: "badge-accent" },
Button({ class: "btn-outline" }, "Notifications"),
);
};
$mount(BadgeDemo, "#demo-badge");
``` ```
### With Tooltip ### With Tooltip
<div id="demo-tooltip"></div> <div id="demo-tooltip" class="card bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></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({ const TooltipDemo = () => {
class: "btn-ghost", return Tooltip({ tip: "Delete item" }, Button({ class: "btn-ghost" }, "Delete"));
tooltip: "Delete item" };
}, "Delete") $mount(TooltipDemo, "#demo-tooltip");
``` ```
### Disabled State ### Combined (Badge + Tooltip)
<div id="demo-disabled"></div> <div id="demo-combined" class="card bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></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 CombinedDemo = () => {
const count = $(0);
Button({ return Tooltip(
class: "btn-primary", { tip: () => `${count()} likes` },
disabled: isDisabled Indicator(
}, "Submit") { value: count, class: "badge-accent" },
Button(
{
class: "btn-primary btn-lg",
icon: "👍",
onclick: () => count(count() + 1),
},
"Like",
)
),
);
};
$mount(CombinedDemo, "#demo-combined");
``` ```
### All Variants ### All Color Variants
<div id="demo-variants"></div> <div id="demo-variants" class="card bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
<script type="module"> ```javascript
import { $, $mount } from 'https://cdn.jsdelivr.net/npm/sigpro@latest/+esm'; const VariantsDemo = () => {
import { Button, Div } from 'https://cdn.jsdelivr.net/npm/sigpro-ui@latest/+esm'; return Div({ class: "flex flex-wrap gap-2 justify-center" }, [
const isLoading = $(false);
const Demo = () => {
return Div({ class: "flex flex-wrap gap-2" }, [
Button({ class: "btn-primary" }, "Primary"), Button({ class: "btn-primary" }, "Primary"),
Button({ class: "btn-secondary" }, "Secondary"), Button({ class: "btn-secondary" }, "Secondary"),
Button({ class: "btn-accent" }, "Accent"), Button({ class: "btn-accent" }, "Accent"),
Button({ class: "btn-ghost" }, "Ghost"), Button({ class: "btn-ghost" }, "Ghost"),
Button({ class: "btn-outline" }, "Outline"), Button({ class: "btn-outline" }, "Outline"),
Button({ class: "btn-success", loading: isLoading, onclick: () => {
isLoading(true);
setTimeout(() => isLoading(false), 1500);
} }, "Loading")
]); ]);
}; };
$mount(VariantsDemo, "#demo-variants");
$mount(Demo, "#demo-variants");
</script>
```javascript
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")
])
``` ```
```
## 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;
}

290
docs/components/checkbox.md Normal file
View File

@@ -0,0 +1,290 @@
# Checkbox
Toggle checkbox component with label, tooltip support, and reactive state management.
## Tag
`Checkbox`
## Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `label` | `string` | `-` | Label text for the checkbox |
| `value` | `boolean \| Signal<boolean>` | `false` | Checked state |
| `tooltip` | `string` | `-` | Tooltip text on hover |
| `toggle` | `boolean` | `false` | Display as toggle switch instead of checkbox |
| `disabled` | `boolean \| Signal<boolean>` | `false` | Disabled state |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `onclick` | `function` | `-` | Click event handler |
## Styling
Checkbox supports all **daisyUI Checkbox and Toggle classes**:
| Category | Keywords | Description |
| :--- | :--- | :--- |
| Color (Checkbox) | `checkbox-primary`, `checkbox-secondary`, `checkbox-accent`, `checkbox-info`, `checkbox-success`, `checkbox-warning`, `checkbox-error` | Checkbox color variants |
| Size (Checkbox) | `checkbox-xs`, `checkbox-sm`, `checkbox-md`, `checkbox-lg` | Checkbox scale |
| Color (Toggle) | `toggle-primary`, `toggle-secondary`, `toggle-accent`, `toggle-info`, `toggle-success`, `toggle-warning`, `toggle-error` | Toggle color variants |
| Size (Toggle) | `toggle-xs`, `toggle-sm`, `toggle-md`, `toggle-lg` | Toggle scale |
> For further details, check the [daisyUI Checkbox Documentation](https://daisyui.com/components/checkbox) and [daisyUI Toggle Documentation](https://daisyui.com/components/toggle) Full reference for CSS classes.
### Example
```javascript
// Checkbox
Checkbox({ class: "checkbox-primary checkbox-lg", label: "Accept terms" });
// Toggle switch
Checkbox({ toggle: true, class: "toggle-success", label: "Enable feature" });
```
## Live Examples
### Basic Checkbox
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const accepted = $(false);
return Checkbox({
label: 'I accept the terms and conditions',
value: accepted,
onclick: () => accepted(!accepted())
});
};
$mount(BasicDemo, '#demo-basic');
```
### Toggle Switch
<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-toggle" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const ToggleDemo = () => {
const enabled = $(false);
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Checkbox({
label: 'Enable notifications',
value: enabled,
toggle: true,
onclick: () => enabled(!enabled())
}),
() => enabled()
? Div({ class: 'alert alert-success' }, 'Notifications are ON')
: Div({ class: 'alert alert-soft' }, 'Notifications are OFF')
]);
};
$mount(ToggleDemo, '#demo-toggle');
```
### With Tooltip
<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-tooltip" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const TooltipDemo = () => {
const darkMode = $(false);
return Checkbox({
label: 'Dark mode',
value: darkMode,
tooltip: 'Enable dark theme preference',
onclick: () => darkMode(!darkMode())
});
};
$mount(TooltipDemo, '#demo-tooltip');
```
### Disabled State
<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-disabled" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const DisabledDemo = () => {
return Div({ class: 'flex flex-col gap-3' }, [
Checkbox({
label: 'Checked and disabled',
value: true,
disabled: true
}),
Checkbox({
label: 'Unchecked and disabled',
value: false,
disabled: true
})
]);
};
$mount(DisabledDemo, '#demo-disabled');
```
### Reactive Multiple Selection
<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-multiple" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const MultipleDemo = () => {
const options = [
{ id: 1, label: 'Option 1', selected: $(false) },
{ id: 2, label: 'Option 2', selected: $(false) },
{ id: 3, label: 'Option 3', selected: $(false) }
];
const selectAll = $(false);
const toggleAll = (value) => {
selectAll(value);
options.forEach(opt => opt.selected(value));
};
const updateSelectAll = () => {
const allSelected = options.every(opt => opt.selected());
selectAll(allSelected);
};
return Div({ class: 'flex flex-col gap-3' }, [
Checkbox({
label: 'Select all',
value: selectAll,
onclick: () => toggleAll(!selectAll())
}),
Div({ class: 'divider my-1' }),
...options.map(opt => Checkbox({
label: opt.label,
value: opt.selected,
onclick: () => {
opt.selected(!opt.selected());
updateSelectAll();
}
})),
Div({ class: 'mt-2 text-sm opacity-70' }, () => {
const count = options.filter(opt => opt.selected()).length;
return `${count} of ${options.length} selected`;
})
]);
};
$mount(MultipleDemo, '#demo-multiple');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
const variant1 = $(true);
const variant2 = $(false);
const variant3 = $(true);
return Div({ class: 'flex flex-col gap-3' }, [
Div({ class: 'flex items-center gap-4' }, [
Checkbox({
label: 'Primary',
value: variant1,
class: 'checkbox-primary',
onclick: () => variant1(!variant1())
}),
Checkbox({
label: 'Secondary',
value: variant2,
class: 'checkbox-secondary',
onclick: () => variant2(!variant2())
})
]),
Div({ class: 'flex items-center gap-4' }, [
Checkbox({
label: 'Accent',
value: variant3,
class: 'checkbox-accent',
onclick: () => variant3(!variant3())
}),
Checkbox({
label: 'Toggle switch',
value: $(false),
toggle: true,
class: 'toggle-primary'
})
])
]);
};
$mount(VariantsDemo, '#demo-variants');
```
### Form Example
<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-form" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const FormDemo = () => {
const subscribe = $(false);
const weekly = $(false);
const monthly = $(true);
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'text-sm font-bold' }, 'Newsletter preferences'),
Checkbox({
label: 'Subscribe to newsletter',
value: subscribe,
onclick: () => subscribe(!subscribe())
}),
() => subscribe() ? Div({ class: 'ml-6 flex flex-col gap-2' }, [
Checkbox({
label: 'Weekly updates',
value: weekly,
onclick: () => weekly(!weekly())
}),
Checkbox({
label: 'Monthly digest',
value: monthly,
onclick: () => monthly(!monthly())
})
]) : null,
() => subscribe() && (weekly() || monthly())
? Div({ class: 'alert alert-success text-sm mt-2' }, 'You will receive updates!')
: subscribe()
? Div({ class: 'alert alert-warning text-sm mt-2' }, 'Select at least one frequency')
: null
]);
};
$mount(FormDemo, '#demo-form');
```

View File

@@ -0,0 +1,225 @@
# Colorpicker
Color picker component with preset color palette, reactive value binding, and customizable appearance.
## Tag
`Colorpicker`
## Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `label` | `string` | `-` | Label text for the button |
| `value` | `string \| Signal<string>` | `'#000000'` | Selected color value (hex format) |
| `class` | `string` | `''` | Additional CSS classes (Tailwind + custom styling) |
## Styling
Colorpicker is a custom component built with Tailwind CSS. The button supports standard **daisyUI Button classes** for consistent styling:
| Category | Keywords | Description |
| :--- | :--- | :--- |
| Size | `btn-xs`, `btn-sm`, `btn-md`, `btn-lg` | Button scale |
| Style | `btn-outline`, `btn-soft`, `btn-ghost` | Visual style variants |
> For further details, check the [daisyUI Button Documentation](https://daisyui.com/components/button) The color picker button accepts standard button styling classes.
## Live Examples
### Basic Colorpicker
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const color = $('#3b82f6');
return Colorpicker({
label: 'Pick a color',
value: color
});
};
$mount(BasicDemo, '#demo-basic');
```
### With Reactive Preview
<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-preview" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const PreviewDemo = () => {
const color = $('#10b981');
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Colorpicker({
label: 'Choose color',
value: color
}),
Div({
class: 'w-full h-20 rounded-lg shadow-inner transition-all duration-200 flex items-center justify-center',
style: () => `background-color: ${color()}`
}, [
Div({ class: 'text-center font-mono text-sm bg-black/20 px-2 py-1 rounded' }, () => color())
])
]);
};
$mount(PreviewDemo, '#demo-preview');
```
### Color Palette Grid
<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-palette" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const PaletteDemo = () => {
const selectedColor = $('#ef4444');
const presets = [
'#ef4444', '#f97316', '#f59e0b', '#eab308',
'#84cc16', '#10b981', '#14b8a6', '#06b6d4',
'#3b82f6', '#6366f1', '#8b5cf6', '#d946ef',
'#ec489a', '#f43f5e', '#6b7280', '#1f2937'
];
return Div({ class: 'flex flex-col gap-4' }, [
Colorpicker({
label: 'Custom color',
value: selectedColor
}),
Div({ class: 'divider text-xs' }, 'Or choose from palette'),
Div({ class: 'grid grid-cols-8 gap-2' }, presets.map(color =>
Button({
class: `w-8 h-8 rounded-lg shadow-sm transition-transform hover:scale-110`,
style: `background-color: ${color}`,
onclick: () => selectedColor(color)
})
)),
Div({ class: 'mt-2 text-center text-sm font-mono' }, () => selectedColor())
]);
};
$mount(PaletteDemo, '#demo-palette');
```
### With Text Color Preview
<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-text" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const TextDemo = () => {
const bgColor = $('#1e293b');
const textColor = $('#f8fafc');
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Div({ class: 'flex gap-4' }, [
Colorpicker({
label: 'Background',
value: bgColor
}),
Colorpicker({
label: 'Text',
value: textColor
})
]),
Div({
class: 'p-6 rounded-lg text-center font-bold transition-all duration-200',
style: () => `background-color: ${bgColor()}; color: ${textColor()}`
}, [
'Preview text with your colors'
])
]);
};
$mount(TextDemo, '#demo-text');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-4"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
return Div({ class: 'flex flex-wrap gap-4 items-center' }, [
Colorpicker({
label: 'Primary',
value: $('#3b82f6')
}),
Colorpicker({
label: 'Success',
value: $('#10b981')
}),
Colorpicker({
label: 'Warning',
value: $('#f59e0b')
}),
Colorpicker({
label: 'Error',
value: $('#ef4444')
})
]);
};
$mount(VariantsDemo, '#demo-variants');
```
### Dynamic Color Swatch
<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-dynamic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const DynamicDemo = () => {
const primary = $('#3b82f6');
const secondary = $('#ef4444');
const accent = $('#10b981');
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Div({ class: 'flex flex-wrap gap-4' }, [
Colorpicker({ label: 'Primary', value: primary }),
Colorpicker({ label: 'Secondary', value: secondary }),
Colorpicker({ label: 'Accent', value: accent })
]),
Div({ class: 'grid grid-cols-3 gap-2 mt-2' }, [
Div({
class: 'h-12 rounded-lg shadow-sm flex items-center justify-center text-xs font-bold text-white',
style: () => `background-color: ${primary()}`
}, 'Primary'),
Div({
class: 'h-12 rounded-lg shadow-sm flex items-center justify-center text-xs font-bold text-white',
style: () => `background-color: ${secondary()}`
}, 'Secondary'),
Div({
class: 'h-12 rounded-lg shadow-sm flex items-center justify-center text-xs font-bold text-white',
style: () => `background-color: ${accent()}`
}, 'Accent')
])
]);
};
$mount(DynamicDemo, '#demo-dynamic');
```

View File

@@ -0,0 +1,205 @@
# Datepicker
Date and date range picker component with calendar interface, time selection, and reactive state management.
## Tag
`Datepicker`
## Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `label` | `string` | `-` | Label text for the input |
| `value` | `string \| {start: string, end: string} \| Signal` | `-` | Selected date or range |
| `range` | `boolean` | `false` | Enable date range selection mode |
| `hour` | `boolean` | `false` | Enable hour selection |
| `placeholder` | `string` | `'Select date...'` | Placeholder text |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
## Styling
Datepicker wraps a **daisyUI Input component** internally. All Input styling classes work:
| Category | Keywords | Description |
| :--- | :--- | :--- |
| Color | `input-primary`, `input-secondary`, `input-accent`, `input-ghost`, `input-info`, `input-success`, `input-warning`, `input-error` | Input color variants |
| Size | `input-xs`, `input-sm`, `input-md`, `input-lg` | Input scale |
| Style | `input-bordered` (default), `input-ghost` | Visual style variants |
> For further details, check the [daisyUI Input Documentation](https://daisyui.com/components/input) Full reference for CSS classes.
## Live Examples
### Basic Datepicker
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const date = $('');
return Datepicker({
label: 'Select date',
value: date,
placeholder: 'Choose a date...'
});
};
$mount(BasicDemo, '#demo-basic');
```
### Date Range Picker
<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-range" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const RangeDemo = () => {
const range = $({ start: '', end: '' });
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Datepicker({
label: 'Date range',
value: range,
range: true,
placeholder: 'Select start and end date...'
}),
() => range().start && range().end ? Div({ class: 'alert alert-success' }, [
`Selected: ${range().start}${range().end}`
]) : null
]);
};
$mount(RangeDemo, '#demo-range');
```
### With Time Selection
<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-time" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const TimeDemo = () => {
const datetime = $('');
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Datepicker({
label: 'Select date and time',
value: datetime,
hour: true,
placeholder: 'Choose date and time...'
}),
() => datetime() ? Div({ class: 'alert alert-info' }, [
`Selected: ${datetime()}`
]) : null
]);
};
$mount(TimeDemo, '#demo-time');
```
### Range with Time
<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-range-time" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const RangeTimeDemo = () => {
const range = $({ start: '', end: '', startHour: 9, endHour: 17 });
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Datepicker({
label: 'Schedule range',
value: range,
range: true,
hour: true,
placeholder: 'Select date and time range...'
}),
() => range().start && range().end ? Div({ class: 'alert alert-primary' }, [
`From ${range().start} ${range().startHour || 9}:00 to ${range().end} ${range().endHour || 17}:00`
]) : null
]);
};
$mount(RangeTimeDemo, '#demo-range-time');
```
### Reactive Display
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const ReactiveDemo = () => {
const date = $('');
const today = new Date().toISOString().split('T')[0];
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Datepicker({
label: 'Select date',
value: date,
placeholder: 'Choose a date...'
}),
Div({ class: 'stats shadow' }, [
Div({ class: 'stat' }, [
Div({ class: 'stat-title' }, 'Selected date'),
Div({ class: 'stat-value text-sm' }, () => date() || 'Not selected'),
Div({ class: 'stat-desc' }, () => date() === today ? 'Today' : '')
])
])
]);
};
$mount(ReactiveDemo, '#demo-reactive');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
return Div({ class: 'flex flex-col gap-4' }, [
Datepicker({
label: 'Single date',
value: $('2024-12-25'),
placeholder: 'Select date...'
}),
Datepicker({
label: 'Date range',
range: true,
value: $({ start: '2024-12-01', end: '2024-12-31' }),
placeholder: 'Select range...'
}),
Datepicker({
label: 'With time',
hour: true,
value: $('2024-12-25T14:00:00'),
placeholder: 'Select date and time...'
})
]);
};
$mount(VariantsDemo, '#demo-variants');
```

493
docs/components/drawer.md Normal file
View File

@@ -0,0 +1,493 @@
# Drawer
Drawer component for creating off-canvas side panels with overlay and toggle functionality.
## Tag
`Drawer`
## Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `id` | `string` | Required | Unique identifier for the drawer |
| `open` | `boolean \| Signal<boolean>` | `false` | Drawer open state |
| `side` | `VNode` | Required | Content to display in the drawer panel |
| `content` | `VNode` | Required | Main page content |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
## Styling
Drawer supports all **daisyUI Drawer classes**:
| Category | Keywords | Description |
| :--- | :--- | :--- |
| Position | `drawer-end` | Drawer opens from the right side |
| Width | `w-64`, `w-80`, `w-96` | Custom drawer width (Tailwind) |
> For further details, check the [daisyUI Drawer Documentation](https://daisyui.com/components/drawer) Full reference for CSS classes.
## Live Examples
### Basic Drawer
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const isOpen = $(false);
return Drawer({
id: 'basic-drawer',
open: isOpen,
side: Div({ class: 'p-4' }, [
Div({ class: 'text-lg font-bold mb-4' }, 'Menu'),
Div({ class: 'flex flex-col gap-2' }, [
Button({ class: 'btn btn-ghost justify-start' }, 'Home'),
Button({ class: 'btn btn-ghost justify-start' }, 'About'),
Button({ class: 'btn btn-ghost justify-start' }, 'Contact')
])
]),
content: Div({ class: 'p-4 text-center' }, [
Button({
class: 'btn btn-primary',
onclick: () => isOpen(true)
}, 'Open Drawer')
])
});
};
$mount(BasicDemo, '#demo-basic');
```
### Navigation Drawer
<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-nav" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const NavDrawer = () => {
const isOpen = $(false);
const activePage = $('home');
const pages = {
home: 'Welcome to the Home Page!',
about: 'About Us - Learn more about our company',
services: 'Our Services - What we offer',
contact: 'Contact Us - Get in touch'
};
return Drawer({
id: 'nav-drawer',
open: isOpen,
side: Div({ class: 'p-4 w-64' }, [
Div({ class: 'text-xl font-bold mb-6' }, 'MyApp'),
Div({ class: 'flex flex-col gap-1' }, [
Button({
class: `btn btn-ghost justify-start ${activePage() === 'home' ? 'btn-active' : ''}`,
onclick: () => {
activePage('home');
isOpen(false);
}
}, '🏠 Home'),
Button({
class: `btn btn-ghost justify-start ${activePage() === 'about' ? 'btn-active' : ''}`,
onclick: () => {
activePage('about');
isOpen(false);
}
}, ' About'),
Button({
class: `btn btn-ghost justify-start ${activePage() === 'services' ? 'btn-active' : ''}`,
onclick: () => {
activePage('services');
isOpen(false);
}
}, '⚙️ Services'),
Button({
class: `btn btn-ghost justify-start ${activePage() === 'contact' ? 'btn-active' : ''}`,
onclick: () => {
activePage('contact');
isOpen(false);
}
}, '📧 Contact')
])
]),
content: Div({ class: 'p-4' }, [
Div({ class: 'flex justify-between items-center mb-4' }, [
Button({
class: 'btn btn-ghost btn-circle',
onclick: () => isOpen(true)
}, '☰'),
Span({ class: 'text-lg font-bold' }, 'MyApp')
]),
Div({ class: 'card bg-base-200 shadow-lg' }, [
Div({ class: 'card-body' }, [
Div({ class: 'text-2xl font-bold mb-2' }, () => activePage().charAt(0).toUpperCase() + activePage().slice(1)),
Div({ class: 'text-lg' }, () => pages[activePage()])
])
])
])
});
};
$mount(NavDrawer, '#demo-nav');
```
### Settings Drawer
<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-settings" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const SettingsDrawer = () => {
const isOpen = $(false);
const darkMode = $(false);
const notifications = $(true);
const autoSave = $(false);
return Drawer({
id: 'settings-drawer',
open: isOpen,
side: Div({ class: 'p-4 w-80' }, [
Div({ class: 'flex justify-between items-center mb-6' }, [
Span({ class: 'text-xl font-bold' }, 'Settings'),
Button({
class: 'btn btn-ghost btn-circle btn-sm',
onclick: () => isOpen(false)
}, '✕')
]),
Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex justify-between items-center' }, [
Span({}, 'Dark Mode'),
Swap({
value: darkMode,
on: "🌙",
off: "☀️",
onclick: () => darkMode(!darkMode())
})
]),
Div({ class: 'flex justify-between items-center' }, [
Span({}, 'Notifications'),
Swap({
value: notifications,
on: "🔔",
off: "🔕",
onclick: () => notifications(!notifications())
})
]),
Div({ class: 'flex justify-between items-center' }, [
Span({}, 'Auto Save'),
Swap({
value: autoSave,
on: "✅",
off: "⭕",
onclick: () => autoSave(!autoSave())
})
])
]),
Div({ class: 'divider my-4' }),
Div({ class: 'flex gap-2' }, [
Button({
class: 'btn btn-primary flex-1',
onclick: () => {
isOpen(false);
Toast('Settings saved!', 'alert-success', 2000);
}
}, 'Save'),
Button({
class: 'btn btn-ghost flex-1',
onclick: () => isOpen(false)
}, 'Cancel')
])
]),
content: Div({ class: 'p-4' }, [
Div({ class: 'flex justify-between items-center' }, [
Span({ class: 'text-lg font-bold' }, 'Dashboard'),
Button({
class: 'btn btn-ghost btn-circle',
onclick: () => isOpen(true)
}, '⚙️')
]),
Div({ class: 'mt-4 grid grid-cols-2 gap-4' }, [
Div({ class: 'stat bg-base-200 rounded-lg p-4' }, [
Div({ class: 'stat-title' }, 'Users'),
Div({ class: 'stat-value' }, '1,234')
]),
Div({ class: 'stat bg-base-200 rounded-lg p-4' }, [
Div({ class: 'stat-title' }, 'Revenue'),
Div({ class: 'stat-value' }, '$45K')
])
])
])
});
};
$mount(SettingsDrawer, '#demo-settings');
```
### Cart Drawer
<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-cart" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const CartDrawer = () => {
const isOpen = $(false);
const cart = $([
{ id: 1, name: 'Product 1', price: 29, quantity: 2 },
{ id: 2, name: 'Product 2', price: 49, quantity: 1 }
]);
const updateQuantity = (id, delta) => {
cart(cart().map(item => {
if (item.id === id) {
const newQty = Math.max(0, item.quantity + delta);
return newQty === 0 ? null : { ...item, quantity: newQty };
}
return item;
}).filter(Boolean));
};
const total = () => cart().reduce((sum, item) => sum + (item.price * item.quantity), 0);
return Drawer({
id: 'cart-drawer',
open: isOpen,
side: Div({ class: 'flex flex-col h-full' }, [
Div({ class: 'p-4 border-b border-base-300' }, [
Div({ class: 'flex justify-between items-center' }, [
Span({ class: 'text-xl font-bold' }, `Cart (${cart().length} items)`),
Button({
class: 'btn btn-ghost btn-circle btn-sm',
onclick: () => isOpen(false)
}, '✕')
])
]),
Div({ class: 'flex-1 overflow-y-auto p-4' }, cart().length === 0
? Div({ class: 'text-center text-gray-500 mt-8' }, 'Your cart is empty')
: Div({ class: 'flex flex-col gap-3' }, cart().map(item =>
Div({ class: 'flex gap-3 items-center p-2 bg-base-200 rounded-lg' }, [
Div({ class: 'flex-1' }, [
Div({ class: 'font-medium' }, item.name),
Div({ class: 'text-sm' }, `$${item.price} each`)
]),
Div({ class: 'flex items-center gap-2' }, [
Button({
class: 'btn btn-xs btn-circle',
onclick: () => updateQuantity(item.id, -1)
}, '-'),
Span({ class: 'w-8 text-center' }, item.quantity),
Button({
class: 'btn btn-xs btn-circle',
onclick: () => updateQuantity(item.id, 1)
}, '+')
]),
Span({ class: 'font-bold w-16 text-right' }, `$${item.price * item.quantity}`)
])
))
),
Div({ class: 'p-4 border-t border-base-300' }, [
Div({ class: 'flex justify-between items-center mb-4' }, [
Span({ class: 'font-bold' }, 'Total'),
Span({ class: 'text-xl font-bold' }, () => `$${total()}`)
]),
Button({
class: 'btn btn-primary w-full',
onclick: () => {
isOpen(false);
Toast('Checkout initiated!', 'alert-success', 2000);
},
disabled: () => cart().length === 0
}, 'Checkout')
])
]),
content: Div({ class: 'p-4' }, [
Div({ class: 'flex justify-between items-center' }, [
Span({ class: 'text-lg font-bold' }, 'Store'),
Button({
class: 'btn btn-primary',
onclick: () => isOpen(true)
}, () => `🛒 Cart (${cart().length})`)
]),
Div({ class: 'mt-4 grid grid-cols-2 gap-4' }, [
Button({
class: 'btn btn-outline h-32 flex flex-col',
onclick: () => {
cart([...cart(), { id: Date.now(), name: 'New Product', price: 39, quantity: 1 }]);
Toast('Added to cart!', 'alert-success', 1500);
}
}, ['📦', 'Add to Cart'])
])
])
});
};
$mount(CartDrawer, '#demo-cart');
```
### Responsive Drawer
<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-responsive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ResponsiveDrawer = () => {
const isOpen = $(false);
const activePage = $('home');
const MenuItems = () => Div({ class: 'flex flex-col gap-1 p-4' }, [
Button({
class: `btn btn-ghost justify-start ${activePage() === 'home' ? 'btn-active' : ''}`,
onclick: () => {
activePage('home');
if (window.innerWidth < 1024) isOpen(false);
}
}, '🏠 Home'),
Button({
class: `btn btn-ghost justify-start ${activePage() === 'analytics' ? 'btn-active' : ''}`,
onclick: () => {
activePage('analytics');
if (window.innerWidth < 1024) isOpen(false);
}
}, '📊 Analytics'),
Button({
class: `btn btn-ghost justify-start ${activePage() === 'settings' ? 'btn-active' : ''}`,
onclick: () => {
activePage('settings');
if (window.innerWidth < 1024) isOpen(false);
}
}, '⚙️ Settings')
]);
return Drawer({
id: 'responsive-drawer',
open: isOpen,
side: Div({ class: 'w-64' }, [
Div({ class: 'text-xl font-bold p-4 border-b border-base-300' }, 'Menu'),
MenuItems()
]),
content: Div({ class: 'flex' }, [
Div({ class: 'hidden lg:block w-64 border-r border-base-300' }, [MenuItems()]),
Div({ class: 'flex-1 p-4' }, [
Div({ class: 'flex justify-between items-center lg:hidden mb-4' }, [
Button({
class: 'btn btn-ghost btn-circle',
onclick: () => isOpen(true)
}, '☰'),
Span({ class: 'text-lg font-bold' }, 'MyApp')
]),
Div({ class: 'card bg-base-200' }, [
Div({ class: 'card-body' }, [
Div({ class: 'text-2xl font-bold' }, () => activePage().charAt(0).toUpperCase() + activePage().slice(1)),
Div({}, 'Content area. On desktop, the menu is always visible on the left.')
])
])
])
])
});
};
$mount(ResponsiveDrawer, '#demo-responsive');
```
### Form Drawer
<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-form" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const FormDrawer = () => {
const isOpen = $(false);
const name = $('');
const email = $('');
const message = $('');
const handleSubmit = () => {
if (name() && email() && message()) {
Toast('Message sent!', 'alert-success', 2000);
isOpen(false);
name('');
email('');
message('');
} else {
Toast('Please fill all fields', 'alert-warning', 2000);
}
};
return Drawer({
id: 'form-drawer',
open: isOpen,
side: Div({ class: 'p-4 w-96' }, [
Div({ class: 'flex justify-between items-center mb-4' }, [
Span({ class: 'text-xl font-bold' }, 'Contact Us'),
Button({
class: 'btn btn-ghost btn-circle btn-sm',
onclick: () => isOpen(false)
}, '✕')
]),
Div({ class: 'flex flex-col gap-4' }, [
Input({
label: 'Name',
value: name,
placeholder: 'Your name',
oninput: (e) => name(e.target.value)
}),
Input({
label: 'Email',
type: 'email',
value: email,
placeholder: 'your@email.com',
oninput: (e) => email(e.target.value)
}),
Div({ class: 'form-control' }, [
Span({ class: 'label-text mb-1' }, 'Message'),
$html('textarea', {
class: 'textarea textarea-bordered h-24',
placeholder: 'Your message',
value: message,
oninput: (e) => message(e.target.value)
})
]),
Div({ class: 'flex gap-2 mt-2' }, [
Button({
class: 'btn btn-primary flex-1',
onclick: handleSubmit
}, 'Send'),
Button({
class: 'btn btn-ghost flex-1',
onclick: () => isOpen(false)
}, 'Cancel')
])
])
]),
content: Div({ class: 'p-4 text-center' }, [
Button({
class: 'btn btn-primary',
onclick: () => isOpen(true)
}, 'Contact Us')
])
});
};
$mount(FormDrawer, '#demo-form');
```

255
docs/components/dropdown.md Normal file
View File

@@ -0,0 +1,255 @@
# Dropdown
Dropdown component for creating menus that appear when triggered. Uses DaisyUI's native details/summary pattern.
## Tag
`Dropdown`
## Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `label` | `string \| VNode \| Signal` | `-` | Button label or content |
| `icon` | `string \| VNode \| Signal` | `-` | Icon displayed next to label |
| `items` | `Array<MenuItem> \| Signal<Array>` | Required | Array of menu items |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
### MenuItem Structure
| Property | Type | Description |
| :--- | :--- | :--- |
| `label` | `string \| VNode` | Menu item text |
| `icon` | `string \| VNode` | Optional icon for the menu item |
| `onclick` | `function` | Click handler |
| `class` | `string` | Additional CSS classes for the menu item |
## Styling
Dropdown supports all **daisyUI Dropdown classes**:
| Category | Keywords | Description |
| :--- | :--- | :--- |
| Position | `dropdown-end` | Align dropdown to the right |
| Direction | `dropdown-top`, `dropdown-bottom`, `dropdown-left`, `dropdown-right` | Dropdown open direction |
| Hover | `dropdown-hover` | Open on hover instead of click |
> For further details, check the [daisyUI Dropdown Documentation](https://daisyui.com/components/dropdown) Full reference for CSS classes.
### Example
```javascript
Dropdown({
label: "Menu",
class: "dropdown-end dropdown-hover",
items: [
{ label: "Profile", onclick: () => console.log("Profile") },
{ label: "Settings", onclick: () => console.log("Settings") }
]
});
```
## Live Examples
### Basic Dropdown
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
return Dropdown({
label: 'Options',
items: [
{ label: 'Profile', onclick: () => Toast('Profile clicked', 'alert-info', 2000) },
{ label: 'Settings', onclick: () => Toast('Settings clicked', 'alert-info', 2000) },
{ label: 'Logout', onclick: () => Toast('Logged out', 'alert-warning', 2000), class: 'text-error' }
]
});
};
$mount(BasicDemo, '#demo-basic');
```
### With Icons
<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-icons" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const IconsDemo = () => {
return Dropdown({
label: 'Menu',
icon: '☰',
items: [
{ icon: '👤', label: 'Profile', onclick: () => Toast('Profile', 'alert-info', 2000) },
{ icon: '⭐', label: 'Favorites', onclick: () => Toast('Favorites', 'alert-info', 2000) },
{ icon: '📁', label: 'Documents', onclick: () => Toast('Documents', 'alert-info', 2000) },
{ icon: '⚙️', label: 'Settings', onclick: () => Toast('Settings', 'alert-info', 2000) }
]
});
};
$mount(IconsDemo, '#demo-icons');
```
### Action Dropdown
<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-actions" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const ActionsDemo = () => {
const handleAction = (action) => {
Toast(`${action} action`, 'alert-info', 2000);
};
return Dropdown({
label: 'Actions',
class: 'dropdown-end',
items: [
{ icon: '✏️', label: 'Edit', onclick: () => handleAction('Edit') },
{ icon: '📋', label: 'Copy', onclick: () => handleAction('Copy') },
{ icon: '🗑️', label: 'Delete', onclick: () => handleAction('Delete'), class: 'text-error' }
]
});
};
$mount(ActionsDemo, '#demo-actions');
```
### User Dropdown
<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-user" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const UserDropdown = () => {
return Dropdown({
label: Span({ class: 'flex items-center gap-2' }, [
Div({ class: 'avatar placeholder' }, [
Div({ class: 'bg-primary text-primary-content rounded-full w-8 h-8 flex items-center justify-center text-sm' }, 'JD')
]),
'John Doe'
]),
class: 'dropdown-end',
items: [
{ label: 'Profile', onclick: () => Toast('Profile', 'alert-info', 2000) },
{ label: 'Settings', onclick: () => Toast('Settings', 'alert-info', 2000) },
{ label: 'Sign Out', onclick: () => Toast('Signed out', 'alert-warning', 2000), class: 'text-error' }
]
});
};
$mount(UserDropdown, '#demo-user');
```
### Reactive Items
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const ReactiveDropdown = () => {
const count = $(0);
const items = () => [
{ label: `Count: ${count()}`, onclick: () => {} },
{ label: 'Increment', onclick: () => count(count() + 1) },
{ label: 'Decrement', onclick: () => count(count() - 1) },
{ label: 'Reset', onclick: () => count(0) }
];
return Dropdown({
label: () => `Counter (${count()})`,
items: items
});
};
$mount(ReactiveDropdown, '#demo-reactive');
```
### Notification Dropdown
<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-notifications" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const NotificationsDropdown = () => {
const notifications = $([
{ id: 1, title: 'New message', time: '5 min ago', read: false },
{ id: 2, title: 'Update available', time: '1 hour ago', read: false },
{ id: 3, title: 'Task completed', time: '2 hours ago', read: true }
]);
const unreadCount = () => notifications().filter(n => !n.read).length;
const markAsRead = (id) => {
notifications(notifications().map(n =>
n.id === id ? { ...n, read: true } : n
));
};
return Dropdown({
label: Span({ class: 'relative' }, [
'🔔',
() => unreadCount() > 0 ? Span({ class: 'badge badge-xs badge-error absolute -top-1 -right-2' }, unreadCount()) : null
]),
class: 'dropdown-end',
items: () => notifications().map(notif => ({
label: Div({ class: 'flex flex-col' }, [
Span({ class: 'font-medium' }, notif.title),
Span({ class: 'text-xs opacity-60' }, notif.time)
]),
class: notif.read ? '' : 'bg-primary/5',
onclick: () => markAsRead(notif.id)
}))
});
};
$mount(NotificationsDropdown, '#demo-notifications');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-4 justify-center"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
const commonItems = [
{ label: 'Item 1', onclick: () => Toast('Item 1', 'alert-info', 2000) },
{ label: 'Item 2', onclick: () => Toast('Item 2', 'alert-info', 2000) },
{ label: 'Item 3', onclick: () => Toast('Item 3', 'alert-info', 2000) }
];
return Div({ class: 'flex flex-wrap gap-4 justify-center' }, [
Dropdown({ label: 'Default', items: commonItems }),
Dropdown({ label: 'With Icon', icon: '☰', items: commonItems }),
Dropdown({ label: 'End Position', class: 'dropdown-end', items: commonItems })
]);
};
$mount(VariantsDemo, '#demo-variants');
```

378
docs/components/fab.md Normal file
View File

@@ -0,0 +1,378 @@
# Fab
Floating Action Button (FAB) component for primary actions with expandable menu options.
## Tag
`Fab`
## Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `icon` | `string \| VNode \| Signal` | `-` | Main FAB icon |
| `label` | `string \| VNode \| Signal` | `-` | Text label for main button |
| `actions` | `Array<Action> \| Signal<Array>` | `[]` | Array of action buttons that expand from FAB |
| `position` | `string` | `'bottom-6 right-6'` | CSS position classes (e.g., 'bottom-6 left-6') |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
### Action Structure
| Property | Type | Description |
| :--- | :--- | :--- |
| `label` | `string \| VNode` | Label text shown next to action button |
| `icon` | `string \| VNode` | Icon for the action button |
| `onclick` | `function` | Click handler |
| `class` | `string` | Additional CSS classes for the action button |
| `text` | `string` | Alternative text when no icon is provided |
## Styling
Fab uses **daisyUI Button classes** for styling the main button and action buttons:
| Category | Keywords | Description |
| :--- | :--- | :--- |
| Color | `btn-primary`, `btn-secondary`, `btn-accent`, `btn-info`, `btn-success`, `btn-warning`, `btn-error` | Visual color variants |
| Size | `btn-xs`, `btn-sm`, `btn-md`, `btn-lg`, `btn-xl` | Button scale |
| Shape | `btn-circle` | Circular button shape (default for FAB) |
> For further details, check the [daisyUI FAB Documentation](https://daisyui.com/components/fab) Full reference for CSS classes.
### Example
```javascript
Fab({
icon: "",
class: "btn-primary btn-lg",
position: "bottom-6 right-6",
actions: [
{ icon: "📝", label: "New Note", onclick: () => console.log("Create note") }
]
});
```
## Live Examples
### Basic FAB
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 min-h-[300px] relative"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
return Div({ class: 'relative h-[300px] w-full bg-base-100 rounded-lg overflow-hidden' }, [
Fab({
icon: '',
actions: [
{ icon: '📝', label: 'New Note', onclick: () => Toast('Create note', 'alert-info', 2000) },
{ icon: '📷', label: 'Take Photo', onclick: () => Toast('Open camera', 'alert-info', 2000) },
{ icon: '📎', label: 'Attach File', onclick: () => Toast('Attach file', 'alert-info', 2000) }
]
})
]);
};
$mount(BasicDemo, '#demo-basic');
```
### With Label
<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-label" class="bg-base-100 p-6 rounded-xl border border-base-300 min-h-[300px] relative"></div>
</div>
</div>
```javascript
const LabelDemo = () => {
return Div({ class: 'relative h-[300px] w-full bg-base-100 rounded-lg overflow-hidden' }, [
Fab({
label: 'Create',
icon: '✨',
actions: [
{ icon: '📝', label: 'Document', onclick: () => Toast('New document', 'alert-success', 2000) },
{ icon: '🎨', label: 'Design', onclick: () => Toast('New design', 'alert-success', 2000) },
{ icon: '📊', label: 'Spreadsheet', onclick: () => Toast('New spreadsheet', 'alert-success', 2000) }
]
})
]);
};
$mount(LabelDemo, '#demo-label');
```
### Different Positions
<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-positions" class="bg-base-100 p-6 rounded-xl border border-base-300 min-h-[500px] relative"></div>
</div>
</div>
```javascript
const PositionsDemo = () => {
const position = $('bottom-6 right-6');
return Div({ class: 'relative h-[500px] w-full bg-base-100 rounded-lg overflow-hidden' }, [
Div({ class: 'absolute top-4 left-4 z-20 bg-base-200 p-2 rounded-lg shadow' }, [
Select({
items: [
{ value: 'bottom-6 right-6', label: 'Bottom Right' },
{ value: 'bottom-6 left-6', label: 'Bottom Left' },
{ value: 'top-6 right-6', label: 'Top Right' },
{ value: 'top-6 left-6', label: 'Top Left' }
],
value: position,
onchange: (e) => position(e.target.value)
})
]),
Div({ class: 'absolute inset-0 flex items-center justify-center text-sm opacity-50 pointer-events-none' }, [
'FAB position changes relative to this container'
]),
Fab({
position: () => position(),
icon: '🧭',
actions: [
{ icon: '⬅️', label: 'Bottom Left', onclick: () => position('bottom-6 left-6') },
{ icon: '➡️', label: 'Bottom Right', onclick: () => position('bottom-6 right-6') },
{ icon: '⬆️', label: 'Top Right', onclick: () => position('top-6 right-6') },
{ icon: '⬇️', label: 'Top Left', onclick: () => position('top-6 left-6') }
]
})
]);
};
$mount(PositionsDemo, '#demo-positions');
```
### Color Variants
<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-colors" class="bg-base-100 p-6 rounded-xl border border-base-300 min-h-[300px] relative"></div>
</div>
</div>
```javascript
const ColorsDemo = () => {
const variant = $('primary');
const variants = {
primary: { class: 'btn-primary', icon: '🔵' },
secondary: { class: 'btn-secondary', icon: '🟣' },
accent: { class: 'btn-accent', icon: '🔴' },
info: { class: 'btn-info', icon: '🔷' },
success: { class: 'btn-success', icon: '🟢' },
warning: { class: 'btn-warning', icon: '🟡' },
error: { class: 'btn-error', icon: '🔴' }
};
return Div({ class: 'relative h-[300px] w-full bg-base-100 rounded-lg overflow-hidden' }, [
Div({ class: 'absolute top-4 left-4 z-20 bg-base-200 p-2 rounded-lg shadow' }, [
Select({
items: Object.keys(variants).map(v => ({ value: v, label: v.charAt(0).toUpperCase() + v.slice(1) })),
value: variant,
onchange: (e) => variant(e.target.value)
})
]),
Fab({
class: variants[variant()].class,
icon: variants[variant()].icon,
actions: [
{ icon: '📝', label: 'Action 1', onclick: () => Toast('Action 1', 'alert-info', 2000) },
{ icon: '🎨', label: 'Action 2', onclick: () => Toast('Action 2', 'alert-info', 2000) },
{ icon: '⚙️', label: 'Action 3', onclick: () => Toast('Action 3', 'alert-info', 2000) }
]
})
]);
};
$mount(ColorsDemo, '#demo-colors');
```
### Reactive Actions
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300 min-h-[300px] relative"></div>
</div>
</div>
```javascript
const ReactiveActions = () => {
const count = $(0);
const actions = () => [
{
icon: '🔢',
label: `Count: ${count()}`,
onclick: () => {}
},
{
icon: '',
label: 'Increment',
onclick: () => count(count() + 1)
},
{
icon: '',
label: 'Decrement',
onclick: () => count(count() - 1)
},
{
icon: '🔄',
label: 'Reset',
onclick: () => count(0)
}
];
return Div({ class: 'relative h-[300px] w-full bg-base-100 rounded-lg overflow-hidden' }, [
Fab({
icon: () => count() > 0 ? `🔢 ${count()}` : '🎛️',
actions: actions
})
]);
};
$mount(ReactiveActions, '#demo-reactive');
```
### Document Actions
<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-document" class="bg-base-100 p-6 rounded-xl border border-base-300 min-h-[300px] relative"></div>
</div>
</div>
```javascript
const DocumentActions = () => {
const saved = $(false);
const handleSave = () => {
saved(true);
Toast('Document saved!', 'alert-success', 2000);
setTimeout(() => saved(false), 3000);
};
return Div({ class: 'relative h-[300px] w-full bg-base-100 rounded-lg overflow-hidden' }, [
Div({ class: 'absolute inset-0 flex flex-col items-center justify-center' }, [
Div({ class: 'text-6xl mb-4' }, '📄'),
Div({ class: 'text-sm opacity-70' }, 'Untitled Document'),
() => saved() ? Div({ class: 'mt-4' }, Alert({ type: 'success', message: '✓ Saved successfully' })) : null
]),
Fab({
icon: '✏️',
actions: [
{ icon: '💾', label: 'Save', onclick: handleSave },
{ icon: '📋', label: 'Copy', onclick: () => Toast('Copied!', 'alert-info', 2000) },
{ icon: '✂️', label: 'Cut', onclick: () => Toast('Cut!', 'alert-info', 2000) },
{ icon: '📎', label: 'Share', onclick: () => Toast('Share dialog', 'alert-info', 2000) }
]
})
]);
};
$mount(DocumentActions, '#demo-document');
```
### Messaging FAB
<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-messaging" class="bg-base-100 p-6 rounded-xl border border-base-300 min-h-[300px] relative"></div>
</div>
</div>
```javascript
const MessagingFAB = () => {
const unread = $(3);
return Div({ class: 'relative h-[300px] w-full bg-base-100 rounded-lg overflow-hidden' }, [
Div({ class: 'absolute inset-0 flex flex-col items-center justify-center' }, [
Div({ class: 'text-6xl mb-4' }, '💬'),
Div({ class: 'text-sm opacity-70' }, 'Messages'),
() => unread() > 0 ? Div({ class: 'badge badge-error mt-2' }, `${unread()} unread`) : null
]),
Fab({
icon: () => `💬${unread() > 0 ? ` ${unread()}` : ''}`,
class: 'btn-primary',
actions: [
{
icon: '👤',
label: 'New Message',
onclick: () => Toast('Start new conversation', 'alert-info', 2000)
},
{
icon: '👥',
label: 'Group Chat',
onclick: () => Toast('Create group', 'alert-info', 2000)
},
{
icon: '📞',
label: 'Voice Call',
onclick: () => Toast('Start call', 'alert-info', 2000)
},
{
icon: '📹',
label: 'Video Call',
onclick: () => Toast('Start video call', 'alert-info', 2000)
},
{
icon: '🔔',
label: () => `Mark as read (${unread()})`,
onclick: () => {
unread(0);
Toast('All messages read', 'alert-success', 2000);
}
}
]
})
]);
};
$mount(MessagingFAB, '#demo-messaging');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 min-h-[400px] relative"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
const actions = [
{ icon: '⭐', label: 'Favorite', onclick: () => Toast('Favorited', 'alert-info', 2000) },
{ icon: '🔔', label: 'Remind', onclick: () => Toast('Reminder set', 'alert-info', 2000) },
{ icon: '📅', label: 'Schedule', onclick: () => Toast('Scheduled', 'alert-info', 2000) }
];
return Div({ class: 'relative h-[400px] w-full bg-base-100 rounded-lg overflow-hidden' }, [
Div({ class: 'grid grid-cols-2 gap-4 p-4 h-full' }, [
Div({ class: 'relative border rounded-lg bg-base-200' }, [
Span({ class: 'absolute top-2 left-2 text-xs opacity-50' }, 'Primary'),
Fab({ icon: '🔵', class: 'btn-primary', actions, position: 'bottom-6 left-6' })
]),
Div({ class: 'relative border rounded-lg bg-base-200' }, [
Span({ class: 'absolute top-2 left-2 text-xs opacity-50' }, 'Secondary'),
Fab({ icon: '🟣', class: 'btn-secondary', actions, position: 'bottom-6 right-6' })
]),
Div({ class: 'relative border rounded-lg bg-base-200' }, [
Span({ class: 'absolute top-2 left-2 text-xs opacity-50' }, 'Accent'),
Fab({ icon: '🔴', class: 'btn-accent', actions, position: 'top-6 left-6' })
]),
Div({ class: 'relative border rounded-lg bg-base-200' }, [
Span({ class: 'absolute top-2 left-2 text-xs opacity-50' }, 'Success'),
Fab({ icon: '🟢', class: 'btn-success', actions, position: 'top-6 right-6' })
])
])
]);
};
$mount(VariantsDemo, '#demo-variants');
```

324
docs/components/fieldset.md Normal file
View File

@@ -0,0 +1,324 @@
# Fieldset
Fieldset component for grouping form fields with optional legend and consistent styling.
## Tag
`Fieldset`
## Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `legend` | `string \| VNode \| Signal` | `-` | Fieldset legend/title |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `children` | `VNode \| Array<VNode>` | Required | Form fields or content inside the fieldset |
## Styling
Fieldset supports all **daisyUI Fieldset classes**:
| Category | Keywords | Description |
| :--- | :--- | :--- |
| Base | `fieldset` | Base fieldset styling |
| Color | `fieldset-primary`, `fieldset-secondary`, `fieldset-accent` | Border color variants |
| Size | `fieldset-xs`, `fieldset-sm`, `fieldset-md`, `fieldset-lg` | Fieldset scale |
> For further details, check the [daisyUI Fieldset Documentation](https://daisyui.com/components/fieldset) Full reference for CSS classes.
### Example
```javascript
Fieldset({
legend: "Personal Information",
class: "fieldset-primary w-full max-w-md"
}, [
Input({ label: "Name", placeholder: "Enter your name" }),
Input({ label: "Email", type: "email" })
]);
```
## Live Examples
### Basic Fieldset
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
return Fieldset({
legend: 'User Information',
class: 'w-full max-w-md mx-auto'
}, [
Div({ class: 'space-y-4' }, [
Input({ label: 'Full Name', placeholder: 'Enter your name' }),
Input({ label: 'Email', type: 'email', placeholder: 'user@example.com' }),
Input({ label: 'Phone', type: 'tel', placeholder: '+1 234 567 890' })
])
]);
};
$mount(BasicDemo, '#demo-basic');
```
### With Reactive Legend
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ReactiveDemo = () => {
const name = $('');
const email = $('');
const isValid = () => name().length > 0 && email().includes('@');
return Fieldset({
legend: () => isValid() ? '✓ Valid Form' : '✗ Incomplete Form',
class: 'w-full max-w-md mx-auto'
}, [
Div({ class: 'space-y-4' }, [
Input({
label: 'Full Name',
value: name,
oninput: (e) => name(e.target.value),
placeholder: 'Enter your name'
}),
Input({
label: 'Email',
type: 'email',
value: email,
oninput: (e) => email(e.target.value),
placeholder: 'user@example.com'
}),
() => isValid()
? Alert({ type: 'success', message: 'Form is ready to submit' })
: Alert({ type: 'warning', message: 'Please fill all required fields' })
])
]);
};
$mount(ReactiveDemo, '#demo-reactive');
```
### Address Form
<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-address" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const AddressDemo = () => {
const address = $('');
const city = $('');
const zip = $('');
const country = $('us');
return Fieldset({
legend: Span({ class: 'flex items-center gap-2' }, ['📍', 'Shipping Address']),
class: 'w-full max-w-md mx-auto'
}, [
Div({ class: 'space-y-4' }, [
Input({ label: 'Street Address', value: address, placeholder: '123 Main St', oninput: (e) => address(e.target.value) }),
Div({ class: 'grid grid-cols-2 gap-4' }, [
Input({ label: 'City', value: city, placeholder: 'City', oninput: (e) => city(e.target.value) }),
Input({ label: 'ZIP Code', value: zip, placeholder: 'ZIP', oninput: (e) => zip(e.target.value) })
]),
Select({
label: 'Country',
value: country,
items: [
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' },
{ value: 'mx', label: 'Mexico' }
],
onchange: (e) => country(e.target.value)
})
])
]);
};
$mount(AddressDemo, '#demo-address');
```
### Payment Method
<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-payment" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const PaymentDemo = () => {
const method = $('credit');
const cardNumber = $('');
const expiry = $('');
const cvv = $('');
return Fieldset({
legend: Span({ class: 'flex items-center gap-2' }, ['💳', 'Payment Details']),
class: 'w-full max-w-md mx-auto'
}, [
Div({ class: 'space-y-4' }, [
Div({ class: 'flex gap-4' }, [
Radio({ label: 'Credit Card', value: method, inputValue: 'credit', onclick: () => method('credit') }),
Radio({ label: 'PayPal', value: method, inputValue: 'paypal', onclick: () => method('paypal') }),
Radio({ label: 'Bank Transfer', value: method, inputValue: 'bank', onclick: () => method('bank') })
]),
() => method() === 'credit' ? Div({ class: 'space-y-4' }, [
Input({ label: 'Card Number', value: cardNumber, placeholder: '1234 5678 9012 3456', oninput: (e) => cardNumber(e.target.value) }),
Div({ class: 'grid grid-cols-2 gap-4' }, [
Input({ label: 'Expiry Date', value: expiry, placeholder: 'MM/YY', oninput: (e) => expiry(e.target.value) }),
Input({ label: 'CVV', type: 'password', value: cvv, placeholder: '123', oninput: (e) => cvv(e.target.value) })
])
]) : null,
() => method() === 'paypal' ? Alert({ type: 'info', message: 'You will be redirected to PayPal after confirming.' }) : null,
() => method() === 'bank' ? Alert({ type: 'warning', message: 'Bank transfer details will be sent via email.' }) : null
])
]);
};
$mount(PaymentDemo, '#demo-payment');
```
### Preferences Panel
<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-preferences" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const PreferencesDemo = () => {
const theme = $('light');
const language = $('en');
const notifications = $(true);
return Fieldset({
legend: Span({ class: 'flex items-center gap-2' }, ['⚙️', 'Preferences']),
class: 'w-full max-w-md mx-auto'
}, [
Div({ class: 'space-y-4' }, [
Div({ class: 'form-control' }, [
Span({ class: 'label-text mb-2' }, 'Theme'),
Div({ class: 'flex gap-4' }, [
Radio({ label: 'Light', value: theme, inputValue: 'light', onclick: () => theme('light') }),
Radio({ label: 'Dark', value: theme, inputValue: 'dark', onclick: () => theme('dark') }),
Radio({ label: 'System', value: theme, inputValue: 'system', onclick: () => theme('system') })
])
]),
Select({
label: 'Language',
value: language,
items: [
{ value: 'en', label: 'English' },
{ value: 'es', label: 'Español' },
{ value: 'fr', label: 'Français' }
],
onchange: (e) => language(e.target.value)
}),
Checkbox({
label: 'Enable notifications',
value: notifications,
onclick: () => notifications(!notifications())
})
])
]);
};
$mount(PreferencesDemo, '#demo-preferences');
```
### Registration Form
<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-registration" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const RegistrationDemo = () => {
const username = $('');
const email = $('');
const password = $('');
const confirmPassword = $('');
const accepted = $(false);
const passwordsMatch = () => password() === confirmPassword();
const isFormValid = () => username() && email().includes('@') && password().length >= 6 && passwordsMatch() && accepted();
const handleSubmit = () => {
if (isFormValid()) {
Toast('Registration successful!', 'alert-success', 2000);
}
};
return Fieldset({
legend: Span({ class: 'flex items-center gap-2' }, ['📝', 'Create Account']),
class: 'w-full max-w-md mx-auto'
}, [
Div({ class: 'space-y-4' }, [
Input({ label: 'Username', value: username, placeholder: 'Choose a username', oninput: (e) => username(e.target.value) }),
Input({ label: 'Email', type: 'email', value: email, placeholder: 'your@email.com', oninput: (e) => email(e.target.value) }),
Input({ label: 'Password', type: 'password', value: password, placeholder: 'Min. 6 characters', oninput: (e) => password(e.target.value) }),
Input({
label: 'Confirm Password',
type: 'password',
value: confirmPassword,
error: () => confirmPassword() && !passwordsMatch() ? 'Passwords do not match' : '',
oninput: (e) => confirmPassword(e.target.value)
}),
Checkbox({
label: 'I accept the Terms and Conditions',
value: accepted,
onclick: () => accepted(!accepted())
}),
() => !isFormValid() && (username() || email() || password()) ? Alert({ type: 'warning', message: 'Please complete all fields correctly' }) : null,
Button({
class: 'btn btn-primary w-full',
onclick: handleSubmit,
disabled: () => !isFormValid()
}, 'Register')
])
]);
};
$mount(RegistrationDemo, '#demo-registration');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
const commonContent = Div({ class: 'space-y-4' }, [
Input({ label: 'Field 1', placeholder: 'Enter value' }),
Input({ label: 'Field 2', placeholder: 'Enter value' }),
Button({ class: 'btn btn-primary' }, 'Submit')
]);
return Div({ class: 'flex flex-col gap-4' }, [
Fieldset({ legend: 'Default Fieldset', class: 'w-full' }, [commonContent]),
Fieldset({ legend: 'With Shadow', class: 'w-full shadow-lg' }, [commonContent]),
Fieldset({ legend: 'With Background', class: 'w-full bg-base-100' }, [commonContent])
]);
};
$mount(VariantsDemo, '#demo-variants');
```

View File

@@ -0,0 +1,284 @@
# Indicator
Indicator component for adding badges, status markers, or notifications to elements. Perfect for showing counts, online status, or alerts.
## Tag
`Indicator`
## Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `value` | `string \| VNode \| Signal` | `-` | Content to display as indicator |
| `class` | `string` | `''` | Additional CSS classes for the badge |
| `children` | `VNode` | `-` | Element to attach the indicator to |
## Styling
Indicator uses **daisyUI Indicator and Badge classes**:
| Category | Keywords | Description |
| :--- | :--- | :--- |
| Position | `indicator-start`, `indicator-end`, `indicator-top`, `indicator-bottom` | Indicator position (default: top-end) |
| Badge Color | `badge-primary`, `badge-secondary`, `badge-accent`, `badge-info`, `badge-success`, `badge-warning`, `badge-error` | Badge visual variants |
| Badge Size | `badge-xs`, `badge-sm`, `badge-md`, `badge-lg` | Badge scale |
> For further details, check the [daisyUI Indicator Documentation](https://daisyui.com/components/indicator) Full reference for CSS classes.
### Example
```javascript
Indicator({ value: "3", class: "badge-primary" },
Button({ class: "btn" }, "Notifications")
);
```
## Live Examples
### Basic Indicator
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-8 justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
Indicator({ value: '3', class: 'badge-primary' },
Div({ class: 'w-16 h-16 bg-base-300 rounded-lg flex items-center justify-center' }, '📦')
),
Indicator({ value: '99+', class: 'badge-secondary' },
Div({ class: 'w-16 h-16 bg-base-300 rounded-lg flex items-center justify-center' }, '🔔')
),
Indicator({ value: 'New', class: 'badge-accent' },
Div({ class: 'w-16 h-16 bg-base-300 rounded-lg flex items-center justify-center' }, '✨')
)
]);
};
$mount(BasicDemo, '#demo-basic');
```
### Online Status Indicator
<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-status" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-8 justify-center"></div>
</div>
</div>
```javascript
const StatusDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
Indicator({ value: '●', class: 'badge-success badge-xs' },
Div({ class: 'avatar placeholder' }, [
Div({ class: 'bg-neutral text-neutral-content rounded-full w-12 h-12 flex items-center justify-center' }, 'JD')
])
),
Indicator({ value: '●', class: 'badge-warning badge-xs' },
Div({ class: 'avatar placeholder' }, [
Div({ class: 'bg-neutral text-neutral-content rounded-full w-12 h-12 flex items-center justify-center' }, 'JS')
])
),
Indicator({ value: '●', class: 'badge-error badge-xs' },
Div({ class: 'avatar placeholder' }, [
Div({ class: 'bg-neutral text-neutral-content rounded-full w-12 h-12 flex items-center justify-center' }, 'BC')
])
)
]);
};
$mount(StatusDemo, '#demo-status');
```
### Reactive Counter
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4 items-center"></div>
</div>
</div>
```javascript
const ReactiveDemo = () => {
const count = $(0);
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Indicator({
value: () => count() > 0 ? count() : null,
class: 'badge-primary'
},
Button({
class: 'btn btn-lg btn-primary',
onclick: () => count(count() + 1)
}, 'Notifications')
),
Div({ class: 'flex gap-2' }, [
Button({
class: 'btn btn-sm',
onclick: () => count(Math.max(0, count() - 1))
}, 'Decrease'),
Button({
class: 'btn btn-sm btn-ghost',
onclick: () => count(0)
}, 'Clear')
])
]);
};
$mount(ReactiveDemo, '#demo-reactive');
```
### Shopping Cart
<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-cart" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const CartDemo = () => {
const cart = $([
{ id: 1, name: 'Product 1', price: 29 },
{ id: 2, name: 'Product 2', price: 49 }
]);
const addItem = () => {
const newId = Math.max(...cart().map(i => i.id), 0) + 1;
cart([...cart(), { id: newId, name: `Product ${newId}`, price: Math.floor(Math.random() * 100) + 10 }]);
Toast('Item added to cart', 'alert-success', 1500);
};
const removeItem = (id) => {
cart(cart().filter(item => item.id !== id));
Toast('Item removed', 'alert-info', 1500);
};
const total = () => cart().reduce((sum, item) => sum + item.price, 0);
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex justify-between items-center' }, [
Indicator({
value: () => cart().length,
class: 'badge-primary'
},
Button({ class: 'btn', onclick: addItem }, '🛒 Add to Cart')
),
Span({ class: 'text-lg font-bold' }, () => `Total: $${total()}`)
]),
() => 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' }, [
Span({}, item.name),
Div({ class: 'flex gap-2 items-center' }, [
Span({ class: 'text-sm font-bold' }, `$${item.price}`),
Button({
class: 'btn btn-xs btn-ghost btn-circle',
onclick: () => removeItem(item.id)
}, '✕')
])
])
))
]);
};
$mount(CartDemo, '#demo-cart');
```
### Email Inbox
<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-inbox" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const InboxDemo = () => {
const unread = $(3);
const messages = $([
{ id: 1, from: 'john@example.com', subject: 'Meeting tomorrow', read: false },
{ id: 2, from: 'jane@example.com', subject: 'Project update', read: false },
{ id: 3, from: 'bob@example.com', subject: 'Question about design', read: false },
{ id: 4, from: 'alice@example.com', subject: 'Weekly report', read: true }
]);
const markAsRead = (id) => {
const msg = messages().find(m => m.id === id);
if (!msg.read) {
msg.read = true;
messages([...messages()]);
unread(unread() - 1);
}
};
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex justify-between items-center' }, [
Indicator({
value: () => unread(),
class: 'badge-primary'
},
Span({ class: 'text-lg font-bold' }, 'Inbox')
),
Button({
class: 'btn btn-sm btn-ghost',
onclick: () => {
messages().forEach(m => m.read = true);
messages([...messages()]);
unread(0);
}
}, 'Mark all read')
]),
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)
}, [
Div({ class: 'font-medium' }, msg.from),
Div({ class: 'text-sm' }, msg.subject),
!msg.read && Span({ class: 'badge badge-xs badge-primary mt-1' }, 'New')
])
))
]);
};
$mount(InboxDemo, '#demo-inbox');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-8 justify-center"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
Indicator({ value: '3', class: 'badge-primary badge-sm' },
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg flex items-center justify-center' }, '📧')
),
Indicator({ value: '99+', class: 'badge-secondary badge-md' },
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg flex items-center justify-center' }, '🔔')
),
Indicator({ value: '●', class: 'badge-success badge-xs' },
Div({ class: 'avatar' }, [
Div({ class: 'w-10 h-10 rounded-full bg-primary' })
])
),
Indicator({ value: '!', class: 'badge-error badge-sm' },
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg flex items-center justify-center' }, '⚠️')
)
]);
};
$mount(VariantsDemo, '#demo-variants');
```

209
docs/components/input.md Normal file
View File

@@ -0,0 +1,209 @@
# Input
Form input component with floating label, icons, password toggle, tooltip, and error states. Fully integrated with DaisyUI and Tailwind.
## Tag
`Input`
## Props
| Prop | Type | Default | Description |
| :----------- | :--------------------------- | :--------- | :----------------------------------------------- |
| `label` | `string` | `-` | Label text (floating style) |
| `type` | `string` | `'text'` | Input type (text, password, email, number, date) |
| `value` | `string \| Signal<string>` | `''` | Input value |
| `placeholder`| `string` | `' '` | Placeholder text |
| `icon` | `string \| VNode \| Signal` | `-` | Icon displayed inside input |
| `tip` | `string` | `-` | Help tooltip text |
| `error` | `string \| Signal<string>` | `-` | Error message to display |
| `disabled` | `boolean \| Signal<boolean>` | `false` | Disabled state |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `oninput` | `function` | `-` | Input event handler |
## Live Examples
### Basic Input
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const name = $('');
return Input({
placeholder: 'Enter your name',
value: name,
oninput: (e) => name(e.target.value)
});
};
$mount(BasicDemo, '#demo-basic');
```
### With Icon
<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-icon" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const IconDemo = () => {
const email = $('');
return Input({
type: 'email',
icon: "✉️",
value: email,
oninput: (e) => email(e.target.value)
});
};
$mount(IconDemo, '#demo-icon');
```
### Password with Toggle
<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-password" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const PasswordDemo = () => {
const password = $('');
return Input({
type: 'password',
value: password,
oninput: (e) => password(e.target.value)
});
};
$mount(PasswordDemo, '#demo-password');
```
### With Tooltip
<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-tooltip" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const TooltipDemo = () => {
const username = $('');
return Input({
tip: 'Must be at least 3 characters',
value: username,
oninput: (e) => username(e.target.value)
});
};
$mount(TooltipDemo, '#demo-tooltip');
```
### Error State
<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-error" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const ErrorDemo = () => {
const email = $('');
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');
```
### Disabled State
<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-disabled" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const DisabledDemo = () => {
return Input({
value: 'john.doe',
disabled: true
});
};
$mount(DisabledDemo, '#demo-disabled');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
const text = $('');
const number = $(0);
return Div({ class: 'flex flex-col gap-4' }, [
Input({
placeholder: 'Type something...',
value: text,
oninput: (e) => text(e.target.value)
}),
Input({
type: 'number',
value: number,
oninput: (e) => number(parseInt(e.target.value) || 0)
}),
Input({
type: 'date',
value: $('2024-01-01')
}),
Input({class: 'input-primary',value:"Primary"}),
Input({class: 'input-secondary', value:"Secondary"}),
Input({class: 'input-accent', value:"Accent"}),
Input({class: 'input-ghost', value:"Ghost"}),
Input({class: 'input-link', value:"Link"}),
Input({class: 'input-info', value:"Info"}),
Input({class: 'input-success', value:"Success"}),
Input({class: 'input-warning', value:"Warning"}),
Input({class: 'input-error', value:"Error"}),
]);
};
$mount(VariantsDemo, '#demo-variants');
```

425
docs/components/list.md Normal file
View File

@@ -0,0 +1,425 @@
# List
List component with custom item rendering, headers, and reactive data binding.
## Tag
`List`
## 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) |
## 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 |
> For further details, check the [daisyUI List Documentation](https://daisyui.com/components/list) Full reference for CSS classes.
## Live Examples
### Basic List
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
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),
]),
});
};
$mount(BasicDemo, "#demo-basic");
```
### With Header
<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-header" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```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" },
];
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,
),
]),
});
};
$mount(HeaderDemo, "#demo-header");
```
### With Icons
<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-icons" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```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" },
];
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" }, "→"),
],
),
});
};
$mount(IconsDemo, "#demo-icons");
```
### With Badges
<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-badges" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```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,
},
];
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,
],
),
});
};
$mount(BadgesDemo, "#demo-badges");
```
### Interactive List
<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-interactive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
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" },
];
const statusColors = {
"In Progress": "badge-warning",
Planning: "badge-info",
Completed: "badge-success",
Review: "badge-accent",
};
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,
),
]),
],
),
}),
() =>
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");
```
### Reactive List (Todo App)
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ReactiveDemo = () => {
const todos = $([
{ id: 1, text: 'Complete documentation', done: false },
{ id: 2, text: 'Review pull requests', done: false },
{ id: 3, text: 'Deploy to production', done: false }
]);
const newTodo = $('');
const addTodo = () => {
if (newTodo().trim()) {
const newId = Math.max(...todos().map(t => t.id), 0) + 1;
todos([...todos(), { id: newId, text: newTodo(), done: false }]);
newTodo('');
}
};
const toggleTodo = (id) => {
todos(todos().map(t => t.id === id ? { ...t, done: !t.done } : t));
};
const deleteTodo = (id) => {
todos(todos().filter(t => t.id !== id));
};
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,
oninput: (e) => newTodo(e.target.value),
onkeypress: (e) => e.key === 'Enter' && addTodo()
}),
Button({ class: 'btn btn-primary', onclick: addTodo }, 'Add')
]),
List({
items: todos,
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');
```
### Avatar List
<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-avatar" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```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 },
];
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,
),
],
),
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");
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-6"></div>
</div>
</div>
```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"),
List({
items: items,
render: (item) => Div({ class: "p-2" }, item),
}),
Div({ class: "text-sm font-bold mt-2" }, "With Shadow"),
List({
items: items,
render: (item) => Div({ class: "p-2" }, item),
class: "shadow-lg",
}),
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",
}),
]);
};
$mount(VariantsDemo, "#demo-variants");
```

426
docs/components/menu.md Normal file
View File

@@ -0,0 +1,426 @@
# Menu
Menu component for creating navigation menus, sidebars, and dropdowns with support for nested items, icons, and active states.
## Tag
`Menu`
## Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `items` | `Array<MenuItem>` | `[]` | Menu items configuration |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
### MenuItem Structure
| Property | Type | Description |
| :--- | :--- | :--- |
| `label` | `string \| VNode` | Menu item text or content |
| `icon` | `string \| VNode` | Optional icon to display |
| `active` | `boolean \| Signal<boolean>` | Active state highlighting |
| `onclick` | `function` | Click handler |
| `children` | `Array<MenuItem>` | Nested submenu items |
| `open` | `boolean` | Whether submenu is open (for nested items) |
## Styling
Menu supports all **daisyUI Menu classes**:
| Category | Keywords | Description |
| :--- | :--- | :--- |
| Direction | `menu-vertical` (default), `menu-horizontal` | Menu orientation |
| Size | `menu-xs`, `menu-sm`, `menu-md`, `menu-lg` | Menu scale |
| Style | `menu-compact` | Reduced padding |
| Background | `bg-base-100`, `bg-base-200` | Background colors |
> For further details, check the [daisyUI Menu Documentation](https://daisyui.com/components/menu) Full reference for CSS classes.
## Live Examples
### Basic Menu
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const activeItem = $('home');
return Menu({
items: [
{
label: 'Home',
active: () => activeItem() === 'home',
onclick: () => activeItem('home')
},
{
label: 'About',
active: () => activeItem() === 'about',
onclick: () => activeItem('about')
},
{
label: 'Contact',
active: () => activeItem() === 'contact',
onclick: () => activeItem('contact')
}
]
});
};
$mount(BasicDemo, '#demo-basic');
```
### With Icons
<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-icons" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const IconsDemo = () => {
const activeItem = $('dashboard');
return Menu({
items: [
{
icon: '🏠',
label: 'Dashboard',
active: () => activeItem() === 'dashboard',
onclick: () => activeItem('dashboard')
},
{
icon: '📊',
label: 'Analytics',
active: () => activeItem() === 'analytics',
onclick: () => activeItem('analytics')
},
{
icon: '⚙️',
label: 'Settings',
active: () => activeItem() === 'settings',
onclick: () => activeItem('settings')
},
{
icon: '👤',
label: 'Profile',
active: () => activeItem() === 'profile',
onclick: () => activeItem('profile')
}
]
});
};
$mount(IconsDemo, '#demo-icons');
```
### Nested Menu
<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-nested" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const NestedDemo = () => {
const activeItem = $('products');
return Menu({
items: [
{
label: 'Dashboard',
onclick: () => activeItem('dashboard'),
active: () => activeItem() === 'dashboard'
},
{
label: 'Products',
icon: '📦',
open: true,
children: [
{
label: 'All Products',
onclick: () => activeItem('all-products'),
active: () => activeItem() === 'all-products'
},
{
label: 'Add New',
onclick: () => activeItem('add-product'),
active: () => activeItem() === 'add-product'
},
{
label: 'Categories',
children: [
{
label: 'Electronics',
onclick: () => activeItem('electronics'),
active: () => activeItem() === 'electronics'
},
{
label: 'Clothing',
onclick: () => activeItem('clothing'),
active: () => activeItem() === 'clothing'
}
]
}
]
},
{
label: 'Orders',
icon: '📋',
onclick: () => activeItem('orders'),
active: () => activeItem() === 'orders'
},
{
label: 'Settings',
icon: '⚙️',
onclick: () => activeItem('settings'),
active: () => activeItem() === 'settings'
}
]
});
};
$mount(NestedDemo, '#demo-nested');
```
### Horizontal Menu
<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-horizontal" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const HorizontalDemo = () => {
const activeItem = $('home');
return Menu({
class: 'menu-horizontal rounded-box',
items: [
{
label: 'Home',
active: () => activeItem() === 'home',
onclick: () => activeItem('home')
},
{
label: 'Products',
children: [
{ label: 'Electronics', onclick: () => activeItem('electronics') },
{ label: 'Clothing', onclick: () => activeItem('clothing') },
{ label: 'Books', onclick: () => activeItem('books') }
]
},
{
label: 'About',
onclick: () => activeItem('about'),
active: () => activeItem() === 'about'
},
{
label: 'Contact',
onclick: () => activeItem('contact'),
active: () => activeItem() === 'contact'
}
]
});
};
$mount(HorizontalDemo, '#demo-horizontal');
```
### Sidebar Menu
<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-sidebar" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const SidebarDemo = () => {
const activeItem = $('dashboard');
return Div({ class: 'flex' }, [
Div({ class: 'w-64' }, [
Menu({
class: 'rounded-box w-full',
items: [
{
icon: '📊',
label: 'Dashboard',
active: () => activeItem() === 'dashboard',
onclick: () => activeItem('dashboard')
},
{
icon: '👥',
label: 'Users',
children: [
{
label: 'All Users',
onclick: () => activeItem('all-users'),
active: () => activeItem() === 'all-users'
},
{
label: 'Add User',
onclick: () => activeItem('add-user'),
active: () => activeItem() === 'add-user'
}
]
},
{
icon: '📁',
label: 'Files',
onclick: () => activeItem('files'),
active: () => activeItem() === 'files'
},
{
icon: '⚙️',
label: 'Settings',
onclick: () => activeItem('settings'),
active: () => activeItem() === 'settings'
}
]
})
]),
Div({ class: 'flex-1 p-4' }, [
Div({ class: 'alert alert-info' }, () => `Current page: ${activeItem()}`)
])
]);
};
$mount(SidebarDemo, '#demo-sidebar');
```
### Account Menu
<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-account" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const AccountDemo = () => {
const notifications = $(3);
return Menu({
class: 'rounded-box w-56',
items: [
{
icon: '👤',
label: 'My Profile',
onclick: () => Toast('Profile clicked', 'alert-info', 2000)
},
{
icon: '📧',
label: 'Messages',
onclick: () => Toast('Messages opened', 'alert-info', 2000)
},
{
icon: '🔔',
label: 'Notifications',
badge: () => notifications(),
onclick: () => {
notifications(0);
Toast('Notifications cleared', 'alert-success', 2000);
}
},
{
icon: '⚙️',
label: 'Settings',
onclick: () => Toast('Settings opened', 'alert-info', 2000)
},
{
icon: '🚪',
label: 'Logout',
onclick: () => Toast('Logged out', 'alert-warning', 2000)
}
]
});
};
$mount(AccountDemo, '#demo-account');
```
### Collapsible Sidebar
<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-collapsible" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const CollapsibleDemo = () => {
const collapsed = $(false);
const activeItem = $('dashboard');
return Div({ class: 'flex gap-4' }, [
Div({ class: `transition-all duration-300 ${collapsed() ? 'w-16' : 'w-64'}` }, [
Button({
class: 'btn btn-ghost btn-sm mb-2 w-full',
onclick: () => collapsed(!collapsed())
}, collapsed() ? '→' : '←'),
Menu({
class: `rounded-box ${collapsed() ? 'menu-compact' : ''}`,
items: [
{ icon: '📊', label: collapsed() ? '' : 'Dashboard', active: () => activeItem() === 'dashboard', onclick: () => activeItem('dashboard') },
{ icon: '👥', label: collapsed() ? '' : 'Users', active: () => activeItem() === 'users', onclick: () => activeItem('users') },
{ icon: '📁', label: collapsed() ? '' : 'Files', active: () => activeItem() === 'files', onclick: () => activeItem('files') },
{ icon: '⚙️', label: collapsed() ? '' : 'Settings', active: () => activeItem() === 'settings', onclick: () => activeItem('settings') }
]
})
]),
Div({ class: 'flex-1' }, [
Div({ class: 'alert alert-info' }, () => `Selected: ${activeItem()}`)
])
]);
};
$mount(CollapsibleDemo, '#demo-collapsible');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-8"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
const items = [
{ label: 'Item 1' },
{ label: 'Item 2' },
{ label: 'Item 3', children: [
{ label: 'Subitem 1' },
{ label: 'Subitem 2' }
]}
];
return Div({ class: 'flex flex-wrap gap-8' }, [
Div({ class: 'w-48' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Default'),
Menu({ items })
]),
Div({ class: 'w-48' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Compact'),
Menu({ items, class: 'menu-compact' })
]),
Div({ class: 'w-48' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'With Shadow'),
Menu({ items, class: 'shadow-lg' })
])
]);
};
$mount(VariantsDemo, '#demo-variants');
```

378
docs/components/modal.md Normal file
View File

@@ -0,0 +1,378 @@
# Modal
Modal dialog component for displaying content in an overlay with customizable actions and backdrop. Uses the native HTML `<dialog>` element for better accessibility and performance.
## Tag
`Modal`
## Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `open` | `boolean \| Signal<boolean>` | `false` | Modal visibility state |
| `title` | `string \| VNode \| Signal` | `-` | Modal title text |
| `buttons` | `Array<VNode> \| VNode` | `-` | Action buttons to display (auto-closes on click) |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `children` | `string \| VNode` | `-` | Modal content |
## Styling
Modal supports all **daisyUI Modal classes**:
| Category | Keywords | Description |
| :--- | :--- | :--- |
| Size | `modal-sm`, `modal-md`, `modal-lg`, `modal-xl` | Modal scale |
| Position | `modal-top`, `modal-middle`, `modal-bottom` | Modal vertical alignment |
> For further details, check the [daisyUI Modal Documentation](https://daisyui.com/components/modal) Full reference for CSS classes.
## Live Examples
### Basic Modal
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const isOpen = $(false);
return Div({ class: 'flex justify-center' }, [
Button({
class: 'btn btn-primary',
onclick: () => {
isOpen(true);
}
}, 'Open Modal'),
Modal({
open: isOpen,
title: 'Basic Modal',
buttons: Button({
class: 'btn-primary',
onclick: () => {
isOpen(false);
}
}, 'Custom Action')
}, [
Div({ class: 'py-4' }, 'This is a basic modal dialog. Press ESC or click Close.')
])
]);
};
$mount(BasicDemo, '#demo-basic');
```
### Modal with Actions
<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-actions" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const ActionsDemo = () => {
const isOpen = $(false);
const confirmed = $(false);
const handleConfirm = () => {
confirmed(true);
isOpen(false);
Toast('Action confirmed!', 'alert-success', 2000);
};
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Button({
class: 'btn btn-primary',
onclick: () => isOpen(true)
}, 'Confirm Action'),
() => confirmed() ? Alert({
type: 'success',
message: 'You confirmed the action!'
}) : null,
Modal({
open: isOpen,
title: 'Confirm Action',
buttons: [
Button({
class: 'btn btn-ghost',
onclick: () => isOpen(false)
}, 'Cancel'),
Button({
class: 'btn btn-primary',
onclick: handleConfirm
}, 'Confirm')
]
}, [
Div({ class: 'py-4' }, 'Are you sure you want to perform this action? This cannot be undone.')
])
]);
};
$mount(ActionsDemo, '#demo-actions');
```
### Form Modal
<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-form" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const FormModal = () => {
const isOpen = $(false);
const name = $('');
const email = $('');
const submitted = $(false);
const handleSubmit = () => {
if (name() && email()) {
submitted(true);
isOpen(false);
Toast(`Welcome ${name()}!`, 'alert-success', 2000);
setTimeout(() => submitted(false), 3000);
}
};
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Button({
class: 'btn btn-primary',
onclick: () => isOpen(true)
}, 'Sign Up'),
() => submitted() ? Alert({
type: 'success',
message: 'Account created successfully!'
}) : null,
Modal({
open: isOpen,
title: 'Create Account',
buttons: [
Button({
class: 'btn btn-ghost',
onclick: () => isOpen(false)
}, 'Cancel'),
Button({
class: 'btn btn-primary',
onclick: handleSubmit
}, 'Sign Up')
]
}, [
Div({ class: 'flex flex-col gap-4 py-2' }, [
Input({
label: 'Full Name',
value: name,
placeholder: 'Enter your name',
oninput: (e) => name(e.target.value)
}),
Input({
label: 'Email',
type: 'email',
value: email,
placeholder: 'Enter your email',
oninput: (e) => email(e.target.value)
})
])
])
]);
};
$mount(FormModal, '#demo-form');
```
### Confirmation Modal
<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-confirm" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const ConfirmDemo = () => {
const isOpen = $(false);
const items = $([
{ id: 1, name: 'Document A' },
{ id: 2, name: 'Document B' },
{ id: 3, name: 'Document C' }
]);
const pendingDelete = $(null);
const confirmDelete = (item) => {
pendingDelete(item);
isOpen(true);
};
const handleDelete = () => {
items(items().filter(i => i.id !== pendingDelete().id));
isOpen(false);
Toast(`Deleted: ${pendingDelete().name}`, 'alert-info', 2000);
};
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex flex-col gap-2' }, items().map(item =>
Div({ class: 'flex justify-between items-center p-3 bg-base-200 rounded-lg' }, [
Span({}, item.name),
Button({
class: 'btn btn-xs btn-error',
onclick: () => confirmDelete(item)
}, 'Delete')
])
)),
Modal({
open: isOpen,
title: 'Delete Confirmation',
buttons: [
Button({
class: 'btn btn-ghost',
onclick: () => isOpen(false)
}, 'Cancel'),
Button({
class: 'btn btn-error',
onclick: handleDelete
}, 'Delete')
]
}, [
Div({ class: 'py-4' }, () => `Are you sure you want to delete "${pendingDelete()?.name}"? This action cannot be undone.`)
])
]);
};
$mount(ConfirmDemo, '#demo-confirm');
```
### Large Content Modal
<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-large" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const LargeDemo = () => {
const isOpen = $(false);
return Div({ class: 'flex justify-center' }, [
Button({
class: 'btn btn-primary',
onclick: () => isOpen(true)
}, 'Open Large Modal'),
Modal({
open: isOpen,
title: 'Terms and Conditions',
buttons: Button({
class: 'btn btn-primary',
onclick: () => isOpen(false)
}, 'I Agree')
}, [
Div({ class: 'py-4 max-h-96 overflow-y-auto' }, [
Div({ class: 'space-y-4' }, [
Div({ class: 'text-sm' }, [
Div({ class: 'font-bold mb-2' }, '1. Introduction'),
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.'
]),
Div({ class: 'text-sm' }, [
Div({ class: 'font-bold mb-2' }, '2. User Obligations'),
'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident.'
]),
Div({ class: 'text-sm' }, [
Div({ class: 'font-bold mb-2' }, '3. Privacy Policy'),
'Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam.'
]),
Div({ class: 'text-sm' }, [
Div({ class: 'font-bold mb-2' }, '4. Termination'),
'At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores.'
])
])
])
])
]);
};
$mount(LargeDemo, '#demo-large');
```
### Multiple Modals
<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-multiple" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-4 justify-center"></div>
</div>
</div>
```javascript
const MultipleDemo = () => {
const modal1 = $(false);
const modal2 = $(false);
const modal3 = $(false);
return Div({ class: 'flex flex-wrap gap-4 justify-center' }, [
Button({ class: 'btn', onclick: () => modal1(true) }, 'Info Modal'),
Button({ class: 'btn', onclick: () => modal2(true) }, 'Success Modal'),
Button({ class: 'btn', onclick: () => modal3(true) }, 'Warning Modal'),
Modal({
open: modal1,
title: 'Information',
buttons: Button({ onclick: () => modal1(false) }, 'OK')
}, 'This is an informational message.'),
Modal({
open: modal2,
title: 'Success!',
buttons: Button({ onclick: () => modal2(false) }, 'Great!')
}, 'Your operation was completed successfully.'),
Modal({
open: modal3,
title: 'Warning',
buttons: Button({ onclick: () => modal3(false) }, 'Understood')
}, 'Please review your input before proceeding.')
]);
};
$mount(MultipleDemo, '#demo-multiple');
```
### Custom Styled Modal
<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-custom" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const CustomDemo = () => {
const isOpen = $(false);
return Div({ class: 'flex justify-center' }, [
Button({
class: 'btn btn-primary',
onclick: () => isOpen(true)
}, 'Open Custom Modal'),
Modal({
open: isOpen,
class: 'bg-gradient-to-r from-primary to-secondary text-primary-content'
}, [
Div({ class: 'text-center py-8' }, [
Div({ class: 'text-6xl mb-4' }, '🎉'),
Div({ class: 'text-2xl font-bold mb-2' }, 'Congratulations!'),
Div({ class: 'mb-6' }, 'You\'ve successfully completed the tutorial.'),
Button({
class: 'btn btn-ghost bg-white/20 hover:bg-white/30',
onclick: () => isOpen(false)
}, 'Get Started')
])
])
]);
};
$mount(CustomDemo, '#demo-custom');
```

276
docs/components/navbar.md Normal file
View File

@@ -0,0 +1,276 @@
# Navbar
Navigation bar component for creating responsive headers with logo, navigation links, and actions.
## Tag
`Navbar`
## Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `children` | `VNode \| Array<VNode>` | `-` | Navbar content (should contain left, center, right sections) |
## Styling
Navbar supports all **daisyUI Navbar classes**:
| Category | Keywords | Description |
| :--- | :--- | :--- |
| Base | `navbar` | Base navbar styling |
| Sections | `navbar-start`, `navbar-center`, `navbar-end` | Content alignment sections |
| Background | `bg-base-100`, `bg-base-200`, `bg-primary`, etc. | Background colors |
| Shadow | `shadow`, `shadow-lg`, `shadow-md` | Box shadow variants |
> For further details, check the [daisyUI Navbar Documentation](https://daisyui.com/components/navbar) Full reference for CSS classes.
## Live Examples
### Basic Navbar
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
return Navbar({ class: 'rounded-box' }, [
Div({ class: 'navbar-start' }, [
Span({ class: 'text-xl font-bold' }, 'Logo')
]),
Div({ class: 'navbar-center hidden lg:flex' }, [
Span({ class: 'text-sm' }, 'Navigation Items')
]),
Div({ class: 'navbar-end' }, [
Button({ class: 'btn btn-ghost btn-sm' }, 'Login')
])
]);
};
$mount(BasicDemo, '#demo-basic');
```
### With Navigation Links
<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-links" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const LinksDemo = () => {
const active = $('home');
return Navbar({ class: 'rounded-box' }, [
Div({ class: 'navbar-start' }, [
Span({ class: 'text-xl font-bold' }, 'MyApp')
]),
Div({ class: 'navbar-center hidden lg:flex' }, [
Div({ class: 'menu menu-horizontal px-1' }, [
Button({
class: `btn btn-ghost btn-sm ${active() === 'home' ? 'btn-active' : ''}`,
onclick: () => active('home')
}, 'Home'),
Button({
class: `btn btn-ghost btn-sm ${active() === 'about' ? 'btn-active' : ''}`,
onclick: () => active('about')
}, 'About'),
Button({
class: `btn btn-ghost btn-sm ${active() === 'contact' ? 'btn-active' : ''}`,
onclick: () => active('contact')
}, 'Contact')
])
]),
Div({ class: 'navbar-end' }, [
Button({ class: 'btn btn-primary btn-sm' }, 'Sign Up')
])
]);
};
$mount(LinksDemo, '#demo-links');
```
### With Search
<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-search" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const SearchDemo = () => {
const searchQuery = $('');
return Navbar({ class: 'rounded-box' }, [
Div({ class: 'navbar-start' }, [
Span({ class: 'text-xl font-bold' }, 'MyApp')
]),
Div({ class: 'navbar-center' }, [
Div({ class: 'form-control' }, [
Input({
placeholder: 'Search...',
value: searchQuery,
class: 'input input-bordered w-48 md:w-auto',
oninput: (e) => searchQuery(e.target.value)
})
])
]),
Div({ class: 'navbar-end' }, [
Button({ class: 'btn btn-ghost btn-circle' }, '🔔'),
Button({ class: 'btn btn-ghost btn-circle' }, '👤')
])
]);
};
$mount(SearchDemo, '#demo-search');
```
### With Avatar and Dropdown
<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-avatar" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const AvatarDemo = () => {
return Navbar({ class: 'rounded-box' }, [
Div({ class: 'navbar-start' }, [
Span({ class: 'text-xl font-bold' }, 'MyApp')
]),
Div({ class: 'navbar-center hidden lg:flex' }, [
Div({ class: 'menu menu-horizontal px-1' }, [
Span({ class: 'text-sm' }, 'Dashboard'),
Span({ class: 'text-sm' }, 'Projects'),
Span({ class: 'text-sm' }, 'Tasks')
])
]),
Div({ class: 'navbar-end' }, [
Div({ class: 'dropdown dropdown-end' }, [
Div({ tabindex: 0, role: 'button', class: 'btn btn-ghost btn-circle avatar' }, [
Div({ class: 'w-8 rounded-full bg-primary text-primary-content flex items-center justify-center' }, 'JD')
]),
Div({ tabindex: 0, class: 'dropdown-content menu bg-base-100 rounded-box z-[1] w-52 p-2 shadow' }, [
Span({ class: 'menu-item' }, 'Profile'),
Span({ class: 'menu-item' }, 'Settings'),
Span({ class: 'menu-item' }, 'Logout')
])
])
])
]);
};
$mount(AvatarDemo, '#demo-avatar');
```
### Responsive Navbar
<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-responsive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ResponsiveDemo = () => {
const isOpen = $(false);
return Div({ class: 'flex flex-col' }, [
Navbar({ class: 'rounded-box' }, [
Div({ class: 'navbar-start' }, [
Button({
class: 'btn btn-ghost btn-circle lg:hidden',
onclick: () => isOpen(!isOpen())
}, '☰')
]),
Div({ class: 'navbar-center' }, [
Span({ class: 'text-xl font-bold' }, 'MyApp')
]),
Div({ class: 'navbar-end' }, [
Button({ class: 'btn btn-ghost btn-circle' }, '🔔')
])
]),
() => isOpen() ? Div({ class: 'flex flex-col gap-2 p-4 bg-base-200 rounded-box mt-2' }, [
Button({ class: 'btn btn-ghost justify-start' }, 'Home'),
Button({ class: 'btn btn-ghost justify-start' }, 'About'),
Button({ class: 'btn btn-ghost justify-start' }, 'Services'),
Button({ class: 'btn btn-ghost justify-start' }, 'Contact')
]) : null
]);
};
$mount(ResponsiveDemo, '#demo-responsive');
```
### With Brand and Actions
<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-brand" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const BrandDemo = () => {
return Navbar({ class: 'rounded-box bg-primary text-primary-content' }, [
Div({ class: 'navbar-start' }, [
Span({ class: 'text-xl font-bold' }, 'Brand')
]),
Div({ class: 'navbar-center hidden lg:flex' }, [
Div({ class: 'menu menu-horizontal px-1' }, [
Span({ class: 'text-sm' }, 'Features'),
Span({ class: 'text-sm' }, 'Pricing'),
Span({ class: 'text-sm' }, 'About')
])
]),
Div({ class: 'navbar-end' }, [
Button({ class: 'btn btn-ghost btn-sm' }, 'Login'),
Button({ class: 'btn btn-outline btn-sm ml-2' }, 'Sign Up')
])
]);
};
$mount(BrandDemo, '#demo-brand');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'text-sm font-bold' }, 'Default Navbar'),
Navbar({ class: 'rounded-box' }, [
Div({ class: 'navbar-start' }, [Span({}, 'Start')]),
Div({ class: 'navbar-center' }, [Span({}, 'Center')]),
Div({ class: 'navbar-end' }, [Span({}, 'End')])
]),
Div({ class: 'text-sm font-bold mt-2' }, 'With Shadow'),
Navbar({ class: 'rounded-box shadow-lg' }, [
Div({ class: 'navbar-start' }, [Span({}, 'Logo')]),
Div({ class: 'navbar-end' }, [Button({ class: 'btn btn-sm' }, 'Button')])
]),
Div({ class: 'text-sm font-bold mt-2' }, 'With Background'),
Navbar({ class: 'rounded-box bg-base-300' }, [
Div({ class: 'navbar-start' }, [Span({ class: 'font-bold' }, 'Colored')]),
Div({ class: 'navbar-end' }, [Span({ class: 'text-sm' }, 'Info')])
])
]);
};
$mount(VariantsDemo, '#demo-variants');
```

409
docs/components/radio.md Normal file
View File

@@ -0,0 +1,409 @@
# Radio
Radio button component with label, tooltip support, and reactive group selection. All radios in the same group share a common `name` attribute for proper HTML semantics.
## Tag
`Radio`
## Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `label` | `string` | `-` | Label text for the radio button |
| `value` | `string \| Signal<string>` | `-` | Selected value signal for the group |
| `inputValue` | `string` | `-` | Value of this radio button |
| `name` | `string` | `-` | Group name (all radios in group should share this) |
| `tooltip` | `string` | `-` | Tooltip text on hover |
| `disabled` | `boolean \| Signal<boolean>` | `false` | Disabled state |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `onclick` | `function` | `-` | Click event handler |
## Styling
Radio supports all **daisyUI Radio classes**:
| Category | Keywords | Description |
| :--- | :--- | :--- |
| Color | `radio-primary`, `radio-secondary`, `radio-accent`, `radio-info`, `radio-success`, `radio-warning`, `radio-error` | Radio color variants |
| Size | `radio-xs`, `radio-sm`, `radio-md`, `radio-lg` | Radio scale |
> For further details, check the [daisyUI Radio Documentation](https://daisyui.com/components/radio) Full reference for CSS classes.
## Live Examples
### Basic Radio Group
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-3"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const selected = $('option1');
return Div({ class: 'flex flex-col gap-3' }, [
Radio({
label: 'Option 1',
name: 'basic-group',
value: selected,
inputValue: 'option1',
onclick: () => selected('option1')
}),
Radio({
label: 'Option 2',
name: 'basic-group',
value: selected,
inputValue: 'option2',
onclick: () => selected('option2')
}),
Radio({
label: 'Option 3',
name: 'basic-group',
value: selected,
inputValue: 'option3',
onclick: () => selected('option3')
}),
Div({ class: 'mt-2 text-sm opacity-70' }, () => `Selected: ${selected()}`)
]);
};
$mount(BasicDemo, '#demo-basic');
```
### With Tooltip
<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-tooltip" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-3"></div>
</div>
</div>
```javascript
const TooltipDemo = () => {
const selected = $('light');
return Div({ class: 'flex flex-col gap-3' }, [
Radio({
label: 'Light mode',
name: 'theme-group',
value: selected,
inputValue: 'light',
tooltip: 'Light theme with white background',
onclick: () => selected('light')
}),
Radio({
label: 'Dark mode',
name: 'theme-group',
value: selected,
inputValue: 'dark',
tooltip: 'Dark theme with black background',
onclick: () => selected('dark')
})
]);
};
$mount(TooltipDemo, '#demo-tooltip');
```
### Disabled State
<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-disabled" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-3"></div>
</div>
</div>
```javascript
const DisabledDemo = () => {
const selected = $('enabled');
return Div({ class: 'flex flex-col gap-3' }, [
Radio({
label: 'Enabled option',
name: 'disabled-group',
value: selected,
inputValue: 'enabled',
onclick: () => selected('enabled')
}),
Radio({
label: 'Disabled option (cannot select)',
name: 'disabled-group',
value: selected,
inputValue: 'disabled',
disabled: true,
onclick: () => selected('disabled')
})
]);
};
$mount(DisabledDemo, '#demo-disabled');
```
### Reactive Preview
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ReactiveDemo = () => {
const size = $('medium');
const color = $('blue');
const sizes = [
{ value: 'small', label: 'Small' },
{ value: 'medium', label: 'Medium' },
{ value: 'large', label: 'Large' }
];
const colors = [
{ value: 'blue', label: 'Blue' },
{ value: 'green', label: 'Green' },
{ value: 'red', label: 'Red' }
];
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'text-sm font-bold' }, 'Select size'),
Div({ class: 'flex gap-4' }, sizes.map(s =>
Radio({
label: s.label,
name: 'size-group',
value: size,
inputValue: s.value,
onclick: () => size(s.value)
})
)),
Div({ class: 'text-sm font-bold mt-2' }, 'Select color'),
Div({ class: 'flex gap-4' }, colors.map(c =>
Radio({
label: c.label,
name: 'color-group',
value: color,
inputValue: c.value,
onclick: () => color(c.value)
})
)),
Div({
class: 'mt-4 p-4 rounded-lg text-center transition-all',
style: () => {
const sizeMap = { small: 'text-sm', medium: 'text-base', large: 'text-lg' };
const colorMap = { blue: '#3b82f6', green: '#10b981', red: '#ef4444' };
return `background-color: ${colorMap[color()]}; color: white; ${sizeMap[size()]}`;
}
}, () => `${size()} ${color()} preview`)
]);
};
$mount(ReactiveDemo, '#demo-reactive');
```
### Payment Method Selection
<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-payment" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const PaymentDemo = () => {
const method = $('credit');
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'text-sm font-bold' }, 'Payment method'),
Div({ class: 'flex flex-col gap-3' }, [
Radio({
label: '💳 Credit Card',
name: 'payment-group',
value: method,
inputValue: 'credit',
onclick: () => method('credit')
}),
Radio({
label: '🏦 Bank Transfer',
name: 'payment-group',
value: method,
inputValue: 'bank',
onclick: () => method('bank')
}),
Radio({
label: '📱 Digital Wallet',
name: 'payment-group',
value: method,
inputValue: 'wallet',
onclick: () => method('wallet')
})
]),
Div({ class: 'alert alert-info mt-2' }, () => {
const messages = {
credit: 'You selected Credit Card. Enter your card details.',
bank: 'You selected Bank Transfer. Use the provided account number.',
wallet: 'You selected Digital Wallet. Scan the QR code to pay.'
};
return messages[method()];
})
]);
};
$mount(PaymentDemo, '#demo-payment');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
const primary = $('primary1');
const secondary = $('secondary1');
const accent = $('accent1');
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'card bg-base-200 p-4' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Primary variant'),
Div({ class: 'flex gap-4' }, [
Radio({
label: 'Option A',
name: 'primary-group',
value: primary,
inputValue: 'primary1',
class: 'radio-primary',
onclick: () => primary('primary1')
}),
Radio({
label: 'Option B',
name: 'primary-group',
value: primary,
inputValue: 'primary2',
class: 'radio-primary',
onclick: () => primary('primary2')
})
])
]),
Div({ class: 'card bg-base-200 p-4' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Secondary variant'),
Div({ class: 'flex gap-4' }, [
Radio({
label: 'Option A',
name: 'secondary-group',
value: secondary,
inputValue: 'secondary1',
class: 'radio-secondary',
onclick: () => secondary('secondary1')
}),
Radio({
label: 'Option B',
name: 'secondary-group',
value: secondary,
inputValue: 'secondary2',
class: 'radio-secondary',
onclick: () => secondary('secondary2')
})
])
]),
Div({ class: 'card bg-base-200 p-4' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Accent variant'),
Div({ class: 'flex gap-4' }, [
Radio({
label: 'Option A',
name: 'accent-group',
value: accent,
inputValue: 'accent1',
class: 'radio-accent',
onclick: () => accent('accent1')
}),
Radio({
label: 'Option B',
name: 'accent-group',
value: accent,
inputValue: 'accent2',
class: 'radio-accent',
onclick: () => accent('accent2')
})
])
])
]);
};
$mount(VariantsDemo, '#demo-variants');
```
### Dynamic Options
<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-dynamic" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const DynamicDemo = () => {
const category = $('cars');
const selected = $('');
const options = {
cars: [
{ value: 'sedan', label: 'Sedan' },
{ value: 'suv', label: 'SUV' },
{ value: 'sports', label: 'Sports' }
],
colors: [
{ value: 'red', label: 'Red' },
{ value: 'blue', label: 'Blue' },
{ value: 'black', label: 'Black' }
]
};
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex gap-4' }, [
Radio({
label: 'Cars',
name: 'category-group',
value: category,
inputValue: 'cars',
onclick: () => {
category('cars');
selected('');
}
}),
Radio({
label: 'Colors',
name: 'category-group',
value: category,
inputValue: 'colors',
onclick: () => {
category('colors');
selected('');
}
})
]),
Div({ class: 'divider my-1' }),
Div({ class: 'text-sm font-bold' }, () => `Select ${category()}`),
Div({ class: 'flex flex-col gap-2' }, () =>
options[category()].map(opt =>
Radio({
label: opt.label,
name: 'dynamic-option-group',
value: selected,
inputValue: opt.value,
onclick: () => selected(opt.value)
})
)
),
() => selected()
? Div({ class: 'alert alert-success mt-2' }, () => `Selected ${category()}: ${selected()}`)
: null
]);
};
$mount(DynamicDemo, '#demo-dynamic');
```

258
docs/components/range.md Normal file
View File

@@ -0,0 +1,258 @@
# Range
Range slider component for selecting numeric values with labels, tooltips, and DaisyUI styling.
## Tag
`Range`
## Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `value` | `number \| Signal<number>` | `0` | Current slider value |
| `min` | `number` | `0` | Minimum value |
| `max` | `number` | `100` | Maximum value |
| `step` | `number` | `1` | Step increment |
| `label` | `string \| VNode \| Signal` | `-` | Label text for the slider |
| `tooltip` | `string` | `-` | Tooltip text on hover |
| `disabled` | `boolean \| Signal<boolean>` | `false` | Disabled state |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `oninput` | `function` | `-` | Input event handler |
## Styling
Range supports all **daisyUI Range classes**:
| Category | Keywords | Description |
| :--- | :--- | :--- |
| Color | `range-primary`, `range-secondary`, `range-accent`, `range-info`, `range-success`, `range-warning`, `range-error` | Range color variants |
| Size | `range-xs`, `range-sm`, `range-md`, `range-lg` | Range scale |
> For further details, check the [daisyUI Range Documentation](https://daisyui.com/components/range) Full reference for CSS classes.
## Live Examples
### Basic Range
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const value = $(50);
return Div({ class: 'flex flex-col gap-4' }, [
Range({
label: 'Volume',
value: value,
min: 0,
max: 100,
oninput: (e) => value(parseInt(e.target.value))
}),
Div({ class: 'text-center' }, () => `Value: ${value()}%`)
]);
};
$mount(BasicDemo, '#demo-basic');
```
### With Tooltip
<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-tooltip" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const TooltipDemo = () => {
const brightness = $(75);
return Div({ class: 'flex flex-col gap-4' }, [
Range({
label: 'Brightness',
value: brightness,
min: 0,
max: 100,
tooltip: () => `${brightness()}% brightness`,
oninput: (e) => brightness(parseInt(e.target.value))
}),
Div({ class: 'w-full h-20 rounded-lg transition-all', style: () => `background-color: hsl(0, 0%, ${brightness()}%)` })
]);
};
$mount(TooltipDemo, '#demo-tooltip');
```
### Color Variants
<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-colors" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const ColorsDemo = () => {
const primary = $(30);
const secondary = $(60);
const accent = $(90);
return Div({ class: 'flex flex-col gap-4' }, [
Range({ label: 'Primary', value: primary, class: 'range-primary', oninput: (e) => primary(e.target.value) }),
Range({ label: 'Secondary', value: secondary, class: 'range-secondary', oninput: (e) => secondary(e.target.value) }),
Range({ label: 'Accent', value: accent, class: 'range-accent', oninput: (e) => accent(e.target.value) })
]);
};
$mount(ColorsDemo, '#demo-colors');
```
### Size Variants
<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-sizes" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const SizesDemo = () => {
const xs = $(25);
const sm = $(50);
const md = $(75);
const lg = $(100);
return Div({ class: 'flex flex-col gap-4' }, [
Range({ label: 'Extra Small', value: xs, class: 'range-xs', oninput: (e) => xs(e.target.value) }),
Range({ label: 'Small', value: sm, class: 'range-sm', oninput: (e) => sm(e.target.value) }),
Range({ label: 'Medium', value: md, class: 'range-md', oninput: (e) => md(e.target.value) }),
Range({ label: 'Large', value: lg, class: 'range-lg', oninput: (e) => lg(e.target.value) })
]);
};
$mount(SizesDemo, '#demo-sizes');
```
### Price Range
<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-price" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const PriceDemo = () => {
const price = $(500);
const maxPrice = 1000;
return Div({ class: 'flex flex-col gap-4' }, [
Range({
label: 'Price Range',
value: price,
min: 0,
max: maxPrice,
step: 10,
oninput: (e) => price(parseInt(e.target.value))
}),
Div({ class: 'flex justify-between' }, [
Span({}, '$0'),
Span({}, () => `$${price()}`),
Span({}, `$${maxPrice}`)
]),
Div({ class: 'mt-2 text-sm opacity-70' }, () => {
if (price() < 250) return 'Budget friendly';
if (price() < 750) return 'Mid range';
return 'Premium';
})
]);
};
$mount(PriceDemo, '#demo-price');
```
### Audio Controls
<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-audio" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const AudioDemo = () => {
const volume = $(50);
const bass = $(40);
const treble = $(60);
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex items-center gap-3' }, [
Span({ class: 'w-12' }, '🔊'),
Range({
value: volume,
min: 0,
max: 100,
class: 'range-primary flex-1',
oninput: (e) => volume(e.target.value)
}),
Span({ class: 'w-12 text-right' }, () => `${volume()}%`)
]),
Div({ class: 'flex items-center gap-3' }, [
Span({ class: 'w-12' }, '🎵 Bass'),
Range({
value: bass,
min: 0,
max: 100,
class: 'range-secondary flex-1',
oninput: (e) => bass(e.target.value)
}),
Span({ class: 'w-12 text-right' }, () => `${bass()}%`)
]),
Div({ class: 'flex items-center gap-3' }, [
Span({ class: 'w-12' }, '🎶 Treble'),
Range({
value: treble,
min: 0,
max: 100,
class: 'range-accent flex-1',
oninput: (e) => treble(e.target.value)
}),
Span({ class: 'w-12 text-right' }, () => `${treble()}%`)
])
]);
};
$mount(AudioDemo, '#demo-audio');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
const value = $(50);
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'text-sm font-bold' }, 'With Label'),
Range({ label: 'Label', value: $(50), oninput: (e) => {} }),
Div({ class: 'text-sm font-bold mt-2' }, 'With Tooltip'),
Range({ tooltip: 'Tooltip message', value: $(50), oninput: (e) => {} }),
Div({ class: 'text-sm font-bold mt-2' }, 'Disabled'),
Range({ disabled: true, value: $(50), oninput: (e) => {} })
]);
};
$mount(VariantsDemo, '#demo-variants');
```

272
docs/components/rating.md Normal file
View File

@@ -0,0 +1,272 @@
# Rating
Star rating component with customizable count, icons, and read-only mode.
## Tag
`Rating`
## Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `value` | `number \| Signal<number>` | `0` | Current rating value |
| `count` | `number \| Signal<number>` | `5` | Number of stars/items |
| `mask` | `string` | `'mask-star'` | Mask shape (`mask-star`, `mask-star-2`, `mask-heart`) |
| `readonly` | `boolean \| Signal<boolean>` | `false` | Disable interaction |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `onchange` | `function` | `-` | Callback when rating changes |
## Styling
Rating supports all **daisyUI Rating classes**:
| Category | Keywords | Description |
| :--- | :--- | :--- |
| Base | `rating` | Base rating container |
| Color | `rating-primary`, `rating-secondary`, `rating-accent`, `rating-info`, `rating-success`, `rating-warning`, `rating-error` | Rating color variants |
| Size | `rating-xs`, `rating-sm`, `rating-md`, `rating-lg` | Rating scale |
| Mask | `mask-star`, `mask-star-2`, `mask-heart` | Rating icon shapes |
> For further details, check the [daisyUI Rating Documentation](https://daisyui.com/components/rating) Full reference for CSS classes.
## Live Examples
### Basic Rating
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const rating = $(3);
return Div({ class: 'flex flex-col gap-2 items-center' }, [
Rating({
value: rating,
count: 5,
onchange: (value) => rating(value)
}),
Div({ class: 'text-sm opacity-70' }, () => `Rating: ${rating()} / 5`)
]);
};
$mount(BasicDemo, '#demo-basic');
```
### Heart Rating
<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-heart" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const HeartDemo = () => {
const rating = $(4);
return Div({ class: 'flex flex-col gap-2 items-center' }, [
Rating({
value: rating,
count: 5,
mask: 'mask-heart',
onchange: (value) => rating(value)
}),
Div({ class: 'text-sm opacity-70' }, () => `${rating()} hearts`)
]);
};
$mount(HeartDemo, '#demo-heart');
```
### Star with Outline
<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-star2" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const Star2Demo = () => {
const rating = $(2);
return Div({ class: 'flex flex-col gap-2 items-center' }, [
Rating({
value: rating,
count: 5,
mask: 'mask-star-2',
onchange: (value) => rating(value)
}),
Div({ class: 'text-sm opacity-70' }, () => `${rating()} stars`)
]);
};
$mount(Star2Demo, '#demo-star2');
```
### Read-only Rating
<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-readonly" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const ReadonlyDemo = () => {
const rating = $(4.5);
return Div({ class: 'flex flex-col gap-2 items-center' }, [
Rating({
value: rating,
count: 5,
readonly: true
}),
Div({ class: 'text-sm opacity-70' }, 'Average rating: 4.5/5 (read-only)')
]);
};
$mount(ReadonlyDemo, '#demo-readonly');
```
### Product Review
<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-review" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ReviewDemo = () => {
const quality = $(4);
const price = $(3);
const support = $(5);
const average = () => Math.round(((quality() + price() + support()) / 3) * 10) / 10;
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'text-lg font-bold' }, 'Product Review'),
Div({ class: 'flex flex-col gap-2' }, [
Div({ class: 'flex justify-between items-center' }, [
Span({ class: 'text-sm w-24' }, 'Quality:'),
Rating({
value: quality,
count: 5,
onchange: (v) => quality(v)
})
]),
Div({ class: 'flex justify-between items-center' }, [
Span({ class: 'text-sm w-24' }, 'Price:'),
Rating({
value: price,
count: 5,
onchange: (v) => price(v)
})
]),
Div({ class: 'flex justify-between items-center' }, [
Span({ class: 'text-sm w-24' }, 'Support:'),
Rating({
value: support,
count: 5,
onchange: (v) => support(v)
})
])
]),
Div({ class: 'divider my-1' }),
Div({ class: 'flex justify-between items-center' }, [
Span({ class: 'font-bold' }, 'Overall:'),
Div({ class: 'text-2xl font-bold text-primary' }, () => average())
])
]);
};
$mount(ReviewDemo, '#demo-review');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-6"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
return Div({ class: 'flex flex-col gap-6' }, [
Div({ class: 'text-center' }, [
Div({ class: 'text-sm mb-2' }, 'Mask Star'),
Rating({ value: $(3), count: 5, mask: 'mask-star' })
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-sm mb-2' }, 'Mask Star 2 (yellow)'),
Rating({ value: $(4), count: 5, mask: 'mask-star-2', class: 'rating-warning' })
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-sm mb-2' }, 'Mask Heart'),
Rating({ value: $(5), count: 5, mask: 'mask-heart', class: 'rating-error' })
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-sm mb-2' }, 'Half Stars (read-only)'),
Rating({ value: $(3.5), count: 5, readonly: true })
])
]);
};
$mount(VariantsDemo, '#demo-variants');
```
### Interactive Feedback
<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-feedback" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const FeedbackDemo = () => {
const rating = $(0);
const messages = {
1: 'Very disappointed 😞',
2: 'Could be better 😕',
3: 'Good 👍',
4: 'Very good 😊',
5: 'Excellent! 🎉'
};
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Div({ class: 'text-center' }, [
Div({ class: 'text-sm mb-2' }, 'How was your experience?'),
Rating({
value: rating,
count: 5,
onchange: (value) => {
rating(value);
if (value >= 4) {
Toast('Thank you for your positive feedback!', 'alert-success', 2000);
} else if (value <= 2) {
Toast('We appreciate your feedback and will improve!', 'alert-warning', 2000);
} else {
Toast('Thanks for your rating!', 'alert-info', 2000);
}
}
})
]),
() => rating() > 0
? Div({ class: 'alert alert-soft text-center' }, [
messages[rating()] || `Rating: ${rating()} stars`
])
: null
]);
};
$mount(FeedbackDemo, '#demo-feedback');
```

217
docs/components/select.md Normal file
View File

@@ -0,0 +1,217 @@
# Select
Dropdown select component with full DaisyUI styling, reactive items, and form integration.
## Tag
`Select`
## Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `label` | `string` | `-` | Label text above select |
| `items` | `Array<{value: string, label: string}> \| Signal<Array>` | `[]` | Array of items with value and label |
| `value` | `string \| Signal<string>` | `''` | Selected value |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `disabled` | `boolean \| Signal<boolean>` | `false` | Disabled state |
| `onchange` | `function` | `-` | Change event handler |
## Styling
Select supports all **daisyUI Select classes**:
| Category | Keywords | Description |
| :--- | :--- | :--- |
| Color | `select-primary`, `select-secondary`, `select-accent`, `select-info`, `select-success`, `select-warning`, `select-error` | Select color variants |
| Size | `select-xs`, `select-sm`, `select-md`, `select-lg` | Select scale |
| Style | `select-bordered` (default), `select-ghost` | Visual style variants |
> For further details, check the [daisyUI Select Documentation](https://daisyui.com/components/select) Full reference for CSS classes.
## Live Examples
### Basic Select
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const selected = $('apple');
return Select({
label: 'Choose a fruit',
items: [
{ value: 'apple', label: '🍎 Apple' },
{ value: 'banana', label: '🍌 Banana' },
{ value: 'orange', label: '🍊 Orange' },
{ value: 'grape', label: '🍇 Grape' }
],
value: selected,
onchange: (e) => selected(e.target.value)
});
};
$mount(BasicDemo, '#demo-basic');
```
### With Reactive Display
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const ReactiveDemo = () => {
const selected = $('small');
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Select({
label: 'Select size',
items: [
{ value: 'small', label: 'Small' },
{ value: 'medium', label: 'Medium' },
{ value: 'large', label: 'Large' }
],
value: selected,
onchange: (e) => selected(e.target.value)
}),
Div({ class: 'alert alert-info' }, () => `You selected: ${selected()}`)
]);
};
$mount(ReactiveDemo, '#demo-reactive');
```
### Disabled State
<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-disabled" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const DisabledDemo = () => {
return Select({
label: 'Country (disabled)',
items: [
{ value: 'mx', label: 'Mexico' },
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' }
],
value: 'mx',
disabled: true
});
};
$mount(DisabledDemo, '#demo-disabled');
```
### Dynamic Items
<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-dynamic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const DynamicDemo = () => {
const category = $('fruits');
const selectedItem = $('');
const items = {
fruits: [
{ value: 'apple', label: '🍎 Apple' },
{ value: 'banana', label: '🍌 Banana' }
],
vegetables: [
{ value: 'carrot', label: '🥕 Carrot' },
{ value: 'broccoli', label: '🥦 Broccoli' }
]
};
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Select({
label: 'Category',
items: [
{ value: 'fruits', label: 'Fruits' },
{ value: 'vegetables', label: 'Vegetables' }
],
value: category,
onchange: (e) => {
category(e.target.value);
selectedItem('');
}
}),
Select({
label: 'Item',
items: () => items[category()] || [],
value: selectedItem,
onchange: (e) => selectedItem(e.target.value)
}),
() => selectedItem() ? Div({ class: 'alert alert-success' }, `Selected: ${selectedItem()}`) : null
]);
};
$mount(DynamicDemo, '#demo-dynamic');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
const primary = $('option1');
const secondary = $('option2');
const ghost = $('');
return Div({ class: 'flex flex-col gap-4' }, [
Select({
label: 'Primary Select',
class: 'select-primary',
items: [
{ value: 'option1', label: 'Option 1' },
{ value: 'option2', label: 'Option 2' },
{ value: 'option3', label: 'Option 3' }
],
value: primary,
onchange: (e) => primary(e.target.value)
}),
Select({
label: 'Secondary Select',
class: 'select-secondary',
items: [
{ value: 'option1', label: 'Option 1' },
{ value: 'option2', label: 'Option 2' }
],
value: secondary,
onchange: (e) => secondary(e.target.value)
}),
Select({
label: 'Ghost Select',
class: 'select-ghost',
items: [
{ value: '', label: 'Select an option' },
{ value: 'opt1', label: 'Option 1' },
{ value: 'opt2', label: 'Option 2' }
],
value: ghost,
onchange: (e) => ghost(e.target.value)
})
]);
};
$mount(VariantsDemo, '#demo-variants');
```

294
docs/components/stack.md Normal file
View File

@@ -0,0 +1,294 @@
# Stack
Stack component for layering multiple elements on top of each other, creating depth and visual hierarchy.
## Tag
`Stack`
## Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `children` | `Array<VNode> \| VNode` | `-` | Elements to stack (first is bottom, last is top) |
## Styling
Stack supports all **daisyUI Stack classes**:
| Category | Keywords | Description |
| :--- | :--- | :--- |
| Base | `stack` | Base stack container |
| Direction | `stack-top`, `stack-bottom`, `stack-start`, `stack-end` | Stack alignment |
| Width | `w-*` (Tailwind) | Custom width for the stack |
> For further details, check the [daisyUI Stack Documentation](https://daisyui.com/components/stack) Full reference for CSS classes.
## Live Examples
### Basic Stack
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
return Stack({ class: 'w-40' }, [
Div({ class: 'bg-primary text-primary-content rounded-lg p-4 shadow-lg' }, 'Layer 1'),
Div({ class: 'bg-secondary text-secondary-content rounded-lg p-4 shadow-lg' }, 'Layer 2'),
Div({ class: 'bg-accent text-accent-content rounded-lg p-4 shadow-lg' }, 'Layer 3')
]);
};
$mount(BasicDemo, '#demo-basic');
```
### Card Stack
<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-cards" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const CardsDemo = () => {
return Stack({ class: 'w-64' }, [
Div({ class: 'card bg-base-100 shadow-xl border border-base-300' }, [
Div({ class: 'card-body p-4' }, [
Span({ class: 'text-sm opacity-70' }, 'Back Card'),
Span({ class: 'font-bold' }, 'Additional info')
])
]),
Div({ class: 'card bg-primary text-primary-content shadow-xl' }, [
Div({ class: 'card-body p-4' }, [
Span({ class: 'text-sm' }, 'Front Card'),
Span({ class: 'font-bold text-lg' }, 'Main Content')
])
])
]);
};
$mount(CardsDemo, '#demo-cards');
```
### Avatar Stack
<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-avatars" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const AvatarsDemo = () => {
return Stack({ class: 'w-32' }, [
Div({ class: 'avatar placeholder' }, [
Div({ class: 'bg-neutral text-neutral-content rounded-full w-16 h-16 flex items-center justify-center' }, 'JD')
]),
Div({ class: 'avatar placeholder' }, [
Div({ class: 'bg-primary text-primary-content rounded-full w-16 h-16 flex items-center justify-center' }, 'JS')
]),
Div({ class: 'avatar placeholder' }, [
Div({ class: 'bg-secondary text-secondary-content rounded-full w-16 h-16 flex items-center justify-center' }, 'BC')
])
]);
};
$mount(AvatarsDemo, '#demo-avatars');
```
### Image Stack
<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-images" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const ImagesDemo = () => {
return Stack({ class: 'w-48' }, [
Div({ class: 'w-full h-32 bg-gradient-to-r from-primary to-secondary rounded-lg shadow-lg' }, [
Div({ class: 'p-2 text-white text-sm' }, 'Background Image')
]),
Div({ class: 'w-full h-32 bg-gradient-to-r from-secondary to-accent rounded-lg shadow-lg translate-x-2 translate-y-2' }, [
Div({ class: 'p-2 text-white text-sm' }, 'Middle Layer')
]),
Div({ class: 'w-full h-32 bg-gradient-to-r from-accent to-primary rounded-lg shadow-lg translate-x-4 translate-y-4 flex items-center justify-center' }, [
Span({ class: 'text-white font-bold' }, 'Top Layer')
])
]);
};
$mount(ImagesDemo, '#demo-images');
```
### Photo Gallery Stack
<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-gallery" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const GalleryDemo = () => {
const photos = [
{ color: 'bg-primary', label: 'Photo 1' },
{ color: 'bg-secondary', label: 'Photo 2' },
{ color: 'bg-accent', label: 'Photo 3' },
{ color: 'bg-info', label: 'Photo 4' }
];
return Stack({ class: 'w-48 cursor-pointer hover:scale-105 transition-transform' }, [
...photos.map((photo, idx) =>
Div({
class: `${photo.color} rounded-lg shadow-lg transition-all`,
style: `transform: translate(${idx * 4}px, ${idx * 4}px);`
}, [
Div({ class: 'p-4 text-white font-bold' }, photo.label)
])
)
]);
};
$mount(GalleryDemo, '#demo-gallery');
```
### Interactive Stack
<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-interactive" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const InteractiveDemo = () => {
const active = $(0);
const colors = ['primary', 'secondary', 'accent', 'info', 'success'];
const labels = ['Home', 'Profile', 'Settings', 'Messages', 'Notifications'];
return Div({ class: 'flex flex-col gap-6 items-center' }, [
Stack({ class: 'w-56' }, colors.map((color, idx) =>
Div({
class: () => `bg-${color} text-${color}-content rounded-lg p-4 shadow-lg transition-all cursor-pointer ${idx === active() ? 'scale-105 z-10' : ''}`,
style: () => `transform: translate(${idx * 8}px, ${idx * 8}px);`,
onclick: () => active(idx)
}, [
Div({ class: 'font-bold' }, labels[idx]),
Div({ class: 'text-sm opacity-80' }, `Layer ${idx + 1}`)
])
)),
Div({ class: 'mt-4 text-center' }, [
Span({ class: 'font-bold' }, () => `Active: ${labels[active()]}`),
Div({ class: 'flex gap-2 mt-2' }, colors.map((_, idx) =>
Button({
class: () => `btn btn-xs ${idx === active() ? 'btn-primary' : 'btn-ghost'}`,
onclick: () => active(idx)
}, `${idx + 1}`)
))
])
]);
};
$mount(InteractiveDemo, '#demo-interactive');
```
### Notification Stack
<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-notifications" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const NotificationsDemo = () => {
const notifications = $([
{ id: 1, message: 'New message from John', type: 'info' },
{ id: 2, message: 'Your order has shipped', type: 'success' },
{ id: 3, message: 'Meeting in 10 minutes', type: 'warning' }
]);
const removeNotification = (id) => {
notifications(notifications().filter(n => n.id !== id));
};
const typeClasses = {
info: 'bg-info text-info-content',
success: 'bg-success text-success-content',
warning: 'bg-warning text-warning-content',
error: 'bg-error text-error-content'
};
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Stack({ class: 'w-80' }, () => notifications().map((notif, idx) =>
Div({
class: () => `${typeClasses[notif.type]} rounded-lg p-3 shadow-lg transition-all cursor-pointer`,
style: () => `transform: translate(${idx * 4}px, ${idx * 4}px);`,
onclick: () => removeNotification(notif.id)
}, [
Div({ class: 'flex justify-between items-center' }, [
Span({ class: 'text-sm' }, notif.message),
Span({ class: 'text-xs opacity-70 cursor-pointer hover:opacity-100' }, '✕')
])
])
)),
() => notifications().length === 0
? Div({ class: 'alert alert-soft' }, 'No notifications')
: Button({
class: 'btn btn-sm btn-ghost mt-2',
onclick: () => notifications([])
}, 'Clear All')
]);
};
$mount(NotificationsDemo, '#demo-notifications');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-8 justify-center"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
Div({ class: 'text-center' }, [
Div({ class: 'text-sm mb-2' }, 'Small Stack'),
Stack({ class: 'w-24' }, [
Div({ class: 'bg-primary rounded p-2 text-xs' }, '1'),
Div({ class: 'bg-secondary rounded p-2 text-xs' }, '2'),
Div({ class: 'bg-accent rounded p-2 text-xs' }, '3')
])
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-sm mb-2' }, 'Medium Stack'),
Stack({ class: 'w-32' }, [
Div({ class: 'bg-primary rounded p-3' }, 'A'),
Div({ class: 'bg-secondary rounded p-3' }, 'B'),
Div({ class: 'bg-accent rounded p-3' }, 'C')
])
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-sm mb-2' }, 'Large Stack'),
Stack({ class: 'w-40' }, [
Div({ class: 'bg-primary rounded p-4' }, 'X'),
Div({ class: 'bg-secondary rounded p-4' }, 'Y'),
Div({ class: 'bg-accent rounded p-4' }, 'Z')
])
])
]);
};
$mount(VariantsDemo, '#demo-variants');
```

329
docs/components/stat.md Normal file
View File

@@ -0,0 +1,329 @@
# Stat
Statistic card component for displaying metrics, counts, and key performance indicators with optional icons and descriptions.
## Tag
`Stat`
## Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `label` | `string \| VNode \| Signal` | `-` | Statistic label/title |
| `value` | `string \| number \| Signal` | `-` | Main statistic value |
| `desc` | `string \| VNode \| Signal` | `-` | Description or trend text |
| `icon` | `string \| VNode \| Signal` | `-` | Icon displayed in the figure area |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
## Styling
Stat supports all **daisyUI Stat classes**:
| Category | Keywords | Description |
| :--- | :--- | :--- |
| Base | `stat` | Base stat container |
| Sections | `stat-figure`, `stat-title`, `stat-value`, `stat-desc` | Stat sub-components |
| Variants | `stat-compact` | Compact spacing variant |
> For further details, check the [daisyUI Stat Documentation](https://daisyui.com/components/stat) Full reference for CSS classes.
## Live Examples
### Basic Stat
<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-basic" 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 BasicDemo = () => {
return Div({ class: 'grid grid-cols-1 md:grid-cols-3 gap-4' }, [
Stat({
label: 'Total Users',
value: '2,345',
desc: '↗︎ 120 new users this month'
}),
Stat({
label: 'Revenue',
value: '$45,678',
desc: '↘︎ 5% decrease from last month'
}),
Stat({
label: 'Conversion Rate',
value: '3.45%',
desc: '↗︎ 0.5% increase'
})
]);
};
$mount(BasicDemo, '#demo-basic');
```
### With Icons
<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-icons" 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 IconsDemo = () => {
return Div({ class: 'grid grid-cols-1 md:grid-cols-3 gap-4' }, [
Stat({
label: 'Active Users',
value: '1,234',
desc: 'Currently online',
icon: Span({ class: 'text-2xl' }, '👥')
}),
Stat({
label: 'New Orders',
value: '89',
desc: 'Today',
icon: Span({ class: 'text-2xl' }, '📦')
}),
Stat({
label: 'Pending Tasks',
value: '23',
desc: 'Need attention',
icon: Span({ class: 'text-2xl' }, '⏳')
})
]);
};
$mount(IconsDemo, '#demo-icons');
```
### Reactive Values
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ReactiveDemo = () => {
const count = $(0);
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'grid grid-cols-1 md:grid-cols-2 gap-4' }, [
Stat({
label: 'Counter',
value: () => count(),
desc: 'Click the button to increase',
icon: Span({ class: 'text-2xl' }, '🔢')
}),
Stat({
label: 'Squared',
value: () => Math.pow(count(), 2),
desc: 'Square of counter',
icon: Span({ class: 'text-2xl' }, '📐')
})
]),
Div({ class: 'flex gap-2 justify-center' }, [
Button({
class: 'btn btn-primary',
onclick: () => count(count() + 1)
}, 'Increment'),
Button({
class: 'btn btn-ghost',
onclick: () => count(0)
}, 'Reset')
])
]);
};
$mount(ReactiveDemo, '#demo-reactive');
```
### Multiple Stats in Row
<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-multiple" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const MultipleDemo = () => {
return Div({ class: 'grid grid-cols-1 md:grid-cols-4 gap-4' }, [
Stat({
label: 'Posts',
value: '1,234',
desc: 'Total content',
icon: Span({ class: 'text-2xl' }, '📝')
}),
Stat({
label: 'Comments',
value: '8,901',
desc: 'Engagement',
icon: Span({ class: 'text-2xl' }, '💬')
}),
Stat({
label: 'Likes',
value: '12,345',
desc: 'Reactions',
icon: Span({ class: 'text-2xl' }, '❤️')
}),
Stat({
label: 'Shares',
value: '456',
desc: 'Viral reach',
icon: Span({ class: 'text-2xl' }, '🔄')
})
]);
};
$mount(MultipleDemo, '#demo-multiple');
```
### Dashboard Example
<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-dashboard" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const DashboardDemo = () => {
const stats = $({
users: 1245,
revenue: 89342,
orders: 342,
satisfaction: 94
});
const updateStats = () => {
stats({
users: stats().users + Math.floor(Math.random() * 50),
revenue: stats().revenue + Math.floor(Math.random() * 1000),
orders: stats().orders + Math.floor(Math.random() * 20),
satisfaction: Math.min(100, stats().satisfaction + Math.floor(Math.random() * 5) - 2)
});
};
return Div({ class: 'flex flex-col gap-6' }, [
Div({ class: 'grid grid-cols-1 md:grid-cols-4 gap-4' }, [
Stat({
label: 'Total Users',
value: () => stats().users.toLocaleString(),
desc: 'Registered users',
icon: Span({ class: 'text-2xl' }, '👥')
}),
Stat({
label: 'Revenue',
value: () => `$${stats().revenue.toLocaleString()}`,
desc: 'This month',
icon: Span({ class: 'text-2xl' }, '💰')
}),
Stat({
label: 'Orders',
value: () => stats().orders.toLocaleString(),
desc: 'Completed',
icon: Span({ class: 'text-2xl' }, '📦')
}),
Stat({
label: 'Satisfaction',
value: () => `${stats().satisfaction}%`,
desc: stats().satisfaction > 90 ? 'Excellent!' : 'Good',
icon: Span({ class: 'text-2xl' }, '😊')
})
]),
Div({ class: 'flex justify-center' }, [
Button({
class: 'btn btn-primary',
onclick: updateStats
}, 'Refresh Data')
])
]);
};
$mount(DashboardDemo, '#demo-dashboard');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 grid grid-cols-1 md:grid-cols-2 gap-4"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
return Div({ class: 'grid grid-cols-1 md:grid-cols-2 gap-4' }, [
Stat({
label: 'Primary Stat',
value: '1,234',
desc: 'With description',
icon: Span({ class: 'text-2xl' }, '⭐'),
class: 'bg-primary/10 text-primary'
}),
Stat({
label: 'Success Stat',
value: '89%',
desc: 'Success rate',
icon: Span({ class: 'text-2xl' }, '✅'),
class: 'bg-success/10 text-success'
}),
Stat({
label: 'Warning Stat',
value: '23',
desc: 'Pending items',
icon: Span({ class: 'text-2xl' }, '⚠️'),
class: 'bg-warning/10 text-warning'
}),
Stat({
label: 'Error Stat',
value: '5',
desc: 'Failed attempts',
icon: Span({ class: 'text-2xl' }, '❌'),
class: 'bg-error/10 text-error'
})
]);
};
$mount(VariantsDemo, '#demo-variants');
```
### Compact Stats
<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-compact" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-4"></div>
</div>
</div>
```javascript
const CompactDemo = () => {
return Div({ class: 'flex flex-wrap gap-4' }, [
Stat({
label: 'Views',
value: '12.3K',
class: 'stat-compact'
}),
Stat({
label: 'Likes',
value: '2,456',
class: 'stat-compact'
}),
Stat({
label: 'Comments',
value: '345',
class: 'stat-compact'
}),
Stat({
label: 'Shares',
value: '89',
class: 'stat-compact'
})
]);
};
$mount(CompactDemo, '#demo-compact');
```

292
docs/components/swap.md Normal file
View File

@@ -0,0 +1,292 @@
# Swap
Toggle component that swaps between two states (on/off) with customizable icons or content.
## Tag
`Swap`
## Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `value` | `boolean \| Signal<boolean>` | `false` | Swap state (true = on, false = off) |
| `on` | `string \| VNode \| Signal` | `-` | Content to show when state is on |
| `off` | `string \| VNode \| Signal` | `-` | Content to show when state is off |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `onclick` | `function` | `-` | Click event handler |
## Styling
Swap supports all **daisyUI Swap classes**:
| Category | Keywords | Description |
| :--- | :--- | :--- |
| Base | `swap` | Base swap container |
| Size | `swap-xs`, `swap-sm`, `swap-md`, `swap-lg` | Swap scale |
| Effect | `swap-rotate`, `swap-flip` | Animation effect on toggle |
> For further details, check the [daisyUI Swap Documentation](https://daisyui.com/components/swap) Full reference for CSS classes.
## Live Examples
### Basic Swap
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const isOn = $(false);
return Swap({
value: isOn,
on: "🌟 ON",
off: "💫 OFF",
onclick: () => isOn(!isOn())
});
};
$mount(BasicDemo, '#demo-basic');
```
### Icon Swap
<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-icons" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const IconsDemo = () => {
const isOn = $(false);
return Swap({
value: isOn,
on: "👁️",
off: "👁️‍🗨️",
onclick: () => isOn(!isOn())
});
};
$mount(IconsDemo, '#demo-icons');
```
### Emoji Swap
<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-emoji" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const EmojiDemo = () => {
const isOn = $(false);
return Swap({
value: isOn,
on: "❤️",
off: "🖤",
onclick: () => isOn(!isOn())
});
};
$mount(EmojiDemo, '#demo-emoji');
```
### Custom Content Swap
<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-custom" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const CustomDemo = () => {
const isOn = $(false);
return Swap({
value: isOn,
on: Div({ class: "badge badge-success gap-1" }, ["✅", " Active"]),
off: Div({ class: "badge badge-ghost gap-1" }, ["⭕", " Inactive"]),
onclick: () => isOn(!isOn())
});
};
$mount(CustomDemo, '#demo-custom');
```
### With Reactive State
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const ReactiveDemo = () => {
const isOn = $(false);
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Swap({
value: isOn,
on: "👁️",
off: "👁️‍🗨️",
onclick: () => isOn(!isOn())
}),
Div({ class: 'text-center' }, () =>
isOn()
? Div({ class: 'alert alert-success' }, 'Content is visible')
: Div({ class: 'alert alert-soft' }, 'Content is hidden')
)
]);
};
$mount(ReactiveDemo, '#demo-reactive');
```
### Toggle Mode Swap
<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-mode" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const ModeDemo = () => {
const darkMode = $(false);
const notifications = $(true);
const sound = $(false);
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Div({ class: 'flex justify-between items-center' }, [
Span({}, 'Dark mode'),
Swap({
value: darkMode,
on: "🌙",
off: "☀️",
onclick: () => darkMode(!darkMode())
})
]),
Div({ class: 'flex justify-between items-center' }, [
Span({}, 'Notifications'),
Swap({
value: notifications,
on: "🔔",
off: "🔕",
onclick: () => notifications(!notifications())
})
]),
Div({ class: 'flex justify-between items-center' }, [
Span({}, 'Sound effects'),
Swap({
value: sound,
on: "🔊",
off: "🔇",
onclick: () => sound(!sound())
})
]),
Div({ class: 'mt-2 p-3 rounded-lg', style: () => darkMode() ? 'background: #1f2937; color: white' : 'background: #f3f4f6' }, [
Div({ class: 'text-sm' }, () => `Mode: ${darkMode() ? 'Dark' : 'Light'} | Notifications: ${notifications() ? 'On' : 'Off'} | Sound: ${sound() ? 'On' : 'Off'}`)
])
]);
};
$mount(ModeDemo, '#demo-mode');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-8 justify-center"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center items-center' }, [
Div({ class: 'text-center' }, [
Div({ class: 'text-xs mb-2' }, 'Volume'),
Swap({
value: $(false),
on: "🔊",
off: "🔇"
})
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-xs mb-2' }, 'Like'),
Swap({
value: $(true),
on: "❤️",
off: "🤍"
})
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-xs mb-2' }, 'Star'),
Swap({
value: $(false),
on: "⭐",
off: "☆"
})
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-xs mb-2' }, 'Check'),
Swap({
value: $(true),
on: "✅",
off: "❌"
})
])
]);
};
$mount(VariantsDemo, '#demo-variants');
```
### Simple Todo Toggle
<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-todo" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const TodoDemo = () => {
const todos = [
{ id: 1, text: 'Complete documentation', completed: $(true) },
{ id: 2, text: 'Review pull requests', completed: $(false) },
{ id: 3, text: 'Deploy to production', completed: $(false) }
];
return Div({ class: 'flex flex-col gap-3' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Todo list'),
...todos.map(todo =>
Div({ class: 'flex items-center justify-between p-2 bg-base-200 rounded-lg' }, [
Span({ class: todo.completed() ? 'line-through opacity-50' : '' }, todo.text),
Swap({
value: todo.completed,
on: "✅",
off: "⬜",
onclick: () => todo.completed(!todo.completed())
})
])
),
Div({ class: 'mt-2 text-sm opacity-70' }, () => {
const completed = todos.filter(t => t.completed()).length;
return `${completed} of ${todos.length} tasks completed`;
})
]);
};
$mount(TodoDemo, '#demo-todo');
```

412
docs/components/table.md Normal file
View File

@@ -0,0 +1,412 @@
# Table
Data table component with sorting, pagination, zebra stripes, pin rows, and custom cell rendering.
## Tag
`Table`
## Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `items` | `Array \| Signal<Array>` | `[]` | Data array to display |
| `columns` | `Array<Column>` | `[]` | Column definitions |
| `keyFn` | `function` | `(item, idx) => idx` | Unique key function for rows |
| `zebra` | `boolean \| Signal<boolean>` | `false` | Enable zebra striping |
| `pinRows` | `boolean \| Signal<boolean>` | `false` | Pin header rows on scroll |
| `empty` | `string \| VNode` | `'No data'` | Content to show when no data |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
### Column Structure
| Property | Type | Description |
| :--- | :--- | :--- |
| `label` | `string` | Column header text |
| `key` | `string` | Property key to display from item |
| `render` | `function(item, index)` | Custom render function for cell content |
| `class` | `string` | Additional CSS classes for column cells |
| `footer` | `string \| VNode` | Footer content for the column |
## Styling
Table supports all **daisyUI Table classes**:
| Category | Keywords | Description |
| :--- | :--- | :--- |
| Base | `table` | Base table styling |
| Variant | `table-zebra` | Zebra striping |
| Size | `table-xs`, `table-sm`, `table-md`, `table-lg`, `table-xl` | Table scale |
| Feature | `table-pin-rows`, `table-pin-cols` | Pin headers/columns |
> For further details, check the [daisyUI Table Documentation](https://daisyui.com/components/table) Full reference for CSS classes.
## Live Examples
### Basic Table
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const users = [
{ id: 1, name: 'John Doe', email: 'john@example.com', role: 'Admin' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'User' },
{ id: 3, name: 'Bob Johnson', email: 'bob@example.com', role: 'Editor' }
];
return Table({
items: users,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' },
{ label: 'Email', key: 'email' },
{ label: 'Role', key: 'role' }
]
});
};
$mount(BasicDemo, '#demo-basic');
```
### With Zebra Stripes
<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-zebra" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ZebraDemo = () => {
const products = [
{ id: 1, name: 'Laptop', price: '$999', stock: 15 },
{ id: 2, name: 'Mouse', price: '$29', stock: 42 },
{ id: 3, name: 'Keyboard', price: '$79', stock: 28 },
{ id: 4, name: 'Monitor', price: '$299', stock: 12 }
];
return Table({
items: products,
columns: [
{ label: 'Product', key: 'name' },
{ label: 'Price', key: 'price' },
{ label: 'Stock', key: 'stock' }
],
zebra: true
});
};
$mount(ZebraDemo, '#demo-zebra');
```
### With Custom Cell Rendering
<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-custom" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const CustomDemo = () => {
const orders = [
{ id: 101, customer: 'Alice', amount: 250, status: 'completed' },
{ id: 102, customer: 'Bob', amount: 89, status: 'pending' },
{ id: 103, customer: 'Charlie', amount: 450, status: 'shipped' }
];
return Table({
items: orders,
columns: [
{ label: 'Order ID', key: 'id' },
{ label: 'Customer', key: 'customer' },
{
label: 'Amount',
key: 'amount',
render: (item) => `$${item.amount}`
},
{
label: 'Status',
key: 'status',
render: (item) => {
const statusClass = {
completed: 'badge badge-success',
pending: 'badge badge-warning',
shipped: 'badge badge-info'
};
return Span({ class: statusClass[item.status] }, item.status);
}
}
],
zebra: true
});
};
$mount(CustomDemo, '#demo-custom');
```
### With Footers
<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-footer" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const FooterDemo = () => {
const sales = [
{ month: 'January', revenue: 12500, expenses: 8900 },
{ month: 'February', revenue: 14200, expenses: 9200 },
{ month: 'March', revenue: 16800, expenses: 10100 }
];
const totalRevenue = sales.reduce((sum, item) => sum + item.revenue, 0);
const totalExpenses = sales.reduce((sum, item) => sum + item.expenses, 0);
return Table({
items: sales,
columns: [
{ label: 'Month', key: 'month' },
{
label: 'Revenue',
key: 'revenue',
render: (item) => `$${item.revenue.toLocaleString()}`,
footer: `Total: $${totalRevenue.toLocaleString()}`
},
{
label: 'Expenses',
key: 'expenses',
render: (item) => `$${item.expenses.toLocaleString()}`,
footer: `Total: $${totalExpenses.toLocaleString()}`
},
{
label: 'Profit',
render: (item) => `$${(item.revenue - item.expenses).toLocaleString()}`,
footer: `$${(totalRevenue - totalExpenses).toLocaleString()}`
}
],
zebra: true
});
};
$mount(FooterDemo, '#demo-footer');
```
### Empty State
<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-empty" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const EmptyDemo = () => {
const emptyList = [];
return Table({
items: emptyList,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' }
],
empty: Div({ class: 'flex flex-col items-center gap-2 p-4' }, [
Span({ class: 'text-2xl' }, '📭'),
Span({}, 'No records found')
])
});
};
$mount(EmptyDemo, '#demo-empty');
```
### Reactive Data
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ReactiveDemo = () => {
const filter = $('all');
const tasks = $([
{ id: 1, title: 'Complete documentation', completed: true },
{ id: 2, title: 'Review pull requests', completed: false },
{ id: 3, title: 'Deploy to production', completed: false },
{ id: 4, title: 'Update dependencies', completed: true }
]);
const filteredTasks = () => {
if (filter() === 'completed') {
return tasks().filter(t => t.completed);
} else if (filter() === 'pending') {
return tasks().filter(t => !t.completed);
}
return tasks();
};
const addTask = () => {
const newId = Math.max(...tasks().map(t => t.id), 0) + 1;
tasks([...tasks(), { id: newId, title: `Task ${newId}`, completed: false }]);
};
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex gap-2' }, [
Button({
class: 'btn btn-sm',
onclick: () => filter('all')
}, 'All'),
Button({
class: 'btn btn-sm',
onclick: () => filter('completed')
}, 'Completed'),
Button({
class: 'btn btn-sm',
onclick: () => filter('pending')
}, 'Pending'),
Button({
class: 'btn btn-sm btn-primary',
onclick: addTask
}, 'Add Task')
]),
Table({
items: filteredTasks,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Title', key: 'title' },
{
label: 'Status',
render: (item) => item.completed
? Span({ class: 'badge badge-success' }, '✓ Done')
: Span({ class: 'badge badge-warning' }, '○ Pending')
}
],
zebra: true
})
]);
};
$mount(ReactiveDemo, '#demo-reactive');
```
### With Actions
<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-actions" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ActionsDemo = () => {
const users = $([
{ id: 1, name: 'John Doe', email: 'john@example.com', active: true },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', active: false },
{ id: 3, name: 'Bob Johnson', email: 'bob@example.com', active: true }
]);
const deleteUser = (id) => {
users(users().filter(u => u.id !== id));
Toast('User deleted', 'alert-info', 2000);
};
const toggleActive = (id) => {
users(users().map(u =>
u.id === id ? { ...u, active: !u.active } : u
));
};
return Table({
items: users,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' },
{ label: 'Email', key: 'email' },
{
label: 'Status',
render: (item) => item.active
? Span({ class: 'badge badge-success' }, 'Active')
: Span({ class: 'badge badge-ghost' }, 'Inactive')
},
{
label: 'Actions',
render: (item) => Div({ class: 'flex gap-1' }, [
Button({
class: 'btn btn-xs btn-ghost',
onclick: () => toggleActive(item.id)
}, item.active ? 'Deactivate' : 'Activate'),
Button({
class: 'btn btn-xs btn-error',
onclick: () => deleteUser(item.id)
}, 'Delete')
])
}
],
zebra: true
});
};
$mount(ActionsDemo, '#demo-actions');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-6"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
const data = [
{ id: 1, name: 'Item 1', value: 100 },
{ id: 2, name: 'Item 2', value: 200 },
{ id: 3, name: 'Item 3', value: 300 }
];
return Div({ class: 'flex flex-col gap-6' }, [
Div({ class: 'text-sm font-bold' }, 'Default Table'),
Table({
items: data,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' },
{ label: 'Value', key: 'value' }
]
}),
Div({ class: 'text-sm font-bold mt-4' }, 'Zebra Stripes'),
Table({
items: data,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' },
{ label: 'Value', key: 'value' }
],
zebra: true
}),
Div({ class: 'text-sm font-bold mt-4' }, 'Compact Table'),
Table({
items: data,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' },
{ label: 'Value', key: 'value' }
],
class: 'table-compact'
})
]);
};
$mount(VariantsDemo, '#demo-variants');
```

409
docs/components/tabs.md Normal file
View File

@@ -0,0 +1,409 @@
# Tabs
Tabs component for organizing content into separate panels with tab navigation.
## Tag
`Tabs`
## Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `items` | `Array<TabItem> \| Signal<Array>` | `[]` | Array of tab items |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
### TabItem Structure
| Property | Type | Description |
| :--- | :--- | :--- |
| `label` | `string \| VNode` | Tab button label |
| `content` | `VNode \| function` | Content to display when tab is active |
| `active` | `boolean \| Signal<boolean>` | Whether this tab is active (only one per group) |
| `disabled` | `boolean` | Whether tab is disabled |
| `tip` | `string` | Tooltip text for the tab |
| `onclick` | `function` | Click handler (optional, overrides default) |
## Styling
Tabs supports all **daisyUI Tabs classes**:
| Category | Keywords | Description |
| :--- | :--- | :--- |
| Base | `tabs` | Base tabs container |
| Variant | `tabs-box`, `tabs-lifted` | Tab style variants |
| Size | `tabs-xs`, `tabs-sm`, `tabs-md`, `tabs-lg` | Tab scale |
| State | `tab-active`, `tab-disabled` | Tab states |
> For further details, check the [daisyUI Tabs Documentation](https://daisyui.com/components/tabs) Full reference for CSS classes.
### Example
```javascript
Tabs({
items: [
{ label: "Tab 1", content: "Content 1", active: true },
{ label: "Tab 2", content: "Content 2" }
],
class: "tabs-box"
});
```
## Live Examples
### Basic Tabs
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const activeTab = $('tab1');
return Tabs({
items: [
{
label: 'Tab 1',
active: () => activeTab() === 'tab1',
onclick: () => activeTab('tab1'),
content: Div({ class: 'p-4' }, 'Content for Tab 1')
},
{
label: 'Tab 2',
active: () => activeTab() === 'tab2',
onclick: () => activeTab('tab2'),
content: Div({ class: 'p-4' }, 'Content for Tab 2')
},
{
label: 'Tab 3',
active: () => activeTab() === 'tab3',
onclick: () => activeTab('tab3'),
content: Div({ class: 'p-4' }, 'Content for Tab 3')
}
]
});
};
$mount(BasicDemo, '#demo-basic');
```
### With Icons
<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-icons" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const IconsDemo = () => {
const activeTab = $('home');
return Tabs({
items: [
{
label: Span({ class: 'flex items-center gap-2' }, ['🏠', 'Home']),
active: () => activeTab() === 'home',
onclick: () => activeTab('home'),
content: Div({ class: 'p-4' }, 'Welcome to the Home tab!')
},
{
label: Span({ class: 'flex items-center gap-2' }, ['⭐', 'Favorites']),
active: () => activeTab() === 'favorites',
onclick: () => activeTab('favorites'),
content: Div({ class: 'p-4' }, 'Your favorite items appear here.')
},
{
label: Span({ class: 'flex items-center gap-2' }, ['⚙️', 'Settings']),
active: () => activeTab() === 'settings',
onclick: () => activeTab('settings'),
content: Div({ class: 'p-4' }, 'Configure your preferences.')
}
]
});
};
$mount(IconsDemo, '#demo-icons');
```
### With Tooltips
<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-tooltips" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const TooltipsDemo = () => {
const activeTab = $('profile');
return Tabs({
items: [
{
label: 'Profile',
tip: 'View your profile information',
active: () => activeTab() === 'profile',
onclick: () => activeTab('profile'),
content: Div({ class: 'p-4' }, 'Profile information here.')
},
{
label: 'Settings',
tip: 'Adjust your preferences',
active: () => activeTab() === 'settings',
onclick: () => activeTab('settings'),
content: Div({ class: 'p-4' }, 'Settings configuration.')
},
{
label: 'Notifications',
tip: 'Manage notifications',
active: () => activeTab() === 'notifications',
onclick: () => activeTab('notifications'),
content: Div({ class: 'p-4' }, 'Notification settings.')
}
]
});
};
$mount(TooltipsDemo, '#demo-tooltips');
```
### Disabled Tab
<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-disabled" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const DisabledDemo = () => {
const activeTab = $('basic');
return Tabs({
items: [
{
label: 'Basic',
active: () => activeTab() === 'basic',
onclick: () => activeTab('basic'),
content: Div({ class: 'p-4' }, 'Basic features available.')
},
{
label: 'Premium',
disabled: true,
tip: 'Upgrade to access',
content: Div({ class: 'p-4' }, 'Premium content (locked)')
},
{
label: 'Pro',
disabled: true,
tip: 'Coming soon',
content: Div({ class: 'p-4' }, 'Pro features (coming soon)')
}
]
});
};
$mount(DisabledDemo, '#demo-disabled');
```
### Reactive Content
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ReactiveDemo = () => {
const activeTab = $('counter');
const count = $(0);
return Tabs({
items: [
{
label: 'Counter',
active: () => activeTab() === 'counter',
onclick: () => activeTab('counter'),
content: Div({ class: 'p-4 text-center' }, [
Div({ class: 'text-4xl font-bold mb-4' }, () => count()),
Button({
class: 'btn btn-primary',
onclick: () => count(count() + 1)
}, 'Increment')
])
},
{
label: 'Timer',
active: () => activeTab() === 'timer',
onclick: () => activeTab('timer'),
content: Div({ class: 'p-4' }, () => `Current time: ${new Date().toLocaleTimeString()}`)
},
{
label: 'Status',
active: () => activeTab() === 'status',
onclick: () => activeTab('status'),
content: Div({ class: 'p-4' }, () => `Counter value: ${count()}, Last updated: ${new Date().toLocaleTimeString()}`)
}
]
});
};
$mount(ReactiveDemo, '#demo-reactive');
```
### Form Tabs
<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-form" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const FormTabs = () => {
const activeTab = $('personal');
const formData = $({
name: '',
email: '',
address: '',
city: '',
notifications: true,
newsletter: false
});
const updateField = (field, value) => {
formData({ ...formData(), [field]: value });
};
const handleSubmit = () => {
Toast('Form submitted!', 'alert-success', 2000);
console.log(formData());
};
return Div({ class: 'flex flex-col gap-4' }, [
Tabs({
items: [
{
label: 'Personal Info',
active: () => activeTab() === 'personal',
onclick: () => activeTab('personal'),
content: Div({ class: 'p-4 space-y-4' }, [
Input({
label: 'Name',
value: () => formData().name,
placeholder: 'Enter your name',
oninput: (e) => updateField('name', e.target.value)
}),
Input({
label: 'Email',
type: 'email',
value: () => formData().email,
placeholder: 'email@example.com',
oninput: (e) => updateField('email', e.target.value)
})
])
},
{
label: 'Address',
active: () => activeTab() === 'address',
onclick: () => activeTab('address'),
content: Div({ class: 'p-4 space-y-4' }, [
Input({
label: 'Address',
value: () => formData().address,
placeholder: 'Street address',
oninput: (e) => updateField('address', e.target.value)
}),
Input({
label: 'City',
value: () => formData().city,
placeholder: 'City',
oninput: (e) => updateField('city', e.target.value)
})
])
},
{
label: 'Preferences',
active: () => activeTab() === 'prefs',
onclick: () => activeTab('prefs'),
content: Div({ class: 'p-4 space-y-4' }, [
Checkbox({
label: 'Email notifications',
value: () => formData().notifications,
onclick: () => updateField('notifications', !formData().notifications)
}),
Checkbox({
label: 'Newsletter subscription',
value: () => formData().newsletter,
onclick: () => updateField('newsletter', !formData().newsletter)
})
])
}
]
}),
Div({ class: 'flex justify-end mt-4' }, [
Button({
class: 'btn btn-primary',
onclick: handleSubmit
}, 'Submit')
])
]);
};
$mount(FormTabs, '#demo-form');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-6"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
const active1 = $('tab1');
const active2 = $('tab1');
const active3 = $('tab1');
const createItems = (active) => [
{
label: 'Tab 1',
active: () => active() === 'tab1',
onclick: () => active('tab1'),
content: Div({ class: 'p-4' }, 'Content 1')
},
{
label: 'Tab 2',
active: () => active() === 'tab2',
onclick: () => active('tab2'),
content: Div({ class: 'p-4' }, 'Content 2')
},
{
label: 'Tab 3',
active: () => active() === 'tab3',
onclick: () => active('tab3'),
content: Div({ class: 'p-4' }, 'Content 3')
}
];
return Div({ class: 'flex flex-col gap-6' }, [
Div({ class: 'text-sm font-bold' }, 'Default Tabs'),
Tabs({ items: createItems(active1) }),
Div({ class: 'text-sm font-bold mt-4' }, 'Boxed Tabs'),
Tabs({ items: createItems(active2), class: 'tabs-box' }),
Div({ class: 'text-sm font-bold mt-4' }, 'Lifted Tabs'),
Tabs({ items: createItems(active3), class: 'tabs-lifted' })
]);
};
$mount(VariantsDemo, '#demo-variants');
```

294
docs/components/timeline.md Normal file
View File

@@ -0,0 +1,294 @@
# Timeline
Timeline component for displaying chronological events, steps, or progress with customizable icons and content.
## Tag
`Timeline`
## Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `items` | `Array<TimelineItem> \| Signal` | `[]` | Timeline items to display |
| `vertical` | `boolean \| Signal<boolean>` | `true` | Vertical or horizontal orientation |
| `compact` | `boolean \| Signal<boolean>` | `false` | Compact mode with less padding |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
### TimelineItem Structure
| Property | Type | Description |
| :--- | :--- | :--- |
| `title` | `string \| VNode \| Signal` | Event title or main text |
| `detail` | `string \| VNode \| Signal` | Additional details or description |
| `icon` | `string \| VNode` | Custom icon (overrides type) |
| `type` | `string` | Type: `'success'`, `'warning'`, `'error'`, `'info'` |
| `completed` | `boolean` | Whether event is completed (affects connector) |
## Styling
Timeline supports all **daisyUI Timeline classes**:
| Category | Keywords | Description |
| :--- | :--- | :--- |
| Base | `timeline`, `timeline-vertical`, `timeline-horizontal` | Timeline orientation |
| Size | `timeline-compact` | Compact spacing variant |
| Color | `bg-primary`, `bg-success`, `bg-warning`, `bg-error`, `bg-info` | Icon background colors |
> For further details, check the [daisyUI Timeline Documentation](https://daisyui.com/components/timeline) Full reference for CSS classes.
## Live Examples
### Basic Timeline
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const events = [
{ title: 'Project Started', detail: 'Initial planning and setup', type: 'info', completed: true },
{ title: 'Design Phase', detail: 'UI/UX design completed', type: 'success', completed: true },
{ title: 'Development', detail: 'Core features implemented', type: 'warning', completed: false },
{ title: 'Testing', detail: 'Quality assurance', type: 'info', completed: false },
{ title: 'Launch', detail: 'Production deployment', type: 'success', completed: false }
];
return Timeline({ items: events });
};
$mount(BasicDemo, '#demo-basic');
```
### Horizontal Timeline
<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-horizontal" class="bg-base-100 p-6 rounded-xl border border-base-300 overflow-x-auto"></div>
</div>
</div>
```javascript
const HorizontalDemo = () => {
const steps = [
{ title: 'Step 1', detail: 'Requirements', type: 'success', completed: true },
{ title: 'Step 2', detail: 'Design', type: 'success', completed: true },
{ title: 'Step 3', detail: 'Development', type: 'warning', completed: false },
{ title: 'Step 4', detail: 'Testing', type: 'info', completed: false },
{ title: 'Step 5', detail: 'Deploy', type: 'info', completed: false }
];
return Timeline({
items: steps,
vertical: false,
class: 'min-w-[600px]'
});
};
$mount(HorizontalDemo, '#demo-horizontal');
```
### Compact Timeline
<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-compact" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const CompactDemo = () => {
const activities = [
{ title: 'User login', detail: '10:30 AM', type: 'success', completed: true },
{ title: 'Viewed dashboard', detail: '10:32 AM', type: 'info', completed: true },
{ title: 'Updated profile', detail: '10:45 AM', type: 'success', completed: true },
{ title: 'Made purchase', detail: '11:00 AM', type: 'warning', completed: false }
];
return Timeline({
items: activities,
compact: true
});
};
$mount(CompactDemo, '#demo-compact');
```
### Custom Icons
<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-icons" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const IconsDemo = () => {
const milestones = [
{ title: 'Kickoff', detail: 'Project kickoff meeting', icon: '🚀', completed: true },
{ title: 'MVP', detail: 'Minimum viable product', icon: '💡', completed: true },
{ title: 'Beta', detail: 'Beta release', icon: '⚙️', completed: false },
{ title: 'Launch', detail: 'Public launch', icon: '🎉', completed: false }
];
return Timeline({ items: milestones });
};
$mount(IconsDemo, '#demo-icons');
```
### Reactive Timeline
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ReactiveDemo = () => {
const currentStep = $(0);
const steps = [
{ title: 'Order Placed', detail: 'Your order has been confirmed', type: 'success' },
{ title: 'Processing', detail: 'Payment verified, preparing shipment', type: 'success' },
{ title: 'Shipped', detail: 'Package on the way', type: 'warning' },
{ title: 'Delivered', detail: 'Arriving soon', type: 'info' }
];
const items = () => steps.map((step, idx) => ({
...step,
completed: idx < currentStep()
}));
const nextStep = () => {
if (currentStep() < steps.length) {
currentStep(currentStep() + 1);
Toast(`Step ${currentStep()}: ${steps[currentStep() - 1].title}`, 'alert-info', 1500);
}
};
const reset = () => {
currentStep(0);
Toast('Order tracking reset', 'alert-warning', 1500);
};
return Div({ class: 'flex flex-col gap-4' }, [
Timeline({ items: items }),
Div({ class: 'flex gap-2 justify-center mt-4' }, [
Button({
class: 'btn btn-primary btn-sm',
onclick: nextStep,
disabled: () => currentStep() >= steps.length
}, 'Next Step'),
Button({
class: 'btn btn-ghost btn-sm',
onclick: reset,
disabled: () => currentStep() === 0
}, 'Reset')
])
]);
};
$mount(ReactiveDemo, '#demo-reactive');
```
### Order Status Tracker
<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-order" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const OrderDemo = () => {
const status = $('pending'); // ← empezar en 'pending'
const statusMap = {
pending: { title: 'Order Pending', detail: 'Awaiting confirmation', type: 'warning' },
confirmed: { title: 'Order Confirmed', detail: 'Payment received', type: 'info' },
processing: { title: 'Processing', detail: 'Preparing your order', type: 'info' },
shipped: { title: 'Shipped', detail: 'Package in transit', type: 'info' },
delivered: { title: 'Delivered', detail: 'Order completed', type: 'success' }
};
const statusOrder = ['pending', 'confirmed', 'processing', 'shipped', 'delivered'];
const currentIndex = () => statusOrder.indexOf(status());
const items = () => statusOrder.map((key, idx) => ({
...statusMap[key],
completed: idx < currentIndex()
}));
return Div({ class: 'flex flex-col gap-4' }, [
Timeline({ items, compact: true }),
Div({ class: 'flex gap-2 justify-center flex-wrap mt-4' }, statusOrder.map(s =>
Button({
class: () => `btn btn-xs ${status() === s ? 'btn-primary' : 'btn-ghost'}`,
onclick: () => status(s)
}, statusMap[s].title)
))
]);
};
$mount(OrderDemo, '#demo-order');
```
### Company History
<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-history" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const HistoryDemo = () => {
const milestones = [
{ title: '2015 - Founded', detail: 'Company started with 3 employees', type: 'success', completed: true },
{ title: '2017 - First Product', detail: 'Launched first software product', type: 'success', completed: true },
{ title: '2019 - Series A', detail: 'Raised $5M, expanded to 50 employees', type: 'success', completed: true },
{ title: '2022 - Global Expansion', detail: 'Opened offices in Europe and Asia', type: 'info', completed: true },
{ title: '2024 - AI Integration', detail: 'Launched AI-powered features', type: 'warning', completed: false },
{ title: '2026 - Future Goals', detail: 'Aiming for market leadership', type: 'info', completed: false }
];
return Timeline({ items: milestones });
};
$mount(HistoryDemo, '#demo-history');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-8"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
const sampleItems = [
{ title: 'Event 1', detail: 'Description here', type: 'success', completed: true },
{ title: 'Event 2', detail: 'Description here', type: 'warning', completed: false },
{ title: 'Event 3', detail: 'Description here', type: 'info', completed: false }
];
return Div({ class: 'flex flex-col gap-8' }, [
Div({ class: 'text-sm font-bold' }, 'Vertical Timeline'),
Timeline({ items: sampleItems }),
Div({ class: 'text-sm font-bold mt-4' }, 'Horizontal Timeline'),
Timeline({ items: sampleItems, vertical: false, class: 'min-w-[500px]' }),
Div({ class: 'text-sm font-bold mt-4' }, 'Compact Timeline'),
Timeline({ items: sampleItems, compact: true })
]);
};
$mount(VariantsDemo, '#demo-variants');
```

343
docs/components/toast.md Normal file
View File

@@ -0,0 +1,343 @@
# Toast
Toast notification utility for displaying temporary messages that automatically dismiss after a specified duration. Can be used programmatically.
## Function
`Toast(message, type = 'alert-info', duration = 3500)`
| Param | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `message` | `string \| VNode` | `-` | Message content to display |
| `type` | `string` | `'alert-info'` | Alert type: `'alert-info'`, `'alert-success'`, `'alert-warning'`, `'alert-error'` |
| `duration` | `number` | `3500` | Auto-dismiss duration in milliseconds |
## Live Examples
### Basic Toast
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-2 justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
return Div({ class: 'flex flex-wrap gap-2 justify-center' }, [
Button({
class: 'btn btn-info',
onclick: () => Toast('This is an info message', 'alert-info', 3000)
}, 'Info Toast'),
Button({
class: 'btn btn-success',
onclick: () => Toast('Operation successful!', 'alert-success', 3000)
}, 'Success Toast'),
Button({
class: 'btn btn-warning',
onclick: () => Toast('Please check your input', 'alert-warning', 3000)
}, 'Warning Toast'),
Button({
class: 'btn btn-error',
onclick: () => Toast('An error occurred', 'alert-error', 3000)
}, 'Error Toast')
]);
};
$mount(BasicDemo, '#demo-basic');
```
### Different Durations
<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-duration" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-2 justify-center"></div>
</div>
</div>
```javascript
const DurationDemo = () => {
return Div({ class: 'flex flex-wrap gap-2 justify-center' }, [
Button({
class: 'btn btn-sm',
onclick: () => Toast('Short (1s)', 'alert-info', 1000)
}, '1 Second'),
Button({
class: 'btn btn-sm',
onclick: () => Toast('Normal (3s)', 'alert-success', 3000)
}, '3 Seconds'),
Button({
class: 'btn btn-sm',
onclick: () => Toast('Long (5s)', 'alert-warning', 5000)
}, '5 Seconds'),
Button({
class: 'btn btn-sm',
onclick: () => Toast('Very Long (8s)', 'alert-error', 8000)
}, '8 Seconds')
]);
};
$mount(DurationDemo, '#demo-duration');
```
### Interactive Toast
<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-interactive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const InteractiveDemo = () => {
const count = $(0);
const showRandomToast = () => {
const types = ['alert-info', 'alert-success', 'alert-warning', 'alert-error'];
const messages = [
'You clicked the button!',
'Action completed successfully',
'Processing your request...',
'Something interesting happened'
];
const randomType = types[Math.floor(Math.random() * types.length)];
const randomMessage = messages[Math.floor(Math.random() * messages.length)];
count(count() + 1);
Toast(`${randomMessage} (${count()})`, randomType, 2000);
};
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Button({
class: 'btn btn-primary',
onclick: showRandomToast
}, 'Show Random Toast'),
Div({ class: 'text-sm opacity-70' }, () => `Toasts shown: ${count()}`)
]);
};
$mount(InteractiveDemo, '#demo-interactive');
```
### Form Validation Toast
<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-form" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const FormToastDemo = () => {
const email = $('');
const password = $('');
const handleSubmit = () => {
if (!email()) {
Toast('Please enter your email', 'alert-warning', 3000);
return;
}
if (!email().includes('@')) {
Toast('Please enter a valid email address', 'alert-error', 3000);
return;
}
if (!password()) {
Toast('Please enter your password', 'alert-warning', 3000);
return;
}
if (password().length < 6) {
Toast('Password must be at least 6 characters', 'alert-error', 3000);
return;
}
Toast('Login successful! Redirecting...', 'alert-success', 2000);
};
return Div({ class: 'flex flex-col gap-4 max-w-md mx-auto' }, [
Div({ class: 'text-lg font-bold text-center' }, 'Login Form'),
Input({
label: 'Email',
type: 'email',
value: email,
placeholder: 'user@example.com',
oninput: (e) => email(e.target.value)
}),
Input({
label: 'Password',
type: 'password',
value: password,
placeholder: 'Enter password',
oninput: (e) => password(e.target.value)
}),
Button({
class: 'btn btn-primary',
onclick: handleSubmit
}, 'Login')
]);
};
$mount(FormToastDemo, '#demo-form');
```
### Success Feedback
<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-feedback" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const FeedbackDemo = () => {
const items = $([
{ id: 1, name: 'Item 1', saved: false },
{ id: 2, name: 'Item 2', saved: false },
{ id: 3, name: 'Item 3', saved: false }
]);
const saveItem = (id) => {
items(items().map(item =>
item.id === id ? { ...item, saved: true } : item
));
Toast(`Item ${id} saved successfully!`, 'alert-success', 2000);
};
const saveAll = () => {
items(items().map(item => ({ ...item, saved: true })));
Toast('All items saved!', 'alert-success', 2000);
};
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex justify-between items-center' }, [
Span({ class: 'font-bold' }, 'Items to Save'),
Button({
class: 'btn btn-sm btn-primary',
onclick: saveAll
}, 'Save All')
]),
Div({ class: 'flex flex-col gap-2' }, items().map(item =>
Div({ class: 'flex justify-between items-center p-3 bg-base-200 rounded-lg' }, [
Span({}, item.name),
item.saved
? Span({ class: 'badge badge-success' }, '✓ Saved')
: Button({
class: 'btn btn-xs btn-primary',
onclick: () => saveItem(item.id)
}, 'Save')
])
))
]);
};
$mount(FeedbackDemo, '#demo-feedback');
```
### Error Handling
<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-error" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ErrorDemo = () => {
const simulateApiCall = () => {
const success = Math.random() > 0.3;
if (success) {
Toast('Data loaded successfully!', 'alert-success', 2000);
} else {
Toast('Failed to load data. Please try again.', 'alert-error', 3000);
}
};
const simulateNetworkError = () => {
Toast('Network error: Unable to connect to server', 'alert-error', 4000);
};
const simulateTimeout = () => {
Toast('Request timeout (5s). Please check your connection.', 'alert-warning', 4000);
};
return Div({ class: 'flex flex-wrap gap-3 justify-center' }, [
Button({
class: 'btn btn-primary',
onclick: simulateApiCall
}, 'Simulate API Call'),
Button({
class: 'btn btn-error',
onclick: simulateNetworkError
}, 'Network Error'),
Button({
class: 'btn btn-warning',
onclick: simulateTimeout
}, 'Timeout')
]);
};
$mount(ErrorDemo, '#demo-error');
```
### Custom Messages
<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-custom" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-2 justify-center"></div>
</div>
</div>
```javascript
const CustomDemo = () => {
const showCustomToast = (type, message) => {
Toast(message, type, 3000);
};
return Div({ class: 'flex flex-wrap gap-2 justify-center' }, [
Button({
class: 'btn btn-info',
onclick: () => showCustomToast('alert-info', '📧 New email received from john@example.com')
}, 'Email'),
Button({
class: 'btn btn-success',
onclick: () => showCustomToast('alert-success', '💰 Payment of $49.99 completed')
}, 'Payment'),
Button({
class: 'btn btn-warning',
onclick: () => showCustomToast('alert-warning', '⚠️ Your session will expire in 5 minutes')
}, 'Session Warning'),
Button({
class: 'btn btn-error',
onclick: () => showCustomToast('alert-error', '🔒 Failed login attempt detected')
}, 'Security Alert')
]);
};
$mount(CustomDemo, '#demo-custom');
```
### Multiple Toasts
<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-multiple" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const MultipleDemo = () => {
const showMultipleToasts = () => {
Toast('First message', 'alert-info', 3000);
setTimeout(() => Toast('Second message', 'alert-success', 3000), 500);
setTimeout(() => Toast('Third message', 'alert-warning', 3000), 1000);
setTimeout(() => Toast('Fourth message', 'alert-error', 3000), 1500);
};
return Div({ class: 'flex justify-center' }, [
Button({
class: 'btn btn-primary',
onclick: showMultipleToasts
}, 'Show Multiple Toasts')
]);
};
$mount(MultipleDemo, '#demo-multiple');
```

319
docs/components/tooltip.md Normal file
View File

@@ -0,0 +1,319 @@
# Tooltip
Tooltip component for displaying helpful hints and additional information on hover.
## Tag
`Tooltip`
## Props
| Prop | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `tip` | `string \| VNode \| Signal` | `-` | Tooltip content to display on hover |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `children` | `VNode` | `-` | Element to attach the tooltip to |
## Styling
Tooltip supports all **daisyUI Tooltip classes**:
| Category | Keywords | Description |
| :--- | :--- | :--- |
| Position | `tooltip-top` (default), `tooltip-bottom`, `tooltip-left`, `tooltip-right` | Tooltip position |
| Color | `tooltip-primary`, `tooltip-secondary`, `tooltip-accent`, `tooltip-info`, `tooltip-success`, `tooltip-warning`, `tooltip-error` | Tooltip color variants |
> For further details, check the [daisyUI Tooltip Documentation](https://daisyui.com/components/tooltip) Full reference for CSS classes.
### Example
```javascript
Tooltip({ tip: "This is a tooltip", class: "tooltip-primary" }, [
Button({ class: "btn" }, "Hover me")
]);
```
## Live Examples
### Basic Tooltip
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-4 justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
return Div({ class: 'flex flex-wrap gap-4 justify-center' }, [
Tooltip({ tip: 'This is a tooltip' }, [
Button({ class: 'btn btn-primary' }, 'Hover me')
]),
Tooltip({ tip: 'Tooltips can be placed on any element' }, [
Span({ class: 'text-sm cursor-help border-b border-dashed' }, 'Help text')
]),
Tooltip({ tip: 'Icons can also have tooltips' }, [
Span({ class: 'text-2xl' }, '')
])
]);
};
$mount(BasicDemo, '#demo-basic');
```
### Tooltip Positions
<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-positions" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-8 justify-center"></div>
</div>
</div>
```javascript
const PositionsDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
Tooltip({ tip: 'Top tooltip', class: 'tooltip-top' }, [
Button({ class: 'btn btn-sm' }, 'Top')
]),
Tooltip({ tip: 'Bottom tooltip', class: 'tooltip-bottom' }, [
Button({ class: 'btn btn-sm' }, 'Bottom')
]),
Tooltip({ tip: 'Left tooltip', class: 'tooltip-left' }, [
Button({ class: 'btn btn-sm' }, 'Left')
]),
Tooltip({ tip: 'Right tooltip', class: 'tooltip-right' }, [
Button({ class: 'btn btn-sm' }, 'Right')
])
]);
};
$mount(PositionsDemo, '#demo-positions');
```
### Tooltip with Icons
<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-icons" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-8 justify-center"></div>
</div>
</div>
```javascript
const IconsDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
Tooltip({ tip: 'Save document' }, [
Button({ class: 'btn btn-ghost btn-circle' }, '💾')
]),
Tooltip({ tip: 'Edit item' }, [
Button({ class: 'btn btn-ghost btn-circle' }, '✏️')
]),
Tooltip({ tip: 'Delete permanently' }, [
Button({ class: 'btn btn-ghost btn-circle text-error' }, '🗑️')
]),
Tooltip({ tip: 'Settings' }, [
Button({ class: 'btn btn-ghost btn-circle' }, '⚙️')
])
]);
};
$mount(IconsDemo, '#demo-icons');
```
### Form Field Tooltips
<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-form" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const FormDemo = () => {
const username = $('');
const email = $('');
return Div({ class: 'flex flex-col gap-4 max-w-md mx-auto' }, [
Div({ class: 'flex items-center gap-2' }, [
Input({
label: 'Username',
value: username,
placeholder: 'Choose a username',
oninput: (e) => username(e.target.value)
}),
Tooltip({ tip: 'Must be at least 3 characters, letters and numbers only' }, [
Span({ class: 'cursor-help text-info' }, '?')
])
]),
Div({ class: 'flex items-center gap-2' }, [
Input({
label: 'Email',
type: 'email',
value: email,
placeholder: 'Enter your email',
oninput: (e) => email(e.target.value)
}),
Tooltip({ tip: 'We\'ll never share your email with anyone' }, [
Span({ class: 'cursor-help text-info' }, '?')
])
])
]);
};
$mount(FormDemo, '#demo-form');
```
### Interactive Tooltip
<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-interactive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const InteractiveDemo = () => {
const tooltipText = $('Hover over the button!');
const updateTooltip = (text) => {
tooltipText(text);
setTimeout(() => {
tooltipText('Hover over the button!');
}, 2000);
};
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Tooltip({ tip: () => tooltipText() }, [
Button({
class: 'btn btn-primary btn-lg',
onclick: () => Toast('Button clicked!', 'alert-info', 2000)
}, 'Interactive Button')
]),
Div({ class: 'flex gap-2 flex-wrap justify-center mt-4' }, [
Button({
class: 'btn btn-xs',
onclick: () => updateTooltip('You clicked the button!')
}, 'Change Tooltip'),
Button({
class: 'btn btn-xs',
onclick: () => updateTooltip('Try hovering now!')
}, 'Change Again')
])
]);
};
$mount(InteractiveDemo, '#demo-interactive');
```
### Rich Tooltip Content
<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-rich" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-4 justify-center"></div>
</div>
</div>
```javascript
const RichDemo = () => {
return Div({ class: 'flex flex-wrap gap-4 justify-center' }, [
Tooltip({
tip: Div({ class: 'text-left p-1' }, [
Div({ class: 'font-bold' }, 'User Info'),
Div({ class: 'text-xs' }, 'John Doe'),
Div({ class: 'text-xs' }, 'john@example.com'),
Div({ class: 'badge badge-xs badge-primary mt-1' }, 'Admin')
])
}, [
Button({ class: 'btn btn-outline' }, 'User Profile')
]),
Tooltip({
tip: Div({ class: 'text-left p-1' }, [
Div({ class: 'font-bold flex items-center gap-1' }, [
Span({}, '⚠️'),
Span({}, 'System Status')
]),
Div({ class: 'text-xs' }, 'All systems operational'),
Div({ class: 'text-xs text-success' }, '✓ 99.9% uptime')
])
}, [
Button({ class: 'btn btn-outline' }, 'System Status')
])
]);
};
$mount(RichDemo, '#demo-rich');
```
### Color Variants
<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-colors" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-4 justify-center"></div>
</div>
</div>
```javascript
const ColorsDemo = () => {
return Div({ class: 'flex flex-wrap gap-4 justify-center' }, [
Tooltip({ tip: 'Primary tooltip', class: 'tooltip-primary' }, [
Button({ class: 'btn btn-primary btn-sm' }, 'Primary')
]),
Tooltip({ tip: 'Secondary tooltip', class: 'tooltip-secondary' }, [
Button({ class: 'btn btn-secondary btn-sm' }, 'Secondary')
]),
Tooltip({ tip: 'Accent tooltip', class: 'tooltip-accent' }, [
Button({ class: 'btn btn-accent btn-sm' }, 'Accent')
]),
Tooltip({ tip: 'Info tooltip', class: 'tooltip-info' }, [
Button({ class: 'btn btn-info btn-sm' }, 'Info')
]),
Tooltip({ tip: 'Success tooltip', class: 'tooltip-success' }, [
Button({ class: 'btn btn-success btn-sm' }, 'Success')
]),
Tooltip({ tip: 'Warning tooltip', class: 'tooltip-warning' }, [
Button({ class: 'btn btn-warning btn-sm' }, 'Warning')
]),
Tooltip({ tip: 'Error tooltip', class: 'tooltip-error' }, [
Button({ class: 'btn btn-error btn-sm' }, 'Error')
])
]);
};
$mount(ColorsDemo, '#demo-colors');
```
### All Tooltip Positions
<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-all-positions" class="bg-base-100 p-6 rounded-xl border border-base-300 grid grid-cols-3 gap-4 justify-items-center"></div>
</div>
</div>
```javascript
const AllPositionsDemo = () => {
return Div({ class: 'grid grid-cols-3 gap-4 justify-items-center' }, [
Div({ class: 'col-start-2' }, [
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', ui: 'tooltip-left' }, [
Button({ class: 'btn btn-sm w-24' }, 'Left')
])
]),
Div({ class: 'col-start-3 row-start-2' }, [
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', ui: 'tooltip-bottom' }, [
Button({ class: 'btn btn-sm w-24' }, 'Bottom')
])
])
]);
};
$mount(AllPositionsDemo, '#demo-all-positions');
```

View File

@@ -0,0 +1,730 @@
# Accordion
Collapsible accordion component for organizing content into expandable sections with DaisyUI styling.
## Tag
`Accordion`
## Props
| Prop | Type | Default | Description |
| :----------- | :-------------------------------------- | :---------- | :----------------------------------------------- |
| `title` | `string \| VNode \| Signal` | Required | Accordion section title |
| `open` | `boolean \| Signal<boolean>` | `false` | Whether the accordion is expanded |
| `name` | `string` | `-` | Group name for radio-style accordions (only one open at a time) |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `children` | `VNode \| Array<VNode>` | Required | Content to display when expanded |
## Live Examples
### Basic Accordion
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const open1 = $(false);
const open2 = $(false);
const open3 = $(false);
return Div({ class: 'flex flex-col gap-2' }, [
Accordion({
title: 'Section 1',
open: open1,
onclick: () => open1(!open1())
}, [
Div({ class: 'p-2' }, 'Content for section 1. This is a basic accordion section.')
]),
Accordion({
title: 'Section 2',
open: open2,
onclick: () => open2(!open2())
}, [
Div({ class: 'p-2' }, 'Content for section 2. You can put any content here.')
]),
Accordion({
title: 'Section 3',
open: open3,
onclick: () => open3(!open3())
}, [
Div({ class: 'p-2' }, 'Content for section 3. Accordions are great for FAQs.')
])
]);
};
$mount(BasicDemo, '#demo-basic');
```
### Group Accordion (Radio Style)
<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-group" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const GroupDemo = () => {
const openSection = $('section1');
return Div({ class: 'flex flex-col gap-2' }, [
Accordion({
title: 'Section 1',
name: 'group',
open: () => openSection() === 'section1',
onclick: () => openSection('section1')
}, [
Div({ class: 'p-2' }, 'Content for section 1. Only one section can be open at a time.')
]),
Accordion({
title: 'Section 2',
name: 'group',
open: () => openSection() === 'section2',
onclick: () => openSection('section2')
}, [
Div({ class: 'p-2' }, 'Content for section 2. Opening this will close section 1.')
]),
Accordion({
title: 'Section 3',
name: 'group',
open: () => openSection() === 'section3',
onclick: () => openSection('section3')
}, [
Div({ class: 'p-2' }, 'Content for section 3. This is useful for FAQ sections.')
])
]);
};
$mount(GroupDemo, '#demo-group');
```
### FAQ Accordion
<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-faq" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const FaqDemo = () => {
const openFaq = $('faq1');
const faqs = [
{ id: 'faq1', question: 'What is this component?', answer: 'This is an accordion component built with DaisyUI and Tailwind CSS for creating collapsible content sections.' },
{ id: 'faq2', question: 'How do I use it?', answer: 'Simply import the Accordion component and pass title and children props. Use the open prop to control expansion.' },
{ id: 'faq3', question: 'Can I have multiple open?', answer: 'Yes! By default, accordions can be opened independently. Use the name prop to create groups where only one can be open.' },
{ id: 'faq4', question: 'Is it accessible?', answer: 'Yes, the accordion uses proper ARIA attributes and keyboard navigation support.' }
];
return Div({ class: 'flex flex-col gap-2' }, faqs.map(faq =>
Accordion({
title: faq.question,
name: 'faq-group',
open: () => openFaq() === faq.id,
onclick: () => openFaq(openFaq() === faq.id ? '' : faq.id)
}, [
Div({ class: 'p-2 text-sm' }, faq.answer)
])
));
};
$mount(FaqDemo, '#demo-faq');
```
### With Rich Content
<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-rich" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const RichDemo = () => {
const open1 = $(true);
const open2 = $(false);
return Div({ class: 'flex flex-col gap-2' }, [
Accordion({
title: Span({ class: 'flex items-center gap-2' }, ['📊', 'Statistics']),
open: open1,
onclick: () => open1(!open1())
}, [
Div({ class: 'p-2' }, [
Div({ class: 'grid grid-cols-2 gap-4' }, [
Div({ class: 'stat bg-base-100 rounded-lg p-3' }, [
Div({ class: 'stat-title' }, 'Users'),
Div({ class: 'stat-value text-lg' }, '1,234')
]),
Div({ class: 'stat bg-base-100 rounded-lg p-3' }, [
Div({ class: 'stat-title' }, 'Revenue'),
Div({ class: 'stat-value text-lg' }, '$45,678')
]),
Div({ class: 'stat bg-base-100 rounded-lg p-3' }, [
Div({ class: 'stat-title' }, 'Growth'),
Div({ class: 'stat-value text-lg text-success' }, '+23%')
]),
Div({ class: 'stat bg-base-100 rounded-lg p-3' }, [
Div({ class: 'stat-title' }, 'Active'),
Div({ class: 'stat-value text-lg' }, '89%')
])
])
])
]),
Accordion({
title: Span({ class: 'flex items-center gap-2' }, ['👥', 'Team Members']),
open: open2,
onclick: () => open2(!open2())
}, [
Div({ class: 'p-2 space-y-2' }, [
Div({ class: 'flex items-center gap-3 p-2 hover:bg-base-100 rounded-lg' }, [
Div({ class: 'avatar placeholder' }, [
Div({ class: 'bg-primary text-primary-content rounded-full w-10 h-10 flex items-center justify-center' }, 'JD')
]),
Div({ class: 'flex-1' }, [
Div({ class: 'font-medium' }, 'John Doe'),
Div({ class: 'text-sm opacity-70' }, 'Developer')
])
]),
Div({ class: 'flex items-center gap-3 p-2 hover:bg-base-100 rounded-lg' }, [
Div({ class: 'avatar placeholder' }, [
Div({ class: 'bg-secondary text-secondary-content rounded-full w-10 h-10 flex items-center justify-center' }, 'JS')
]),
Div({ class: 'flex-1' }, [
Div({ class: 'font-medium' }, 'Jane Smith'),
Div({ class: 'text-sm opacity-70' }, 'Designer')
])
])
])
])
]);
};
$mount(RichDemo, '#demo-rich');
```
### Form Accordion
<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-form" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const FormAccordion = () => {
const openStep = $('step1');
const formData = $({
name: '',
email: '',
address: '',
payment: 'credit'
});
const updateField = (field, value) => {
formData({ ...formData(), [field]: value });
};
const nextStep = () => {
if (openStep() === 'step1') openStep('step2');
else if (openStep() === 'step2') openStep('step3');
};
const prevStep = () => {
if (openStep() === 'step2') openStep('step1');
else if (openStep() === 'step3') openStep('step2');
};
const handleSubmit = () => {
Toast('Form submitted!', 'alert-success', 2000);
console.log(formData());
};
return Div({ class: 'flex flex-col gap-2' }, [
Accordion({
title: Span({ class: 'flex items-center gap-2' }, ['1⃣', 'Personal Information']),
name: 'form-steps',
open: () => openStep() === 'step1',
onclick: () => openStep('step1')
}, [
Div({ class: 'p-4 space-y-4' }, [
Input({
label: 'Full Name',
value: () => formData().name,
placeholder: 'Enter your name',
oninput: (e) => updateField('name', e.target.value)
}),
Input({
label: 'Email',
type: 'email',
value: () => formData().email,
placeholder: 'email@example.com',
oninput: (e) => updateField('email', e.target.value)
}),
Div({ class: 'flex justify-end mt-2' }, [
Button({
class: 'btn btn-primary btn-sm',
onclick: nextStep,
disabled: () => !formData().name || !formData().email
}, 'Next →')
])
])
]),
Accordion({
title: Span({ class: 'flex items-center gap-2' }, ['2⃣', 'Address']),
name: 'form-steps',
open: () => openStep() === 'step2',
onclick: () => openStep('step2')
}, [
Div({ class: 'p-4 space-y-4' }, [
Input({
label: 'Address',
value: () => formData().address,
placeholder: 'Street address',
oninput: (e) => updateField('address', e.target.value)
}),
Div({ class: 'flex justify-between mt-2' }, [
Button({ class: 'btn btn-ghost btn-sm', onclick: prevStep }, '← Back'),
Button({
class: 'btn btn-primary btn-sm',
onclick: nextStep
}, 'Next →')
])
])
]),
Accordion({
title: Span({ class: 'flex items-center gap-2' }, ['3⃣', 'Payment']),
name: 'form-steps',
open: () => openStep() === 'step3',
onclick: () => openStep('step3')
}, [
Div({ class: 'p-4 space-y-4' }, [
Div({ class: 'flex flex-col gap-2' }, [
Radio({
label: 'Credit Card',
value: () => formData().payment,
radioValue: 'credit',
onclick: () => updateField('payment', 'credit')
}),
Radio({
label: 'PayPal',
value: () => formData().payment,
radioValue: 'paypal',
onclick: () => updateField('payment', 'paypal')
}),
Radio({
label: 'Bank Transfer',
value: () => formData().payment,
radioValue: 'bank',
onclick: () => updateField('payment', 'bank')
})
]),
Div({ class: 'flex justify-between mt-2' }, [
Button({ class: 'btn btn-ghost btn-sm', onclick: prevStep }, '← Back'),
Button({ class: 'btn btn-success btn-sm', onclick: handleSubmit }, 'Submit')
])
])
])
]);
};
$mount(FormAccordion, '#demo-form');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-6"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
const open1 = $(true);
const open2 = $(false);
const open3 = $(false);
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'text-sm font-bold' }, 'Default Accordion'),
Div({ class: 'flex flex-col gap-2' }, [
Accordion({ title: 'Default style', open: open1, onclick: () => open1(!open1()) }, [
Div({ class: 'p-2' }, 'Default accordion with standard styling.')
])
]),
Div({ class: 'text-sm font-bold mt-2' }, 'Custom Styling'),
Div({ class: 'flex flex-col gap-2' }, [
Accordion({
title: Span({ class: 'text-primary font-bold' }, 'Primary Title'),
open: open2,
onclick: () => open2(!open2()),
class: 'bg-primary/5 border-primary/20'
}, [
Div({ class: 'p-2' }, 'Accordion with custom styling and primary color.')
])
]),
Div({ class: 'text-sm font-bold mt-2' }, 'With Icons'),
Div({ class: 'flex flex-col gap-2' }, [
Accordion({
title: Span({ class: 'flex items-center gap-2' }, ['✨', 'Featured Content']),
open: open3,
onclick: () => open3(!open3())
}, [
Div({ class: 'p-2' }, 'Accordion with emoji icons in the title.')
])
])
]);
};
$mount(VariantsDemo, '#demo-variants');
```
<script>
(function() {
const initAccordionExamples = () => {
// 1. Basic Accordion
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
const open1 = $(false);
const open2 = $(false);
const open3 = $(false);
return Div({ class: 'flex flex-col gap-2' }, [
Accordion({
title: 'Section 1',
open: open1,
onclick: () => open1(!open1())
}, [
Div({ class: 'p-2' }, 'Content for section 1. This is a basic accordion section.')
]),
Accordion({
title: 'Section 2',
open: open2,
onclick: () => open2(!open2())
}, [
Div({ class: 'p-2' }, 'Content for section 2. You can put any content here.')
]),
Accordion({
title: 'Section 3',
open: open3,
onclick: () => open3(!open3())
}, [
Div({ class: 'p-2' }, 'Content for section 3. Accordions are great for FAQs.')
])
]);
};
$mount(BasicDemo, basicTarget);
}
// 2. Group Accordion (Radio Style)
const groupTarget = document.querySelector('#demo-group');
if (groupTarget && !groupTarget.hasChildNodes()) {
const GroupDemo = () => {
const openSection = $('section1');
return Div({ class: 'flex flex-col gap-2' }, [
Accordion({
title: 'Section 1',
name: 'group',
open: () => openSection() === 'section1',
onclick: () => openSection('section1')
}, [
Div({ class: 'p-2' }, 'Content for section 1. Only one section can be open at a time.')
]),
Accordion({
title: 'Section 2',
name: 'group',
open: () => openSection() === 'section2',
onclick: () => openSection('section2')
}, [
Div({ class: 'p-2' }, 'Content for section 2. Opening this will close section 1.')
]),
Accordion({
title: 'Section 3',
name: 'group',
open: () => openSection() === 'section3',
onclick: () => openSection('section3')
}, [
Div({ class: 'p-2' }, 'Content for section 3. This is useful for FAQ sections.')
])
]);
};
$mount(GroupDemo, groupTarget);
}
// 3. FAQ Accordion
const faqTarget = document.querySelector('#demo-faq');
if (faqTarget && !faqTarget.hasChildNodes()) {
const FaqDemo = () => {
const openFaq = $('faq1');
const faqs = [
{ id: 'faq1', question: 'What is this component?', answer: 'This is an accordion component built with DaisyUI and Tailwind CSS for creating collapsible content sections.' },
{ id: 'faq2', question: 'How do I use it?', answer: 'Simply import the Accordion component and pass title and children props. Use the open prop to control expansion.' },
{ id: 'faq3', question: 'Can I have multiple open?', answer: 'Yes! By default, accordions can be opened independently. Use the name prop to create groups where only one can be open.' },
{ id: 'faq4', question: 'Is it accessible?', answer: 'Yes, the accordion uses proper ARIA attributes and keyboard navigation support.' }
];
return Div({ class: 'flex flex-col gap-2' }, faqs.map(faq =>
Accordion({
title: faq.question,
name: 'faq-group',
open: () => openFaq() === faq.id,
onclick: () => openFaq(openFaq() === faq.id ? '' : faq.id)
}, [
Div({ class: 'p-2 text-sm' }, faq.answer)
])
));
};
$mount(FaqDemo, faqTarget);
}
// 4. With Rich Content
const richTarget = document.querySelector('#demo-rich');
if (richTarget && !richTarget.hasChildNodes()) {
const RichDemo = () => {
const open1 = $(true);
const open2 = $(false);
return Div({ class: 'flex flex-col gap-2' }, [
Accordion({
title: Span({ class: 'flex items-center gap-2' }, ['📊', 'Statistics']),
open: open1,
onclick: () => open1(!open1())
}, [
Div({ class: 'p-2' }, [
Div({ class: 'grid grid-cols-2 gap-4' }, [
Div({ class: 'stat bg-base-100 rounded-lg p-3' }, [
Div({ class: 'stat-title' }, 'Users'),
Div({ class: 'stat-value text-lg' }, '1,234')
]),
Div({ class: 'stat bg-base-100 rounded-lg p-3' }, [
Div({ class: 'stat-title' }, 'Revenue'),
Div({ class: 'stat-value text-lg' }, '$45,678')
]),
Div({ class: 'stat bg-base-100 rounded-lg p-3' }, [
Div({ class: 'stat-title' }, 'Growth'),
Div({ class: 'stat-value text-lg text-success' }, '+23%')
]),
Div({ class: 'stat bg-base-100 rounded-lg p-3' }, [
Div({ class: 'stat-title' }, 'Active'),
Div({ class: 'stat-value text-lg' }, '89%')
])
])
])
]),
Accordion({
title: Span({ class: 'flex items-center gap-2' }, ['👥', 'Team Members']),
open: open2,
onclick: () => open2(!open2())
}, [
Div({ class: 'p-2 space-y-2' }, [
Div({ class: 'flex items-center gap-3 p-2 hover:bg-base-100 rounded-lg' }, [
Div({ class: 'avatar placeholder' }, [
Div({ class: 'bg-primary text-primary-content rounded-full w-10 h-10 flex items-center justify-center' }, 'JD')
]),
Div({ class: 'flex-1' }, [
Div({ class: 'font-medium' }, 'John Doe'),
Div({ class: 'text-sm opacity-70' }, 'Developer')
])
]),
Div({ class: 'flex items-center gap-3 p-2 hover:bg-base-100 rounded-lg' }, [
Div({ class: 'avatar placeholder' }, [
Div({ class: 'bg-secondary text-secondary-content rounded-full w-10 h-10 flex items-center justify-center' }, 'JS')
]),
Div({ class: 'flex-1' }, [
Div({ class: 'font-medium' }, 'Jane Smith'),
Div({ class: 'text-sm opacity-70' }, 'Designer')
])
])
])
])
]);
};
$mount(RichDemo, richTarget);
}
// 5. Form Accordion
const formTarget = document.querySelector('#demo-form');
if (formTarget && !formTarget.hasChildNodes()) {
const FormAccordion = () => {
const openStep = $('step1');
const formData = $({
name: '',
email: '',
address: '',
payment: 'credit'
});
const updateField = (field, value) => {
formData({ ...formData(), [field]: value });
};
const nextStep = () => {
if (openStep() === 'step1') openStep('step2');
else if (openStep() === 'step2') openStep('step3');
};
const prevStep = () => {
if (openStep() === 'step2') openStep('step1');
else if (openStep() === 'step3') openStep('step2');
};
const handleSubmit = () => {
Toast('Form submitted!', 'alert-success', 2000);
console.log(formData());
};
return Div({ class: 'flex flex-col gap-2' }, [
Accordion({
title: Span({ class: 'flex items-center gap-2' }, ['1⃣', 'Personal Information']),
name: 'form-steps',
open: () => openStep() === 'step1',
onclick: () => openStep('step1')
}, [
Div({ class: 'p-4 space-y-4' }, [
Input({
label: 'Full Name',
value: () => formData().name,
placeholder: 'Enter your name',
oninput: (e) => updateField('name', e.target.value)
}),
Input({
label: 'Email',
type: 'email',
value: () => formData().email,
placeholder: 'email@example.com',
oninput: (e) => updateField('email', e.target.value)
}),
Div({ class: 'flex justify-end mt-2' }, [
Button({
class: 'btn btn-primary btn-sm',
onclick: nextStep,
disabled: () => !formData().name || !formData().email
}, 'Next →')
])
])
]),
Accordion({
title: Span({ class: 'flex items-center gap-2' }, ['2⃣', 'Address']),
name: 'form-steps',
open: () => openStep() === 'step2',
onclick: () => openStep('step2')
}, [
Div({ class: 'p-4 space-y-4' }, [
Input({
label: 'Address',
value: () => formData().address,
placeholder: 'Street address',
oninput: (e) => updateField('address', e.target.value)
}),
Div({ class: 'flex justify-between mt-2' }, [
Button({ class: 'btn btn-ghost btn-sm', onclick: prevStep }, '← Back'),
Button({
class: 'btn btn-primary btn-sm',
onclick: nextStep
}, 'Next →')
])
])
]),
Accordion({
title: Span({ class: 'flex items-center gap-2' }, ['3⃣', 'Payment']),
name: 'form-steps',
open: () => openStep() === 'step3',
onclick: () => openStep('step3')
}, [
Div({ class: 'p-4 space-y-4' }, [
Div({ class: 'flex flex-col gap-2' }, [
Radio({
label: 'Credit Card',
value: () => formData().payment,
radioValue: 'credit',
onclick: () => updateField('payment', 'credit')
}),
Radio({
label: 'PayPal',
value: () => formData().payment,
radioValue: 'paypal',
onclick: () => updateField('payment', 'paypal')
}),
Radio({
label: 'Bank Transfer',
value: () => formData().payment,
radioValue: 'bank',
onclick: () => updateField('payment', 'bank')
})
]),
Div({ class: 'flex justify-between mt-2' }, [
Button({ class: 'btn btn-ghost btn-sm', onclick: prevStep }, '← Back'),
Button({ class: 'btn btn-success btn-sm', onclick: handleSubmit }, 'Submit')
])
])
])
]);
};
$mount(FormAccordion, formTarget);
}
// 6. All Variants
const variantsTarget = document.querySelector('#demo-variants');
if (variantsTarget && !variantsTarget.hasChildNodes()) {
const VariantsDemo = () => {
const open1 = $(true);
const open2 = $(false);
const open3 = $(false);
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'text-sm font-bold' }, 'Default Accordion'),
Div({ class: 'flex flex-col gap-2' }, [
Accordion({ title: 'Default style', open: open1, onclick: () => open1(!open1()) }, [
Div({ class: 'p-2' }, 'Default accordion with standard styling.')
])
]),
Div({ class: 'text-sm font-bold mt-2' }, 'Custom Styling'),
Div({ class: 'flex flex-col gap-2' }, [
Accordion({
title: Span({ class: 'text-primary font-bold' }, 'Primary Title'),
open: open2,
onclick: () => open2(!open2()),
class: 'bg-primary/5 border-primary/20'
}, [
Div({ class: 'p-2' }, 'Accordion with custom styling and primary color.')
])
]),
Div({ class: 'text-sm font-bold mt-2' }, 'With Icons'),
Div({ class: 'flex flex-col gap-2' }, [
Accordion({
title: Span({ class: 'flex items-center gap-2' }, ['✨', 'Featured Content']),
open: open3,
onclick: () => open3(!open3())
}, [
Div({ class: 'p-2' }, 'Accordion with emoji icons in the title.')
])
])
]);
};
$mount(VariantsDemo, variantsTarget);
}
};
initAccordionExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initAccordionExamples);
});
}
})();
</script>

View File

@@ -0,0 +1,521 @@
# Alert
Alert component for displaying contextual messages, notifications, and feedback with different severity levels. Supports soft and solid variants.
## Tag
`Alert`
## Props
| Prop | Type | Default | Description |
| :----------- | :--------------------------- | :---------- | :----------------------------------------------- |
| `type` | `string` | `'info'` | Alert type: 'info', 'success', 'warning', 'error' |
| `soft` | `boolean \| Signal<boolean>` | `true` | Use soft variant (subtle background) |
| `actions` | `VNode \| function` | `-` | Optional action buttons or content |
| `message` | `string \| VNode \| Signal` | `-` | Alert message content |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `children` | `string \| VNode` | `-` | Alert content (alternative to `message`) |
## Live Examples
### Basic Alerts
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-3"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
return Div({ class: 'flex flex-col gap-3' }, [
Alert({ type: 'info', message: 'This is an informational message.' }),
Alert({ type: 'success', message: 'Operation completed successfully!' }),
Alert({ type: 'warning', message: 'Please review your input before proceeding.' }),
Alert({ type: 'error', message: 'An error occurred while processing your request.' })
]);
};
$mount(BasicDemo, '#demo-basic');
```
### Soft vs Solid Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-3"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
return Div({ class: 'flex flex-col gap-3' }, [
Alert({ type: 'info', soft: true, message: 'Soft info alert (default)' }),
Alert({ type: 'info', soft: false, message: 'Solid info alert' }),
Alert({ type: 'success', soft: true, message: 'Soft success alert' }),
Alert({ type: 'success', soft: false, message: 'Solid success alert' })
]);
};
$mount(VariantsDemo, '#demo-variants');
```
### With Actions
<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-actions" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-3"></div>
</div>
</div>
```javascript
const ActionsDemo = () => {
const showUndo = $(false);
const deletedItem = $(null);
const deleteItem = (item) => {
deletedItem(item);
showUndo(true);
setTimeout(() => {
if (showUndo()) {
showUndo(false);
Toast('Item permanently deleted', 'alert-info', 2000);
}
}, 5000);
};
const undoDelete = () => {
showUndo(false);
Toast(`Restored: ${deletedItem()}`, 'alert-success', 2000);
};
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex gap-2' }, [
Button({ class: 'btn btn-sm', onclick: () => deleteItem('Document A') }, 'Delete Document A'),
Button({ class: 'btn btn-sm', onclick: () => deleteItem('Document B') }, 'Delete Document B')
]),
() => showUndo() ? Alert({
type: 'warning',
soft: true,
message: `Deleted: ${deletedItem()}`,
actions: Button({
class: 'btn btn-sm btn-primary',
onclick: undoDelete
}, 'Undo')
}) : null
]);
};
$mount(ActionsDemo, '#demo-actions');
```
### Dismissible Alert
<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-dismissible" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-3"></div>
</div>
</div>
```javascript
const DismissibleDemo = () => {
const visible = $(true);
return Div({ class: 'flex flex-col gap-3' }, [
() => visible() ? Alert({
type: 'info',
message: 'This alert can be dismissed. Click the X button to close.',
actions: Button({
class: 'btn btn-xs btn-circle btn-ghost',
onclick: () => visible(false)
}, '✕')
}) : null,
() => !visible() ? Button({
class: 'btn btn-sm btn-ghost',
onclick: () => visible(true)
}, 'Show Alert') : null
]);
};
$mount(DismissibleDemo, '#demo-dismissible');
```
### Reactive Alert
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ReactiveDemo = () => {
const email = $('');
const error = $('');
const validateEmail = (value) => {
email(value);
if (value && !value.includes('@')) {
error('Please enter a valid email address');
} else {
error('');
}
};
return Div({ class: 'flex flex-col gap-4' }, [
Input({
label: 'Email Address',
placeholder: 'Enter your email',
value: email,
oninput: (e) => validateEmail(e.target.value)
}),
() => error() ? Alert({ type: 'error', message: error() }) : null,
() => email() && !error() ? Alert({
type: 'success',
message: `Valid email: ${email()}`
}) : null
]);
};
$mount(ReactiveDemo, '#demo-reactive');
```
### Form Validation
<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-form" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const FormDemo = () => {
const name = $('');
const email = $('');
const submitted = $(false);
const errors = $({ name: '', email: '' });
const validate = () => {
const newErrors = {
name: name().trim() ? '' : 'Name is required',
email: email().trim() ? (email().includes('@') ? '' : 'Invalid email') : 'Email is required'
};
errors(newErrors);
return !newErrors.name && !newErrors.email;
};
const handleSubmit = () => {
if (validate()) {
submitted(true);
setTimeout(() => submitted(false), 3000);
Toast('Form submitted successfully!', 'alert-success', 2000);
}
};
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'text-lg font-bold' }, 'Contact Form'),
Input({
label: 'Name',
value: name,
error: () => errors().name,
oninput: (e) => {
name(e.target.value);
validate();
}
}),
Input({
label: 'Email',
value: email,
error: () => errors().email,
oninput: (e) => {
email(e.target.value);
validate();
}
}),
Button({ class: 'btn btn-primary', onclick: handleSubmit }, 'Submit'),
() => submitted() ? Alert({
type: 'success',
message: 'Thank you! We will contact you soon.'
}) : null,
() => (errors().name || errors().email) ? Alert({
type: 'error',
message: 'Please fix the errors above before submitting.'
}) : null
]);
};
$mount(FormDemo, '#demo-form');
```
### Icon Alerts
<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-icons" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-3"></div>
</div>
</div>
```javascript
const IconsDemo = () => {
return Div({ class: 'flex flex-col gap-3' }, [
Alert({ type: 'info', message: 'Information alert with custom icon' }),
Alert({ type: 'success', message: 'Success alert with custom icon' }),
Alert({ type: 'warning', message: 'Warning alert with custom icon' }),
Alert({ type: 'error', message: 'Error alert with custom icon' })
]);
};
$mount(IconsDemo, '#demo-icons');
```
### All Types
<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-all" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-3"></div>
</div>
</div>
```javascript
const AllTypesDemo = () => {
return Div({ class: 'flex flex-col gap-3' }, [
Alert({ type: 'info', message: ' This is an info alert' }),
Alert({ type: 'success', message: '✅ This is a success alert' }),
Alert({ type: 'warning', message: '⚠️ This is a warning alert' }),
Alert({ type: 'error', message: '❌ This is an error alert' })
]);
};
$mount(AllTypesDemo, '#demo-all');
```
<script>
(function() {
const initAlertExamples = () => {
// 1. Basic Alerts
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
return Div({ class: 'flex flex-col gap-3' }, [
Alert({ type: 'info', message: 'This is an informational message.' }),
Alert({ type: 'success', message: 'Operation completed successfully!' }),
Alert({ type: 'warning', message: 'Please review your input before proceeding.' }),
Alert({ type: 'error', message: 'An error occurred while processing your request.' })
]);
};
$mount(BasicDemo, basicTarget);
}
// 2. Soft vs Solid Variants
const variantsTarget = document.querySelector('#demo-variants');
if (variantsTarget && !variantsTarget.hasChildNodes()) {
const VariantsDemo = () => {
return Div({ class: 'flex flex-col gap-3' }, [
Alert({ type: 'info', soft: true, message: 'Soft info alert (default)' }),
Alert({ type: 'info', soft: false, message: 'Solid info alert' }),
Alert({ type: 'success', soft: true, message: 'Soft success alert' }),
Alert({ type: 'success', soft: false, message: 'Solid success alert' })
]);
};
$mount(VariantsDemo, variantsTarget);
}
// 3. With Actions
const actionsTarget = document.querySelector('#demo-actions');
if (actionsTarget && !actionsTarget.hasChildNodes()) {
const ActionsDemo = () => {
const showUndo = $(false);
const deletedItem = $(null);
const deleteItem = (item) => {
deletedItem(item);
showUndo(true);
setTimeout(() => {
if (showUndo()) {
showUndo(false);
Toast('Item permanently deleted', 'alert-info', 2000);
}
}, 5000);
};
const undoDelete = () => {
showUndo(false);
Toast(`Restored: ${deletedItem()}`, 'alert-success', 2000);
};
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex gap-2' }, [
Button({ class: 'btn btn-sm', onclick: () => deleteItem('Document A') }, 'Delete Document A'),
Button({ class: 'btn btn-sm', onclick: () => deleteItem('Document B') }, 'Delete Document B')
]),
() => showUndo() ? Alert({
type: 'warning',
soft: true,
message: `Deleted: ${deletedItem()}`,
actions: Button({
class: 'btn btn-sm btn-primary',
onclick: undoDelete
}, 'Undo')
}) : null
]);
};
$mount(ActionsDemo, actionsTarget);
}
// 4. Dismissible Alert
const dismissibleTarget = document.querySelector('#demo-dismissible');
if (dismissibleTarget && !dismissibleTarget.hasChildNodes()) {
const DismissibleDemo = () => {
const visible = $(true);
return Div({ class: 'flex flex-col gap-3' }, [
() => visible() ? Alert({
type: 'info',
message: 'This alert can be dismissed. Click the X button to close.',
actions: Button({
class: 'btn btn-xs btn-circle btn-ghost',
onclick: () => visible(false)
}, '✕')
}) : null,
() => !visible() ? Button({
class: 'btn btn-sm btn-ghost',
onclick: () => visible(true)
}, 'Show Alert') : null
]);
};
$mount(DismissibleDemo, dismissibleTarget);
}
// 5. Reactive Alert
const reactiveTarget = document.querySelector('#demo-reactive');
if (reactiveTarget && !reactiveTarget.hasChildNodes()) {
const ReactiveDemo = () => {
const email = $('');
const error = $('');
const validateEmail = (value) => {
email(value);
if (value && !value.includes('@')) {
error('Please enter a valid email address');
} else {
error('');
}
};
return Div({ class: 'flex flex-col gap-4' }, [
Input({
label: 'Email Address',
placeholder: 'Enter your email',
value: email,
oninput: (e) => validateEmail(e.target.value)
}),
() => error() ? Alert({ type: 'error', message: error() }) : null,
() => email() && !error() ? Alert({
type: 'success',
message: `Valid email: ${email()}`
}) : null
]);
};
$mount(ReactiveDemo, reactiveTarget);
}
// 6. Form Validation
const formTarget = document.querySelector('#demo-form');
if (formTarget && !formTarget.hasChildNodes()) {
const FormDemo = () => {
const name = $('');
const email = $('');
const submitted = $(false);
const errors = $({ name: '', email: '' });
const validate = () => {
const newErrors = {
name: name().trim() ? '' : 'Name is required',
email: email().trim() ? (email().includes('@') ? '' : 'Invalid email') : 'Email is required'
};
errors(newErrors);
return !newErrors.name && !newErrors.email;
};
const handleSubmit = () => {
if (validate()) {
submitted(true);
setTimeout(() => submitted(false), 3000);
Toast('Form submitted successfully!', 'alert-success', 2000);
}
};
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'text-lg font-bold' }, 'Contact Form'),
Input({
label: 'Name',
value: name,
error: () => errors().name,
oninput: (e) => {
name(e.target.value);
validate();
}
}),
Input({
label: 'Email',
value: email,
error: () => errors().email,
oninput: (e) => {
email(e.target.value);
validate();
}
}),
Button({ class: 'btn btn-primary', onclick: handleSubmit }, 'Submit'),
() => submitted() ? Alert({
type: 'success',
message: 'Thank you! We will contact you soon.'
}) : null,
() => (errors().name || errors().email) ? Alert({
type: 'error',
message: 'Please fix the errors above before submitting.'
}) : null
]);
};
$mount(FormDemo, formTarget);
}
// 7. Icon Alerts
const iconsTarget = document.querySelector('#demo-icons');
if (iconsTarget && !iconsTarget.hasChildNodes()) {
const IconsDemo = () => {
return Div({ class: 'flex flex-col gap-3' }, [
Alert({ type: 'info', message: 'Information alert with custom icon' }),
Alert({ type: 'success', message: 'Success alert with custom icon' }),
Alert({ type: 'warning', message: 'Warning alert with custom icon' }),
Alert({ type: 'error', message: 'Error alert with custom icon' })
]);
};
$mount(IconsDemo, iconsTarget);
}
// 8. All Types
const allTarget = document.querySelector('#demo-all');
if (allTarget && !allTarget.hasChildNodes()) {
const AllTypesDemo = () => {
return Div({ class: 'flex flex-col gap-3' }, [
Alert({ type: 'info', message: ' This is an info alert' }),
Alert({ type: 'success', message: '✅ This is a success alert' }),
Alert({ type: 'warning', message: '⚠️ This is a warning alert' }),
Alert({ type: 'error', message: '❌ This is an error alert' })
]);
};
$mount(AllTypesDemo, allTarget);
}
};
initAlertExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initAlertExamples);
});
}
})();
</script>

View File

@@ -0,0 +1,376 @@
# Autocomplete
Searchable dropdown with autocomplete functionality, keyboard navigation, and reactive options.
## Tag
`Autocomplete`
## Props
| Prop | Type | Default | Description |
| :----------- | :------------------------------------------------ | :------------------ | :----------------------------------------------- |
| `label` | `string` | `-` | Label text for the input |
| `options` | `Array<string \| {value: string, label: string}>` | `[]` | Options to search from |
| `value` | `string \| Signal<string>` | `''` | Selected value |
| `placeholder`| `string` | `'Search...'` | Placeholder text |
| `onSelect` | `function` | `-` | Called when an option is selected |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
## Live Examples
### Basic Autocomplete
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const selected = $('');
const fruits = ['Apple', 'Banana', 'Orange', 'Grape', 'Strawberry', 'Mango', 'Pineapple', 'Watermelon'];
return Autocomplete({
label: 'Search fruit',
options: fruits,
value: selected,
onSelect: (value) => selected(value)
});
};
$mount(BasicDemo, '#demo-basic');
```
### With Objects
<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-objects" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const ObjectsDemo = () => {
const selected = $('');
const selectedLabel = $('');
const countries = [
{ value: 'mx', label: 'Mexico' },
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' },
{ value: 'br', label: 'Brazil' },
{ value: 'ar', label: 'Argentina' },
{ value: 'es', label: 'Spain' }
];
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Autocomplete({
label: 'Search country',
options: countries,
value: selectedLabel,
onSelect: (item) => {
const selectedItem = typeof item === 'string'
? countries.find(c => c.label === item)
: item;
selected(selectedItem?.value || '');
selectedLabel(selectedItem?.label || '');
}
}),
Div({ class: 'alert alert-success' }, [
`Selected: ${selected()} - ${selectedLabel()}`
])
]);
};
$mount(ObjectsDemo, '#demo-objects');
```
### With Reactive Display
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const ReactiveDemo = () => {
const selected = $('');
const programmingLanguages = [
'JavaScript', 'Python', 'Java', 'C++', 'Ruby', 'Go', 'Rust', 'TypeScript', 'Swift', 'Kotlin'
];
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Autocomplete({
label: 'Programming language',
options: programmingLanguages,
value: selected,
onSelect: (value) => selected(value)
}),
() => selected() ? Div({ class: 'alert alert-info' }, [
`You selected: ${selected()}`
]) : null
]);
};
$mount(ReactiveDemo, '#demo-reactive');
```
### Dynamic Options
<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-dynamic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const DynamicDemo = () => {
const selected = $('');
const filterType = $('all');
const allItems = {
fruits: ['Apple', 'Banana', 'Orange', 'Mango'],
vegetables: ['Carrot', 'Broccoli', 'Spinach', 'Potato'],
all: ['Apple', 'Banana', 'Orange', 'Mango', 'Carrot', 'Broccoli', 'Spinach', 'Potato']
};
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Select({
label: 'Category',
options: [
{ value: 'all', label: 'All items' },
{ value: 'fruits', label: 'Fruits' },
{ value: 'vegetables', label: 'Vegetables' }
],
value: filterType,
onchange: (e) => filterType(e.target.value)
}),
Autocomplete({
label: 'Search item',
options: () => allItems[filterType()],
value: selected,
onSelect: (value) => selected(value)
})
]);
};
$mount(DynamicDemo, '#demo-dynamic');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
const colors = ['Red', 'Blue', 'Green', 'Yellow', 'Purple', 'Orange', 'Pink', 'Brown', 'Black', 'White'];
return Div({ class: 'flex flex-col gap-4' }, [
Div({}, [
Autocomplete({
label: 'Primary style',
class: 'input-primary',
options: colors,
value: $(''),
placeholder: 'Search colors...'
})
]),
Div({}, [
Autocomplete({
label: 'Secondary style',
class: 'input-secondary',
options: colors,
value: $(''),
placeholder: 'Search colors...'
})
]),
Div({}, [
Autocomplete({
label: 'Ghost style',
class: 'input-ghost',
options: colors,
value: $(''),
placeholder: 'Search colors...'
})
])
]);
};
$mount(VariantsDemo, '#demo-variants');
```
<script>
(function() {
const initAutocompleteExamples = () => {
// 1. Basic Autocomplete
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
const selected = $('');
const fruits = ['Apple', 'Banana', 'Orange', 'Grape', 'Strawberry', 'Mango', 'Pineapple', 'Watermelon'];
return Autocomplete({
label: 'Search fruit',
options: fruits,
value: selected,
onSelect: (value) => selected(value)
});
};
$mount(BasicDemo, basicTarget);
}
// 2. With Objects
const objectsTarget = document.querySelector('#demo-objects');
if (objectsTarget && !objectsTarget.hasChildNodes()) {
const ObjectsDemo = () => {
const selected = $('');
const selectedLabel = $('');
const countries = [
{ value: 'mx', label: 'Mexico' },
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' },
{ value: 'br', label: 'Brazil' },
{ value: 'ar', label: 'Argentina' },
{ value: 'es', label: 'Spain' }
];
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Autocomplete({
label: 'Search country',
options: countries,
value: selectedLabel,
onSelect: (item) => {
const selectedItem = typeof item === 'string'
? countries.find(c => c.label === item)
: item;
selected(selectedItem?.value || '');
selectedLabel(selectedItem?.label || '');
}
}),
Div({ class: 'alert alert-success' }, [
`Selected: ${selected()} - ${selectedLabel()}`
])
]);
};
$mount(ObjectsDemo, objectsTarget);
}
// 3. Reactive Display
const reactiveTarget = document.querySelector('#demo-reactive');
if (reactiveTarget && !reactiveTarget.hasChildNodes()) {
const ReactiveDemo = () => {
const selected = $('');
const programmingLanguages = [
'JavaScript', 'Python', 'Java', 'C++', 'Ruby', 'Go', 'Rust', 'TypeScript', 'Swift', 'Kotlin'
];
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Autocomplete({
label: 'Programming language',
options: programmingLanguages,
value: selected,
onSelect: (value) => selected(value)
}),
() => selected() ? Div({ class: 'alert alert-info' }, [
`You selected: ${selected()}`
]) : null
]);
};
$mount(ReactiveDemo, reactiveTarget);
}
// 4. Dynamic Options
const dynamicTarget = document.querySelector('#demo-dynamic');
if (dynamicTarget && !dynamicTarget.hasChildNodes()) {
const DynamicDemo = () => {
const selected = $('');
const filterType = $('all');
const allItems = {
fruits: ['Apple', 'Banana', 'Orange', 'Mango'],
vegetables: ['Carrot', 'Broccoli', 'Spinach', 'Potato'],
all: ['Apple', 'Banana', 'Orange', 'Mango', 'Carrot', 'Broccoli', 'Spinach', 'Potato']
};
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Select({
label: 'Category',
options: [
{ value: 'all', label: 'All items' },
{ value: 'fruits', label: 'Fruits' },
{ value: 'vegetables', label: 'Vegetables' }
],
value: filterType,
onchange: (e) => filterType(e.target.value)
}),
Autocomplete({
label: 'Search item',
options: () => allItems[filterType()],
value: selected,
onSelect: (value) => selected(value)
})
]);
};
$mount(DynamicDemo, dynamicTarget);
}
// 5. All Variants
const variantsTarget = document.querySelector('#demo-variants');
if (variantsTarget && !variantsTarget.hasChildNodes()) {
const VariantsDemo = () => {
const colors = ['Red', 'Blue', 'Green', 'Yellow', 'Purple', 'Orange', 'Pink', 'Brown', 'Black', 'White'];
return Div({ class: 'flex flex-col gap-4' }, [
Div({}, [
Autocomplete({
label: 'Primary style',
class: 'input-primary',
options: colors,
value: $(''),
placeholder: 'Search colors...'
})
]),
Div({}, [
Autocomplete({
label: 'Secondary style',
class: 'input-secondary',
options: colors,
value: $(''),
placeholder: 'Search colors...'
})
]),
Div({}, [
Autocomplete({
label: 'Ghost style',
class: 'input-ghost',
options: colors,
value: $(''),
placeholder: 'Search colors...'
})
])
]);
};
$mount(VariantsDemo, variantsTarget);
}
};
initAutocompleteExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initAutocompleteExamples);
});
}
})();
</script>

View File

@@ -0,0 +1,537 @@
# Badge
Badge component for displaying counts, labels, and status indicators with DaisyUI styling.
## Tag
`Badge`
## Props
| Prop | Type | Default | Description |
| :----------- | :--------------------------- | :---------- | :----------------------------------------------- |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI badge variants) |
| `children` | `string \| VNode` | `-` | Badge content |
## Live Examples
### Basic Badge
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-2"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
return Div({ class: 'flex flex-wrap gap-2' }, [
Badge({}, 'Default'),
Badge({ class: 'badge-primary' }, 'Primary'),
Badge({ class: 'badge-secondary' }, 'Secondary'),
Badge({ class: 'badge-accent' }, 'Accent'),
Badge({ class: 'badge-info' }, 'Info'),
Badge({ class: 'badge-success' }, 'Success'),
Badge({ class: 'badge-warning' }, 'Warning'),
Badge({ class: 'badge-error' }, 'Error')
]);
};
$mount(BasicDemo, '#demo-basic');
```
### Badge Sizes
<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-sizes" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-2 items-center"></div>
</div>
</div>
```javascript
const SizesDemo = () => {
return Div({ class: 'flex flex-wrap gap-2 items-center' }, [
Badge({ class: 'badge-xs' }, 'Extra Small'),
Badge({ class: 'badge-sm' }, 'Small'),
Badge({}, 'Default'),
Badge({ class: 'badge-md' }, 'Medium'),
Badge({ class: 'badge-lg' }, 'Large')
]);
};
$mount(SizesDemo, '#demo-sizes');
```
### Outline Badges
<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-outline" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-2"></div>
</div>
</div>
```javascript
const OutlineDemo = () => {
return Div({ class: 'flex flex-wrap gap-2' }, [
Badge({ class: 'badge-outline' }, 'Default'),
Badge({ class: 'badge-outline badge-primary' }, 'Primary'),
Badge({ class: 'badge-outline badge-secondary' }, 'Secondary'),
Badge({ class: 'badge-outline badge-accent' }, 'Accent'),
Badge({ class: 'badge-outline badge-info' }, 'Info'),
Badge({ class: 'badge-outline badge-success' }, 'Success'),
Badge({ class: 'badge-outline badge-warning' }, 'Warning'),
Badge({ class: 'badge-outline badge-error' }, 'Error')
]);
};
$mount(OutlineDemo, '#demo-outline');
```
### Ghost Badges
<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-ghost" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-2"></div>
</div>
</div>
```javascript
const GhostDemo = () => {
return Div({ class: 'flex flex-wrap gap-2' }, [
Badge({ class: 'badge-ghost' }, 'Default'),
Badge({ class: 'badge-ghost badge-primary' }, 'Primary'),
Badge({ class: 'badge-ghost badge-secondary' }, 'Secondary'),
Badge({ class: 'badge-ghost badge-accent' }, 'Accent'),
Badge({ class: 'badge-ghost badge-info' }, 'Info'),
Badge({ class: 'badge-ghost badge-success' }, 'Success'),
Badge({ class: 'badge-ghost badge-warning' }, 'Warning'),
Badge({ class: 'badge-ghost badge-error' }, 'Error')
]);
};
$mount(GhostDemo, '#demo-ghost');
```
### With Icons
<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-icons" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-2"></div>
</div>
</div>
```javascript
const IconsDemo = () => {
return Div({ class: 'flex flex-wrap gap-2' }, [
Badge({ class: 'gap-1' }, [
Icons.iconSuccess,
Span({}, 'Success')
]),
Badge({ class: 'gap-1 badge-warning' }, [
Icons.iconWarning,
Span({}, 'Warning')
]),
Badge({ class: 'gap-1 badge-error' }, [
Icons.iconError,
Span({}, 'Error')
]),
Badge({ class: 'gap-1 badge-info' }, [
Icons.iconInfo,
Span({}, 'Info')
]),
Badge({ class: 'gap-1' }, [
Span({}, '★'),
Span({}, '4.5')
])
]);
};
$mount(IconsDemo, '#demo-icons');
```
### Status Badges
<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-status" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const StatusDemo = () => {
const statuses = [
{ label: 'Active', class: 'badge-success' },
{ label: 'Pending', class: 'badge-warning' },
{ label: 'Completed', class: 'badge-info' },
{ label: 'Failed', class: 'badge-error' },
{ label: 'Archived', class: 'badge-ghost' }
];
return Div({ class: 'flex flex-col gap-2' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Order Status'),
Div({ class: 'flex flex-wrap gap-2' }, statuses.map(status =>
Badge({ class: status.class }, status.label)
))
]);
};
$mount(StatusDemo, '#demo-status');
```
### Count Badges
<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-count" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-4 items-center"></div>
</div>
</div>
```javascript
const CountDemo = () => {
const notifications = $(3);
const messages = $(5);
const updates = $(0);
return Div({ class: 'flex flex-wrap gap-6' }, [
Div({ class: 'flex items-center gap-2' }, [
Span({}, 'Notifications'),
Badge({ class: 'badge-primary' }, () => notifications())
]),
Div({ class: 'flex items-center gap-2' }, [
Span({}, 'Messages'),
Badge({ class: 'badge-secondary' }, () => messages())
]),
Div({ class: 'flex items-center gap-2' }, [
Span({}, 'Updates'),
Badge({ class: 'badge-ghost' }, () => updates() || '0')
])
]);
};
$mount(CountDemo, '#demo-count');
```
### Interactive Badge
<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-interactive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const InteractiveDemo = () => {
const count = $(0);
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Div({ class: 'flex items-center gap-4' }, [
Button({ class: 'btn btn-sm', onclick: () => count(count() - 1) }, '-'),
Badge({ class: 'badge-primary text-lg min-w-[4rem] justify-center' }, () => count()),
Button({ class: 'btn btn-sm', onclick: () => count(count() + 1) }, '+')
]),
Button({
class: 'btn btn-ghost btn-sm',
onclick: () => count(0)
}, 'Reset')
]);
};
$mount(InteractiveDemo, '#demo-interactive');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
return Div({ class: 'flex flex-col gap-6' }, [
Div({ class: 'flex flex-wrap gap-2' }, [
Badge({ class: 'badge-xs' }, 'XS'),
Badge({ class: 'badge-sm' }, 'SM'),
Badge({}, 'MD'),
Badge({ class: 'badge-lg' }, 'LG')
]),
Div({ class: 'flex flex-wrap gap-2' }, [
Badge({ class: 'badge-primary badge-sm' }, 'Primary'),
Badge({ class: 'badge-secondary badge-sm' }, 'Secondary'),
Badge({ class: 'badge-accent badge-sm' }, 'Accent')
]),
Div({ class: 'flex flex-wrap gap-2' }, [
Badge({ class: 'badge-outline badge-primary' }, 'Outline'),
Badge({ class: 'badge-ghost badge-primary' }, 'Ghost')
])
]);
};
$mount(VariantsDemo, '#demo-variants');
```
### Inline with Text
<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-inline" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const InlineDemo = () => {
return Div({ class: 'space-y-2' }, [
Div({ class: 'text-sm' }, [
'Your order is ',
Badge({ class: 'badge-success badge-sm' }, 'Confirmed'),
' and will be shipped soon.'
]),
Div({ class: 'text-sm' }, [
'This feature is ',
Badge({ class: 'badge-warning badge-sm' }, 'Beta'),
' and may change.'
]),
Div({ class: 'text-sm' }, [
'Version ',
Badge({ class: 'badge-info badge-xs' }, 'v2.1.0'),
' released on March 2026'
])
]);
};
$mount(InlineDemo, '#demo-inline');
```
<script>
(function() {
const initBadgeExamples = () => {
// 1. Basic Badge
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
return Div({ class: 'flex flex-wrap gap-2' }, [
Badge({}, 'Default'),
Badge({ class: 'badge-primary' }, 'Primary'),
Badge({ class: 'badge-secondary' }, 'Secondary'),
Badge({ class: 'badge-accent' }, 'Accent'),
Badge({ class: 'badge-info' }, 'Info'),
Badge({ class: 'badge-success' }, 'Success'),
Badge({ class: 'badge-warning' }, 'Warning'),
Badge({ class: 'badge-error' }, 'Error')
]);
};
$mount(BasicDemo, basicTarget);
}
// 2. Badge Sizes
const sizesTarget = document.querySelector('#demo-sizes');
if (sizesTarget && !sizesTarget.hasChildNodes()) {
const SizesDemo = () => {
return Div({ class: 'flex flex-wrap gap-2 items-center' }, [
Badge({ class: 'badge-xs' }, 'Extra Small'),
Badge({ class: 'badge-sm' }, 'Small'),
Badge({}, 'Default'),
Badge({ class: 'badge-md' }, 'Medium'),
Badge({ class: 'badge-lg' }, 'Large')
]);
};
$mount(SizesDemo, sizesTarget);
}
// 3. Outline Badges
const outlineTarget = document.querySelector('#demo-outline');
if (outlineTarget && !outlineTarget.hasChildNodes()) {
const OutlineDemo = () => {
return Div({ class: 'flex flex-wrap gap-2' }, [
Badge({ class: 'badge-outline' }, 'Default'),
Badge({ class: 'badge-outline badge-primary' }, 'Primary'),
Badge({ class: 'badge-outline badge-secondary' }, 'Secondary'),
Badge({ class: 'badge-outline badge-accent' }, 'Accent'),
Badge({ class: 'badge-outline badge-info' }, 'Info'),
Badge({ class: 'badge-outline badge-success' }, 'Success'),
Badge({ class: 'badge-outline badge-warning' }, 'Warning'),
Badge({ class: 'badge-outline badge-error' }, 'Error')
]);
};
$mount(OutlineDemo, outlineTarget);
}
// 4. Ghost Badges
const ghostTarget = document.querySelector('#demo-ghost');
if (ghostTarget && !ghostTarget.hasChildNodes()) {
const GhostDemo = () => {
return Div({ class: 'flex flex-wrap gap-2' }, [
Badge({ class: 'badge-ghost' }, 'Default'),
Badge({ class: 'badge-ghost badge-primary' }, 'Primary'),
Badge({ class: 'badge-ghost badge-secondary' }, 'Secondary'),
Badge({ class: 'badge-ghost badge-accent' }, 'Accent'),
Badge({ class: 'badge-ghost badge-info' }, 'Info'),
Badge({ class: 'badge-ghost badge-success' }, 'Success'),
Badge({ class: 'badge-ghost badge-warning' }, 'Warning'),
Badge({ class: 'badge-ghost badge-error' }, 'Error')
]);
};
$mount(GhostDemo, ghostTarget);
}
// 5. With Icons
const iconsTarget = document.querySelector('#demo-icons');
if (iconsTarget && !iconsTarget.hasChildNodes()) {
const IconsDemo = () => {
return Div({ class: 'flex flex-wrap gap-2' }, [
Badge({ class: 'gap-1' }, [
Img({src: Icons.iconSuccess}),
Span({}, 'Success')
]),
Badge({ class: 'gap-1 badge-warning' }, [
Img({src: Icons.iconWarning}),
Span({}, 'Warning')
]),
Badge({ class: 'gap-1 badge-error' }, [
Img({src: Icons.iconError}),
Span({}, 'Error')
]),
Badge({ class: 'gap-1 badge-info' }, [
Img({src: Icons.iconInfo}),
Span({}, 'Info')
]),
Badge({ class: 'gap-1' }, [
Span({}, '★'),
Span({}, '4.5')
])
]);
};
$mount(IconsDemo, iconsTarget);
}
// 6. Status Badges
const statusTarget = document.querySelector('#demo-status');
if (statusTarget && !statusTarget.hasChildNodes()) {
const StatusDemo = () => {
const statuses = [
{ label: 'Active', class: 'badge-success' },
{ label: 'Pending', class: 'badge-warning' },
{ label: 'Completed', class: 'badge-info' },
{ label: 'Failed', class: 'badge-error' },
{ label: 'Archived', class: 'badge-ghost' }
];
return Div({ class: 'flex flex-col gap-2' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Order Status'),
Div({ class: 'flex flex-wrap gap-2' }, statuses.map(status =>
Badge({ class: status.class }, status.label)
))
]);
};
$mount(StatusDemo, statusTarget);
}
// 7. Count Badges
const countTarget = document.querySelector('#demo-count');
if (countTarget && !countTarget.hasChildNodes()) {
const CountDemo = () => {
const notifications = $(3);
const messages = $(5);
const updates = $(0);
return Div({ class: 'flex flex-wrap gap-6' }, [
Div({ class: 'flex items-center gap-2' }, [
Span({}, 'Notifications'),
Badge({ class: 'badge-primary' }, () => notifications())
]),
Div({ class: 'flex items-center gap-2' }, [
Span({}, 'Messages'),
Badge({ class: 'badge-secondary' }, () => messages())
]),
Div({ class: 'flex items-center gap-2' }, [
Span({}, 'Updates'),
Badge({ class: 'badge-ghost' }, () => updates() || '0')
])
]);
};
$mount(CountDemo, countTarget);
}
// 8. Interactive Badge
const interactiveTarget = document.querySelector('#demo-interactive');
if (interactiveTarget && !interactiveTarget.hasChildNodes()) {
const InteractiveDemo = () => {
const count = $(0);
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Div({ class: 'flex items-center gap-4' }, [
Button({ class: 'btn btn-sm', onclick: () => count(count() - 1) }, '-'),
Badge({ class: 'badge-primary text-lg min-w-[4rem] justify-center' }, () => count()),
Button({ class: 'btn btn-sm', onclick: () => count(count() + 1) }, '+')
]),
Button({
class: 'btn btn-ghost btn-sm',
onclick: () => count(0)
}, 'Reset')
]);
};
$mount(InteractiveDemo, interactiveTarget);
}
// 9. All Variants
const variantsTarget = document.querySelector('#demo-variants');
if (variantsTarget && !variantsTarget.hasChildNodes()) {
const VariantsDemo = () => {
return Div({ class: 'flex flex-col gap-6' }, [
Div({ class: 'flex flex-wrap gap-2' }, [
Badge({ class: 'badge-xs' }, 'XS'),
Badge({ class: 'badge-sm' }, 'SM'),
Badge({}, 'MD'),
Badge({ class: 'badge-lg' }, 'LG')
]),
Div({ class: 'flex flex-wrap gap-2' }, [
Badge({ class: 'badge-primary badge-sm' }, 'Primary'),
Badge({ class: 'badge-secondary badge-sm' }, 'Secondary'),
Badge({ class: 'badge-accent badge-sm' }, 'Accent')
]),
Div({ class: 'flex flex-wrap gap-2' }, [
Badge({ class: 'badge-outline badge-primary' }, 'Outline'),
Badge({ class: 'badge-ghost badge-primary' }, 'Ghost')
])
]);
};
$mount(VariantsDemo, variantsTarget);
}
// 10. Inline with Text
const inlineTarget = document.querySelector('#demo-inline');
if (inlineTarget && !inlineTarget.hasChildNodes()) {
const InlineDemo = () => {
return Div({ class: 'space-y-2' }, [
Div({ class: 'text-sm' }, [
'Your order is ',
Badge({ class: 'badge-success badge-sm' }, 'Confirmed'),
' and will be shipped soon.'
]),
Div({ class: 'text-sm' }, [
'This feature is ',
Badge({ class: 'badge-warning badge-sm' }, 'Beta'),
' and may change.'
]),
Div({ class: 'text-sm' }, [
'Version ',
Badge({ class: 'badge-info badge-xs' }, 'v2.1.0'),
' released on March 2026'
])
]);
};
$mount(InlineDemo, inlineTarget);
}
};
initBadgeExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initBadgeExamples);
});
}
})();
</script>

View File

@@ -0,0 +1,248 @@
# Button
Styled button with full DaisyUI support and reactive states.
## Tag
`Button`
## Props
| Prop | Type | Default | Description |
| :----------- | :--------------------------- | :------------------ | :------------------------------------------ |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `disabled` | `boolean \| Signal<boolean>` | `false` | Disabled state |
| `loading` | `boolean \| Signal<boolean>` | `false` | Shows loading spinner |
| `badge` | `string \| Signal<string>` | `-` | Badge text displayed on corner |
| `badgeClass` | `string` | `'badge-secondary'` | Badge styling classes |
| `tooltip` | `string \| Signal<string>` | `-` | Tooltip text on hover |
| `icon` | `string \| VNode \| Signal` | `-` | Icon displayed before text |
| `onclick` | `function` | `-` | Click event handler |
| `type` | `string` | `'button'` | Native button type |
## Live Examples
### Basic Button
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
return Button({ class: "btn btn-primary" }, "Click Me");
};
$mount(BasicDemo, "#demo-basic");
```
### With Loading State
<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-loading" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const LoadingDemo = () => {
const isSaving = $(false);
return Button(
{
class: "btn btn-success",
loading: isSaving,
onclick: async () => {
isSaving(true);
await new Promise((resolve) => setTimeout(resolve, 2000));
isSaving(false);
},
},
"Save Changes",
);
};
$mount(LoadingDemo, "#demo-loading");
```
### With Badge
<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-badge" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const BadgeDemo = () => {
return Button(
{
class: "btn btn-outline",
badge: "3",
badgeClass: "badge-accent",
},
"Notifications",
);
};
$mount(BadgeDemo, "#demo-badge");
```
### With Tooltip
<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-tooltip" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const TooltipDemo = () => {
return Button(
{
class: "btn btn-ghost",
tooltip: "Delete item",
},
"Delete",
);
};
$mount(TooltipDemo, "#demo-tooltip");
```
### Disabled State
<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-disabled" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const DisabledDemo = () => {
const isDisabled = $(true);
return Button(
{
class: "btn btn-primary",
disabled: isDisabled,
},
"Submit",
);
};
$mount(DisabledDemo, "#demo-disabled");
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
return Div({ class: "flex flex-wrap gap-2 justify-center" }, [
Button({ class: "btn btn-primary" }, "Primary"),
Button({ class: "btn btn-secondary" }, "Secondary"),
Button({ class: "btn btn-accent" }, "Accent"),
Button({ class: "btn btn-ghost" }, "Ghost"),
Button({ class: "btn btn-outline" }, "Outline"),
]);
};
$mount(VariantsDemo, "#demo-variants");
```
<script>
(function() {
const initButtonExamples = () => {
// 1. Basic Button
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => Button({ class: "btn btn-primary" }, "Click Me");
$mount(BasicDemo, basicTarget);
}
// 2. Loading State
const loadingTarget = document.querySelector('#demo-loading');
if (loadingTarget && !loadingTarget.hasChildNodes()) {
const LoadingDemo = () => {
const isSaving = $(false);
return Button({
class: "btn btn-success",
loading: isSaving,
onclick: async () => {
isSaving(true);
await new Promise(resolve => setTimeout(resolve, 2000));
isSaving(false);
}
}, "Save Changes");
};
$mount(LoadingDemo, loadingTarget);
}
// 3. Badge
const badgeTarget = document.querySelector('#demo-badge');
if (badgeTarget && !badgeTarget.hasChildNodes()) {
const BadgeDemo = () => Button({
class: "btn btn-outline",
badge: "3",
badgeClass: "badge-accent"
}, "Notifications");
$mount(BadgeDemo, badgeTarget);
}
// 4. Tooltip
const tooltipTarget = document.querySelector('#demo-tooltip');
if (tooltipTarget && !tooltipTarget.hasChildNodes()) {
const TooltipDemo = () => Button({
class: "btn btn-ghost",
tooltip: "Delete item"
}, "Delete");
$mount(TooltipDemo, tooltipTarget);
}
// 5. Disabled State
const disabledTarget = document.querySelector('#demo-disabled');
if (disabledTarget && !disabledTarget.hasChildNodes()) {
const DisabledDemo = () => {
const isDisabled = $(true);
return Button({
class: "btn btn-primary",
disabled: isDisabled
}, "Submit");
};
$mount(DisabledDemo, disabledTarget);
}
// 6. All Variants
const variantsTarget = document.querySelector('#demo-variants');
if (variantsTarget && !variantsTarget.hasChildNodes()) {
const VariantsDemo = () => Div({ class: "flex flex-wrap gap-2 justify-center" }, [
Button({ class: "btn btn-primary" }, "Primary"),
Button({ class: "btn btn-secondary" }, "Secondary"),
Button({ class: "btn btn-accent" }, "Accent"),
Button({ class: "btn btn-ghost" }, "Ghost"),
Button({ class: "btn btn-outline" }, "Outline")
]);
$mount(VariantsDemo, variantsTarget);
}
};
// Ejecutar la función después de definirla
initButtonExamples();
// Registrar para navegación en Docsify
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initButtonExamples);
});
}
})();
</script>

View File

@@ -0,0 +1,168 @@
> # Button(props, children?: string | Signal | [...]): HTMLElement
---
## Props
| Prop | Type | Default | Description |
| :----------- | :--------------------------- | :------------------ | :------------------------------- |
| `class` | `string` | `''` | Additional CSS classes (daisyUI) |
| `disabled` | `boolean \| Signal<boolean>` | `false` | Disabled state |
| `loading` | `boolean \| Signal<boolean>` | `false` | Shows loading spinner |
| `badge` | `string \| Signal<string>` | `-` | Badge text displayed on corner |
| `badgeClass` | `string` | `'badge-secondary'` | Badge styling classes |
| `tooltip` | `string \| Signal<string>` | `-` | Tooltip text on hover |
| `icon` | `string \| VNode \| Signal` | `-` | Icon displayed before text |
| `onclick` | `function` | `-` | Click event handler |
| `type` | `string` | `'button'` | Native button type |
## Class Options
For more detailed information about the underlying styling system, visit the daisyUI documentation:
- [daisyUI Button](https://daisyui.com/components/button)
| Class Name | Category | Description |
| :-------------- | :------------ | :------------------------------------ |
| `btn-neutral` | `Color` 🎨 | Neutral brand color |
| `btn-primary` | `Color` 🎨 | Primary brand color |
| `btn-secondary` | `Color` 🎨 | Secondary brand color |
| `btn-accent` | `Color` 🎨 | Accent brand color |
| `btn-info` | `Color` 🎨 | Informational blue color |
| `btn-success` | `Color` 🎨 | Success green color |
| `btn-warning` | `Color` 🎨 | Warning yellow color |
| `btn-error` | `Color` 🎨 | Error red color |
| `btn-xl` | `Size` 📏 | Extra large scale |
| `btn-lg` | `Size` 📏 | Large scale |
| `btn-md` | `Size` 📏 | Medium scale (Default) |
| `btn-sm` | `Size` 📏 | Small scale |
| `btn-xs` | `Size` 📏 | Extra small scale |
| `btn-outline` | `Style` ✨ | Transparent with colored border |
| `btn-dash` | `Style` ✨ | Dashed border style |
| `btn-soft` | `Style` ✨ | Low opacity background color |
| `btn-ghost` | `Style` ✨ | No background, hover effect only |
| `btn-link` | `Style` ✨ | Looks like a text link |
| `btn-square` | `Shape` 📐 | 1:1 aspect ratio |
| `btn-circle` | `Shape` 📐 | 1:1 aspect ratio with rounded corners |
| `btn-wide` | `Shape` 📐 | Extra horizontal padding |
| `btn-block` | `Shape` 📐 | Full width of container |
| `btn-active` | `Behavior` ⚙️ | Forced active/pressed state |
| `btn-disabled` | `Behavior` ⚙️ | Visual and functional disabled state |
### Basic Button
<div id="demo-basic" class="card bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
```javascript
const BasicDemo = () => {
return Button({ class: "btn-primary" }, "Click Me");
};
$mount(BasicDemo, "#demo-basic");
```
### With Loading State
<div id="demo-loading" class="card bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
```javascript
const LoadingDemo = () => {
const isSaving = $(false);
return Button(
{
class: "btn-success",
loading: isSaving,
onclick: async () => {
isSaving(true);
await new Promise((resolve) => setTimeout(resolve, 2000));
isSaving(false);
},
},
"Save Changes",
);
};
$mount(LoadingDemo, "#demo-loading");
```
### With Badge
<div id="demo-badge" class="card bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
```javascript
const BadgeDemo = () => {
return Button(
{
class: "btn-outline",
badge: "3",
badgeClass: "badge-accent",
},
"Notifications",
);
};
$mount(BadgeDemo, "#demo-badge");
```
### With Tooltip
<div id="demo-tooltip" class="card bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
```javascript
const TooltipDemo = () => {
return Button(
{
class: "btn-ghost",
tooltip: "Delete item",
},
"Delete",
);
};
$mount(TooltipDemo, "#demo-tooltip");
```
### Disabled State
<div id="demo-disabled" class="card bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
```javascript
const DisabledDemo = () => {
const isDisabled = $(true);
return Button(
{
class: "btn-primary btn-disabled",
},
"Submit",
);
};
$mount(DisabledDemo, "#demo-disabled");
```
### All Variants
<div id="demo-variants" class="card bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
```javascript
const VariantsDemo = () => {
return Div({ class: "flex flex-wrap gap-2 justify-center" }, [
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"),
]);
};
$mount(VariantsDemo, "#demo-variants");
```
<div id="demo-test" class="card bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
```javascript
const TestDemo = () => {
return Div({ class: "flex flex-wrap gap-2 justify-center" }, [
$html("span", {class: "indicator"},[
5,
Button('Click')])
]);
};
$mount(TestDemo, "#demo-test");
```

View File

@@ -0,0 +1,479 @@
# Checkbox
Toggle checkbox component with label, tooltip support, and reactive state management.
## Tag
`Checkbox`
## Props
| Prop | Type | Default | Description |
| :----------- | :--------------------------- | :---------- | :----------------------------------------------- |
| `label` | `string` | `-` | Label text for the checkbox |
| `value` | `boolean \| Signal<boolean>` | `false` | Checked state |
| `tooltip` | `string` | `-` | Tooltip text on hover |
| `toggle` | `boolean` | `false` | Display as toggle switch instead of checkbox |
| `disabled` | `boolean \| Signal<boolean>` | `false` | Disabled state |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `onclick` | `function` | `-` | Click event handler |
## Live Examples
### Basic Checkbox
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const accepted = $(false);
return Checkbox({
label: 'I accept the terms and conditions',
value: accepted,
onclick: () => accepted(!accepted())
});
};
$mount(BasicDemo, '#demo-basic');
```
### Toggle Switch
<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-toggle" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const ToggleDemo = () => {
const enabled = $(false);
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Checkbox({
label: 'Enable notifications',
value: enabled,
toggle: true,
onclick: () => enabled(!enabled())
}),
() => enabled()
? Div({ class: 'alert alert-success' }, 'Notifications are ON')
: Div({ class: 'alert alert-soft' }, 'Notifications are OFF')
]);
};
$mount(ToggleDemo, '#demo-toggle');
```
### With Tooltip
<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-tooltip" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const TooltipDemo = () => {
const darkMode = $(false);
return Checkbox({
label: 'Dark mode',
value: darkMode,
tooltip: 'Enable dark theme preference',
onclick: () => darkMode(!darkMode())
});
};
$mount(TooltipDemo, '#demo-tooltip');
```
### Disabled State
<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-disabled" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const DisabledDemo = () => {
return Div({ class: 'flex flex-col gap-3' }, [
Checkbox({
label: 'Checked and disabled',
value: true,
disabled: true
}),
Checkbox({
label: 'Unchecked and disabled',
value: false,
disabled: true
})
]);
};
$mount(DisabledDemo, '#demo-disabled');
```
### Reactive Multiple Selection
<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-multiple" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const MultipleDemo = () => {
const options = [
{ id: 1, label: 'Option 1', selected: $(false) },
{ id: 2, label: 'Option 2', selected: $(false) },
{ id: 3, label: 'Option 3', selected: $(false) }
];
const selectAll = $(false);
const toggleAll = (value) => {
selectAll(value);
options.forEach(opt => opt.selected(value));
};
const updateSelectAll = () => {
const allSelected = options.every(opt => opt.selected());
selectAll(allSelected);
};
return Div({ class: 'flex flex-col gap-3' }, [
Checkbox({
label: 'Select all',
value: selectAll,
onclick: () => toggleAll(!selectAll())
}),
Div({ class: 'divider my-1' }),
...options.map(opt => Checkbox({
label: opt.label,
value: opt.selected,
onclick: () => {
opt.selected(!opt.selected());
updateSelectAll();
}
})),
Div({ class: 'mt-2 text-sm opacity-70' }, () => {
const count = options.filter(opt => opt.selected()).length;
return `${count} of ${options.length} selected`;
})
]);
};
$mount(MultipleDemo, '#demo-multiple');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
const variant1 = $(true);
const variant2 = $(false);
const variant3 = $(true);
return Div({ class: 'flex flex-col gap-3' }, [
Div({ class: 'flex items-center gap-4' }, [
Checkbox({
label: 'Primary',
value: variant1,
class: 'checkbox-primary',
onclick: () => variant1(!variant1())
}),
Checkbox({
label: 'Secondary',
value: variant2,
class: 'checkbox-secondary',
onclick: () => variant2(!variant2())
})
]),
Div({ class: 'flex items-center gap-4' }, [
Checkbox({
label: 'Accent',
value: variant3,
class: 'checkbox-accent',
onclick: () => variant3(!variant3())
}),
Checkbox({
label: 'Toggle switch',
value: $(false),
toggle: true,
class: 'toggle-primary'
})
])
]);
};
$mount(VariantsDemo, '#demo-variants');
```
### Form Example
<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-form" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const FormDemo = () => {
const subscribe = $(false);
const weekly = $(false);
const monthly = $(true);
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'text-sm font-bold' }, 'Newsletter preferences'),
Checkbox({
label: 'Subscribe to newsletter',
value: subscribe,
onclick: () => subscribe(!subscribe())
}),
() => subscribe() ? Div({ class: 'ml-6 flex flex-col gap-2' }, [
Checkbox({
label: 'Weekly updates',
value: weekly,
onclick: () => weekly(!weekly())
}),
Checkbox({
label: 'Monthly digest',
value: monthly,
onclick: () => monthly(!monthly())
})
]) : null,
() => subscribe() && (weekly() || monthly())
? Div({ class: 'alert alert-success text-sm mt-2' }, 'You will receive updates!')
: subscribe()
? Div({ class: 'alert alert-warning text-sm mt-2' }, 'Select at least one frequency')
: null
]);
};
$mount(FormDemo, '#demo-form');
```
<script>
(function() {
const initCheckboxExamples = () => {
// 1. Basic Checkbox
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
const accepted = $(false);
return Checkbox({
label: 'I accept the terms and conditions',
value: accepted,
onclick: () => accepted(!accepted())
});
};
$mount(BasicDemo, basicTarget);
}
// 2. Toggle Switch
const toggleTarget = document.querySelector('#demo-toggle');
if (toggleTarget && !toggleTarget.hasChildNodes()) {
const ToggleDemo = () => {
const enabled = $(false);
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Checkbox({
label: 'Enable notifications',
value: enabled,
toggle: true,
onclick: () => enabled(!enabled())
}),
() => enabled()
? Div({ class: 'alert alert-success' }, 'Notifications are ON')
: Div({ class: 'alert alert-soft' }, 'Notifications are OFF')
]);
};
$mount(ToggleDemo, toggleTarget);
}
// 3. With Tooltip
const tooltipTarget = document.querySelector('#demo-tooltip');
if (tooltipTarget && !tooltipTarget.hasChildNodes()) {
const TooltipDemo = () => {
const darkMode = $(false);
return Checkbox({
label: 'Dark mode',
value: darkMode,
tooltip: 'Enable dark theme preference',
onclick: () => darkMode(!darkMode())
});
};
$mount(TooltipDemo, tooltipTarget);
}
// 4. Disabled State
const disabledTarget = document.querySelector('#demo-disabled');
if (disabledTarget && !disabledTarget.hasChildNodes()) {
const DisabledDemo = () => {
return Div({ class: 'flex flex-col gap-3' }, [
Checkbox({
label: 'Checked and disabled',
value: true,
disabled: true
}),
Checkbox({
label: 'Unchecked and disabled',
value: false,
disabled: true
})
]);
};
$mount(DisabledDemo, disabledTarget);
}
// 5. Reactive Multiple Selection
const multipleTarget = document.querySelector('#demo-multiple');
if (multipleTarget && !multipleTarget.hasChildNodes()) {
const MultipleDemo = () => {
const options = [
{ id: 1, label: 'Option 1', selected: $(false) },
{ id: 2, label: 'Option 2', selected: $(false) },
{ id: 3, label: 'Option 3', selected: $(false) }
];
const selectAll = $(false);
const toggleAll = (value) => {
selectAll(value);
options.forEach(opt => opt.selected(value));
};
const updateSelectAll = () => {
const allSelected = options.every(opt => opt.selected());
selectAll(allSelected);
};
return Div({ class: 'flex flex-col gap-3' }, [
Checkbox({
label: 'Select all',
value: selectAll,
onclick: () => toggleAll(!selectAll())
}),
Div({ class: 'divider my-1' }),
...options.map(opt => Checkbox({
label: opt.label,
value: opt.selected,
onclick: () => {
opt.selected(!opt.selected());
updateSelectAll();
}
})),
Div({ class: 'mt-2 text-sm opacity-70' }, () => {
const count = options.filter(opt => opt.selected()).length;
return `${count} of ${options.length} selected`;
})
]);
};
$mount(MultipleDemo, multipleTarget);
}
// 6. All Variants
const variantsTarget = document.querySelector('#demo-variants');
if (variantsTarget && !variantsTarget.hasChildNodes()) {
const VariantsDemo = () => {
const variant1 = $(true);
const variant2 = $(false);
const variant3 = $(true);
return Div({ class: 'flex flex-col gap-3' }, [
Div({ class: 'flex items-center gap-4' }, [
Checkbox({
label: 'Primary',
value: variant1,
class: 'checkbox-primary',
onclick: () => variant1(!variant1())
}),
Checkbox({
label: 'Secondary',
value: variant2,
class: 'checkbox-secondary',
onclick: () => variant2(!variant2())
})
]),
Div({ class: 'flex items-center gap-4' }, [
Checkbox({
label: 'Accent',
value: variant3,
class: 'checkbox-accent',
onclick: () => variant3(!variant3())
}),
Checkbox({
label: 'Toggle switch',
value: $(false),
toggle: true,
class: 'toggle-primary'
})
])
]);
};
$mount(VariantsDemo, variantsTarget);
}
// 7. Form Example
const formTarget = document.querySelector('#demo-form');
if (formTarget && !formTarget.hasChildNodes()) {
const FormDemo = () => {
const subscribe = $(false);
const weekly = $(false);
const monthly = $(true);
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'text-sm font-bold' }, 'Newsletter preferences'),
Checkbox({
label: 'Subscribe to newsletter',
value: subscribe,
onclick: () => subscribe(!subscribe())
}),
() => subscribe() ? Div({ class: 'ml-6 flex flex-col gap-2' }, [
Checkbox({
label: 'Weekly updates',
value: weekly,
onclick: () => weekly(!weekly())
}),
Checkbox({
label: 'Monthly digest',
value: monthly,
onclick: () => monthly(!monthly())
})
]) : null,
() => subscribe() && (weekly() || monthly())
? Div({ class: 'alert alert-success text-sm mt-2' }, 'You will receive updates!')
: subscribe()
? Div({ class: 'alert alert-warning text-sm mt-2' }, 'Select at least one frequency')
: null
]);
};
$mount(FormDemo, formTarget);
}
};
initCheckboxExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initCheckboxExamples);
});
}
})();
</script>

View File

@@ -0,0 +1,382 @@
# Colorpicker
Color picker component with preset color palette, reactive value binding, and customizable appearance.
## Tag
`Colorpicker`
## Props
| Prop | Type | Default | Description |
| :----------- | :--------------------------- | :---------- | :----------------------------------------------- |
| `label` | `string` | `-` | Label text for the button |
| `value` | `string \| Signal<string>` | `'#000000'` | Selected color value (hex format) |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
## Live Examples
### Basic Colorpicker
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const color = $('#3b82f6');
return Colorpicker({
label: 'Pick a color',
value: color
});
};
$mount(BasicDemo, '#demo-basic');
```
### With Reactive Preview
<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-preview" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const PreviewDemo = () => {
const color = $('#10b981');
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Colorpicker({
label: 'Choose color',
value: color
}),
Div({
class: 'w-full h-20 rounded-lg shadow-inner transition-all duration-200',
style: () => `background-color: ${color()}`
}, [
Div({ class: 'text-center leading-20 pt-6 opacity-70' }, () => color())
])
]);
};
$mount(PreviewDemo, '#demo-preview');
```
### Color Palette Grid
<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-palette" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const PaletteDemo = () => {
const selectedColor = $('#ef4444');
const presets = [
'#ef4444', '#f97316', '#f59e0b', '#eab308',
'#84cc16', '#10b981', '#14b8a6', '#06b6d4',
'#3b82f6', '#6366f1', '#8b5cf6', '#d946ef',
'#ec489a', '#f43f5e', '#6b7280', '#1f2937'
];
return Div({ class: 'flex flex-col gap-4' }, [
Colorpicker({
label: 'Custom color',
value: selectedColor
}),
Div({ class: 'divider text-xs' }, 'Or choose from palette'),
Div({ class: 'grid grid-cols-8 gap-2' }, presets.map(color =>
Button({
class: `w-8 h-8 rounded-lg shadow-sm transition-transform hover:scale-110`,
style: `background-color: ${color}`,
onclick: () => selectedColor(color)
})
)),
Div({ class: 'mt-2 text-center text-sm font-mono' }, () => selectedColor())
]);
};
$mount(PaletteDemo, '#demo-palette');
```
### With Text Color Preview
<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-text" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const TextDemo = () => {
const bgColor = $('#1e293b');
const textColor = $('#f8fafc');
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Div({ class: 'flex gap-4' }, [
Colorpicker({
label: 'Background',
value: bgColor
}),
Colorpicker({
label: 'Text',
value: textColor
})
]),
Div({
class: 'p-6 rounded-lg text-center font-bold transition-all duration-200',
style: () => `background-color: ${bgColor()}; color: ${textColor()}`
}, [
'Preview text with your colors'
])
]);
};
$mount(TextDemo, '#demo-text');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-4"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
return Div({ class: 'flex flex-wrap gap-4 items-center' }, [
Colorpicker({
label: 'Primary',
value: $('#3b82f6')
}),
Colorpicker({
label: 'Success',
value: $('#10b981')
}),
Colorpicker({
label: 'Warning',
value: $('#f59e0b')
}),
Colorpicker({
label: 'Error',
value: $('#ef4444')
})
]);
};
$mount(VariantsDemo, '#demo-variants');
```
### Dynamic Color Swatch
<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-dynamic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const DynamicDemo = () => {
const primary = $('#3b82f6');
const secondary = $('#ef4444');
const accent = $('#10b981');
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Div({ class: 'flex flex-wrap gap-4' }, [
Colorpicker({ label: 'Primary', value: primary }),
Colorpicker({ label: 'Secondary', value: secondary }),
Colorpicker({ label: 'Accent', value: accent })
]),
Div({ class: 'grid grid-cols-3 gap-2 mt-2' }, [
Div({
class: 'h-12 rounded-lg shadow-sm flex items-center justify-center text-xs font-bold',
style: () => `background-color: ${primary()}; color: white`
}, 'Primary'),
Div({
class: 'h-12 rounded-lg shadow-sm flex items-center justify-center text-xs font-bold',
style: () => `background-color: ${secondary()}; color: white`
}, 'Secondary'),
Div({
class: 'h-12 rounded-lg shadow-sm flex items-center justify-center text-xs font-bold',
style: () => `background-color: ${accent()}; color: white`
}, 'Accent')
])
]);
};
$mount(DynamicDemo, '#demo-dynamic');
```
<script>
(function() {
const initColorpickerExamples = () => {
// 1. Basic Colorpicker
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
const color = $('#3b82f6');
return Colorpicker({
label: 'Pick a color',
value: color
});
};
$mount(BasicDemo, basicTarget);
}
// 2. With Reactive Preview
const previewTarget = document.querySelector('#demo-preview');
if (previewTarget && !previewTarget.hasChildNodes()) {
const PreviewDemo = () => {
const color = $('#10b981');
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Colorpicker({
label: 'Choose color',
value: color
}),
Div({
class: 'w-full h-20 rounded-lg shadow-inner transition-all duration-200 flex items-center justify-center',
style: () => `background-color: ${color()}`
}, [
Div({ class: 'text-center font-mono text-sm bg-black/20 px-2 py-1 rounded' }, () => color())
])
]);
};
$mount(PreviewDemo, previewTarget);
}
// 3. Color Palette Grid
const paletteTarget = document.querySelector('#demo-palette');
if (paletteTarget && !paletteTarget.hasChildNodes()) {
const PaletteDemo = () => {
const selectedColor = $('#ef4444');
const presets = [
'#ef4444', '#f97316', '#f59e0b', '#eab308',
'#84cc16', '#10b981', '#14b8a6', '#06b6d4',
'#3b82f6', '#6366f1', '#8b5cf6', '#d946ef',
'#ec489a', '#f43f5e', '#6b7280', '#1f2937'
];
return Div({ class: 'flex flex-col gap-4' }, [
Colorpicker({
label: 'Custom color',
value: selectedColor
}),
Div({ class: 'divider text-xs' }, 'Or choose from palette'),
Div({ class: 'grid grid-cols-8 gap-2' }, presets.map(color =>
Button({
class: `w-8 h-8 rounded-lg shadow-sm transition-transform hover:scale-110`,
style: `background-color: ${color}`,
onclick: () => selectedColor(color)
})
)),
Div({ class: 'mt-2 text-center text-sm font-mono' }, () => selectedColor())
]);
};
$mount(PaletteDemo, paletteTarget);
}
// 4. With Text Color Preview
const textTarget = document.querySelector('#demo-text');
if (textTarget && !textTarget.hasChildNodes()) {
const TextDemo = () => {
const bgColor = $('#1e293b');
const textColor = $('#f8fafc');
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Div({ class: 'flex gap-4' }, [
Colorpicker({
label: 'Background',
value: bgColor
}),
Colorpicker({
label: 'Text',
value: textColor
})
]),
Div({
class: 'p-6 rounded-lg text-center font-bold transition-all duration-200',
style: () => `background-color: ${bgColor()}; color: ${textColor()}`
}, [
'Preview text with your colors'
])
]);
};
$mount(TextDemo, textTarget);
}
// 5. All Variants
const variantsTarget = document.querySelector('#demo-variants');
if (variantsTarget && !variantsTarget.hasChildNodes()) {
const VariantsDemo = () => {
return Div({ class: 'flex flex-wrap gap-4 items-center' }, [
Colorpicker({
label: 'Primary',
value: $('#3b82f6')
}),
Colorpicker({
label: 'Success',
value: $('#10b981')
}),
Colorpicker({
label: 'Warning',
value: $('#f59e0b')
}),
Colorpicker({
label: 'Error',
value: $('#ef4444')
})
]);
};
$mount(VariantsDemo, variantsTarget);
}
// 6. Dynamic Color Swatch
const dynamicTarget = document.querySelector('#demo-dynamic');
if (dynamicTarget && !dynamicTarget.hasChildNodes()) {
const DynamicDemo = () => {
const primary = $('#3b82f6');
const secondary = $('#ef4444');
const accent = $('#10b981');
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Div({ class: 'flex flex-wrap gap-4' }, [
Colorpicker({ label: 'Primary', value: primary }),
Colorpicker({ label: 'Secondary', value: secondary }),
Colorpicker({ label: 'Accent', value: accent })
]),
Div({ class: 'grid grid-cols-3 gap-2 mt-2' }, [
Div({
class: 'h-12 rounded-lg shadow-sm flex items-center justify-center text-xs font-bold text-white',
style: () => `background-color: ${primary()}`
}, 'Primary'),
Div({
class: 'h-12 rounded-lg shadow-sm flex items-center justify-center text-xs font-bold text-white',
style: () => `background-color: ${secondary()}`
}, 'Secondary'),
Div({
class: 'h-12 rounded-lg shadow-sm flex items-center justify-center text-xs font-bold text-white',
style: () => `background-color: ${accent()}`
}, 'Accent')
])
]);
};
$mount(DynamicDemo, dynamicTarget);
}
};
initColorpickerExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initColorpickerExamples);
});
}
})();
</script>

View File

@@ -0,0 +1,339 @@
# Datepicker
Date and date range picker component with calendar interface, time selection, and reactive state management.
## Tag
`Datepicker`
## Props
| Prop | Type | Default | Description |
| :----------- | :------------------------------------------------ | :----------------------- | :----------------------------------------------- |
| `label` | `string` | `-` | Label text for the input |
| `value` | `string \| {start: string, end: string} \| Signal` | `-` | Selected date or range |
| `range` | `boolean` | `false` | Enable date range selection mode |
| `hour` | `boolean` | `false` | Enable hour selection |
| `placeholder`| `string` | `'Select date...'` | Placeholder text |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
## Live Examples
### Basic Datepicker
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const date = $('');
return Datepicker({
label: 'Select date',
value: date,
placeholder: 'Choose a date...'
});
};
$mount(BasicDemo, '#demo-basic');
```
### Date Range Picker
<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-range" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const RangeDemo = () => {
const range = $({ start: '', end: '' });
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Datepicker({
label: 'Date range',
value: range,
range: true,
placeholder: 'Select start and end date...'
}),
() => range().start && range().end ? Div({ class: 'alert alert-success' }, [
`Selected: ${range().start}${range().end}`
]) : null
]);
};
$mount(RangeDemo, '#demo-range');
```
### With Time Selection
<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-time" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const TimeDemo = () => {
const datetime = $('');
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Datepicker({
label: 'Select date and time',
value: datetime,
hour: true,
placeholder: 'Choose date and time...'
}),
() => datetime() ? Div({ class: 'alert alert-info' }, [
`Selected: ${datetime()}`
]) : null
]);
};
$mount(TimeDemo, '#demo-time');
```
### Range with Time
<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-range-time" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const RangeTimeDemo = () => {
const range = $({ start: '', end: '', startHour: 9, endHour: 17 });
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Datepicker({
label: 'Schedule range',
value: range,
range: true,
hour: true,
placeholder: 'Select date and time range...'
}),
() => range().start && range().end ? Div({ class: 'alert alert-primary' }, [
`From ${range().start} ${range().startHour || 9}:00 to ${range().end} ${range().endHour || 17}:00`
]) : null
]);
};
$mount(RangeTimeDemo, '#demo-range-time');
```
### Reactive Display
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const ReactiveDemo = () => {
const date = $('');
const today = new Date().toISOString().split('T')[0];
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Datepicker({
label: 'Select date',
value: date,
placeholder: 'Choose a date...'
}),
Div({ class: 'stats shadow' }, [
Div({ class: 'stat' }, [
Div({ class: 'stat-title' }, 'Selected date'),
Div({ class: 'stat-value text-sm' }, () => date() || 'Not selected'),
Div({ class: 'stat-desc' }, () => date() === today ? 'Today' : '')
])
])
]);
};
$mount(ReactiveDemo, '#demo-reactive');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
return Div({ class: 'flex flex-col gap-4' }, [
Datepicker({
label: 'Single date',
value: $('2024-12-25'),
placeholder: 'Select date...'
}),
Datepicker({
label: 'Date range',
range: true,
value: $({ start: '2024-12-01', end: '2024-12-31' }),
placeholder: 'Select range...'
}),
Datepicker({
label: 'With time',
hour: true,
value: $('2024-12-25T14:00:00'),
placeholder: 'Select date and time...'
})
]);
};
$mount(VariantsDemo, '#demo-variants');
```
<script>
(function() {
const initDatepickerExamples = () => {
// 1. Basic Datepicker
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
const date = $('');
return Datepicker({
label: 'Select date',
value: date,
placeholder: 'Choose a date...'
});
};
$mount(BasicDemo, basicTarget);
}
// 2. Date Range Picker
const rangeTarget = document.querySelector('#demo-range');
if (rangeTarget && !rangeTarget.hasChildNodes()) {
const RangeDemo = () => {
const range = $({ start: '', end: '' });
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Datepicker({
label: 'Date range',
value: range,
range: true,
placeholder: 'Select start and end date...'
}),
() => range().start && range().end ? Div({ class: 'alert alert-success' }, [
`Selected: ${range().start} → ${range().end}`
]) : null
]);
};
$mount(RangeDemo, rangeTarget);
}
// 3. With Time Selection
const timeTarget = document.querySelector('#demo-time');
if (timeTarget && !timeTarget.hasChildNodes()) {
const TimeDemo = () => {
const datetime = $('');
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Datepicker({
label: 'Select date and time',
value: datetime,
hour: true,
placeholder: 'Choose date and time...'
}),
() => datetime() ? Div({ class: 'alert alert-info' }, [
`Selected: ${datetime()}`
]) : null
]);
};
$mount(TimeDemo, timeTarget);
}
// 4. Range with Time
const rangeTimeTarget = document.querySelector('#demo-range-time');
if (rangeTimeTarget && !rangeTimeTarget.hasChildNodes()) {
const RangeTimeDemo = () => {
const range = $({ start: '', end: '', startHour: 9, endHour: 17 });
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Datepicker({
label: 'Schedule range',
value: range,
range: true,
hour: true,
placeholder: 'Select date and time range...'
}),
() => range().start && range().end ? Div({ class: 'alert alert-primary' }, [
`From ${range().start} ${range().startHour || 9}:00 to ${range().end} ${range().endHour || 17}:00`
]) : null
]);
};
$mount(RangeTimeDemo, rangeTimeTarget);
}
// 5. Reactive Display
const reactiveTarget = document.querySelector('#demo-reactive');
if (reactiveTarget && !reactiveTarget.hasChildNodes()) {
const ReactiveDemo = () => {
const date = $('');
const today = new Date().toISOString().split('T')[0];
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Datepicker({
label: 'Select date',
value: date,
placeholder: 'Choose a date...'
}),
Div({ class: 'stats shadow' }, [
Div({ class: 'stat' }, [
Div({ class: 'stat-title' }, 'Selected date'),
Div({ class: 'stat-value text-sm' }, () => date() || 'Not selected'),
Div({ class: 'stat-desc' }, () => date() === today ? 'Today' : '')
])
])
]);
};
$mount(ReactiveDemo, reactiveTarget);
}
// 6. All Variants
const variantsTarget = document.querySelector('#demo-variants');
if (variantsTarget && !variantsTarget.hasChildNodes()) {
const VariantsDemo = () => {
return Div({ class: 'flex flex-col gap-4' }, [
Datepicker({
label: 'Single date',
value: $('2024-12-25'),
placeholder: 'Select date...'
}),
Datepicker({
label: 'Date range',
range: true,
value: $({ start: '2024-12-01', end: '2024-12-31' }),
placeholder: 'Select range...'
}),
Datepicker({
label: 'With time',
hour: true,
value: $('2024-12-25T14:00:00'),
placeholder: 'Select date and time...'
})
]);
};
$mount(VariantsDemo, variantsTarget);
}
};
initDatepickerExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initDatepickerExamples);
});
}
})();
</script>

View File

@@ -0,0 +1,918 @@
# Drawer
Drawer component for creating off-canvas side panels with overlay and toggle functionality.
## Tag
`Drawer`
## Props
| Prop | Type | Default | Description |
| :----------- | :--------------------------- | :---------- | :----------------------------------------------- |
| `id` | `string` | Required | Unique identifier for the drawer |
| `open` | `boolean \| Signal<boolean>` | `false` | Drawer open state |
| `side` | `VNode` | Required | Content to display in the drawer panel |
| `content` | `VNode` | Required | Main page content |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
## Live Examples
### Basic Drawer
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const isOpen = $(false);
return Drawer({
id: 'basic-drawer',
open: isOpen,
side: Div({ class: 'p-4' }, [
Div({ class: 'text-lg font-bold mb-4' }, 'Menu'),
Div({ class: 'flex flex-col gap-2' }, [
Button({ class: 'btn btn-ghost justify-start' }, 'Home'),
Button({ class: 'btn btn-ghost justify-start' }, 'About'),
Button({ class: 'btn btn-ghost justify-start' }, 'Contact')
])
]),
content: Div({ class: 'p-4 text-center' }, [
Button({
class: 'btn btn-primary',
onclick: () => isOpen(true)
}, 'Open Drawer')
])
});
};
$mount(BasicDemo, '#demo-basic');
```
### Navigation Drawer
<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-nav" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const NavDrawer = () => {
const isOpen = $(false);
const activePage = $('home');
const pages = {
home: 'Welcome to the Home Page!',
about: 'About Us - Learn more about our company',
services: 'Our Services - What we offer',
contact: 'Contact Us - Get in touch'
};
return Drawer({
id: 'nav-drawer',
open: isOpen,
side: Div({ class: 'p-4 w-64' }, [
Div({ class: 'text-xl font-bold mb-6' }, 'MyApp'),
Div({ class: 'flex flex-col gap-1' }, [
Button({
class: `btn btn-ghost justify-start ${activePage() === 'home' ? 'btn-active' : ''}`,
onclick: () => {
activePage('home');
isOpen(false);
}
}, '🏠 Home'),
Button({
class: `btn btn-ghost justify-start ${activePage() === 'about' ? 'btn-active' : ''}`,
onclick: () => {
activePage('about');
isOpen(false);
}
}, ' About'),
Button({
class: `btn btn-ghost justify-start ${activePage() === 'services' ? 'btn-active' : ''}`,
onclick: () => {
activePage('services');
isOpen(false);
}
}, '⚙️ Services'),
Button({
class: `btn btn-ghost justify-start ${activePage() === 'contact' ? 'btn-active' : ''}`,
onclick: () => {
activePage('contact');
isOpen(false);
}
}, '📧 Contact')
])
]),
content: Div({ class: 'p-4' }, [
Div({ class: 'flex justify-between items-center mb-4' }, [
Button({
class: 'btn btn-ghost btn-circle',
onclick: () => isOpen(true)
}, '☰'),
Span({ class: 'text-lg font-bold' }, 'MyApp')
]),
Div({ class: 'card bg-base-200 shadow-lg' }, [
Div({ class: 'card-body' }, [
Div({ class: 'text-2xl font-bold mb-2' }, () => activePage().charAt(0).toUpperCase() + activePage().slice(1)),
Div({ class: 'text-lg' }, () => pages[activePage()])
])
])
])
});
};
$mount(NavDrawer, '#demo-nav');
```
### Settings Drawer
<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-settings" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const SettingsDrawer = () => {
const isOpen = $(false);
const darkMode = $(false);
const notifications = $(true);
const autoSave = $(false);
return Drawer({
id: 'settings-drawer',
open: isOpen,
side: Div({ class: 'p-4 w-80' }, [
Div({ class: 'flex justify-between items-center mb-6' }, [
Span({ class: 'text-xl font-bold' }, 'Settings'),
Button({
class: 'btn btn-ghost btn-circle btn-sm',
onclick: () => isOpen(false)
}, '✕')
]),
Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex justify-between items-center' }, [
Span({}, 'Dark Mode'),
Swap({
value: darkMode,
on: "🌙",
off: "☀️",
onclick: () => darkMode(!darkMode())
})
]),
Div({ class: 'flex justify-between items-center' }, [
Span({}, 'Notifications'),
Swap({
value: notifications,
on: "🔔",
off: "🔕",
onclick: () => notifications(!notifications())
})
]),
Div({ class: 'flex justify-between items-center' }, [
Span({}, 'Auto Save'),
Swap({
value: autoSave,
on: "✅",
off: "⭕",
onclick: () => autoSave(!autoSave())
})
])
]),
Div({ class: 'divider my-4' }),
Div({ class: 'flex gap-2' }, [
Button({
class: 'btn btn-primary flex-1',
onclick: () => {
isOpen(false);
Toast('Settings saved!', 'alert-success', 2000);
}
}, 'Save'),
Button({
class: 'btn btn-ghost flex-1',
onclick: () => isOpen(false)
}, 'Cancel')
])
]),
content: Div({ class: 'p-4' }, [
Div({ class: 'flex justify-between items-center' }, [
Span({ class: 'text-lg font-bold' }, 'Dashboard'),
Button({
class: 'btn btn-ghost btn-circle',
onclick: () => isOpen(true)
}, '⚙️')
]),
Div({ class: 'mt-4 grid grid-cols-2 gap-4' }, [
Div({ class: 'stat bg-base-200 rounded-lg p-4' }, [
Div({ class: 'stat-title' }, 'Users'),
Div({ class: 'stat-value' }, '1,234')
]),
Div({ class: 'stat bg-base-200 rounded-lg p-4' }, [
Div({ class: 'stat-title' }, 'Revenue'),
Div({ class: 'stat-value' }, '$45K')
])
])
])
});
};
$mount(SettingsDrawer, '#demo-settings');
```
### Cart Drawer
<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-cart" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const CartDrawer = () => {
const isOpen = $(false);
const cart = $([
{ id: 1, name: 'Product 1', price: 29, quantity: 2 },
{ id: 2, name: 'Product 2', price: 49, quantity: 1 }
]);
const updateQuantity = (id, delta) => {
cart(cart().map(item => {
if (item.id === id) {
const newQty = Math.max(0, item.quantity + delta);
return newQty === 0 ? null : { ...item, quantity: newQty };
}
return item;
}).filter(Boolean));
};
const total = () => cart().reduce((sum, item) => sum + (item.price * item.quantity), 0);
return Drawer({
id: 'cart-drawer',
open: isOpen,
side: Div({ class: 'flex flex-col h-full' }, [
Div({ class: 'p-4 border-b border-base-300' }, [
Div({ class: 'flex justify-between items-center' }, [
Span({ class: 'text-xl font-bold' }, `Cart (${cart().length} items)`),
Button({
class: 'btn btn-ghost btn-circle btn-sm',
onclick: () => isOpen(false)
}, '✕')
])
]),
Div({ class: 'flex-1 overflow-y-auto p-4' }, cart().length === 0
? Div({ class: 'text-center text-gray-500 mt-8' }, 'Your cart is empty')
: Div({ class: 'flex flex-col gap-3' }, cart().map(item =>
Div({ class: 'flex gap-3 items-center p-2 bg-base-200 rounded-lg' }, [
Div({ class: 'flex-1' }, [
Div({ class: 'font-medium' }, item.name),
Div({ class: 'text-sm' }, `$${item.price} each`)
]),
Div({ class: 'flex items-center gap-2' }, [
Button({
class: 'btn btn-xs btn-circle',
onclick: () => updateQuantity(item.id, -1)
}, '-'),
Span({ class: 'w-8 text-center' }, item.quantity),
Button({
class: 'btn btn-xs btn-circle',
onclick: () => updateQuantity(item.id, 1)
}, '+')
]),
Span({ class: 'font-bold w-16 text-right' }, `$${item.price * item.quantity}`)
])
))
),
Div({ class: 'p-4 border-t border-base-300' }, [
Div({ class: 'flex justify-between items-center mb-4' }, [
Span({ class: 'font-bold' }, 'Total'),
Span({ class: 'text-xl font-bold' }, () => `$${total()}`)
]),
Button({
class: 'btn btn-primary w-full',
onclick: () => {
isOpen(false);
Toast('Checkout initiated!', 'alert-success', 2000);
},
disabled: () => cart().length === 0
}, 'Checkout')
])
]),
content: Div({ class: 'p-4' }, [
Div({ class: 'flex justify-between items-center' }, [
Span({ class: 'text-lg font-bold' }, 'Store'),
Button({
class: 'btn btn-primary',
onclick: () => isOpen(true)
}, () => `🛒 Cart (${cart().length})`)
]),
Div({ class: 'mt-4 grid grid-cols-2 gap-4' }, [
Button({
class: 'btn btn-outline h-32 flex flex-col',
onclick: () => {
cart([...cart(), { id: Date.now(), name: 'New Product', price: 39, quantity: 1 }]);
Toast('Added to cart!', 'alert-success', 1500);
}
}, ['📦', 'Add to Cart'])
])
])
});
};
$mount(CartDrawer, '#demo-cart');
```
### Responsive Drawer
<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-responsive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ResponsiveDrawer = () => {
const isOpen = $(false);
const activePage = $('home');
const MenuItems = () => Div({ class: 'flex flex-col gap-1 p-4' }, [
Button({
class: `btn btn-ghost justify-start ${activePage() === 'home' ? 'btn-active' : ''}`,
onclick: () => {
activePage('home');
if (window.innerWidth < 1024) isOpen(false);
}
}, '🏠 Home'),
Button({
class: `btn btn-ghost justify-start ${activePage() === 'analytics' ? 'btn-active' : ''}`,
onclick: () => {
activePage('analytics');
if (window.innerWidth < 1024) isOpen(false);
}
}, '📊 Analytics'),
Button({
class: `btn btn-ghost justify-start ${activePage() === 'settings' ? 'btn-active' : ''}`,
onclick: () => {
activePage('settings');
if (window.innerWidth < 1024) isOpen(false);
}
}, '⚙️ Settings')
]);
return Drawer({
id: 'responsive-drawer',
open: isOpen,
side: Div({ class: 'w-64' }, [
Div({ class: 'text-xl font-bold p-4 border-b border-base-300' }, 'Menu'),
MenuItems()
]),
content: Div({ class: 'flex' }, [
Div({ class: 'hidden lg:block w-64 border-r border-base-300' }, [MenuItems()]),
Div({ class: 'flex-1 p-4' }, [
Div({ class: 'flex justify-between items-center lg:hidden mb-4' }, [
Button({
class: 'btn btn-ghost btn-circle',
onclick: () => isOpen(true)
}, '☰'),
Span({ class: 'text-lg font-bold' }, 'MyApp')
]),
Div({ class: 'card bg-base-200' }, [
Div({ class: 'card-body' }, [
Div({ class: 'text-2xl font-bold' }, () => activePage().charAt(0).toUpperCase() + activePage().slice(1)),
Div({}, 'Content area. On desktop, the menu is always visible on the left.')
])
])
])
])
});
};
$mount(ResponsiveDrawer, '#demo-responsive');
```
### Form Drawer
<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-form" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const FormDrawer = () => {
const isOpen = $(false);
const name = $('');
const email = $('');
const message = $('');
const handleSubmit = () => {
if (name() && email() && message()) {
Toast('Message sent!', 'alert-success', 2000);
isOpen(false);
name('');
email('');
message('');
} else {
Toast('Please fill all fields', 'alert-warning', 2000);
}
};
return Drawer({
id: 'form-drawer',
open: isOpen,
side: Div({ class: 'p-4 w-96' }, [
Div({ class: 'flex justify-between items-center mb-4' }, [
Span({ class: 'text-xl font-bold' }, 'Contact Us'),
Button({
class: 'btn btn-ghost btn-circle btn-sm',
onclick: () => isOpen(false)
}, '✕')
]),
Div({ class: 'flex flex-col gap-4' }, [
Input({
label: 'Name',
value: name,
placeholder: 'Your name',
oninput: (e) => name(e.target.value)
}),
Input({
label: 'Email',
type: 'email',
value: email,
placeholder: 'your@email.com',
oninput: (e) => email(e.target.value)
}),
Div({ class: 'form-control' }, [
Span({ class: 'label-text mb-1' }, 'Message'),
$html('textarea', {
class: 'textarea textarea-bordered h-24',
placeholder: 'Your message',
value: message,
oninput: (e) => message(e.target.value)
})
]),
Div({ class: 'flex gap-2 mt-2' }, [
Button({
class: 'btn btn-primary flex-1',
onclick: handleSubmit
}, 'Send'),
Button({
class: 'btn btn-ghost flex-1',
onclick: () => isOpen(false)
}, 'Cancel')
])
])
]),
content: Div({ class: 'p-4 text-center' }, [
Button({
class: 'btn btn-primary',
onclick: () => isOpen(true)
}, 'Contact Us')
])
});
};
$mount(FormDrawer, '#demo-form');
```
<script>
(function() {
const initDrawerExamples = () => {
// 1. Basic Drawer
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
const isOpen = $(false);
return Drawer({
id: 'basic-drawer',
open: isOpen,
side: Div({ class: 'p-4' }, [
Div({ class: 'text-lg font-bold mb-4' }, 'Menu'),
Div({ class: 'flex flex-col gap-2' }, [
Button({ class: 'btn btn-ghost justify-start' }, 'Home'),
Button({ class: 'btn btn-ghost justify-start' }, 'About'),
Button({ class: 'btn btn-ghost justify-start' }, 'Contact')
])
]),
content: Div({ class: 'p-4 text-center' }, [
Button({
class: 'btn btn-primary',
onclick: () => isOpen(true)
}, 'Open Drawer')
])
});
};
$mount(BasicDemo, basicTarget);
}
// 2. Navigation Drawer
const navTarget = document.querySelector('#demo-nav');
if (navTarget && !navTarget.hasChildNodes()) {
const NavDrawer = () => {
const isOpen = $(false);
const activePage = $('home');
const pages = {
home: 'Welcome to the Home Page!',
about: 'About Us - Learn more about our company',
services: 'Our Services - What we offer',
contact: 'Contact Us - Get in touch'
};
return Drawer({
id: 'nav-drawer',
open: isOpen,
side: Div({ class: 'p-4 w-64' }, [
Div({ class: 'text-xl font-bold mb-6' }, 'MyApp'),
Div({ class: 'flex flex-col gap-1' }, [
Button({
class: `btn btn-ghost justify-start ${activePage() === 'home' ? 'btn-active' : ''}`,
onclick: () => {
activePage('home');
isOpen(false);
}
}, '🏠 Home'),
Button({
class: `btn btn-ghost justify-start ${activePage() === 'about' ? 'btn-active' : ''}`,
onclick: () => {
activePage('about');
isOpen(false);
}
}, ' About'),
Button({
class: `btn btn-ghost justify-start ${activePage() === 'services' ? 'btn-active' : ''}`,
onclick: () => {
activePage('services');
isOpen(false);
}
}, '⚙️ Services'),
Button({
class: `btn btn-ghost justify-start ${activePage() === 'contact' ? 'btn-active' : ''}`,
onclick: () => {
activePage('contact');
isOpen(false);
}
}, '📧 Contact')
])
]),
content: Div({ class: 'p-4' }, [
Div({ class: 'flex justify-between items-center mb-4' }, [
Button({
class: 'btn btn-ghost btn-circle',
onclick: () => isOpen(true)
}, '☰'),
Span({ class: 'text-lg font-bold' }, 'MyApp')
]),
Div({ class: 'card bg-base-200 shadow-lg' }, [
Div({ class: 'card-body' }, [
Div({ class: 'text-2xl font-bold mb-2' }, () => activePage().charAt(0).toUpperCase() + activePage().slice(1)),
Div({ class: 'text-lg' }, () => pages[activePage()])
])
])
])
});
};
$mount(NavDrawer, navTarget);
}
// 3. Settings Drawer
const settingsTarget = document.querySelector('#demo-settings');
if (settingsTarget && !settingsTarget.hasChildNodes()) {
const SettingsDrawer = () => {
const isOpen = $(false);
const darkMode = $(false);
const notifications = $(true);
const autoSave = $(false);
return Drawer({
id: 'settings-drawer',
open: isOpen,
side: Div({ class: 'p-4 w-80' }, [
Div({ class: 'flex justify-between items-center mb-6' }, [
Span({ class: 'text-xl font-bold' }, 'Settings'),
Button({
class: 'btn btn-ghost btn-circle btn-sm',
onclick: () => isOpen(false)
}, '✕')
]),
Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex justify-between items-center' }, [
Span({}, 'Dark Mode'),
Swap({
value: darkMode,
on: "🌙",
off: "☀️",
onclick: () => darkMode(!darkMode())
})
]),
Div({ class: 'flex justify-between items-center' }, [
Span({}, 'Notifications'),
Swap({
value: notifications,
on: "🔔",
off: "🔕",
onclick: () => notifications(!notifications())
})
]),
Div({ class: 'flex justify-between items-center' }, [
Span({}, 'Auto Save'),
Swap({
value: autoSave,
on: "✅",
off: "⭕",
onclick: () => autoSave(!autoSave())
})
])
]),
Div({ class: 'divider my-4' }),
Div({ class: 'flex gap-2' }, [
Button({
class: 'btn btn-primary flex-1',
onclick: () => {
isOpen(false);
Toast('Settings saved!', 'alert-success', 2000);
}
}, 'Save'),
Button({
class: 'btn btn-ghost flex-1',
onclick: () => isOpen(false)
}, 'Cancel')
])
]),
content: Div({ class: 'p-4' }, [
Div({ class: 'flex justify-between items-center' }, [
Span({ class: 'text-lg font-bold' }, 'Dashboard'),
Button({
class: 'btn btn-ghost btn-circle',
onclick: () => isOpen(true)
}, '⚙️')
]),
Div({ class: 'mt-4 grid grid-cols-2 gap-4' }, [
Div({ class: 'stat bg-base-200 rounded-lg p-4' }, [
Div({ class: 'stat-title' }, 'Users'),
Div({ class: 'stat-value' }, '1,234')
]),
Div({ class: 'stat bg-base-200 rounded-lg p-4' }, [
Div({ class: 'stat-title' }, 'Revenue'),
Div({ class: 'stat-value' }, '$45K')
])
])
])
});
};
$mount(SettingsDrawer, settingsTarget);
}
// 4. Cart Drawer
const cartTarget = document.querySelector('#demo-cart');
if (cartTarget && !cartTarget.hasChildNodes()) {
const CartDrawer = () => {
const isOpen = $(false);
const cart = $([
{ id: 1, name: 'Product 1', price: 29, quantity: 2 },
{ id: 2, name: 'Product 2', price: 49, quantity: 1 }
]);
const updateQuantity = (id, delta) => {
cart(cart().map(item => {
if (item.id === id) {
const newQty = Math.max(0, item.quantity + delta);
return newQty === 0 ? null : { ...item, quantity: newQty };
}
return item;
}).filter(Boolean));
};
const total = () => cart().reduce((sum, item) => sum + (item.price * item.quantity), 0);
return Drawer({
id: 'cart-drawer',
open: isOpen,
side: Div({ class: 'flex flex-col h-full' }, [
Div({ class: 'p-4 border-b border-base-300' }, [
Div({ class: 'flex justify-between items-center' }, [
Span({ class: 'text-xl font-bold' }, `Cart (${cart().length} items)`),
Button({
class: 'btn btn-ghost btn-circle btn-sm',
onclick: () => isOpen(false)
}, '✕')
])
]),
Div({ class: 'flex-1 overflow-y-auto p-4' }, cart().length === 0
? Div({ class: 'text-center text-gray-500 mt-8' }, 'Your cart is empty')
: Div({ class: 'flex flex-col gap-3' }, cart().map(item =>
Div({ class: 'flex gap-3 items-center p-2 bg-base-200 rounded-lg' }, [
Div({ class: 'flex-1' }, [
Div({ class: 'font-medium' }, item.name),
Div({ class: 'text-sm' }, `$${item.price} each`)
]),
Div({ class: 'flex items-center gap-2' }, [
Button({
class: 'btn btn-xs btn-circle',
onclick: () => updateQuantity(item.id, -1)
}, '-'),
Span({ class: 'w-8 text-center' }, item.quantity),
Button({
class: 'btn btn-xs btn-circle',
onclick: () => updateQuantity(item.id, 1)
}, '+')
]),
Span({ class: 'font-bold w-16 text-right' }, `$${item.price * item.quantity}`)
])
))
),
Div({ class: 'p-4 border-t border-base-300' }, [
Div({ class: 'flex justify-between items-center mb-4' }, [
Span({ class: 'font-bold' }, 'Total'),
Span({ class: 'text-xl font-bold' }, () => `$${total()}`)
]),
Button({
class: 'btn btn-primary w-full',
onclick: () => {
isOpen(false);
Toast('Checkout initiated!', 'alert-success', 2000);
},
disabled: () => cart().length === 0
}, 'Checkout')
])
]),
content: Div({ class: 'p-4' }, [
Div({ class: 'flex justify-between items-center' }, [
Span({ class: 'text-lg font-bold' }, 'Store'),
Button({
class: 'btn btn-primary',
onclick: () => isOpen(true)
}, () => `🛒 Cart (${cart().length})`)
]),
Div({ class: 'mt-4 grid grid-cols-2 gap-4' }, [
Button({
class: 'btn btn-outline h-32 flex flex-col',
onclick: () => {
cart([...cart(), { id: Date.now(), name: 'New Product', price: 39, quantity: 1 }]);
Toast('Added to cart!', 'alert-success', 1500);
}
}, ['📦', 'Add to Cart'])
])
])
});
};
$mount(CartDrawer, cartTarget);
}
// 5. Responsive Drawer
const responsiveTarget = document.querySelector('#demo-responsive');
if (responsiveTarget && !responsiveTarget.hasChildNodes()) {
const ResponsiveDrawer = () => {
const isOpen = $(false);
const activePage = $('home');
const MenuItems = () => Div({ class: 'flex flex-col gap-1 p-4' }, [
Button({
class: `btn btn-ghost justify-start ${activePage() === 'home' ? 'btn-active' : ''}`,
onclick: () => {
activePage('home');
if (window.innerWidth < 1024) isOpen(false);
}
}, '🏠 Home'),
Button({
class: `btn btn-ghost justify-start ${activePage() === 'analytics' ? 'btn-active' : ''}`,
onclick: () => {
activePage('analytics');
if (window.innerWidth < 1024) isOpen(false);
}
}, '📊 Analytics'),
Button({
class: `btn btn-ghost justify-start ${activePage() === 'settings' ? 'btn-active' : ''}`,
onclick: () => {
activePage('settings');
if (window.innerWidth < 1024) isOpen(false);
}
}, '⚙️ Settings')
]);
return Drawer({
id: 'responsive-drawer',
open: isOpen,
side: Div({ class: 'w-64' }, [
Div({ class: 'text-xl font-bold p-4 border-b border-base-300' }, 'Menu'),
MenuItems()
]),
content: Div({ class: 'flex' }, [
Div({ class: 'hidden lg:block w-64 border-r border-base-300' }, [MenuItems()]),
Div({ class: 'flex-1 p-4' }, [
Div({ class: 'flex justify-between items-center lg:hidden mb-4' }, [
Button({
class: 'btn btn-ghost btn-circle',
onclick: () => isOpen(true)
}, '☰'),
Span({ class: 'text-lg font-bold' }, 'MyApp')
]),
Div({ class: 'card bg-base-200' }, [
Div({ class: 'card-body' }, [
Div({ class: 'text-2xl font-bold' }, () => activePage().charAt(0).toUpperCase() + activePage().slice(1)),
Div({}, 'Content area. On desktop, the menu is always visible on the left.')
])
])
])
])
});
};
$mount(ResponsiveDrawer, responsiveTarget);
}
// 6. Form Drawer
const formTarget = document.querySelector('#demo-form');
if (formTarget && !formTarget.hasChildNodes()) {
const FormDrawer = () => {
const isOpen = $(false);
const name = $('');
const email = $('');
const message = $('');
const handleSubmit = () => {
if (name() && email() && message()) {
Toast('Message sent!', 'alert-success', 2000);
isOpen(false);
name('');
email('');
message('');
} else {
Toast('Please fill all fields', 'alert-warning', 2000);
}
};
return Drawer({
id: 'form-drawer',
open: isOpen,
side: Div({ class: 'p-4 w-96' }, [
Div({ class: 'flex justify-between items-center mb-4' }, [
Span({ class: 'text-xl font-bold' }, 'Contact Us'),
Button({
class: 'btn btn-ghost btn-circle btn-sm',
onclick: () => isOpen(false)
}, '✕')
]),
Div({ class: 'flex flex-col gap-4' }, [
Input({
label: 'Name',
value: name,
placeholder: 'Your name',
oninput: (e) => name(e.target.value)
}),
Input({
label: 'Email',
type: 'email',
value: email,
placeholder: 'your@email.com',
oninput: (e) => email(e.target.value)
}),
Div({ class: 'form-control' }, [
Span({ class: 'label-text mb-1' }, 'Message'),
$html('textarea', {
class: 'textarea textarea-bordered h-24',
placeholder: 'Your message',
value: message,
oninput: (e) => message(e.target.value)
})
]),
Div({ class: 'flex gap-2 mt-2' }, [
Button({
class: 'btn btn-primary flex-1',
onclick: handleSubmit
}, 'Send'),
Button({
class: 'btn btn-ghost flex-1',
onclick: () => isOpen(false)
}, 'Cancel')
])
])
]),
content: Div({ class: 'p-4 text-center' }, [
Button({
class: 'btn btn-primary',
onclick: () => isOpen(true)
}, 'Contact Us')
])
});
};
$mount(FormDrawer, formTarget);
}
};
initDrawerExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initDrawerExamples);
});
}
})();
</script>

View File

@@ -0,0 +1,489 @@
# Dropdown
Dropdown component for creating menus, selectors, and action panels that appear when triggered. Supports both array-based items and custom content.
## Tag
`Dropdown`
## Props
| Prop | Type | Default | Description |
| :----------- | :-------------------------------------- | :---------- | :----------------------------------------------- |
| `label` | `string \| VNode \| Signal` | `-` | Button label or content |
| `icon` | `string \| VNode \| Signal` | `-` | Icon displayed next to label |
| `items` | `Array<MenuItem> \| Signal<Array>` | `-` | Array of menu items (alternative to children) |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `children` | `VNode \| function` | `-` | Custom dropdown content (alternative to items) |
### MenuItem Structure (when using `items`)
| Property | Type | Description |
| :---------- | :--------------------------- | :----------------------------------------------- |
| `label` | `string \| VNode` | Menu item text |
| `icon` | `string \| VNode` | Optional icon for the menu item |
| `onclick` | `function` | Click handler |
| `class` | `string` | Additional CSS classes for the menu item |
## Live Examples
### Basic Dropdown (Items Array)
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
return Dropdown({
label: 'Options',
items: [
{ label: 'Profile', onclick: () => Toast('Profile clicked', 'alert-info', 2000) },
{ label: 'Settings', onclick: () => Toast('Settings clicked', 'alert-info', 2000) },
{ label: 'Logout', onclick: () => Toast('Logged out', 'alert-warning', 2000), class: 'text-error' }
]
});
};
$mount(BasicDemo, '#demo-basic');
```
### With Icons (Items Array)
<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-icons" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const IconsDemo = () => {
return Dropdown({
label: 'Menu',
icon: '☰',
items: [
{ icon: '👤', label: 'Profile', onclick: () => Toast('Profile', 'alert-info', 2000) },
{ icon: '⭐', label: 'Favorites', onclick: () => Toast('Favorites', 'alert-info', 2000) },
{ icon: '📁', label: 'Documents', onclick: () => Toast('Documents', 'alert-info', 2000) },
{ icon: '⚙️', label: 'Settings', onclick: () => Toast('Settings', 'alert-info', 2000) }
]
});
};
$mount(IconsDemo, '#demo-icons');
```
### Action Dropdown (Items Array)
<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-actions" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const ActionsDemo = () => {
const handleAction = (action) => {
Toast(`${action} action`, 'alert-info', 2000);
};
return Dropdown({
label: 'Actions',
class: 'dropdown-end',
items: [
{ icon: '✏️', label: 'Edit', onclick: () => handleAction('Edit') },
{ icon: '📋', label: 'Copy', onclick: () => handleAction('Copy') },
{ icon: '🗑️', label: 'Delete', onclick: () => handleAction('Delete'), class: 'text-error' }
]
});
};
$mount(ActionsDemo, '#demo-actions');
```
### User Dropdown (Items Array)
<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-user" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const UserDropdown = () => {
return Dropdown({
label: Span({ class: 'flex items-center gap-2' }, [
Div({ class: 'avatar placeholder' }, [
Div({ class: 'bg-primary text-primary-content rounded-full w-8 h-8 flex items-center justify-center text-sm' }, 'JD')
]),
'John Doe'
]),
class: 'dropdown-end',
items: [
{ label: 'Profile', onclick: () => Toast('Profile', 'alert-info', 2000) },
{ label: 'Settings', onclick: () => Toast('Settings', 'alert-info', 2000) },
{ label: 'Sign Out', onclick: () => Toast('Signed out', 'alert-warning', 2000), class: 'text-error' }
]
});
};
$mount(UserDropdown, '#demo-user');
```
### Reactive Items
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const ReactiveDropdown = () => {
const count = $(0);
const items = () => [
{ label: `Count: ${count()}`, onclick: () => {} },
{ label: 'Increment', onclick: () => count(count() + 1) },
{ label: 'Decrement', onclick: () => count(count() - 1) },
{ label: 'Reset', onclick: () => count(0) }
];
return Dropdown({
label: () => `Counter (${count()})`,
items: items
});
};
$mount(ReactiveDropdown, '#demo-reactive');
```
### Notification Dropdown (Custom Children)
<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-notifications" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const NotificationsDropdown = () => {
const notifications = $([
{ id: 1, title: 'New message', time: '5 min ago', read: false },
{ id: 2, title: 'Update available', time: '1 hour ago', read: false },
{ id: 3, title: 'Task completed', time: '2 hours ago', read: true }
]);
const markAsRead = (id) => {
notifications(notifications().map(n =>
n.id === id ? { ...n, read: true } : n
));
};
const unreadCount = () => notifications().filter(n => !n.read).length;
return Dropdown({
label: Span({ class: 'relative' }, [
'🔔',
() => unreadCount() > 0 ? Span({ class: 'badge badge-xs badge-error absolute -top-1 -right-2' }, unreadCount()) : null
]),
class: 'dropdown-end',
children: () => Div({ class: 'w-80' }, [
Div({ class: 'p-3 border-b border-base-300 font-bold' }, `Notifications (${unreadCount()} unread)`),
Div({ class: 'max-h-64 overflow-y-auto' }, notifications().map(notif =>
Div({
class: `p-3 border-b border-base-300 cursor-pointer hover:bg-base-200 ${!notif.read ? 'bg-primary/5' : ''}`,
onclick: () => markAsRead(notif.id)
}, [
Div({ class: 'font-medium' }, notif.title),
Div({ class: 'text-xs opacity-60' }, notif.time),
!notif.read && Span({ class: 'badge badge-xs badge-primary mt-1' }, 'New')
])
)),
Div({ class: 'p-2 text-center' }, [
Button({
class: 'btn btn-xs btn-ghost w-full',
onclick: () => notifications([])
}, 'Clear all')
])
])
});
};
$mount(NotificationsDropdown, '#demo-notifications');
```
### Custom Content Dropdown
<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-custom" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const CustomDropdown = () => {
const selected = $('Option 1');
return Dropdown({
label: () => selected(),
children: () => Div({ class: 'p-4 min-w-48' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Select an option'),
Div({ class: 'flex flex-col gap-1' }, [
Button({
class: 'btn btn-ghost btn-sm justify-start',
onclick: () => selected('Option 1')
}, 'Option 1'),
Button({
class: 'btn btn-ghost btn-sm justify-start',
onclick: () => selected('Option 2')
}, 'Option 2'),
Button({
class: 'btn btn-ghost btn-sm justify-start',
onclick: () => selected('Option 3')
}, 'Option 3')
])
])
});
};
$mount(CustomDropdown, '#demo-custom');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-4 justify-center"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
const commonItems = [
{ label: 'Item 1', onclick: () => Toast('Item 1', 'alert-info', 2000) },
{ label: 'Item 2', onclick: () => Toast('Item 2', 'alert-info', 2000) },
{ label: 'Item 3', onclick: () => Toast('Item 3', 'alert-info', 2000) }
];
return Div({ class: 'flex flex-wrap gap-4 justify-center' }, [
Dropdown({ label: 'Default', items: commonItems }),
Dropdown({ label: 'With Icon', icon: '☰', items: commonItems }),
Dropdown({ label: 'End Position', class: 'dropdown-end', items: commonItems })
]);
};
$mount(VariantsDemo, '#demo-variants');
```
<script>
(function() {
const initDropdownExamples = () => {
// 1. Basic Dropdown (Items Array)
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
return Dropdown({
label: 'Options',
items: [
{ label: 'Profile', onclick: () => Toast('Profile clicked', 'alert-info', 2000) },
{ label: 'Settings', onclick: () => Toast('Settings clicked', 'alert-info', 2000) },
{ label: 'Logout', onclick: () => Toast('Logged out', 'alert-warning', 2000), class: 'text-error' }
]
});
};
$mount(BasicDemo, basicTarget);
}
// 2. With Icons (Items Array)
const iconsTarget = document.querySelector('#demo-icons');
if (iconsTarget && !iconsTarget.hasChildNodes()) {
const IconsDemo = () => {
return Dropdown({
label: 'Menu',
icon: '☰',
items: [
{ icon: '👤', label: 'Profile', onclick: () => Toast('Profile', 'alert-info', 2000) },
{ icon: '⭐', label: 'Favorites', onclick: () => Toast('Favorites', 'alert-info', 2000) },
{ icon: '📁', label: 'Documents', onclick: () => Toast('Documents', 'alert-info', 2000) },
{ icon: '⚙️', label: 'Settings', onclick: () => Toast('Settings', 'alert-info', 2000) }
]
});
};
$mount(IconsDemo, iconsTarget);
}
// 3. Action Dropdown (Items Array)
const actionsTarget = document.querySelector('#demo-actions');
if (actionsTarget && !actionsTarget.hasChildNodes()) {
const ActionsDemo = () => {
const handleAction = (action) => {
Toast(`${action} action`, 'alert-info', 2000);
};
return Dropdown({
label: 'Actions',
class: 'dropdown-end',
items: [
{ icon: '✏️', label: 'Edit', onclick: () => handleAction('Edit') },
{ icon: '📋', label: 'Copy', onclick: () => handleAction('Copy') },
{ icon: '🗑️', label: 'Delete', onclick: () => handleAction('Delete'), class: 'text-error' }
]
});
};
$mount(ActionsDemo, actionsTarget);
}
// 4. User Dropdown (Items Array)
const userTarget = document.querySelector('#demo-user');
if (userTarget && !userTarget.hasChildNodes()) {
const UserDropdown = () => {
return Dropdown({
label: Span({ class: 'flex items-center gap-2' }, [
Div({ class: 'avatar placeholder' }, [
Div({ class: 'bg-primary text-primary-content rounded-full w-8 h-8 flex items-center justify-center text-sm' }, 'JD')
]),
'John Doe'
]),
class: 'dropdown-end',
items: [
{ label: 'Profile', onclick: () => Toast('Profile', 'alert-info', 2000) },
{ label: 'Settings', onclick: () => Toast('Settings', 'alert-info', 2000) },
{ label: 'Sign Out', onclick: () => Toast('Signed out', 'alert-warning', 2000), class: 'text-error' }
]
});
};
$mount(UserDropdown, userTarget);
}
// 5. Reactive Items
const reactiveTarget = document.querySelector('#demo-reactive');
if (reactiveTarget && !reactiveTarget.hasChildNodes()) {
const ReactiveDropdown = () => {
const count = $(0);
const items = () => [
{ label: `Count: ${count()}`, onclick: () => {} },
{ label: 'Increment', onclick: () => count(count() + 1) },
{ label: 'Decrement', onclick: () => count(count() - 1) },
{ label: 'Reset', onclick: () => count(0) }
];
return Dropdown({
label: () => `Counter (${count()})`,
items: items
});
};
$mount(ReactiveDropdown, reactiveTarget);
}
// 6. Notification Dropdown (Custom Children)
const notifTarget = document.querySelector('#demo-notifications');
if (notifTarget && !notifTarget.hasChildNodes()) {
const NotificationsDropdown = () => {
const notifications = $([
{ id: 1, title: 'New message', time: '5 min ago', read: false },
{ id: 2, title: 'Update available', time: '1 hour ago', read: false },
{ id: 3, title: 'Task completed', time: '2 hours ago', read: true }
]);
const markAsRead = (id) => {
notifications(notifications().map(n =>
n.id === id ? { ...n, read: true } : n
));
};
const unreadCount = () => notifications().filter(n => !n.read).length;
return Dropdown({
label: Span({ class: 'relative' }, [
'🔔',
() => unreadCount() > 0 ? Span({ class: 'badge badge-xs badge-error absolute -top-1 -right-2' }, unreadCount()) : null
]),
class: 'dropdown-end',
children: () => Div({ class: 'w-80' }, [
Div({ class: 'p-3 border-b border-base-300 font-bold' }, `Notifications (${unreadCount()} unread)`),
Div({ class: 'max-h-64 overflow-y-auto' }, notifications().map(notif =>
Div({
class: `p-3 border-b border-base-300 cursor-pointer hover:bg-base-200 ${!notif.read ? 'bg-primary/5' : ''}`,
onclick: () => markAsRead(notif.id)
}, [
Div({ class: 'font-medium' }, notif.title),
Div({ class: 'text-xs opacity-60' }, notif.time),
!notif.read && Span({ class: 'badge badge-xs badge-primary mt-1' }, 'New')
])
)),
Div({ class: 'p-2 text-center' }, [
Button({
class: 'btn btn-xs btn-ghost w-full',
onclick: () => notifications([])
}, 'Clear all')
])
])
});
};
$mount(NotificationsDropdown, notifTarget);
}
// 7. Custom Content Dropdown
const customTarget = document.querySelector('#demo-custom');
if (customTarget && !customTarget.hasChildNodes()) {
const CustomDropdown = () => {
const selected = $('Option 1');
return Dropdown({
label: () => selected(),
children: () => Div({ class: 'p-4 min-w-48' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Select an option'),
Div({ class: 'flex flex-col gap-1' }, [
Button({
class: 'btn btn-ghost btn-sm justify-start',
onclick: () => selected('Option 1')
}, 'Option 1'),
Button({
class: 'btn btn-ghost btn-sm justify-start',
onclick: () => selected('Option 2')
}, 'Option 2'),
Button({
class: 'btn btn-ghost btn-sm justify-start',
onclick: () => selected('Option 3')
}, 'Option 3')
])
])
});
};
$mount(CustomDropdown, customTarget);
}
// 8. All Variants
const variantsTarget = document.querySelector('#demo-variants');
if (variantsTarget && !variantsTarget.hasChildNodes()) {
const VariantsDemo = () => {
const commonItems = [
{ label: 'Item 1', onclick: () => Toast('Item 1', 'alert-info', 2000) },
{ label: 'Item 2', onclick: () => Toast('Item 2', 'alert-info', 2000) },
{ label: 'Item 3', onclick: () => Toast('Item 3', 'alert-info', 2000) }
];
return Div({ class: 'flex flex-wrap gap-4 justify-center' }, [
Dropdown({ label: 'Default', items: commonItems }),
Dropdown({ label: 'With Icon', icon: '☰', items: commonItems }),
Dropdown({ label: 'End Position', class: 'dropdown-end', items: commonItems })
]);
};
$mount(VariantsDemo, variantsTarget);
}
};
initDropdownExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initDropdownExamples);
});
}
})();
</script>

688
docs/components_old/fab.md Normal file
View File

@@ -0,0 +1,688 @@
# Fab
Floating Action Button (FAB) component for primary actions with expandable menu options. Each example uses a container with `position: relative` and explicit height to position the FAB correctly.
## Tag
`Fab`
## Props
| Prop | Type | Default | Description |
| :----------- | :-------------------------------------- | :--------------- | :----------------------------------------------- |
| `icon` | `string \| VNode \| Signal` | `-` | Main FAB icon |
| `label` | `string \| VNode \| Signal` | `-` | Text label for main button |
| `actions` | `Array<Action> \| Signal<Array>` | `[]` | Array of action buttons that expand from FAB |
| `position` | `string` | `'bottom-6 right-6'` | CSS position classes (e.g., 'bottom-6 left-6') |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
### Action Structure
| Property | Type | Description |
| :---------- | :--------------------------- | :----------------------------------------------- |
| `label` | `string \| VNode` | Label text shown next to action button |
| `icon` | `string \| VNode` | Icon for the action button |
| `onclick` | `function` | Click handler |
| `class` | `string` | Additional CSS classes for the action button |
## Live Examples
### Basic FAB
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 min-h-[300px] relative"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
return Div({ class: 'relative h-[300px] w-full bg-base-100 rounded-lg overflow-hidden' }, [
Fab({
icon: '',
actions: [
{ icon: '📝', label: 'New Note', onclick: () => Toast('Create note', 'alert-info', 2000) },
{ icon: '📷', label: 'Take Photo', onclick: () => Toast('Open camera', 'alert-info', 2000) },
{ icon: '📎', label: 'Attach File', onclick: () => Toast('Attach file', 'alert-info', 2000) }
]
})
]);
};
$mount(BasicDemo, '#demo-basic');
```
### With Label
<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-label" class="bg-base-100 p-6 rounded-xl border border-base-300 min-h-[300px] relative"></div>
</div>
</div>
```javascript
const LabelDemo = () => {
return Div({ class: 'relative h-[300px] w-full bg-base-100 rounded-lg overflow-hidden' }, [
Fab({
label: 'Create',
icon: '✨',
actions: [
{ icon: '📝', label: 'Document', onclick: () => Toast('New document', 'alert-success', 2000) },
{ icon: '🎨', label: 'Design', onclick: () => Toast('New design', 'alert-success', 2000) },
{ icon: '📊', label: 'Spreadsheet', onclick: () => Toast('New spreadsheet', 'alert-success', 2000) }
]
})
]);
};
$mount(LabelDemo, '#demo-label');
```
### Different Positions
<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-positions" class="bg-base-100 p-6 rounded-xl border border-base-300 min-h-[500px] relative"></div>
</div>
</div>
```javascript
const PositionsDemo = () => {
const position = $('bottom-6 right-6');
return Div({ class: 'relative h-[500px] w-full bg-base-100 rounded-lg overflow-hidden' }, [
Div({ class: 'absolute top-4 left-4 z-20 bg-base-200 p-2 rounded-lg shadow' }, [
Select({
value: position,
options: [
{ value: 'bottom-6 right-6', label: 'Bottom Right' },
{ value: 'bottom-6 left-6', label: 'Bottom Left' },
{ value: 'top-6 right-6', label: 'Top Right' },
{ value: 'top-6 left-6', label: 'Top Left' }
],
onchange: (e) => position(e.target.value)
})
]),
Div({ class: 'absolute inset-0 flex items-center justify-center text-sm opacity-50 pointer-events-none' }, [
'FAB position changes relative to this container'
]),
Fab({
position: () => position(),
icon: '🧭',
actions: [
{ icon: '⬅️', label: 'Bottom Left', onclick: () => position('bottom-6 left-6') },
{ icon: '➡️', label: 'Bottom Right', onclick: () => position('bottom-6 right-6') },
{ icon: '⬆️', label: 'Top Right', onclick: () => position('top-6 right-6') },
{ icon: '⬇️', label: 'Top Left', onclick: () => position('top-6 left-6') }
]
})
]);
};
$mount(PositionsDemo, '#demo-positions');
```
### Color Variants
<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-colors" class="bg-base-100 p-6 rounded-xl border border-base-300 min-h-[300px] relative"></div>
</div>
</div>
```javascript
const ColorsDemo = () => {
const variant = $('primary');
const variants = {
primary: { class: 'btn-primary', icon: '🔵' },
secondary: { class: 'btn-secondary', icon: '🟣' },
accent: { class: 'btn-accent', icon: '🔴' },
info: { class: 'btn-info', icon: '🔷' },
success: { class: 'btn-success', icon: '🟢' },
warning: { class: 'btn-warning', icon: '🟡' },
error: { class: 'btn-error', icon: '🔴' }
};
return Div({ class: 'relative h-[300px] w-full bg-base-100 rounded-lg overflow-hidden' }, [
Div({ class: 'absolute top-4 left-4 z-20 bg-base-200 p-2 rounded-lg shadow' }, [
Select({
value: variant,
options: Object.keys(variants).map(v => ({ value: v, label: v.charAt(0).toUpperCase() + v.slice(1) })),
onchange: (e) => variant(e.target.value)
})
]),
Fab({
class: variants[variant()].class,
icon: variants[variant()].icon,
actions: [
{ icon: '📝', label: 'Action 1', onclick: () => Toast('Action 1', 'alert-info', 2000) },
{ icon: '🎨', label: 'Action 2', onclick: () => Toast('Action 2', 'alert-info', 2000) },
{ icon: '⚙️', label: 'Action 3', onclick: () => Toast('Action 3', 'alert-info', 2000) }
]
})
]);
};
$mount(ColorsDemo, '#demo-colors');
```
### Reactive Actions
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300 min-h-[300px] relative"></div>
</div>
</div>
```javascript
const ReactiveActions = () => {
const count = $(0);
const actions = () => [
{
icon: '🔢',
label: `Count: ${count()}`,
onclick: () => {}
},
{
icon: '',
label: 'Increment',
onclick: () => count(count() + 1)
},
{
icon: '',
label: 'Decrement',
onclick: () => count(count() - 1)
},
{
icon: '🔄',
label: 'Reset',
onclick: () => count(0)
}
];
return Div({ class: 'relative h-[300px] w-full bg-base-100 rounded-lg overflow-hidden' }, [
Fab({
icon: () => count() > 0 ? `🔢 ${count()}` : '🎛️',
actions: actions
})
]);
};
$mount(ReactiveActions, '#demo-reactive');
```
### Document Actions
<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-document" class="bg-base-100 p-6 rounded-xl border border-base-300 min-h-[300px] relative"></div>
</div>
</div>
```javascript
const DocumentActions = () => {
const saved = $(false);
const handleSave = () => {
saved(true);
Toast('Document saved!', 'alert-success', 2000);
setTimeout(() => saved(false), 3000);
};
return Div({ class: 'relative h-[300px] w-full bg-base-100 rounded-lg overflow-hidden' }, [
Div({ class: 'absolute inset-0 flex flex-col items-center justify-center' }, [
Div({ class: 'text-6xl mb-4' }, '📄'),
Div({ class: 'text-sm opacity-70' }, 'Untitled Document'),
() => saved() ? Div({ class: 'mt-4' }, Alert({ type: 'success', message: '✓ Saved successfully' })) : null
]),
Fab({
icon: '✏️',
actions: [
{ icon: '💾', label: 'Save', onclick: handleSave },
{ icon: '📋', label: 'Copy', onclick: () => Toast('Copied!', 'alert-info', 2000) },
{ icon: '✂️', label: 'Cut', onclick: () => Toast('Cut!', 'alert-info', 2000) },
{ icon: '📎', label: 'Share', onclick: () => Toast('Share dialog', 'alert-info', 2000) }
]
})
]);
};
$mount(DocumentActions, '#demo-document');
```
### Messaging FAB
<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-messaging" class="bg-base-100 p-6 rounded-xl border border-base-300 min-h-[300px] relative"></div>
</div>
</div>
```javascript
const MessagingFAB = () => {
const unread = $(3);
return Div({ class: 'relative h-[300px] w-full bg-base-100 rounded-lg overflow-hidden' }, [
Div({ class: 'absolute inset-0 flex flex-col items-center justify-center' }, [
Div({ class: 'text-6xl mb-4' }, '💬'),
Div({ class: 'text-sm opacity-70' }, 'Messages'),
() => unread() > 0 ? Div({ class: 'badge badge-error mt-2' }, `${unread()} unread`) : null
]),
Fab({
icon: () => `💬${unread() > 0 ? ` ${unread()}` : ''}`,
class: 'btn-primary',
actions: [
{
icon: '👤',
label: 'New Message',
onclick: () => Toast('Start new conversation', 'alert-info', 2000)
},
{
icon: '👥',
label: 'Group Chat',
onclick: () => Toast('Create group', 'alert-info', 2000)
},
{
icon: '📞',
label: 'Voice Call',
onclick: () => Toast('Start call', 'alert-info', 2000)
},
{
icon: '📹',
label: 'Video Call',
onclick: () => Toast('Start video call', 'alert-info', 2000)
},
{
icon: '🔔',
label: () => `Mark as read (${unread()})`,
onclick: () => {
unread(0);
Toast('All messages read', 'alert-success', 2000);
}
}
]
})
]);
};
$mount(MessagingFAB, '#demo-messaging');
```
### Flower Style FAB
<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-flower" class="bg-base-100 p-6 rounded-xl border border-base-300 min-h-[300px] relative"></div>
</div>
</div>
```javascript
const FlowerDemo = () => {
return Div({ class: 'relative h-[300px] w-full bg-base-100 rounded-lg overflow-hidden' }, [
Div({ class: 'absolute inset-0 flex items-center justify-center text-sm opacity-50' }, [
'Flower style FAB (quarter circle arrangement)'
]),
Fab({
icon: '🌸',
class: 'fab-flower',
actions: [
{ icon: '📷', label: 'Camera', onclick: () => Toast('Camera', 'alert-info', 2000) },
{ icon: '🎨', label: 'Gallery', onclick: () => Toast('Gallery', 'alert-info', 2000) },
{ icon: '🎤', label: 'Voice', onclick: () => Toast('Voice', 'alert-info', 2000) },
{ icon: '📍', label: 'Location', onclick: () => Toast('Location', 'alert-info', 2000) }
]
})
]);
};
$mount(FlowerDemo, '#demo-flower');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 min-h-[400px] relative"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
const actions = [
{ icon: '⭐', label: 'Favorite', onclick: () => Toast('Favorited', 'alert-info', 2000) },
{ icon: '🔔', label: 'Remind', onclick: () => Toast('Reminder set', 'alert-info', 2000) },
{ icon: '📅', label: 'Schedule', onclick: () => Toast('Scheduled', 'alert-info', 2000) }
];
return Div({ class: 'relative h-[400px] w-full bg-base-100 rounded-lg overflow-hidden' }, [
Div({ class: 'grid grid-cols-2 gap-4 p-4 h-full' }, [
Div({ class: 'relative border rounded-lg bg-base-200' }, [
Span({ class: 'absolute top-2 left-2 text-xs opacity-50' }, 'Primary'),
Fab({ icon: '🔵', class: 'btn-primary', actions, position: 'bottom-6 left-6' })
]),
Div({ class: 'relative border rounded-lg bg-base-200' }, [
Span({ class: 'absolute top-2 left-2 text-xs opacity-50' }, 'Secondary'),
Fab({ icon: '🟣', class: 'btn-secondary', actions, position: 'bottom-6 right-6' })
]),
Div({ class: 'relative border rounded-lg bg-base-200' }, [
Span({ class: 'absolute top-2 left-2 text-xs opacity-50' }, 'Accent'),
Fab({ icon: '🔴', class: 'btn-accent', actions, position: 'top-6 left-6' })
]),
Div({ class: 'relative border rounded-lg bg-base-200' }, [
Span({ class: 'absolute top-2 left-2 text-xs opacity-50' }, 'Success'),
Fab({ icon: '🟢', class: 'btn-success', actions, position: 'top-6 right-6' })
])
])
]);
};
$mount(VariantsDemo, '#demo-variants');
```
<script>
(function() {
const initFabExamples = () => {
// 1. Basic FAB
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
return Div({ class: 'relative h-[300px] w-full bg-base-100 rounded-lg overflow-hidden' }, [
Fab({
icon: '',
actions: [
{ icon: '📝', label: 'New Note', onclick: () => Toast('Create note', 'alert-info', 2000) },
{ icon: '📷', label: 'Take Photo', onclick: () => Toast('Open camera', 'alert-info', 2000) },
{ icon: '📎', label: 'Attach File', onclick: () => Toast('Attach file', 'alert-info', 2000) }
]
})
]);
};
$mount(BasicDemo, basicTarget);
}
// 2. With Label
const labelTarget = document.querySelector('#demo-label');
if (labelTarget && !labelTarget.hasChildNodes()) {
const LabelDemo = () => {
return Div({ class: 'relative h-[300px] w-full bg-base-100 rounded-lg overflow-hidden' }, [
Fab({
label: 'Create',
icon: '✨',
actions: [
{ icon: '📝', label: 'Document', onclick: () => Toast('New document', 'alert-success', 2000) },
{ icon: '🎨', label: 'Design', onclick: () => Toast('New design', 'alert-success', 2000) },
{ icon: '📊', label: 'Spreadsheet', onclick: () => Toast('New spreadsheet', 'alert-success', 2000) }
]
})
]);
};
$mount(LabelDemo, labelTarget);
}
// 3. Different Positions
const positionsTarget = document.querySelector('#demo-positions');
if (positionsTarget && !positionsTarget.hasChildNodes()) {
const PositionsDemo = () => {
const position = $('bottom-6 right-6');
return Div({ class: 'relative h-[500px] w-full bg-base-100 rounded-lg overflow-hidden' }, [
Div({ class: 'absolute top-4 left-4 z-20 bg-base-200 p-2 rounded-lg shadow' }, [
Select({
value: position,
options: [
{ value: 'bottom-6 right-6', label: 'Bottom Right' },
{ value: 'bottom-6 left-6', label: 'Bottom Left' },
{ value: 'top-6 right-6', label: 'Top Right' },
{ value: 'top-6 left-6', label: 'Top Left' }
],
onchange: (e) => position(e.target.value)
})
]),
Div({ class: 'absolute inset-0 flex items-center justify-center text-sm opacity-50 pointer-events-none' }, [
'FAB position changes relative to this container'
]),
Fab({
position: () => position(),
icon: '🧭',
actions: [
{ icon: '⬅️', label: 'Bottom Left', onclick: () => position('bottom-6 left-6') },
{ icon: '➡️', label: 'Bottom Right', onclick: () => position('bottom-6 right-6') },
{ icon: '⬆️', label: 'Top Right', onclick: () => position('top-6 right-6') },
{ icon: '⬇️', label: 'Top Left', onclick: () => position('top-6 left-6') }
]
})
]);
};
$mount(PositionsDemo, positionsTarget);
}
// 4. Color Variants
const colorsTarget = document.querySelector('#demo-colors');
if (colorsTarget && !colorsTarget.hasChildNodes()) {
const ColorsDemo = () => {
const variant = $('primary');
const variants = {
primary: { class: 'btn-primary', icon: '🔵' },
secondary: { class: 'btn-secondary', icon: '🟣' },
accent: { class: 'btn-accent', icon: '🔴' },
info: { class: 'btn-info', icon: '🔷' },
success: { class: 'btn-success', icon: '🟢' },
warning: { class: 'btn-warning', icon: '🟡' },
error: { class: 'btn-error', icon: '🔴' }
};
return Div({ class: 'relative h-[300px] w-full bg-base-100 rounded-lg overflow-hidden' }, [
Div({ class: 'absolute top-4 left-4 z-20 bg-base-200 p-2 rounded-lg shadow' }, [
Select({
value: variant,
options: Object.keys(variants).map(v => ({ value: v, label: v.charAt(0).toUpperCase() + v.slice(1) })),
onchange: (e) => variant(e.target.value)
})
]),
Fab({
class: variants[variant()].class,
icon: variants[variant()].icon,
actions: [
{ icon: '📝', label: 'Action 1', onclick: () => Toast('Action 1', 'alert-info', 2000) },
{ icon: '🎨', label: 'Action 2', onclick: () => Toast('Action 2', 'alert-info', 2000) },
{ icon: '⚙️', label: 'Action 3', onclick: () => Toast('Action 3', 'alert-info', 2000) }
]
})
]);
};
$mount(ColorsDemo, colorsTarget);
}
// 5. Reactive Actions
const reactiveTarget = document.querySelector('#demo-reactive');
if (reactiveTarget && !reactiveTarget.hasChildNodes()) {
const ReactiveActions = () => {
const count = $(0);
const actions = () => [
{
icon: '🔢',
label: `Count: ${count()}`,
onclick: () => {}
},
{
icon: '',
label: 'Increment',
onclick: () => count(count() + 1)
},
{
icon: '',
label: 'Decrement',
onclick: () => count(count() - 1)
},
{
icon: '🔄',
label: 'Reset',
onclick: () => count(0)
}
];
return Div({ class: 'relative h-[300px] w-full bg-base-100 rounded-lg overflow-hidden' }, [
Fab({
icon: () => count() > 0 ? `🔢 ${count()}` : '🎛️',
actions: actions
})
]);
};
$mount(ReactiveActions, reactiveTarget);
}
// 6. Document Actions
const documentTarget = document.querySelector('#demo-document');
if (documentTarget && !documentTarget.hasChildNodes()) {
const DocumentActions = () => {
const saved = $(false);
const handleSave = () => {
saved(true);
Toast('Document saved!', 'alert-success', 2000);
setTimeout(() => saved(false), 3000);
};
return Div({ class: 'relative h-[300px] w-full bg-base-100 rounded-lg overflow-hidden' }, [
Div({ class: 'absolute inset-0 flex flex-col items-center justify-center' }, [
Div({ class: 'text-6xl mb-4' }, '📄'),
Div({ class: 'text-sm opacity-70' }, 'Untitled Document'),
() => saved() ? Div({ class: 'mt-4' }, Alert({ type: 'success', message: '✓ Saved successfully' })) : null
]),
Fab({
icon: '✏️',
actions: [
{ icon: '💾', label: 'Save', onclick: handleSave },
{ icon: '📋', label: 'Copy', onclick: () => Toast('Copied!', 'alert-info', 2000) },
{ icon: '✂️', label: 'Cut', onclick: () => Toast('Cut!', 'alert-info', 2000) },
{ icon: '📎', label: 'Share', onclick: () => Toast('Share dialog', 'alert-info', 2000) }
]
})
]);
};
$mount(DocumentActions, documentTarget);
}
// 7. Messaging FAB
const messagingTarget = document.querySelector('#demo-messaging');
if (messagingTarget && !messagingTarget.hasChildNodes()) {
const MessagingFAB = () => {
const unread = $(3);
return Div({ class: 'relative h-[300px] w-full bg-base-100 rounded-lg overflow-hidden' }, [
Div({ class: 'absolute inset-0 flex flex-col items-center justify-center' }, [
Div({ class: 'text-6xl mb-4' }, '💬'),
Div({ class: 'text-sm opacity-70' }, 'Messages'),
() => unread() > 0 ? Div({ class: 'badge badge-error mt-2' }, `${unread()} unread`) : null
]),
Fab({
icon: () => `💬${unread() > 0 ? ` ${unread()}` : ''}`,
class: 'btn-primary',
actions: [
{
icon: '👤',
label: 'New Message',
onclick: () => Toast('Start new conversation', 'alert-info', 2000)
},
{
icon: '👥',
label: 'Group Chat',
onclick: () => Toast('Create group', 'alert-info', 2000)
},
{
icon: '📞',
label: 'Voice Call',
onclick: () => Toast('Start call', 'alert-info', 2000)
},
{
icon: '📹',
label: 'Video Call',
onclick: () => Toast('Start video call', 'alert-info', 2000)
},
{
icon: '🔔',
label: () => `Mark as read (${unread()})`,
onclick: () => {
unread(0);
Toast('All messages read', 'alert-success', 2000);
}
}
]
})
]);
};
$mount(MessagingFAB, messagingTarget);
}
// 8. Flower Style FAB
const flowerTarget = document.querySelector('#demo-flower');
if (flowerTarget && !flowerTarget.hasChildNodes()) {
const FlowerDemo = () => {
return Div({ class: 'relative h-[300px] w-full bg-base-100 rounded-lg overflow-hidden' }, [
Div({ class: 'absolute inset-0 flex items-center justify-center text-sm opacity-50' }, [
'Flower style FAB (quarter circle arrangement)'
]),
Fab({
icon: '🌸',
class: 'fab-flower',
actions: [
{ icon: '📷', label: 'Camera', onclick: () => Toast('Camera', 'alert-info', 2000) },
{ icon: '🎨', label: 'Gallery', onclick: () => Toast('Gallery', 'alert-info', 2000) },
{ icon: '🎤', label: 'Voice', onclick: () => Toast('Voice', 'alert-info', 2000) },
{ icon: '📍', label: 'Location', onclick: () => Toast('Location', 'alert-info', 2000) }
]
})
]);
};
$mount(FlowerDemo, flowerTarget);
}
// 9. All Variants
const variantsTarget = document.querySelector('#demo-variants');
if (variantsTarget && !variantsTarget.hasChildNodes()) {
const VariantsDemo = () => {
const actions = [
{ icon: '⭐', label: 'Favorite', onclick: () => Toast('Favorited', 'alert-info', 2000) },
{ icon: '🔔', label: 'Remind', onclick: () => Toast('Reminder set', 'alert-info', 2000) },
{ icon: '📅', label: 'Schedule', onclick: () => Toast('Scheduled', 'alert-info', 2000) }
];
return Div({ class: 'relative h-[400px] w-full bg-base-100 rounded-lg overflow-hidden' }, [
Div({ class: 'grid grid-cols-2 gap-4 p-4 h-full' }, [
Div({ class: 'relative border rounded-lg bg-base-200' }, [
Span({ class: 'absolute top-2 left-2 text-xs opacity-50' }, 'Primary'),
Fab({ icon: '🔵', class: 'btn-primary', actions, position: 'bottom-6 left-6' })
]),
Div({ class: 'relative border rounded-lg bg-base-200' }, [
Span({ class: 'absolute top-2 left-2 text-xs opacity-50' }, 'Secondary'),
Fab({ icon: '🟣', class: 'btn-secondary', actions, position: 'bottom-6 right-6' })
]),
Div({ class: 'relative border rounded-lg bg-base-200' }, [
Span({ class: 'absolute top-2 left-2 text-xs opacity-50' }, 'Accent'),
Fab({ icon: '🔴', class: 'btn-accent', actions, position: 'top-6 left-6' })
]),
Div({ class: 'relative border rounded-lg bg-base-200' }, [
Span({ class: 'absolute top-2 left-2 text-xs opacity-50' }, 'Success'),
Fab({ icon: '🟢', class: 'btn-success', actions, position: 'top-6 right-6' })
])
])
]);
};
$mount(VariantsDemo, variantsTarget);
}
};
initFabExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initFabExamples);
});
}
})();
</script>

View File

@@ -0,0 +1,549 @@
# Fieldset
Fieldset component for grouping form fields with optional legend and consistent styling.
## Tag
`Fieldset`
## Props
| Prop | Type | Default | Description |
| :----------- | :--------------------------- | :---------- | :----------------------------------------------- |
| `legend` | `string \| VNode \| Signal` | `-` | Fieldset legend/title |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `children` | `VNode \| Array<VNode>` | Required | Form fields or content inside the fieldset |
## Live Examples
### Basic Fieldset
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
return Fieldset({
legend: 'User Information',
class: 'w-full max-w-md mx-auto'
}, [
Div({ class: 'space-y-4' }, [
Input({ label: 'Full Name', placeholder: 'Enter your name' }),
Input({ label: 'Email', type: 'email', placeholder: 'user@example.com' }),
Input({ label: 'Phone', type: 'tel', placeholder: '+1 234 567 890' })
])
]);
};
$mount(BasicDemo, '#demo-basic');
```
### With Reactive Legend
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ReactiveDemo = () => {
const name = $('');
const email = $('');
const isValid = () => name().length > 0 && email().includes('@');
return Fieldset({
legend: () => isValid() ? '✓ Valid Form' : '✗ Incomplete Form',
class: 'w-full max-w-md mx-auto'
}, [
Div({ class: 'space-y-4' }, [
Input({
label: 'Full Name',
value: name,
oninput: (e) => name(e.target.value),
placeholder: 'Enter your name'
}),
Input({
label: 'Email',
type: 'email',
value: email,
oninput: (e) => email(e.target.value),
placeholder: 'user@example.com'
}),
() => isValid()
? Alert({ type: 'success', message: 'Form is ready to submit' })
: Alert({ type: 'warning', message: 'Please fill all required fields' })
])
]);
};
$mount(ReactiveDemo, '#demo-reactive');
```
### Address Form
<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-address" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const AddressDemo = () => {
const address = $('');
const city = $('');
const zip = $('');
const country = $('us');
return Fieldset({
legend: Span({ class: 'flex items-center gap-2' }, ['📍', 'Shipping Address']),
class: 'w-full max-w-md mx-auto'
}, [
Div({ class: 'space-y-4' }, [
Input({ label: 'Street Address', value: address, placeholder: '123 Main St', oninput: (e) => address(e.target.value) }),
Div({ class: 'grid grid-cols-2 gap-4' }, [
Input({ label: 'City', value: city, placeholder: 'City', oninput: (e) => city(e.target.value) }),
Input({ label: 'ZIP Code', value: zip, placeholder: 'ZIP', oninput: (e) => zip(e.target.value) })
]),
Select({
label: 'Country',
value: country,
options: [
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' },
{ value: 'mx', label: 'Mexico' }
],
onchange: (e) => country(e.target.value)
})
])
]);
};
$mount(AddressDemo, '#demo-address');
```
### Payment Method
<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-payment" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const PaymentDemo = () => {
const method = $('credit');
const cardNumber = $('');
const expiry = $('');
const cvv = $('');
return Fieldset({
legend: Span({ class: 'flex items-center gap-2' }, ['💳', 'Payment Details']),
class: 'w-full max-w-md mx-auto'
}, [
Div({ class: 'space-y-4' }, [
Div({ class: 'flex gap-4' }, [
Radio({ label: 'Credit Card', value: method, radioValue: 'credit', onclick: () => method('credit') }),
Radio({ label: 'PayPal', value: method, radioValue: 'paypal', onclick: () => method('paypal') }),
Radio({ label: 'Bank Transfer', value: method, radioValue: 'bank', onclick: () => method('bank') })
]),
() => method() === 'credit' ? Div({ class: 'space-y-4' }, [
Input({ label: 'Card Number', value: cardNumber, placeholder: '1234 5678 9012 3456', oninput: (e) => cardNumber(e.target.value) }),
Div({ class: 'grid grid-cols-2 gap-4' }, [
Input({ label: 'Expiry Date', value: expiry, placeholder: 'MM/YY', oninput: (e) => expiry(e.target.value) }),
Input({ label: 'CVV', type: 'password', value: cvv, placeholder: '123', oninput: (e) => cvv(e.target.value) })
])
]) : null,
() => method() === 'paypal' ? Alert({ type: 'info', message: 'You will be redirected to PayPal after confirming.' }) : null,
() => method() === 'bank' ? Alert({ type: 'warning', message: 'Bank transfer details will be sent via email.' }) : null
])
]);
};
$mount(PaymentDemo, '#demo-payment');
```
### Preferences Panel
<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-preferences" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const PreferencesDemo = () => {
const theme = $('light');
const language = $('en');
const notifications = $(true);
return Fieldset({
legend: Span({ class: 'flex items-center gap-2' }, ['⚙️', 'Preferences']),
class: 'w-full max-w-md mx-auto'
}, [
Div({ class: 'space-y-4' }, [
Div({ class: 'form-control' }, [
Span({ class: 'label-text mb-2' }, 'Theme'),
Div({ class: 'flex gap-4' }, [
Radio({ label: 'Light', value: theme, radioValue: 'light', onclick: () => theme('light') }),
Radio({ label: 'Dark', value: theme, radioValue: 'dark', onclick: () => theme('dark') }),
Radio({ label: 'System', value: theme, radioValue: 'system', onclick: () => theme('system') })
])
]),
Select({
label: 'Language',
value: language,
options: [
{ value: 'en', label: 'English' },
{ value: 'es', label: 'Español' },
{ value: 'fr', label: 'Français' }
],
onchange: (e) => language(e.target.value)
}),
Checkbox({
label: 'Enable notifications',
value: notifications,
onclick: () => notifications(!notifications())
})
])
]);
};
$mount(PreferencesDemo, '#demo-preferences');
```
### Registration Form
<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-registration" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const RegistrationDemo = () => {
const username = $('');
const email = $('');
const password = $('');
const confirmPassword = $('');
const accepted = $(false);
const passwordsMatch = () => password() === confirmPassword();
const isFormValid = () => username() && email().includes('@') && password().length >= 6 && passwordsMatch() && accepted();
const handleSubmit = () => {
if (isFormValid()) {
Toast('Registration successful!', 'alert-success', 2000);
}
};
return Fieldset({
legend: Span({ class: 'flex items-center gap-2' }, ['📝', 'Create Account']),
class: 'w-full max-w-md mx-auto'
}, [
Div({ class: 'space-y-4' }, [
Input({ label: 'Username', value: username, placeholder: 'Choose a username', oninput: (e) => username(e.target.value) }),
Input({ label: 'Email', type: 'email', value: email, placeholder: 'your@email.com', oninput: (e) => email(e.target.value) }),
Input({ label: 'Password', type: 'password', value: password, placeholder: 'Min. 6 characters', oninput: (e) => password(e.target.value) }),
Input({
label: 'Confirm Password',
type: 'password',
value: confirmPassword,
error: () => confirmPassword() && !passwordsMatch() ? 'Passwords do not match' : '',
oninput: (e) => confirmPassword(e.target.value)
}),
Checkbox({
label: 'I accept the Terms and Conditions',
value: accepted,
onclick: () => accepted(!accepted())
}),
() => !isFormValid() && (username() || email() || password()) ? Alert({ type: 'warning', message: 'Please complete all fields correctly' }) : null,
Button({
class: 'btn btn-primary w-full',
onclick: handleSubmit,
disabled: () => !isFormValid()
}, 'Register')
])
]);
};
$mount(RegistrationDemo, '#demo-registration');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
const commonContent = Div({ class: 'space-y-4' }, [
Input({ label: 'Field 1', placeholder: 'Enter value' }),
Input({ label: 'Field 2', placeholder: 'Enter value' }),
Button({ class: 'btn btn-primary' }, 'Submit')
]);
return Div({ class: 'flex flex-col gap-4' }, [
Fieldset({ legend: 'Default Fieldset', class: 'w-full' }, [commonContent]),
Fieldset({ legend: 'With Shadow', class: 'w-full shadow-lg' }, [commonContent]),
Fieldset({ legend: 'With Background', class: 'w-full bg-base-100' }, [commonContent])
]);
};
$mount(VariantsDemo, '#demo-variants');
```
<script>
(function() {
const initFieldsetExamples = () => {
// 1. Basic Fieldset
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
return Fieldset({
legend: 'User Information',
class: 'w-full max-w-md mx-auto'
}, [
Div({ class: 'space-y-4' }, [
Input({ label: 'Full Name', placeholder: 'Enter your name' }),
Input({ label: 'Email', type: 'email', placeholder: 'user@example.com' }),
Input({ label: 'Phone', type: 'tel', placeholder: '+1 234 567 890' })
])
]);
};
$mount(BasicDemo, basicTarget);
}
// 2. With Reactive Legend
const reactiveTarget = document.querySelector('#demo-reactive');
if (reactiveTarget && !reactiveTarget.hasChildNodes()) {
const ReactiveDemo = () => {
const name = $('');
const email = $('');
const isValid = () => name().length > 0 && email().includes('@');
return Fieldset({
legend: () => isValid() ? '✓ Valid Form' : '✗ Incomplete Form',
class: 'w-full max-w-md mx-auto'
}, [
Div({ class: 'space-y-4' }, [
Input({
label: 'Full Name',
value: name,
oninput: (e) => name(e.target.value),
placeholder: 'Enter your name'
}),
Input({
label: 'Email',
type: 'email',
value: email,
oninput: (e) => email(e.target.value),
placeholder: 'user@example.com'
}),
() => isValid()
? Alert({ type: 'success', message: 'Form is ready to submit' })
: Alert({ type: 'warning', message: 'Please fill all required fields' })
])
]);
};
$mount(ReactiveDemo, reactiveTarget);
}
// 3. Address Form
const addressTarget = document.querySelector('#demo-address');
if (addressTarget && !addressTarget.hasChildNodes()) {
const AddressDemo = () => {
const address = $('');
const city = $('');
const zip = $('');
const country = $('us');
return Fieldset({
legend: Span({ class: 'flex items-center gap-2' }, ['📍', 'Shipping Address']),
class: 'w-full max-w-md mx-auto'
}, [
Div({ class: 'space-y-4' }, [
Input({ label: 'Street Address', value: address, placeholder: '123 Main St', oninput: (e) => address(e.target.value) }),
Div({ class: 'grid grid-cols-2 gap-4' }, [
Input({ label: 'City', value: city, placeholder: 'City', oninput: (e) => city(e.target.value) }),
Input({ label: 'ZIP Code', value: zip, placeholder: 'ZIP', oninput: (e) => zip(e.target.value) })
]),
Select({
label: 'Country',
value: country,
options: [
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' },
{ value: 'mx', label: 'Mexico' }
],
onchange: (e) => country(e.target.value)
})
])
]);
};
$mount(AddressDemo, addressTarget);
}
// 4. Payment Method
const paymentTarget = document.querySelector('#demo-payment');
if (paymentTarget && !paymentTarget.hasChildNodes()) {
const PaymentDemo = () => {
const method = $('credit');
const cardNumber = $('');
const expiry = $('');
const cvv = $('');
return Fieldset({
legend: Span({ class: 'flex items-center gap-2' }, ['💳', 'Payment Details']),
class: 'w-full max-w-md mx-auto'
}, [
Div({ class: 'space-y-4' }, [
Div({ class: 'flex gap-4' }, [
Radio({ label: 'Credit Card', value: method, radioValue: 'credit', onclick: () => method('credit') }),
Radio({ label: 'PayPal', value: method, radioValue: 'paypal', onclick: () => method('paypal') }),
Radio({ label: 'Bank Transfer', value: method, radioValue: 'bank', onclick: () => method('bank') })
]),
() => method() === 'credit' ? Div({ class: 'space-y-4' }, [
Input({ label: 'Card Number', value: cardNumber, placeholder: '1234 5678 9012 3456', oninput: (e) => cardNumber(e.target.value) }),
Div({ class: 'grid grid-cols-2 gap-4' }, [
Input({ label: 'Expiry Date', value: expiry, placeholder: 'MM/YY', oninput: (e) => expiry(e.target.value) }),
Input({ label: 'CVV', type: 'password', value: cvv, placeholder: '123', oninput: (e) => cvv(e.target.value) })
])
]) : null,
() => method() === 'paypal' ? Alert({ type: 'info', message: 'You will be redirected to PayPal after confirming.' }) : null,
() => method() === 'bank' ? Alert({ type: 'warning', message: 'Bank transfer details will be sent via email.' }) : null
])
]);
};
$mount(PaymentDemo, paymentTarget);
}
// 5. Preferences Panel
const preferencesTarget = document.querySelector('#demo-preferences');
if (preferencesTarget && !preferencesTarget.hasChildNodes()) {
const PreferencesDemo = () => {
const theme = $('light');
const language = $('en');
const notifications = $(true);
return Fieldset({
legend: Span({ class: 'flex items-center gap-2' }, ['⚙️', 'Preferences']),
class: 'w-full max-w-md mx-auto'
}, [
Div({ class: 'space-y-4' }, [
Div({ class: 'form-control' }, [
Span({ class: 'label-text mb-2' }, 'Theme'),
Div({ class: 'flex gap-4' }, [
Radio({ label: 'Light', value: theme, radioValue: 'light', onclick: () => theme('light') }),
Radio({ label: 'Dark', value: theme, radioValue: 'dark', onclick: () => theme('dark') }),
Radio({ label: 'System', value: theme, radioValue: 'system', onclick: () => theme('system') })
])
]),
Select({
label: 'Language',
value: language,
options: [
{ value: 'en', label: 'English' },
{ value: 'es', label: 'Español' },
{ value: 'fr', label: 'Français' }
],
onchange: (e) => language(e.target.value)
}),
Checkbox({
label: 'Enable notifications',
value: notifications,
onclick: () => notifications(!notifications())
})
])
]);
};
$mount(PreferencesDemo, preferencesTarget);
}
// 6. Registration Form
const registrationTarget = document.querySelector('#demo-registration');
if (registrationTarget && !registrationTarget.hasChildNodes()) {
const RegistrationDemo = () => {
const username = $('');
const email = $('');
const password = $('');
const confirmPassword = $('');
const accepted = $(false);
const passwordsMatch = () => password() === confirmPassword();
const isFormValid = () => username() && email().includes('@') && password().length >= 6 && passwordsMatch() && accepted();
const handleSubmit = () => {
if (isFormValid()) {
Toast('Registration successful!', 'alert-success', 2000);
}
};
return Fieldset({
legend: Span({ class: 'flex items-center gap-2' }, ['📝', 'Create Account']),
class: 'w-full max-w-md mx-auto'
}, [
Div({ class: 'space-y-4' }, [
Input({ label: 'Username', value: username, placeholder: 'Choose a username', oninput: (e) => username(e.target.value) }),
Input({ label: 'Email', type: 'email', value: email, placeholder: 'your@email.com', oninput: (e) => email(e.target.value) }),
Input({ label: 'Password', type: 'password', value: password, placeholder: 'Min. 6 characters', oninput: (e) => password(e.target.value) }),
Input({
label: 'Confirm Password',
type: 'password',
value: confirmPassword,
error: () => confirmPassword() && !passwordsMatch() ? 'Passwords do not match' : '',
oninput: (e) => confirmPassword(e.target.value)
}),
Checkbox({
label: 'I accept the Terms and Conditions',
value: accepted,
onclick: () => accepted(!accepted())
}),
() => !isFormValid() && (username() || email() || password()) ? Alert({ type: 'warning', message: 'Please complete all fields correctly' }) : null,
Button({
class: 'btn btn-primary w-full',
onclick: handleSubmit,
disabled: () => !isFormValid()
}, 'Register')
])
]);
};
$mount(RegistrationDemo, registrationTarget);
}
// 7. All Variants
const variantsTarget = document.querySelector('#demo-variants');
if (variantsTarget && !variantsTarget.hasChildNodes()) {
const VariantsDemo = () => {
const commonContent = Div({ class: 'space-y-4' }, [
Input({ label: 'Field 1', placeholder: 'Enter value' }),
Input({ label: 'Field 2', placeholder: 'Enter value' }),
Button({ class: 'btn btn-primary' }, 'Submit')
]);
return Div({ class: 'flex flex-col gap-4' }, [
Fieldset({ legend: 'Default Fieldset', class: 'w-full' }, [commonContent]),
Fieldset({ legend: 'With Shadow', class: 'w-full shadow-lg' }, [commonContent]),
Fieldset({ legend: 'With Background', class: 'w-full bg-base-100' }, [commonContent])
]);
};
$mount(VariantsDemo, variantsTarget);
}
};
initFieldsetExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initFieldsetExamples);
});
}
})();
</script>

View File

@@ -0,0 +1,556 @@
# Indicator
Indicator component for adding badges, status markers, or notifications to elements. Perfect for showing counts, online status, or alerts.
## Tag
`Indicator`
## Props
| Prop | Type | Default | Description |
| :----------- | :--------------------------- | :---------- | :----------------------------------------------- |
| `badge` | `string \| VNode \| Signal` | `-` | Content to display as indicator |
| `badgeClass` | `string` | `''` | Additional CSS classes for the badge |
| `class` | `string` | `''` | Additional CSS classes for the container |
| `children` | `VNode` | `-` | Element to attach the indicator to |
## Live Examples
### Basic Indicator
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-8 justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
Indicator({ badge: '3', badgeClass: 'badge-primary' }, [
Div({ class: 'w-16 h-16 bg-base-300 rounded-lg flex items-center justify-center' }, '📦')
]),
Indicator({ badge: '99+', badgeClass: 'badge-secondary' }, [
Div({ class: 'w-16 h-16 bg-base-300 rounded-lg flex items-center justify-center' }, '🔔')
]),
Indicator({ badge: 'New', badgeClass: 'badge-accent' }, [
Div({ class: 'w-16 h-16 bg-base-300 rounded-lg flex items-center justify-center' }, '✨')
])
]);
};
$mount(BasicDemo, '#demo-basic');
```
### Online Status Indicator
<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-status" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-8 justify-center"></div>
</div>
</div>
```javascript
const StatusDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
Div({ class: 'avatar placeholder' }, [
Div({ class: 'bg-neutral text-neutral-content rounded-full w-12 h-12 flex items-center justify-center' }, 'JD')
])
]),
Indicator({ badge: '●', badgeClass: 'badge-warning badge-xs' }, [
Div({ class: 'avatar placeholder' }, [
Div({ class: 'bg-neutral text-neutral-content rounded-full w-12 h-12 flex items-center justify-center' }, 'JS')
])
]),
Indicator({ badge: '●', badgeClass: 'badge-error badge-xs' }, [
Div({ class: 'avatar placeholder' }, [
Div({ class: 'bg-neutral text-neutral-content rounded-full w-12 h-12 flex items-center justify-center' }, 'BC')
])
])
]);
};
$mount(StatusDemo, '#demo-status');
```
### Reactive Counter
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4 items-center"></div>
</div>
</div>
```javascript
const ReactiveDemo = () => {
const count = $(0);
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Indicator({
badge: () => count() > 0 ? count() : null,
badgeClass: 'badge-primary'
}, [
Button({
class: 'btn btn-lg btn-primary',
onclick: () => count(count() + 1)
}, 'Notifications')
]),
Div({ class: 'flex gap-2' }, [
Button({
class: 'btn btn-sm',
onclick: () => count(Math.max(0, count() - 1))
}, 'Decrease'),
Button({
class: 'btn btn-sm btn-ghost',
onclick: () => count(0)
}, 'Clear')
])
]);
};
$mount(ReactiveDemo, '#demo-reactive');
```
### Shopping Cart
<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-cart" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const CartDemo = () => {
const cart = $([
{ id: 1, name: 'Product 1', price: 29 },
{ id: 2, name: 'Product 2', price: 49 }
]);
const addItem = () => {
const newId = Math.max(...cart().map(i => i.id), 0) + 1;
cart([...cart(), { id: newId, name: `Product ${newId}`, price: Math.floor(Math.random() * 100) + 10 }]);
Toast('Item added to cart', 'alert-success', 1500);
};
const removeItem = (id) => {
cart(cart().filter(item => item.id !== id));
Toast('Item removed', 'alert-info', 1500);
};
const total = () => cart().reduce((sum, item) => sum + item.price, 0);
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex justify-between items-center' }, [
Indicator({
badge: () => cart().length,
badgeClass: 'badge-primary'
}, [
Button({ class: 'btn', onclick: addItem }, '🛒 Add to Cart')
]),
Span({ class: 'text-lg font-bold' }, () => `Total: $${total()}`)
]),
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' }, [
Span({}, item.name),
Div({ class: 'flex gap-2 items-center' }, [
Span({ class: 'text-sm font-bold' }, `$${item.price}`),
Button({
class: 'btn btn-xs btn-ghost btn-circle',
onclick: () => removeItem(item.id)
}, '✕')
])
])
))
]);
};
$mount(CartDemo, '#demo-cart');
```
### Email Inbox
<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-inbox" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const InboxDemo = () => {
const unread = $(3);
const messages = $([
{ id: 1, from: 'john@example.com', subject: 'Meeting tomorrow', read: false },
{ id: 2, from: 'jane@example.com', subject: 'Project update', read: false },
{ id: 3, from: 'bob@example.com', subject: 'Question about design', read: false },
{ id: 4, from: 'alice@example.com', subject: 'Weekly report', read: true }
]);
const markAsRead = (id) => {
const msg = messages().find(m => m.id === id);
if (!msg.read) {
msg.read = true;
messages([...messages()]);
unread(unread() - 1);
}
};
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex justify-between items-center' }, [
Indicator({
badge: () => unread(),
badgeClass: 'badge-primary'
}, [
Span({ class: 'text-lg font-bold' }, 'Inbox')
]),
Button({
class: 'btn btn-sm btn-ghost',
onclick: () => {
messages().forEach(m => m.read = true);
messages([...messages()]);
unread(0);
}
}, 'Mark all read')
]),
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)
}, [
Div({ class: 'font-medium' }, msg.from),
Div({ class: 'text-sm' }, msg.subject),
!msg.read && Span({ class: 'badge badge-xs badge-primary mt-1' }, 'New')
])
))
]);
};
$mount(InboxDemo, '#demo-inbox');
```
### Custom Position
<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-positions" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-8 justify-center"></div>
</div>
</div>
```javascript
const PositionsDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
Div({ class: 'text-center' }, [
Div({ class: 'text-xs mb-2' }, 'Top-Left'),
Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg' })
])
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-xs mb-2' }, 'Top-Right'),
Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg' })
])
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-xs mb-2' }, 'Bottom-Left'),
Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg' })
])
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-xs mb-2' }, 'Bottom-Right'),
Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg' })
])
])
]);
};
$mount(PositionsDemo, '#demo-positions');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-8 justify-center"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
Indicator({ badge: '3', badgeClass: 'badge-primary badge-sm' }, [
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg flex items-center justify-center' }, '📧')
]),
Indicator({ badge: '99+', badgeClass: 'badge-secondary badge-md' }, [
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg flex items-center justify-center' }, '🔔')
]),
Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
Div({ class: 'avatar' }, [
Div({ class: 'w-10 h-10 rounded-full bg-primary' })
])
]),
Indicator({ badge: '!', badgeClass: 'badge-error badge-sm' }, [
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg flex items-center justify-center' }, '⚠️')
])
]);
};
$mount(VariantsDemo, '#demo-variants');
```
<script>
(function() {
const initIndicatorExamples = () => {
// 1. Basic Indicator
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
Indicator({ badge: '3', badgeClass: 'badge-primary' }, [
Div({ class: 'w-16 h-16 bg-base-300 rounded-lg flex items-center justify-center' }, '📦')
]),
Indicator({ badge: '99+', badgeClass: 'badge-secondary' }, [
Div({ class: 'w-16 h-16 bg-base-300 rounded-lg flex items-center justify-center' }, '🔔')
]),
Indicator({ badge: 'New', badgeClass: 'badge-accent' }, [
Div({ class: 'w-16 h-16 bg-base-300 rounded-lg flex items-center justify-center' }, '✨')
])
]);
};
$mount(BasicDemo, basicTarget);
}
// 2. Online Status Indicator
const statusTarget = document.querySelector('#demo-status');
if (statusTarget && !statusTarget.hasChildNodes()) {
const StatusDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
Div({ class: 'avatar placeholder' }, [
Div({ class: 'bg-neutral text-neutral-content rounded-full w-12 h-12 flex items-center justify-center' }, 'JD')
])
]),
Indicator({ badge: '●', badgeClass: 'badge-warning badge-xs' }, [
Div({ class: 'avatar placeholder' }, [
Div({ class: 'bg-neutral text-neutral-content rounded-full w-12 h-12 flex items-center justify-center' }, 'JS')
])
]),
Indicator({ badge: '●', badgeClass: 'badge-error badge-xs' }, [
Div({ class: 'avatar placeholder' }, [
Div({ class: 'bg-neutral text-neutral-content rounded-full w-12 h-12 flex items-center justify-center' }, 'BC')
])
])
]);
};
$mount(StatusDemo, statusTarget);
}
// 3. Reactive Counter
const reactiveTarget = document.querySelector('#demo-reactive');
if (reactiveTarget && !reactiveTarget.hasChildNodes()) {
const ReactiveDemo = () => {
const count = $(0);
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Indicator({
badge: () => count() > 0 ? count() : null,
badgeClass: 'badge-primary'
}, [
Button({
class: 'btn btn-lg btn-primary',
onclick: () => count(count() + 1)
}, 'Notifications')
]),
Div({ class: 'flex gap-2' }, [
Button({
class: 'btn btn-sm',
onclick: () => count(Math.max(0, count() - 1))
}, 'Decrease'),
Button({
class: 'btn btn-sm btn-ghost',
onclick: () => count(0)
}, 'Clear')
])
]);
};
$mount(ReactiveDemo, reactiveTarget);
}
// 4. Shopping Cart
const cartTarget = document.querySelector('#demo-cart');
if (cartTarget && !cartTarget.hasChildNodes()) {
const CartDemo = () => {
const cart = $([
{ id: 1, name: 'Product 1', price: 29 },
{ id: 2, name: 'Product 2', price: 49 }
]);
const addItem = () => {
const newId = Math.max(...cart().map(i => i.id), 0) + 1;
cart([...cart(), { id: newId, name: `Product ${newId}`, price: Math.floor(Math.random() * 100) + 10 }]);
Toast('Item added to cart', 'alert-success', 1500);
};
const removeItem = (id) => {
cart(cart().filter(item => item.id !== id));
Toast('Item removed', 'alert-info', 1500);
};
const total = () => cart().reduce((sum, item) => sum + item.price, 0);
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex justify-between items-center' }, [
Indicator({
badge: () => cart().length,
badgeClass: 'badge-primary'
}, [
Button({ class: 'btn', onclick: addItem }, '🛒 Add to Cart')
]),
Span({ class: 'text-lg font-bold' }, () => `Total: $${total()}`)
]),
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' }, [
Span({}, item.name),
Div({ class: 'flex gap-2 items-center' }, [
Span({ class: 'text-sm font-bold' }, `$${item.price}`),
Button({
class: 'btn btn-xs btn-ghost btn-circle',
onclick: () => removeItem(item.id)
}, '✕')
])
])
))
]);
};
$mount(CartDemo, cartTarget);
}
// 5. Email Inbox
const inboxTarget = document.querySelector('#demo-inbox');
if (inboxTarget && !inboxTarget.hasChildNodes()) {
const InboxDemo = () => {
const unread = $(3);
const messages = $([
{ id: 1, from: 'john@example.com', subject: 'Meeting tomorrow', read: false },
{ id: 2, from: 'jane@example.com', subject: 'Project update', read: false },
{ id: 3, from: 'bob@example.com', subject: 'Question about design', read: false },
{ id: 4, from: 'alice@example.com', subject: 'Weekly report', read: true }
]);
const markAsRead = (id) => {
const msg = messages().find(m => m.id === id);
if (!msg.read) {
msg.read = true;
messages([...messages()]);
unread(unread() - 1);
}
};
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex justify-between items-center' }, [
Indicator({
badge: () => unread(),
badgeClass: 'badge-primary'
}, [
Span({ class: 'text-lg font-bold' }, 'Inbox')
]),
Button({
class: 'btn btn-sm btn-ghost',
onclick: () => {
messages().forEach(m => m.read = true);
messages([...messages()]);
unread(0);
}
}, 'Mark all read')
]),
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)
}, [
Div({ class: 'font-medium' }, msg.from),
Div({ class: 'text-sm' }, msg.subject),
!msg.read && Span({ class: 'badge badge-xs badge-primary mt-1' }, 'New')
])
))
]);
};
$mount(InboxDemo, inboxTarget);
}
// 6. Custom Position
const positionsTarget = document.querySelector('#demo-positions');
if (positionsTarget && !positionsTarget.hasChildNodes()) {
const PositionsDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
Div({ class: 'text-center' }, [
Div({ class: 'text-xs mb-2' }, 'Top-Left'),
Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg' })
])
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-xs mb-2' }, 'Top-Right'),
Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg' })
])
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-xs mb-2' }, 'Bottom-Left'),
Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg' })
])
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-xs mb-2' }, 'Bottom-Right'),
Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg' })
])
])
]);
};
$mount(PositionsDemo, positionsTarget);
}
// 7. All Variants
const variantsTarget = document.querySelector('#demo-variants');
if (variantsTarget && !variantsTarget.hasChildNodes()) {
const VariantsDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
Indicator({ badge: '3', badgeClass: 'badge-primary badge-sm' }, [
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg flex items-center justify-center' }, '📧')
]),
Indicator({ badge: '99+', badgeClass: 'badge-secondary badge-md' }, [
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg flex items-center justify-center' }, '🔔')
]),
Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
Div({ class: 'avatar' }, [
Div({ class: 'w-10 h-10 rounded-full bg-primary' })
])
]),
Indicator({ badge: '!', badgeClass: 'badge-error badge-sm' }, [
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg flex items-center justify-center' }, '⚠️')
])
]);
};
$mount(VariantsDemo, variantsTarget);
}
};
initIndicatorExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initIndicatorExamples);
});
}
})();
</script>

View File

@@ -0,0 +1,348 @@
# Input
Form input component with floating label, icons, password toggle, tooltip, and error states. Fully integrated with DaisyUI and Tailwind.
## Tag
`Input`
## Props
| Prop | Type | Default | Description |
| :----------- | :--------------------------- | :--------- | :----------------------------------------------- |
| `label` | `string` | `-` | Label text (floating style) |
| `type` | `string` | `'text'` | Input type (text, password, email, number, date) |
| `value` | `string \| Signal<string>` | `''` | Input value |
| `placeholder`| `string` | `' '` | Placeholder text |
| `icon` | `string \| VNode \| Signal` | `-` | Icon displayed inside input |
| `tip` | `string` | `-` | Help tooltip text |
| `error` | `string \| Signal<string>` | `-` | Error message to display |
| `disabled` | `boolean \| Signal<boolean>` | `false` | Disabled state |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `oninput` | `function` | `-` | Input event handler |
## Live Examples
### Basic Input
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const name = $('');
return Input({
label: 'Full Name',
placeholder: 'Enter your name',
value: name,
oninput: (e) => name(e.target.value)
});
};
$mount(BasicDemo, '#demo-basic');
```
### With Icon
<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-icon" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const IconDemo = () => {
const email = $('');
return Input({
label: 'Email',
type: 'email',
icon: Icons.iconMail,
value: email,
oninput: (e) => email(e.target.value)
});
};
$mount(IconDemo, '#demo-icon');
```
### Password with Toggle
<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-password" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const PasswordDemo = () => {
const password = $('');
return Input({
label: 'Password',
type: 'password',
value: password,
oninput: (e) => password(e.target.value)
});
};
$mount(PasswordDemo, '#demo-password');
```
### With Tooltip
<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-tooltip" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const TooltipDemo = () => {
const username = $('');
return Input({
label: 'Username',
tip: 'Must be at least 3 characters',
value: username,
oninput: (e) => username(e.target.value)
});
};
$mount(TooltipDemo, '#demo-tooltip');
```
### Error State
<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-error" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const ErrorDemo = () => {
const email = $('');
const isValid = $(true);
const validate = (value) => {
const valid = value.includes('@') && value.includes('.');
isValid(valid);
email(value);
};
return Input({
label: 'Email',
type: 'email',
value: email,
error: () => !isValid() && email() ? 'Invalid email address' : '',
oninput: (e) => validate(e.target.value)
});
};
$mount(ErrorDemo, '#demo-error');
```
### Disabled State
<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-disabled" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const DisabledDemo = () => {
return Input({
label: 'Username',
value: 'john.doe',
disabled: true
});
};
$mount(DisabledDemo, '#demo-disabled');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
const text = $('');
const number = $(0);
return Div({ class: 'flex flex-col gap-4' }, [
Input({
label: 'Text Input',
placeholder: 'Type something...',
value: text,
oninput: (e) => text(e.target.value)
}),
Input({
label: 'Number Input',
type: 'number',
value: number,
oninput: (e) => number(parseInt(e.target.value) || 0)
}),
Input({
label: 'Date Input',
type: 'date',
value: $('2024-01-01')
})
]);
};
$mount(VariantsDemo, '#demo-variants');
```
<script>
(function() {
const initInputExamples = () => {
// 1. Basic Input
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
const name = $('');
return Input({
label: 'Full Name',
placeholder: 'Enter your name',
value: name,
oninput: (e) => name(e.target.value)
});
};
$mount(BasicDemo, basicTarget);
}
// 2. With Icon
const iconTarget = document.querySelector('#demo-icon');
if (iconTarget && !iconTarget.hasChildNodes()) {
const IconDemo = () => {
const email = $('');
return Input({
label: 'Email',
type: 'email',
icon: Icons.iconMail,
value: email,
oninput: (e) => email(e.target.value)
});
};
$mount(IconDemo, iconTarget);
}
// 3. Password with Toggle
const passwordTarget = document.querySelector('#demo-password');
if (passwordTarget && !passwordTarget.hasChildNodes()) {
const PasswordDemo = () => {
const password = $('');
return Input({
label: 'Password',
type: 'password',
value: password,
oninput: (e) => password(e.target.value)
});
};
$mount(PasswordDemo, passwordTarget);
}
// 4. With Tooltip
const tooltipTarget = document.querySelector('#demo-tooltip');
if (tooltipTarget && !tooltipTarget.hasChildNodes()) {
const TooltipDemo = () => {
const username = $('');
return Input({
label: 'Username',
tip: 'Must be at least 3 characters',
value: username,
oninput: (e) => username(e.target.value)
});
};
$mount(TooltipDemo, tooltipTarget);
}
// 5. Error State
const errorTarget = document.querySelector('#demo-error');
if (errorTarget && !errorTarget.hasChildNodes()) {
const ErrorDemo = () => {
const email = $('');
const isValid = $(true);
const validate = (value) => {
const valid = value.includes('@') && value.includes('.');
isValid(valid);
email(value);
};
return Input({
label: 'Email',
type: 'email',
value: email,
error: () => !isValid() && email() ? 'Invalid email address' : '',
oninput: (e) => validate(e.target.value)
});
};
$mount(ErrorDemo, errorTarget);
}
// 6. Disabled State
const disabledTarget = document.querySelector('#demo-disabled');
if (disabledTarget && !disabledTarget.hasChildNodes()) {
const DisabledDemo = () => {
return Input({
label: 'Username',
value: 'john.doe',
disabled: true
});
};
$mount(DisabledDemo, disabledTarget);
}
// 7. All Variants
const variantsTarget = document.querySelector('#demo-variants');
if (variantsTarget && !variantsTarget.hasChildNodes()) {
const VariantsDemo = () => {
const text = $('');
const number = $(0);
return Div({ class: 'flex flex-col gap-4' }, [
Input({
label: 'Text Input',
placeholder: 'Type something...',
value: text,
oninput: (e) => text(e.target.value)
}),
Input({
label: 'Number Input',
type: 'number',
value: number,
oninput: (e) => number(parseInt(e.target.value) || 0)
}),
Input({
label: 'Date Input',
type: 'date',
value: $('2024-01-01')
})
]);
};
$mount(VariantsDemo, variantsTarget);
}
};
initInputExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initInputExamples);
});
}
})();
</script>

601
docs/components_old/list.md Normal file
View File

@@ -0,0 +1,601 @@
# List
List component with custom item rendering, headers, and reactive data binding.
## Tag
`List`
## Props
| Prop | Type | Default | Description |
| :----------- | :-------------------------------------- | :--------------- | :----------------------------------------------- |
| `items` | `Array \| Signal<Array>` | `[]` | Data array to display |
| `header` | `string \| VNode \| Signal` | `-` | Optional header content |
| `render` | `function(item, index)` | `-` | 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) |
## Live Examples
### Basic List
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
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)
])
});
};
$mount(BasicDemo, '#demo-basic');
```
### With Header
<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-header" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```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' }
];
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)
])
});
};
$mount(HeaderDemo, '#demo-header');
```
### With Icons
<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-icons" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```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' }
];
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' }, '→')
])
});
};
$mount(IconsDemo, '#demo-icons');
```
### With Badges
<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-badges" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```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 }
];
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
])
});
};
$mount(BadgesDemo, '#demo-badges');
```
### Interactive List
<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-interactive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
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' }
];
const statusColors = {
'In Progress': 'badge-warning',
'Planning': 'badge-info',
'Completed': 'badge-success',
'Review': 'badge-accent'
};
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)
])
])
}),
() => 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');
```
### Reactive List
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ReactiveDemo = () => {
const todos = $([
{ id: 1, text: 'Complete documentation', done: false },
{ id: 2, text: 'Review pull requests', done: false },
{ id: 3, text: 'Deploy to production', done: false }
]);
const newTodo = $('');
const addTodo = () => {
if (newTodo().trim()) {
const newId = Math.max(...todos().map(t => t.id), 0) + 1;
todos([...todos(), { id: newId, text: newTodo(), done: false }]);
newTodo('');
}
};
const toggleTodo = (id) => {
todos(todos().map(t =>
t.id === id ? { ...t, done: !t.done } : t
));
};
const deleteTodo = (id) => {
todos(todos().filter(t => t.id !== id));
};
const pendingCount = () => todos().filter(t => !t.done).length;
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()
}),
Button({ class: 'btn btn-primary', onclick: addTodo }, 'Add')
]),
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)
}, '✕')
])
}),
Div({ class: 'text-sm opacity-70 mt-2' }, () => `${pendingCount()} tasks remaining`)
]);
};
$mount(ReactiveDemo, '#demo-reactive');
```
### Avatar List
<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-avatar" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```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 }
];
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)
]),
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');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-6"></div>
</div>
</div>
```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'),
List({
items: items,
render: (item) => Div({ class: 'p-2' }, item)
}),
Div({ class: 'text-sm font-bold mt-2' }, 'With Shadow'),
List({
items: items,
render: (item) => Div({ class: 'p-2' }, item),
class: 'shadow-lg'
}),
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'
})
]);
};
$mount(VariantsDemo, '#demo-variants');
```
<script>
(function() {
const initListExamples = () => {
// 1. Basic List
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
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)
])
});
};
$mount(BasicDemo, basicTarget);
}
// 2. With Header
const headerTarget = document.querySelector('#demo-header');
if (headerTarget && !headerTarget.hasChildNodes()) {
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' }
];
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)
])
});
};
$mount(HeaderDemo, headerTarget);
}
// 3. With Icons
const iconsTarget = document.querySelector('#demo-icons');
if (iconsTarget && !iconsTarget.hasChildNodes()) {
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' }
];
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' }, '→')
])
});
};
$mount(IconsDemo, iconsTarget);
}
// 4. With Badges
const badgesTarget = document.querySelector('#demo-badges');
if (badgesTarget && !badgesTarget.hasChildNodes()) {
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 }
];
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
])
});
};
$mount(BadgesDemo, badgesTarget);
}
// 5. Interactive List
const interactiveTarget = document.querySelector('#demo-interactive');
if (interactiveTarget && !interactiveTarget.hasChildNodes()) {
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' }
];
const statusColors = {
'In Progress': 'badge-warning',
'Planning': 'badge-info',
'Completed': 'badge-success',
'Review': 'badge-accent'
};
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)
])
])
}),
() => 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, interactiveTarget);
}
// 6. Reactive List
const reactiveTarget = document.querySelector('#demo-reactive');
if (reactiveTarget && !reactiveTarget.hasChildNodes()) {
const ReactiveDemo = () => {
const todos = $([
{ id: 1, text: 'Complete documentation', done: false },
{ id: 2, text: 'Review pull requests', done: false },
{ id: 3, text: 'Deploy to production', done: false }
]);
const newTodo = $('');
const addTodo = () => {
if (newTodo().trim()) {
const newId = Math.max(...todos().map(t => t.id), 0) + 1;
todos([...todos(), { id: newId, text: newTodo(), done: false }]);
newTodo('');
}
};
const toggleTodo = (id) => {
todos(todos().map(t =>
t.id === id ? { ...t, done: !t.done } : t
));
};
const deleteTodo = (id) => {
todos(todos().filter(t => t.id !== id));
};
const pendingCount = () => todos().filter(t => !t.done).length;
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()
}),
Button({ class: 'btn btn-primary', onclick: addTodo }, 'Add')
]),
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)
}, '✕')
])
}),
Div({ class: 'text-sm opacity-70 mt-2' }, () => `${pendingCount()} tasks remaining`)
]);
};
$mount(ReactiveDemo, reactiveTarget);
}
// 7. Avatar List
const avatarTarget = document.querySelector('#demo-avatar');
if (avatarTarget && !avatarTarget.hasChildNodes()) {
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 }
];
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)
]),
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, avatarTarget);
}
// 8. All Variants
const variantsTarget = document.querySelector('#demo-variants');
if (variantsTarget && !variantsTarget.hasChildNodes()) {
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'),
List({
items: items,
render: (item) => Div({ class: 'p-2' }, item)
}),
Div({ class: 'text-sm font-bold mt-2' }, 'With Shadow'),
List({
items: items,
render: (item) => Div({ class: 'p-2' }, item),
class: 'shadow-lg'
}),
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'
})
]);
};
$mount(VariantsDemo, variantsTarget);
}
};
initListExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initListExamples);
});
}
})();
</script>

View File

@@ -0,0 +1,597 @@
# Loading
Loading spinner component for indicating loading states with customizable size and colors.
## Tag
`Loading`
## Props
| Prop | Type | Default | Description |
| :----------- | :--------------------------- | :--------------- | :----------------------------------------------- |
| `$show` | `boolean \| Signal<boolean>` | `true` | Show/hide the loading spinner |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI loading variants)|
## Live Examples
### Basic Loading
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-8 justify-center items-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center items-center' }, [
Loading({ $show: true, class: 'loading-spinner' }),
Loading({ $show: true, class: 'loading-dots' }),
Loading({ $show: true, class: 'loading-ring' }),
Loading({ $show: true, class: 'loading-ball' }),
Loading({ $show: true, class: 'loading-bars' }),
Loading({ $show: true, class: 'loading-infinity' })
]);
};
$mount(BasicDemo, '#demo-basic');
```
### Loading Sizes
<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-sizes" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-8 justify-center items-center"></div>
</div>
</div>
```javascript
const SizesDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center items-center' }, [
Div({ class: 'text-center' }, [
Loading({ $show: true, class: 'loading-spinner loading-xs' }),
Div({ class: 'text-xs mt-2' }, 'Extra Small')
]),
Div({ class: 'text-center' }, [
Loading({ $show: true, class: 'loading-spinner loading-sm' }),
Div({ class: 'text-xs mt-2' }, 'Small')
]),
Div({ class: 'text-center' }, [
Loading({ $show: true, class: 'loading-spinner loading-md' }),
Div({ class: 'text-xs mt-2' }, 'Medium')
]),
Div({ class: 'text-center' }, [
Loading({ $show: true, class: 'loading-spinner loading-lg' }),
Div({ class: 'text-xs mt-2' }, 'Large')
])
]);
};
$mount(SizesDemo, '#demo-sizes');
```
### Loading Colors
<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-colors" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-8 justify-center items-center"></div>
</div>
</div>
```javascript
const ColorsDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center items-center' }, [
Loading({ $show: true, class: 'loading-spinner text-primary' }),
Loading({ $show: true, class: 'loading-spinner text-secondary' }),
Loading({ $show: true, class: 'loading-spinner text-accent' }),
Loading({ $show: true, class: 'loading-spinner text-info' }),
Loading({ $show: true, class: 'loading-spinner text-success' }),
Loading({ $show: true, class: 'loading-spinner text-warning' }),
Loading({ $show: true, class: 'loading-spinner text-error' })
]);
};
$mount(ColorsDemo, '#demo-colors');
```
### Button Loading State
<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-button" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4 items-center"></div>
</div>
</div>
```javascript
const ButtonDemo = () => {
const isLoading = $(false);
const handleClick = async () => {
isLoading(true);
await new Promise(resolve => setTimeout(resolve, 2000));
isLoading(false);
Toast('Operation completed!', 'alert-success', 2000);
};
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Button({
class: 'btn btn-primary',
loading: isLoading,
onclick: handleClick
}, 'Submit'),
Div({ class: 'text-sm opacity-70' }, 'Click the button to see loading state')
]);
};
$mount(ButtonDemo, '#demo-button');
```
### Async Data Loading
<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-async" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const AsyncDemo = () => {
const loading = $(false);
const data = $(null);
const error = $(null);
const loadData = async () => {
loading(true);
error(null);
data(null);
try {
await new Promise(resolve => setTimeout(resolve, 2000));
const result = {
users: 1234,
posts: 5678,
comments: 9012
};
data(result);
Toast('Data loaded successfully!', 'alert-success', 2000);
} catch (err) {
error('Failed to load data');
Toast('Error loading data', 'alert-error', 2000);
} finally {
loading(false);
}
};
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex justify-center' }, [
Button({
class: 'btn btn-primary',
onclick: loadData,
disabled: () => loading()
}, loading() ? 'Loading...' : 'Load Data')
]),
Div({ class: 'relative min-h-[200px] flex items-center justify-center' }, [
() => loading() ? Loading({ $show: true, class: 'loading-spinner loading-lg' }) : null,
() => data() ? Div({ class: 'grid grid-cols-3 gap-4 w-full' }, [
Div({ class: 'stat' }, [
Div({ class: 'stat-title' }, 'Users'),
Div({ class: 'stat-value' }, data().users)
]),
Div({ class: 'stat' }, [
Div({ class: 'stat-title' }, 'Posts'),
Div({ class: 'stat-value' }, data().posts)
]),
Div({ class: 'stat' }, [
Div({ class: 'stat-title' }, 'Comments'),
Div({ class: 'stat-value' }, data().comments)
])
]) : null,
() => error() ? Alert({ type: 'error', message: error() }) : null
])
]);
};
$mount(AsyncDemo, '#demo-async');
```
### Full Page Loading
<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-fullpage" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const FullpageDemo = () => {
const isLoading = $(false);
const simulatePageLoad = () => {
isLoading(true);
setTimeout(() => {
isLoading(false);
Toast('Page loaded!', 'alert-success', 2000);
}, 2000);
};
return Div({ class: 'relative' }, [
Div({ class: 'flex justify-center p-8' }, [
Button({
class: 'btn btn-primary',
onclick: simulatePageLoad
}, 'Simulate Page Load')
]),
() => isLoading() ? Loading({ $show: true, class: 'loading-spinner loading-lg' }) : null
]);
};
$mount(FullpageDemo, '#demo-fullpage');
```
### Conditional Loading
<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-conditional" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ConditionalDemo = () => {
const loadingState = $('idle');
const content = $('');
const simulateAction = (action) => {
loadingState('loading');
setTimeout(() => {
content(`Action: ${action} completed at ${new Date().toLocaleTimeString()}`);
loadingState('success');
setTimeout(() => loadingState('idle'), 1500);
}, 1500);
};
const loadingStates = {
idle: null,
loading: Loading({ $show: true, class: 'loading-spinner text-primary' }),
success: Alert({ type: 'success', message: '✓ Done!' })
};
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex gap-2 justify-center' }, [
Button({
class: 'btn btn-sm',
onclick: () => simulateAction('Save')
}, 'Save'),
Button({
class: 'btn btn-sm',
onclick: () => simulateAction('Update')
}, 'Update'),
Button({
class: 'btn btn-sm',
onclick: () => simulateAction('Delete')
}, 'Delete')
]),
Div({ class: 'flex justify-center min-h-[100px] items-center' }, [
() => loadingStates[loadingState()],
() => content() && loadingState() === 'idle' ? Div({ class: 'text-center' }, content()) : null
])
]);
};
$mount(ConditionalDemo, '#demo-conditional');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-6"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
return Div({ class: 'flex flex-col gap-6' }, [
Div({ class: 'text-center' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Spinner'),
Div({ class: 'flex gap-4 justify-center' }, [
Loading({ $show: true, class: 'loading-spinner loading-xs' }),
Loading({ $show: true, class: 'loading-spinner loading-sm' }),
Loading({ $show: true, class: 'loading-spinner loading-md' }),
Loading({ $show: true, class: 'loading-spinner loading-lg' })
])
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Dots'),
Div({ class: 'flex gap-4 justify-center' }, [
Loading({ $show: true, class: 'loading-dots loading-xs' }),
Loading({ $show: true, class: 'loading-dots loading-sm' }),
Loading({ $show: true, class: 'loading-dots loading-md' }),
Loading({ $show: true, class: 'loading-dots loading-lg' })
])
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Ring'),
Div({ class: 'flex gap-4 justify-center' }, [
Loading({ $show: true, class: 'loading-ring loading-xs' }),
Loading({ $show: true, class: 'loading-ring loading-sm' }),
Loading({ $show: true, class: 'loading-ring loading-md' }),
Loading({ $show: true, class: 'loading-ring loading-lg' })
])
])
]);
};
$mount(VariantsDemo, '#demo-variants');
```
<script>
(function() {
const initLoadingExamples = () => {
// 1. Basic Loading
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center items-center' }, [
Loading({ $show: true, class: 'loading-spinner' }),
Loading({ $show: true, class: 'loading-dots' }),
Loading({ $show: true, class: 'loading-ring' }),
Loading({ $show: true, class: 'loading-ball' }),
Loading({ $show: true, class: 'loading-bars' }),
Loading({ $show: true, class: 'loading-infinity' })
]);
};
$mount(BasicDemo, basicTarget);
}
// 2. Loading Sizes
const sizesTarget = document.querySelector('#demo-sizes');
if (sizesTarget && !sizesTarget.hasChildNodes()) {
const SizesDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center items-center' }, [
Div({ class: 'text-center' }, [
Loading({ $show: true, class: 'loading-spinner loading-xs' }),
Div({ class: 'text-xs mt-2' }, 'Extra Small')
]),
Div({ class: 'text-center' }, [
Loading({ $show: true, class: 'loading-spinner loading-sm' }),
Div({ class: 'text-xs mt-2' }, 'Small')
]),
Div({ class: 'text-center' }, [
Loading({ $show: true, class: 'loading-spinner loading-md' }),
Div({ class: 'text-xs mt-2' }, 'Medium')
]),
Div({ class: 'text-center' }, [
Loading({ $show: true, class: 'loading-spinner loading-lg' }),
Div({ class: 'text-xs mt-2' }, 'Large')
])
]);
};
$mount(SizesDemo, sizesTarget);
}
// 3. Loading Colors
const colorsTarget = document.querySelector('#demo-colors');
if (colorsTarget && !colorsTarget.hasChildNodes()) {
const ColorsDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center items-center' }, [
Loading({ $show: true, class: 'loading-spinner text-primary' }),
Loading({ $show: true, class: 'loading-spinner text-secondary' }),
Loading({ $show: true, class: 'loading-spinner text-accent' }),
Loading({ $show: true, class: 'loading-spinner text-info' }),
Loading({ $show: true, class: 'loading-spinner text-success' }),
Loading({ $show: true, class: 'loading-spinner text-warning' }),
Loading({ $show: true, class: 'loading-spinner text-error' })
]);
};
$mount(ColorsDemo, colorsTarget);
}
// 4. Button Loading State
const buttonTarget = document.querySelector('#demo-button');
if (buttonTarget && !buttonTarget.hasChildNodes()) {
const ButtonDemo = () => {
const isLoading = $(false);
const handleClick = async () => {
isLoading(true);
await new Promise(resolve => setTimeout(resolve, 2000));
isLoading(false);
Toast('Operation completed!', 'alert-success', 2000);
};
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Button({
class: 'btn btn-primary',
loading: isLoading,
onclick: handleClick
}, 'Submit'),
Div({ class: 'text-sm opacity-70' }, 'Click the button to see loading state')
]);
};
$mount(ButtonDemo, buttonTarget);
}
// 5. Async Data Loading
const asyncTarget = document.querySelector('#demo-async');
if (asyncTarget && !asyncTarget.hasChildNodes()) {
const AsyncDemo = () => {
const loading = $(false);
const data = $(null);
const error = $(null);
const loadData = async () => {
loading(true);
error(null);
data(null);
try {
await new Promise(resolve => setTimeout(resolve, 2000));
const result = {
users: 1234,
posts: 5678,
comments: 9012
};
data(result);
Toast('Data loaded successfully!', 'alert-success', 2000);
} catch (err) {
error('Failed to load data');
Toast('Error loading data', 'alert-error', 2000);
} finally {
loading(false);
}
};
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex justify-center' }, [
Button({
class: 'btn btn-primary',
onclick: loadData,
disabled: () => loading()
}, loading() ? 'Loading...' : 'Load Data')
]),
Div({ class: 'relative min-h-[200px] flex items-center justify-center' }, [
() => loading() ? Loading({ $show: true, class: 'loading-spinner loading-lg' }) : null,
() => data() ? Div({ class: 'grid grid-cols-3 gap-4 w-full' }, [
Div({ class: 'stat' }, [
Div({ class: 'stat-title' }, 'Users'),
Div({ class: 'stat-value' }, data().users)
]),
Div({ class: 'stat' }, [
Div({ class: 'stat-title' }, 'Posts'),
Div({ class: 'stat-value' }, data().posts)
]),
Div({ class: 'stat' }, [
Div({ class: 'stat-title' }, 'Comments'),
Div({ class: 'stat-value' }, data().comments)
])
]) : null,
() => error() ? Alert({ type: 'error', message: error() }) : null
])
]);
};
$mount(AsyncDemo, asyncTarget);
}
// 6. Full Page Loading
const fullpageTarget = document.querySelector('#demo-fullpage');
if (fullpageTarget && !fullpageTarget.hasChildNodes()) {
const FullpageDemo = () => {
const isLoading = $(false);
const simulatePageLoad = () => {
isLoading(true);
setTimeout(() => {
isLoading(false);
Toast('Page loaded!', 'alert-success', 2000);
}, 2000);
};
return Div({ class: 'relative' }, [
Div({ class: 'flex justify-center p-8' }, [
Button({
class: 'btn btn-primary',
onclick: simulatePageLoad
}, 'Simulate Page Load')
]),
() => isLoading() ? Loading({ $show: true, class: 'loading-spinner loading-lg' }) : null
]);
};
$mount(FullpageDemo, fullpageTarget);
}
// 7. Conditional Loading
const conditionalTarget = document.querySelector('#demo-conditional');
if (conditionalTarget && !conditionalTarget.hasChildNodes()) {
const ConditionalDemo = () => {
const loadingState = $('idle');
const content = $('');
const simulateAction = (action) => {
loadingState('loading');
setTimeout(() => {
content(`Action: ${action} completed at ${new Date().toLocaleTimeString()}`);
loadingState('success');
setTimeout(() => loadingState('idle'), 1500);
}, 1500);
};
const loadingStates = {
idle: null,
loading: Loading({ $show: true, class: 'loading-spinner text-primary' }),
success: Alert({ type: 'success', message: '✓ Done!' })
};
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex gap-2 justify-center' }, [
Button({
class: 'btn btn-sm',
onclick: () => simulateAction('Save')
}, 'Save'),
Button({
class: 'btn btn-sm',
onclick: () => simulateAction('Update')
}, 'Update'),
Button({
class: 'btn btn-sm',
onclick: () => simulateAction('Delete')
}, 'Delete')
]),
Div({ class: 'flex justify-center min-h-[100px] items-center' }, [
() => loadingStates[loadingState()],
() => content() && loadingState() === 'idle' ? Div({ class: 'text-center' }, content()) : null
])
]);
};
$mount(ConditionalDemo, conditionalTarget);
}
// 8. All Variants
const variantsTarget = document.querySelector('#demo-variants');
if (variantsTarget && !variantsTarget.hasChildNodes()) {
const VariantsDemo = () => {
return Div({ class: 'flex flex-col gap-6' }, [
Div({ class: 'text-center' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Spinner'),
Div({ class: 'flex gap-4 justify-center' }, [
Loading({ $show: true, class: 'loading-spinner loading-xs' }),
Loading({ $show: true, class: 'loading-spinner loading-sm' }),
Loading({ $show: true, class: 'loading-spinner loading-md' }),
Loading({ $show: true, class: 'loading-spinner loading-lg' })
])
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Dots'),
Div({ class: 'flex gap-4 justify-center' }, [
Loading({ $show: true, class: 'loading-dots loading-xs' }),
Loading({ $show: true, class: 'loading-dots loading-sm' }),
Loading({ $show: true, class: 'loading-dots loading-md' }),
Loading({ $show: true, class: 'loading-dots loading-lg' })
])
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Ring'),
Div({ class: 'flex gap-4 justify-center' }, [
Loading({ $show: true, class: 'loading-ring loading-xs' }),
Loading({ $show: true, class: 'loading-ring loading-sm' }),
Loading({ $show: true, class: 'loading-ring loading-md' }),
Loading({ $show: true, class: 'loading-ring loading-lg' })
])
])
]);
};
$mount(VariantsDemo, variantsTarget);
}
};
initLoadingExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initLoadingExamples);
});
}
})();
</script>

760
docs/components_old/menu.md Normal file
View File

@@ -0,0 +1,760 @@
# Menu
Menu component for creating navigation menus, sidebars, and dropdowns with support for nested items, icons, and active states.
## Tag
`Menu`
## Props
| Prop | Type | Default | Description |
| :----------- | :-------------------------------------- | :---------- | :----------------------------------------------- |
| `items` | `Array<MenuItem>` | `[]` | Menu items configuration |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
### MenuItem Structure
| Property | Type | Description |
| :---------- | :--------------------------- | :----------------------------------------------- |
| `label` | `string \| VNode` | Menu item text or content |
| `icon` | `string \| VNode` | Optional icon to display |
| `active` | `boolean \| Signal<boolean>` | Active state highlighting |
| `onclick` | `function` | Click handler |
| `children` | `Array<MenuItem>` | Nested submenu items |
| `open` | `boolean` | Whether submenu is open (for nested items) |
## Live Examples
### Basic Menu
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const activeItem = $('home');
return Menu({
items: [
{
label: 'Home',
active: () => activeItem() === 'home',
onclick: () => activeItem('home')
},
{
label: 'About',
active: () => activeItem() === 'about',
onclick: () => activeItem('about')
},
{
label: 'Contact',
active: () => activeItem() === 'contact',
onclick: () => activeItem('contact')
}
]
});
};
$mount(BasicDemo, '#demo-basic');
```
### With Icons
<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-icons" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const IconsDemo = () => {
const activeItem = $('dashboard');
return Menu({
items: [
{
icon: '🏠',
label: 'Dashboard',
active: () => activeItem() === 'dashboard',
onclick: () => activeItem('dashboard')
},
{
icon: '📊',
label: 'Analytics',
active: () => activeItem() === 'analytics',
onclick: () => activeItem('analytics')
},
{
icon: '⚙️',
label: 'Settings',
active: () => activeItem() === 'settings',
onclick: () => activeItem('settings')
},
{
icon: '👤',
label: 'Profile',
active: () => activeItem() === 'profile',
onclick: () => activeItem('profile')
}
]
});
};
$mount(IconsDemo, '#demo-icons');
```
### Nested Menu
<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-nested" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const NestedDemo = () => {
const activeItem = $('products');
return Menu({
items: [
{
label: 'Dashboard',
onclick: () => activeItem('dashboard'),
active: () => activeItem() === 'dashboard'
},
{
label: 'Products',
icon: '📦',
open: true,
children: [
{
label: 'All Products',
onclick: () => activeItem('all-products'),
active: () => activeItem() === 'all-products'
},
{
label: 'Add New',
onclick: () => activeItem('add-product'),
active: () => activeItem() === 'add-product'
},
{
label: 'Categories',
children: [
{
label: 'Electronics',
onclick: () => activeItem('electronics'),
active: () => activeItem() === 'electronics'
},
{
label: 'Clothing',
onclick: () => activeItem('clothing'),
active: () => activeItem() === 'clothing'
}
]
}
]
},
{
label: 'Orders',
icon: '📋',
onclick: () => activeItem('orders'),
active: () => activeItem() === 'orders'
},
{
label: 'Settings',
icon: '⚙️',
onclick: () => activeItem('settings'),
active: () => activeItem() === 'settings'
}
]
});
};
$mount(NestedDemo, '#demo-nested');
```
### Horizontal Menu
<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-horizontal" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const HorizontalDemo = () => {
const activeItem = $('home');
return Menu({
class: 'menu-horizontal rounded-box',
items: [
{
label: 'Home',
active: () => activeItem() === 'home',
onclick: () => activeItem('home')
},
{
label: 'Products',
children: [
{ label: 'Electronics', onclick: () => activeItem('electronics') },
{ label: 'Clothing', onclick: () => activeItem('clothing') },
{ label: 'Books', onclick: () => activeItem('books') }
]
},
{
label: 'About',
onclick: () => activeItem('about'),
active: () => activeItem() === 'about'
},
{
label: 'Contact',
onclick: () => activeItem('contact'),
active: () => activeItem() === 'contact'
}
]
});
};
$mount(HorizontalDemo, '#demo-horizontal');
```
### Sidebar Menu
<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-sidebar" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const SidebarDemo = () => {
const activeItem = $('dashboard');
return Div({ class: 'flex' }, [
Div({ class: 'w-64' }, [
Menu({
class: 'rounded-box w-full',
items: [
{
icon: '📊',
label: 'Dashboard',
active: () => activeItem() === 'dashboard',
onclick: () => activeItem('dashboard')
},
{
icon: '👥',
label: 'Users',
children: [
{
label: 'All Users',
onclick: () => activeItem('all-users'),
active: () => activeItem() === 'all-users'
},
{
label: 'Add User',
onclick: () => activeItem('add-user'),
active: () => activeItem() === 'add-user'
}
]
},
{
icon: '📁',
label: 'Files',
onclick: () => activeItem('files'),
active: () => activeItem() === 'files'
},
{
icon: '⚙️',
label: 'Settings',
onclick: () => activeItem('settings'),
active: () => activeItem() === 'settings'
}
]
})
]),
Div({ class: 'flex-1 p-4' }, [
Div({ class: 'alert alert-info' }, () => `Current page: ${activeItem()}`)
])
]);
};
$mount(SidebarDemo, '#demo-sidebar');
```
### Account Menu
<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-account" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const AccountDemo = () => {
const [notifications, setNotifications] = $(3);
return Menu({
class: 'rounded-box w-56',
items: [
{
icon: '👤',
label: 'My Profile',
onclick: () => Toast('Profile clicked', 'alert-info', 2000)
},
{
icon: '📧',
label: 'Messages',
badge: '3',
onclick: () => Toast('Messages opened', 'alert-info', 2000)
},
{
icon: '🔔',
label: 'Notifications',
badge: () => notifications(),
onclick: () => {
setNotifications(0);
Toast('Notifications cleared', 'alert-success', 2000);
}
},
{
icon: '⚙️',
label: 'Settings',
onclick: () => Toast('Settings opened', 'alert-info', 2000)
},
{
icon: '🚪',
label: 'Logout',
onclick: () => Toast('Logged out', 'alert-warning', 2000)
}
]
});
};
$mount(AccountDemo, '#demo-account');
```
### Collapsible Sidebar
<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-collapsible" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const CollapsibleDemo = () => {
const collapsed = $(false);
const activeItem = $('dashboard');
return Div({ class: 'flex gap-4' }, [
Div({ class: `transition-all duration-300 ${collapsed() ? 'w-16' : 'w-64'}` }, [
Button({
class: 'btn btn-ghost btn-sm mb-2 w-full',
onclick: () => collapsed(!collapsed())
}, collapsed() ? '→' : '←'),
Menu({
class: `rounded-box ${collapsed() ? 'menu-compact' : ''}`,
items: [
{ icon: '📊', label: collapsed() ? '' : 'Dashboard', active: () => activeItem() === 'dashboard', onclick: () => activeItem('dashboard') },
{ icon: '👥', label: collapsed() ? '' : 'Users', active: () => activeItem() === 'users', onclick: () => activeItem('users') },
{ icon: '📁', label: collapsed() ? '' : 'Files', active: () => activeItem() === 'files', onclick: () => activeItem('files') },
{ icon: '⚙️', label: collapsed() ? '' : 'Settings', active: () => activeItem() === 'settings', onclick: () => activeItem('settings') }
]
})
]),
Div({ class: 'flex-1' }, [
Div({ class: 'alert alert-info' }, () => `Selected: ${activeItem()}`)
])
]);
};
$mount(CollapsibleDemo, '#demo-collapsible');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-8"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
const items = [
{ label: 'Item 1' },
{ label: 'Item 2' },
{ label: 'Item 3', children: [
{ label: 'Subitem 1' },
{ label: 'Subitem 2' }
]}
];
return Div({ class: 'flex flex-wrap gap-8' }, [
Div({ class: 'w-48' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Default'),
Menu({ items })
]),
Div({ class: 'w-48' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Compact'),
Menu({ items, class: 'menu-compact' })
]),
Div({ class: 'w-48' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'With Shadow'),
Menu({ items, class: 'shadow-lg' })
])
]);
};
$mount(VariantsDemo, '#demo-variants');
```
<script>
(function() {
const initMenuExamples = () => {
// 1. Basic Menu
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
const activeItem = $('home');
return Menu({
items: [
{
label: 'Home',
active: () => activeItem() === 'home',
onclick: () => activeItem('home')
},
{
label: 'About',
active: () => activeItem() === 'about',
onclick: () => activeItem('about')
},
{
label: 'Contact',
active: () => activeItem() === 'contact',
onclick: () => activeItem('contact')
}
]
});
};
$mount(BasicDemo, basicTarget);
}
// 2. With Icons
const iconsTarget = document.querySelector('#demo-icons');
if (iconsTarget && !iconsTarget.hasChildNodes()) {
const IconsDemo = () => {
const activeItem = $('dashboard');
return Menu({
items: [
{
icon: '🏠',
label: 'Dashboard',
active: () => activeItem() === 'dashboard',
onclick: () => activeItem('dashboard')
},
{
icon: '📊',
label: 'Analytics',
active: () => activeItem() === 'analytics',
onclick: () => activeItem('analytics')
},
{
icon: '⚙️',
label: 'Settings',
active: () => activeItem() === 'settings',
onclick: () => activeItem('settings')
},
{
icon: '👤',
label: 'Profile',
active: () => activeItem() === 'profile',
onclick: () => activeItem('profile')
}
]
});
};
$mount(IconsDemo, iconsTarget);
}
// 3. Nested Menu
const nestedTarget = document.querySelector('#demo-nested');
if (nestedTarget && !nestedTarget.hasChildNodes()) {
const NestedDemo = () => {
const activeItem = $('products');
return Menu({
items: [
{
label: 'Dashboard',
onclick: () => activeItem('dashboard'),
active: () => activeItem() === 'dashboard'
},
{
label: 'Products',
icon: '📦',
open: true,
children: [
{
label: 'All Products',
onclick: () => activeItem('all-products'),
active: () => activeItem() === 'all-products'
},
{
label: 'Add New',
onclick: () => activeItem('add-product'),
active: () => activeItem() === 'add-product'
},
{
label: 'Categories',
children: [
{
label: 'Electronics',
onclick: () => activeItem('electronics'),
active: () => activeItem() === 'electronics'
},
{
label: 'Clothing',
onclick: () => activeItem('clothing'),
active: () => activeItem() === 'clothing'
}
]
}
]
},
{
label: 'Orders',
icon: '📋',
onclick: () => activeItem('orders'),
active: () => activeItem() === 'orders'
},
{
label: 'Settings',
icon: '⚙️',
onclick: () => activeItem('settings'),
active: () => activeItem() === 'settings'
}
]
});
};
$mount(NestedDemo, nestedTarget);
}
// 4. Horizontal Menu
const horizontalTarget = document.querySelector('#demo-horizontal');
if (horizontalTarget && !horizontalTarget.hasChildNodes()) {
const HorizontalDemo = () => {
const activeItem = $('home');
return Menu({
class: 'menu-horizontal rounded-box',
items: [
{
label: 'Home',
active: () => activeItem() === 'home',
onclick: () => activeItem('home')
},
{
label: 'Products',
children: [
{ label: 'Electronics', onclick: () => activeItem('electronics') },
{ label: 'Clothing', onclick: () => activeItem('clothing') },
{ label: 'Books', onclick: () => activeItem('books') }
]
},
{
label: 'About',
onclick: () => activeItem('about'),
active: () => activeItem() === 'about'
},
{
label: 'Contact',
onclick: () => activeItem('contact'),
active: () => activeItem() === 'contact'
}
]
});
};
$mount(HorizontalDemo, horizontalTarget);
}
// 5. Sidebar Menu
const sidebarTarget = document.querySelector('#demo-sidebar');
if (sidebarTarget && !sidebarTarget.hasChildNodes()) {
const SidebarDemo = () => {
const activeItem = $('dashboard');
return Div({ class: 'flex' }, [
Div({ class: 'w-64' }, [
Menu({
class: 'rounded-box w-full',
items: [
{
icon: '📊',
label: 'Dashboard',
active: () => activeItem() === 'dashboard',
onclick: () => activeItem('dashboard')
},
{
icon: '👥',
label: 'Users',
children: [
{
label: 'All Users',
onclick: () => activeItem('all-users'),
active: () => activeItem() === 'all-users'
},
{
label: 'Add User',
onclick: () => activeItem('add-user'),
active: () => activeItem() === 'add-user'
}
]
},
{
icon: '📁',
label: 'Files',
onclick: () => activeItem('files'),
active: () => activeItem() === 'files'
},
{
icon: '⚙️',
label: 'Settings',
onclick: () => activeItem('settings'),
active: () => activeItem() === 'settings'
}
]
})
]),
Div({ class: 'flex-1 p-4' }, [
Div({ class: 'alert alert-info' }, () => `Current page: ${activeItem()}`)
])
]);
};
$mount(SidebarDemo, sidebarTarget);
}
// 6. Account Menu
const accountTarget = document.querySelector('#demo-account');
if (accountTarget && !accountTarget.hasChildNodes()) {
const AccountDemo = () => {
const [notifications, setNotifications] = $(3);
return Menu({
class: 'rounded-box w-56',
items: [
{
icon: '👤',
label: 'My Profile',
onclick: () => Toast('Profile clicked', 'alert-info', 2000)
},
{
icon: '📧',
label: 'Messages',
badge: '3',
onclick: () => Toast('Messages opened', 'alert-info', 2000)
},
{
icon: '🔔',
label: 'Notifications',
badge: () => notifications(),
onclick: () => {
setNotifications(0);
Toast('Notifications cleared', 'alert-success', 2000);
}
},
{
icon: '⚙️',
label: 'Settings',
onclick: () => Toast('Settings opened', 'alert-info', 2000)
},
{
icon: '🚪',
label: 'Logout',
onclick: () => Toast('Logged out', 'alert-warning', 2000)
}
]
});
};
$mount(AccountDemo, accountTarget);
}
// 7. Collapsible Sidebar
const collapsibleTarget = document.querySelector('#demo-collapsible');
if (collapsibleTarget && !collapsibleTarget.hasChildNodes()) {
const CollapsibleDemo = () => {
const collapsed = $(false);
const activeItem = $('dashboard');
return Div({ class: 'flex gap-4' }, [
Div({ class: `transition-all duration-300 ${collapsed() ? 'w-16' : 'w-64'}` }, [
Button({
class: 'btn btn-ghost btn-sm mb-2 w-full',
onclick: () => collapsed(!collapsed())
}, collapsed() ? '→' : '←'),
Menu({
class: `rounded-box ${collapsed() ? 'menu-compact' : ''}`,
items: [
{ icon: '📊', label: collapsed() ? '' : 'Dashboard', active: () => activeItem() === 'dashboard', onclick: () => activeItem('dashboard') },
{ icon: '👥', label: collapsed() ? '' : 'Users', active: () => activeItem() === 'users', onclick: () => activeItem('users') },
{ icon: '📁', label: collapsed() ? '' : 'Files', active: () => activeItem() === 'files', onclick: () => activeItem('files') },
{ icon: '⚙️', label: collapsed() ? '' : 'Settings', active: () => activeItem() === 'settings', onclick: () => activeItem('settings') }
]
})
]),
Div({ class: 'flex-1' }, [
Div({ class: 'alert alert-info' }, () => `Selected: ${activeItem()}`)
])
]);
};
$mount(CollapsibleDemo, collapsibleTarget);
}
// 8. All Variants
const variantsTarget = document.querySelector('#demo-variants');
if (variantsTarget && !variantsTarget.hasChildNodes()) {
const VariantsDemo = () => {
const items = [
{ label: 'Item 1' },
{ label: 'Item 2' },
{ label: 'Item 3', children: [
{ label: 'Subitem 1' },
{ label: 'Subitem 2' }
]}
];
return Div({ class: 'flex flex-wrap gap-8' }, [
Div({ class: 'w-48' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Default'),
Menu({ items })
]),
Div({ class: 'w-48' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Compact'),
Menu({ items, class: 'menu-compact' })
]),
Div({ class: 'w-48' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'With Shadow'),
Menu({ items, class: 'shadow-lg' })
])
]);
};
$mount(VariantsDemo, variantsTarget);
}
};
initMenuExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initMenuExamples);
});
}
})();
</script>

View File

@@ -0,0 +1,667 @@
# Modal
Modal dialog component for displaying content in an overlay with customizable actions and backdrop.
## Tag
`Modal`
## Props
| Prop | Type | Default | Description |
| :----------- | :--------------------------- | :---------- | :----------------------------------------------- |
| `open` | `boolean \| Signal<boolean>` | `false` | Modal visibility state |
| `title` | `string \| VNode \| Signal` | `-` | Modal title text |
| `buttons` | `Array<VNode> \| VNode` | `-` | Action buttons to display (auto-closes on click) |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `children` | `string \| VNode` | `-` | Modal content |
## Live Examples
### Basic Modal
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const isOpen = $(false);
return Div({ class: 'flex justify-center' }, [
Button({
class: 'btn btn-primary',
onclick: () => isOpen(true)
}, 'Open Modal'),
Modal({
open: isOpen,
title: 'Basic Modal',
buttons: Button({ onclick: () => isOpen(false) }, 'Close')
}, [
Div({ class: 'py-4' }, 'This is a basic modal dialog. You can put any content here.')
])
]);
};
$mount(BasicDemo, '#demo-basic');
```
### Modal with Actions
<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-actions" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const ActionsDemo = () => {
const isOpen = $(false);
const confirmed = $(false);
const handleConfirm = () => {
confirmed(true);
isOpen(false);
Toast('Action confirmed!', 'alert-success', 2000);
};
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Button({
class: 'btn btn-primary',
onclick: () => isOpen(true)
}, 'Confirm Action'),
() => confirmed() ? Alert({
type: 'success',
message: 'You confirmed the action!'
}) : null,
Modal({
open: isOpen,
title: 'Confirm Action',
buttons: [
Button({
class: 'btn btn-ghost',
onclick: () => isOpen(false)
}, 'Cancel'),
Button({
class: 'btn btn-primary',
onclick: handleConfirm
}, 'Confirm')
]
}, [
Div({ class: 'py-4' }, 'Are you sure you want to perform this action? This cannot be undone.')
])
]);
};
$mount(ActionsDemo, '#demo-actions');
```
### Form Modal
<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-form" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const FormModal = () => {
const isOpen = $(false);
const name = $('');
const email = $('');
const submitted = $(false);
const handleSubmit = () => {
if (name() && email()) {
submitted(true);
isOpen(false);
Toast(`Welcome ${name()}!`, 'alert-success', 2000);
setTimeout(() => submitted(false), 3000);
}
};
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Button({
class: 'btn btn-primary',
onclick: () => isOpen(true)
}, 'Sign Up'),
() => submitted() ? Alert({
type: 'success',
message: 'Account created successfully!'
}) : null,
Modal({
open: isOpen,
title: 'Create Account',
buttons: [
Button({
class: 'btn btn-ghost',
onclick: () => isOpen(false)
}, 'Cancel'),
Button({
class: 'btn btn-primary',
onclick: handleSubmit
}, 'Sign Up')
]
}, [
Div({ class: 'flex flex-col gap-4 py-2' }, [
Input({
label: 'Full Name',
value: name,
placeholder: 'Enter your name',
oninput: (e) => name(e.target.value)
}),
Input({
label: 'Email',
type: 'email',
value: email,
placeholder: 'Enter your email',
oninput: (e) => email(e.target.value)
})
])
])
]);
};
$mount(FormModal, '#demo-form');
```
### Confirmation Modal
<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-confirm" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const ConfirmDemo = () => {
const isOpen = $(false);
const items = $([
{ id: 1, name: 'Document A' },
{ id: 2, name: 'Document B' },
{ id: 3, name: 'Document C' }
]);
const pendingDelete = $(null);
const confirmDelete = (item) => {
pendingDelete(item);
isOpen(true);
};
const handleDelete = () => {
items(items().filter(i => i.id !== pendingDelete().id));
isOpen(false);
Toast(`Deleted: ${pendingDelete().name}`, 'alert-info', 2000);
};
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex flex-col gap-2' }, items().map(item =>
Div({ class: 'flex justify-between items-center p-3 bg-base-200 rounded-lg' }, [
Span({}, item.name),
Button({
class: 'btn btn-xs btn-error',
onclick: () => confirmDelete(item)
}, 'Delete')
])
)),
Modal({
open: isOpen,
title: 'Delete Confirmation',
buttons: [
Button({
class: 'btn btn-ghost',
onclick: () => isOpen(false)
}, 'Cancel'),
Button({
class: 'btn btn-error',
onclick: handleDelete
}, 'Delete')
]
}, [
Div({ class: 'py-4' }, () => `Are you sure you want to delete "${pendingDelete()?.name}"? This action cannot be undone.`)
])
]);
};
$mount(ConfirmDemo, '#demo-confirm');
```
### Large Content Modal
<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-large" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const LargeDemo = () => {
const isOpen = $(false);
return Div({ class: 'flex justify-center' }, [
Button({
class: 'btn btn-primary',
onclick: () => isOpen(true)
}, 'Open Large Modal'),
Modal({
open: isOpen,
title: 'Terms and Conditions',
buttons: Button({
class: 'btn btn-primary',
onclick: () => isOpen(false)
}, 'I Agree')
}, [
Div({ class: 'py-4 max-h-96 overflow-y-auto' }, [
Div({ class: 'space-y-4' }, [
Div({ class: 'text-sm' }, [
Div({ class: 'font-bold mb-2' }, '1. Introduction'),
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.'
]),
Div({ class: 'text-sm' }, [
Div({ class: 'font-bold mb-2' }, '2. User Obligations'),
'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident.'
]),
Div({ class: 'text-sm' }, [
Div({ class: 'font-bold mb-2' }, '3. Privacy Policy'),
'Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam.'
]),
Div({ class: 'text-sm' }, [
Div({ class: 'font-bold mb-2' }, '4. Termination'),
'At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores.'
])
])
])
])
]);
};
$mount(LargeDemo, '#demo-large');
```
### Multiple Modals
<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-multiple" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-4 justify-center"></div>
</div>
</div>
```javascript
const MultipleDemo = () => {
const modal1 = $(false);
const modal2 = $(false);
const modal3 = $(false);
return Div({ class: 'flex flex-wrap gap-4 justify-center' }, [
Button({ class: 'btn', onclick: () => modal1(true) }, 'Info Modal'),
Button({ class: 'btn', onclick: () => modal2(true) }, 'Success Modal'),
Button({ class: 'btn', onclick: () => modal3(true) }, 'Warning Modal'),
Modal({
open: modal1,
title: 'Information',
buttons: Button({ onclick: () => modal1(false) }, 'OK')
}, 'This is an informational message.'),
Modal({
open: modal2,
title: 'Success!',
buttons: Button({ onclick: () => modal2(false) }, 'Great!')
}, 'Your operation was completed successfully.'),
Modal({
open: modal3,
title: 'Warning',
buttons: Button({ onclick: () => modal3(false) }, 'Understood')
}, 'Please review your input before proceeding.')
]);
};
$mount(MultipleDemo, '#demo-multiple');
```
### Custom Styled Modal
<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-custom" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const CustomDemo = () => {
const isOpen = $(false);
return Div({ class: 'flex justify-center' }, [
Button({
class: 'btn btn-primary',
onclick: () => isOpen(true)
}, 'Open Custom Modal'),
Modal({
open: isOpen,
class: 'bg-gradient-to-r from-primary to-secondary text-primary-content'
}, [
Div({ class: 'text-center py-8' }, [
Div({ class: 'text-6xl mb-4' }, '🎉'),
Div({ class: 'text-2xl font-bold mb-2' }, 'Congratulations!'),
Div({ class: 'mb-6' }, 'You\'ve successfully completed the tutorial.'),
Button({
class: 'btn btn-ghost bg-white/20 hover:bg-white/30',
onclick: () => isOpen(false)
}, 'Get Started')
])
])
]);
};
$mount(CustomDemo, '#demo-custom');
```
<script>
(function() {
const initModalExamples = () => {
// 1. Basic Modal
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
const isOpen = $(false);
return Div({ class: 'flex justify-center' }, [
Button({
class: 'btn btn-primary',
onclick: () => isOpen(true)
}, 'Open Modal'),
Modal({
open: isOpen,
title: 'Basic Modal',
buttons: Button({ onclick: () => isOpen(false) }, 'Close')
}, [
Div({ class: 'py-4' }, 'This is a basic modal dialog. You can put any content here.')
])
]);
};
$mount(BasicDemo, basicTarget);
}
// 2. Modal with Actions
const actionsTarget = document.querySelector('#demo-actions');
if (actionsTarget && !actionsTarget.hasChildNodes()) {
const ActionsDemo = () => {
const isOpen = $(false);
const confirmed = $(false);
const handleConfirm = () => {
confirmed(true);
isOpen(false);
Toast('Action confirmed!', 'alert-success', 2000);
};
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Button({
class: 'btn btn-primary',
onclick: () => isOpen(true)
}, 'Confirm Action'),
() => confirmed() ? Alert({
type: 'success',
message: 'You confirmed the action!'
}) : null,
Modal({
open: isOpen,
title: 'Confirm Action',
buttons: [
Button({
class: 'btn btn-ghost',
onclick: () => isOpen(false)
}, 'Cancel'),
Button({
class: 'btn btn-primary',
onclick: handleConfirm
}, 'Confirm')
]
}, [
Div({ class: 'py-4' }, 'Are you sure you want to perform this action? This cannot be undone.')
])
]);
};
$mount(ActionsDemo, actionsTarget);
}
// 3. Form Modal
const formTarget = document.querySelector('#demo-form');
if (formTarget && !formTarget.hasChildNodes()) {
const FormModal = () => {
const isOpen = $(false);
const name = $('');
const email = $('');
const submitted = $(false);
const handleSubmit = () => {
if (name() && email()) {
submitted(true);
isOpen(false);
Toast(`Welcome ${name()}!`, 'alert-success', 2000);
setTimeout(() => submitted(false), 3000);
}
};
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Button({
class: 'btn btn-primary',
onclick: () => isOpen(true)
}, 'Sign Up'),
() => submitted() ? Alert({
type: 'success',
message: 'Account created successfully!'
}) : null,
Modal({
open: isOpen,
title: 'Create Account',
buttons: [
Button({
class: 'btn btn-ghost',
onclick: () => isOpen(false)
}, 'Cancel'),
Button({
class: 'btn btn-primary',
onclick: handleSubmit
}, 'Sign Up')
]
}, [
Div({ class: 'flex flex-col gap-4 py-2' }, [
Input({
label: 'Full Name',
value: name,
placeholder: 'Enter your name',
oninput: (e) => name(e.target.value)
}),
Input({
label: 'Email',
type: 'email',
value: email,
placeholder: 'Enter your email',
oninput: (e) => email(e.target.value)
})
])
])
]);
};
$mount(FormModal, formTarget);
}
// 4. Confirmation Modal
const confirmTarget = document.querySelector('#demo-confirm');
if (confirmTarget && !confirmTarget.hasChildNodes()) {
const ConfirmDemo = () => {
const isOpen = $(false);
const items = $([
{ id: 1, name: 'Document A' },
{ id: 2, name: 'Document B' },
{ id: 3, name: 'Document C' }
]);
const pendingDelete = $(null);
const confirmDelete = (item) => {
pendingDelete(item);
isOpen(true);
};
const handleDelete = () => {
items(items().filter(i => i.id !== pendingDelete().id));
isOpen(false);
Toast(`Deleted: ${pendingDelete().name}`, 'alert-info', 2000);
};
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex flex-col gap-2' }, items().map(item =>
Div({ class: 'flex justify-between items-center p-3 bg-base-200 rounded-lg' }, [
Span({}, item.name),
Button({
class: 'btn btn-xs btn-error',
onclick: () => confirmDelete(item)
}, 'Delete')
])
)),
Modal({
open: isOpen,
title: 'Delete Confirmation',
buttons: [
Button({
class: 'btn btn-ghost',
onclick: () => isOpen(false)
}, 'Cancel'),
Button({
class: 'btn btn-error',
onclick: handleDelete
}, 'Delete')
]
}, [
Div({ class: 'py-4' }, () => `Are you sure you want to delete "${pendingDelete()?.name}"? This action cannot be undone.`)
])
]);
};
$mount(ConfirmDemo, confirmTarget);
}
// 5. Large Content Modal
const largeTarget = document.querySelector('#demo-large');
if (largeTarget && !largeTarget.hasChildNodes()) {
const LargeDemo = () => {
const isOpen = $(false);
return Div({ class: 'flex justify-center' }, [
Button({
class: 'btn btn-primary',
onclick: () => isOpen(true)
}, 'Open Large Modal'),
Modal({
open: isOpen,
title: 'Terms and Conditions',
buttons: Button({
class: 'btn btn-primary',
onclick: () => isOpen(false)
}, 'I Agree')
}, [
Div({ class: 'py-4 max-h-96 overflow-y-auto' }, [
Div({ class: 'space-y-4' }, [
Div({ class: 'text-sm' }, [
Div({ class: 'font-bold mb-2' }, '1. Introduction'),
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.'
]),
Div({ class: 'text-sm' }, [
Div({ class: 'font-bold mb-2' }, '2. User Obligations'),
'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident.'
]),
Div({ class: 'text-sm' }, [
Div({ class: 'font-bold mb-2' }, '3. Privacy Policy'),
'Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam.'
]),
Div({ class: 'text-sm' }, [
Div({ class: 'font-bold mb-2' }, '4. Termination'),
'At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores.'
])
])
])
])
]);
};
$mount(LargeDemo, largeTarget);
}
// 6. Multiple Modals
const multipleTarget = document.querySelector('#demo-multiple');
if (multipleTarget && !multipleTarget.hasChildNodes()) {
const MultipleDemo = () => {
const modal1 = $(false);
const modal2 = $(false);
const modal3 = $(false);
return Div({ class: 'flex flex-wrap gap-4 justify-center' }, [
Button({ class: 'btn', onclick: () => modal1(true) }, 'Info Modal'),
Button({ class: 'btn', onclick: () => modal2(true) }, 'Success Modal'),
Button({ class: 'btn', onclick: () => modal3(true) }, 'Warning Modal'),
Modal({
open: modal1,
title: 'Information',
buttons: Button({ onclick: () => modal1(false) }, 'OK')
}, 'This is an informational message.'),
Modal({
open: modal2,
title: 'Success!',
buttons: Button({ onclick: () => modal2(false) }, 'Great!')
}, 'Your operation was completed successfully.'),
Modal({
open: modal3,
title: 'Warning',
buttons: Button({ onclick: () => modal3(false) }, 'Understood')
}, 'Please review your input before proceeding.')
]);
};
$mount(MultipleDemo, multipleTarget);
}
// 7. Custom Styled Modal
const customTarget = document.querySelector('#demo-custom');
if (customTarget && !customTarget.hasChildNodes()) {
const CustomDemo = () => {
const isOpen = $(false);
return Div({ class: 'flex justify-center' }, [
Button({
class: 'btn btn-primary',
onclick: () => isOpen(true)
}, 'Open Custom Modal'),
Modal({
open: isOpen,
class: 'bg-gradient-to-r from-primary to-secondary text-primary-content'
}, [
Div({ class: 'text-center py-8' }, [
Div({ class: 'text-6xl mb-4' }, '🎉'),
Div({ class: 'text-2xl font-bold mb-2' }, 'Congratulations!'),
Div({ class: 'mb-6' }, 'You\'ve successfully completed the tutorial.'),
Button({
class: 'btn btn-ghost bg-white/20 hover:bg-white/30',
onclick: () => isOpen(false)
}, 'Get Started')
])
])
]);
};
$mount(CustomDemo, customTarget);
}
};
initModalExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initModalExamples);
});
}
})();
</script>

View File

@@ -0,0 +1,551 @@
# Navbar
Navigation bar component for creating responsive headers with logo, navigation links, and actions.
## Tag
`Navbar`
## Props
| Prop | Type | Default | Description |
| :----------- | :--------------------------- | :---------- | :----------------------------------------------- |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `children` | `VNode \| Array<VNode>` | `-` | Navbar content (should contain left, center, right sections) |
## Live Examples
### Basic Navbar
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
return Navbar({ class: 'rounded-box' }, [
Div({ class: 'navbar-start' }, [
Span({ class: 'text-xl font-bold' }, 'Logo')
]),
Div({ class: 'navbar-center hidden lg:flex' }, [
Span({ class: 'text-sm' }, 'Navigation Items')
]),
Div({ class: 'navbar-end' }, [
Button({ class: 'btn btn-ghost btn-sm' }, 'Login')
])
]);
};
$mount(BasicDemo, '#demo-basic');
```
### With Navigation Links
<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-links" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const LinksDemo = () => {
const active = $('home');
return Navbar({ class: 'rounded-box' }, [
Div({ class: 'navbar-start' }, [
Span({ class: 'text-xl font-bold' }, 'MyApp')
]),
Div({ class: 'navbar-center hidden lg:flex' }, [
Div({ class: 'menu menu-horizontal px-1' }, [
Button({
class: `btn btn-ghost btn-sm ${active() === 'home' ? 'btn-active' : ''}`,
onclick: () => active('home')
}, 'Home'),
Button({
class: `btn btn-ghost btn-sm ${active() === 'about' ? 'btn-active' : ''}`,
onclick: () => active('about')
}, 'About'),
Button({
class: `btn btn-ghost btn-sm ${active() === 'contact' ? 'btn-active' : ''}`,
onclick: () => active('contact')
}, 'Contact')
])
]),
Div({ class: 'navbar-end' }, [
Button({ class: 'btn btn-primary btn-sm' }, 'Sign Up')
])
]);
};
$mount(LinksDemo, '#demo-links');
```
### With Dropdown Menu
<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-dropdown" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const DropdownDemo = () => {
return Navbar({ class: 'rounded-box' }, [
Div({ class: 'navbar-start' }, [
Div({ class: 'dropdown' }, [
Div({ tabindex: 0, role: 'button', class: 'btn btn-ghost lg:hidden' }, [
Span({ class: 'sr-only' }, 'Open menu'),
Icons.iconInfo
]),
Div({ tabindex: 0, class: 'dropdown-content menu bg-base-100 rounded-box z-[1] w-52 p-2 shadow' }, [
Div({ class: 'menu-item' }, 'Home'),
Div({ class: 'menu-item' }, 'About'),
Div({ class: 'menu-item' }, 'Contact')
])
]),
Span({ class: 'text-xl font-bold' }, 'MyApp')
]),
Div({ class: 'navbar-center hidden lg:flex' }, [
Div({ class: 'menu menu-horizontal px-1' }, [
Div({ class: 'menu-item' }, 'Home'),
Div({ class: 'menu-item' }, 'About'),
Div({ class: 'menu-item' }, 'Contact')
])
]),
Div({ class: 'navbar-end' }, [
Button({ class: 'btn btn-primary btn-sm' }, 'Login')
])
]);
};
$mount(DropdownDemo, '#demo-dropdown');
```
### With Search
<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-search" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const SearchDemo = () => {
const searchQuery = $('');
return Navbar({ class: 'rounded-box' }, [
Div({ class: 'navbar-start' }, [
Span({ class: 'text-xl font-bold' }, 'MyApp')
]),
Div({ class: 'navbar-center' }, [
Div({ class: 'form-control' }, [
Input({
placeholder: 'Search...',
value: searchQuery,
class: 'input input-bordered w-48 md:w-auto',
oninput: (e) => searchQuery(e.target.value)
})
])
]),
Div({ class: 'navbar-end' }, [
Button({ class: 'btn btn-ghost btn-circle' }, '🔔'),
Button({ class: 'btn btn-ghost btn-circle' }, '👤')
])
]);
};
$mount(SearchDemo, '#demo-search');
```
### With Avatar and Dropdown
<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-avatar" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const AvatarDemo = () => {
return Navbar({ class: 'rounded-box' }, [
Div({ class: 'navbar-start' }, [
Span({ class: 'text-xl font-bold' }, 'MyApp')
]),
Div({ class: 'navbar-center hidden lg:flex' }, [
Div({ class: 'menu menu-horizontal px-1' }, [
Div({ class: 'menu-item' }, 'Dashboard'),
Div({ class: 'menu-item' }, 'Projects'),
Div({ class: 'menu-item' }, 'Tasks')
])
]),
Div({ class: 'navbar-end' }, [
Div({ class: 'dropdown dropdown-end' }, [
Div({ tabindex: 0, role: 'button', class: 'btn btn-ghost btn-circle avatar' }, [
Div({ class: 'w-8 rounded-full bg-primary text-primary-content flex items-center justify-center' }, 'JD')
]),
Div({ tabindex: 0, class: 'dropdown-content menu bg-base-100 rounded-box z-[1] w-52 p-2 shadow' }, [
Div({ class: 'menu-item' }, 'Profile'),
Div({ class: 'menu-item' }, 'Settings'),
Div({ class: 'menu-item' }, 'Logout')
])
])
])
]);
};
$mount(AvatarDemo, '#demo-avatar');
```
### Responsive Navbar
<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-responsive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ResponsiveDemo = () => {
const isOpen = $(false);
return Div({ class: 'flex flex-col' }, [
Navbar({ class: 'rounded-box' }, [
Div({ class: 'navbar-start' }, [
Button({
class: 'btn btn-ghost btn-circle lg:hidden',
onclick: () => isOpen(!isOpen())
}, '☰')
]),
Div({ class: 'navbar-center' }, [
Span({ class: 'text-xl font-bold' }, 'MyApp')
]),
Div({ class: 'navbar-end' }, [
Button({ class: 'btn btn-ghost btn-circle' }, '🔔')
])
]),
() => isOpen() ? Div({ class: 'flex flex-col gap-2 p-4 bg-base-200 rounded-box mt-2' }, [
Button({ class: 'btn btn-ghost justify-start' }, 'Home'),
Button({ class: 'btn btn-ghost justify-start' }, 'About'),
Button({ class: 'btn btn-ghost justify-start' }, 'Services'),
Button({ class: 'btn btn-ghost justify-start' }, 'Contact')
]) : null
]);
};
$mount(ResponsiveDemo, '#demo-responsive');
```
### With Brand and Actions
<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-brand" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const BrandDemo = () => {
return Navbar({ class: 'rounded-box bg-primary text-primary-content' }, [
Div({ class: 'navbar-start' }, [
Span({ class: 'text-xl font-bold' }, 'Brand')
]),
Div({ class: 'navbar-center hidden lg:flex' }, [
Div({ class: 'menu menu-horizontal px-1' }, [
Span({ class: 'text-sm' }, 'Features'),
Span({ class: 'text-sm' }, 'Pricing'),
Span({ class: 'text-sm' }, 'About')
])
]),
Div({ class: 'navbar-end' }, [
Button({ class: 'btn btn-ghost btn-sm' }, 'Login'),
Button({ class: 'btn btn-outline btn-sm ml-2' }, 'Sign Up')
])
]);
};
$mount(BrandDemo, '#demo-brand');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'text-sm font-bold' }, 'Default Navbar'),
Navbar({ class: 'rounded-box' }, [
Div({ class: 'navbar-start' }, [Span({}, 'Start')]),
Div({ class: 'navbar-center' }, [Span({}, 'Center')]),
Div({ class: 'navbar-end' }, [Span({}, 'End')])
]),
Div({ class: 'text-sm font-bold mt-2' }, 'With Shadow'),
Navbar({ class: 'rounded-box shadow-lg' }, [
Div({ class: 'navbar-start' }, [Span({}, 'Logo')]),
Div({ class: 'navbar-end' }, [Button({ class: 'btn btn-sm' }, 'Button')])
]),
Div({ class: 'text-sm font-bold mt-2' }, 'With Background'),
Navbar({ class: 'rounded-box bg-base-300' }, [
Div({ class: 'navbar-start' }, [Span({ class: 'font-bold' }, 'Colored')]),
Div({ class: 'navbar-end' }, [Span({ class: 'text-sm' }, 'Info')])
])
]);
};
$mount(VariantsDemo, '#demo-variants');
```
<script>
(function() {
const initNavbarExamples = () => {
// 1. Basic Navbar
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
return Navbar({ class: 'rounded-box' }, [
Div({ class: 'navbar-start' }, [
Span({ class: 'text-xl font-bold' }, 'Logo')
]),
Div({ class: 'navbar-center hidden lg:flex' }, [
Span({ class: 'text-sm' }, 'Navigation Items')
]),
Div({ class: 'navbar-end' }, [
Button({ class: 'btn btn-ghost btn-sm' }, 'Login')
])
]);
};
$mount(BasicDemo, basicTarget);
}
// 2. With Navigation Links
const linksTarget = document.querySelector('#demo-links');
if (linksTarget && !linksTarget.hasChildNodes()) {
const LinksDemo = () => {
const active = $('home');
return Navbar({ class: 'rounded-box' }, [
Div({ class: 'navbar-start' }, [
Span({ class: 'text-xl font-bold' }, 'MyApp')
]),
Div({ class: 'navbar-center hidden lg:flex' }, [
Div({ class: 'menu menu-horizontal px-1' }, [
Button({
class: `btn btn-ghost btn-sm ${active() === 'home' ? 'btn-active' : ''}`,
onclick: () => active('home')
}, 'Home'),
Button({
class: `btn btn-ghost btn-sm ${active() === 'about' ? 'btn-active' : ''}`,
onclick: () => active('about')
}, 'About'),
Button({
class: `btn btn-ghost btn-sm ${active() === 'contact' ? 'btn-active' : ''}`,
onclick: () => active('contact')
}, 'Contact')
])
]),
Div({ class: 'navbar-end' }, [
Button({ class: 'btn btn-primary btn-sm' }, 'Sign Up')
])
]);
};
$mount(LinksDemo, linksTarget);
}
// 3. With Dropdown Menu
const dropdownTarget = document.querySelector('#demo-dropdown');
if (dropdownTarget && !dropdownTarget.hasChildNodes()) {
const DropdownDemo = () => {
return Navbar({ class: 'rounded-box' }, [
Div({ class: 'navbar-start' }, [
Div({ class: 'dropdown' }, [
Div({ tabindex: 0, role: 'button', class: 'btn btn-ghost lg:hidden' }, [
Span({ class: 'sr-only' }, 'Open menu'),
Icons.iconInfo
]),
Div({ tabindex: 0, class: 'dropdown-content menu bg-base-100 rounded-box z-[1] w-52 p-2 shadow' }, [
Div({ class: 'menu-item' }, 'Home'),
Div({ class: 'menu-item' }, 'About'),
Div({ class: 'menu-item' }, 'Contact')
])
]),
Span({ class: 'text-xl font-bold' }, 'MyApp')
]),
Div({ class: 'navbar-center hidden lg:flex' }, [
Div({ class: 'menu menu-horizontal px-1' }, [
Div({ class: 'menu-item' }, 'Home'),
Div({ class: 'menu-item' }, 'About'),
Div({ class: 'menu-item' }, 'Contact')
])
]),
Div({ class: 'navbar-end' }, [
Button({ class: 'btn btn-primary btn-sm' }, 'Login')
])
]);
};
$mount(DropdownDemo, dropdownTarget);
}
// 4. With Search
const searchTarget = document.querySelector('#demo-search');
if (searchTarget && !searchTarget.hasChildNodes()) {
const SearchDemo = () => {
const searchQuery = $('');
return Navbar({ class: 'rounded-box' }, [
Div({ class: 'navbar-start' }, [
Span({ class: 'text-xl font-bold' }, 'MyApp')
]),
Div({ class: 'navbar-center' }, [
Div({ class: 'form-control' }, [
Input({
placeholder: 'Search...',
value: searchQuery,
class: 'input input-bordered w-48 md:w-auto',
oninput: (e) => searchQuery(e.target.value)
})
])
]),
Div({ class: 'navbar-end' }, [
Button({ class: 'btn btn-ghost btn-circle' }, '🔔'),
Button({ class: 'btn btn-ghost btn-circle' }, '👤')
])
]);
};
$mount(SearchDemo, searchTarget);
}
// 5. With Avatar and Dropdown
const avatarTarget = document.querySelector('#demo-avatar');
if (avatarTarget && !avatarTarget.hasChildNodes()) {
const AvatarDemo = () => {
return Navbar({ class: 'rounded-box' }, [
Div({ class: 'navbar-start' }, [
Span({ class: 'text-xl font-bold' }, 'MyApp')
]),
Div({ class: 'navbar-center hidden lg:flex' }, [
Div({ class: 'menu menu-horizontal px-1' }, [
Div({ class: 'menu-item' }, 'Dashboard'),
Div({ class: 'menu-item' }, 'Projects'),
Div({ class: 'menu-item' }, 'Tasks')
])
]),
Div({ class: 'navbar-end' }, [
Div({ class: 'dropdown dropdown-end' }, [
Div({ tabindex: 0, role: 'button', class: 'btn btn-ghost btn-circle avatar' }, [
Div({ class: 'w-8 rounded-full bg-primary text-primary-content flex items-center justify-center' }, 'JD')
]),
Div({ tabindex: 0, class: 'dropdown-content menu bg-base-100 rounded-box z-[1] w-52 p-2 shadow' }, [
Div({ class: 'menu-item' }, 'Profile'),
Div({ class: 'menu-item' }, 'Settings'),
Div({ class: 'menu-item' }, 'Logout')
])
])
])
]);
};
$mount(AvatarDemo, avatarTarget);
}
// 6. Responsive Navbar
const responsiveTarget = document.querySelector('#demo-responsive');
if (responsiveTarget && !responsiveTarget.hasChildNodes()) {
const ResponsiveDemo = () => {
const isOpen = $(false);
return Div({ class: 'flex flex-col' }, [
Navbar({ class: 'rounded-box' }, [
Div({ class: 'navbar-start' }, [
Button({
class: 'btn btn-ghost btn-circle lg:hidden',
onclick: () => isOpen(!isOpen())
}, '☰')
]),
Div({ class: 'navbar-center' }, [
Span({ class: 'text-xl font-bold' }, 'MyApp')
]),
Div({ class: 'navbar-end' }, [
Button({ class: 'btn btn-ghost btn-circle' }, '🔔')
])
]),
() => isOpen() ? Div({ class: 'flex flex-col gap-2 p-4 bg-base-200 rounded-box mt-2' }, [
Button({ class: 'btn btn-ghost justify-start' }, 'Home'),
Button({ class: 'btn btn-ghost justify-start' }, 'About'),
Button({ class: 'btn btn-ghost justify-start' }, 'Services'),
Button({ class: 'btn btn-ghost justify-start' }, 'Contact')
]) : null
]);
};
$mount(ResponsiveDemo, responsiveTarget);
}
// 7. With Brand and Actions
const brandTarget = document.querySelector('#demo-brand');
if (brandTarget && !brandTarget.hasChildNodes()) {
const BrandDemo = () => {
return Navbar({ class: 'rounded-box bg-primary text-primary-content' }, [
Div({ class: 'navbar-start' }, [
Span({ class: 'text-xl font-bold' }, 'Brand')
]),
Div({ class: 'navbar-center hidden lg:flex' }, [
Div({ class: 'menu menu-horizontal px-1' }, [
Span({ class: 'text-sm' }, 'Features'),
Span({ class: 'text-sm' }, 'Pricing'),
Span({ class: 'text-sm' }, 'About')
])
]),
Div({ class: 'navbar-end' }, [
Button({ class: 'btn btn-ghost btn-sm' }, 'Login'),
Button({ class: 'btn btn-outline btn-sm ml-2' }, 'Sign Up')
])
]);
};
$mount(BrandDemo, brandTarget);
}
// 8. All Variants
const variantsTarget = document.querySelector('#demo-variants');
if (variantsTarget && !variantsTarget.hasChildNodes()) {
const VariantsDemo = () => {
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'text-sm font-bold' }, 'Default Navbar'),
Navbar({ class: 'rounded-box' }, [
Div({ class: 'navbar-start' }, [Span({}, 'Start')]),
Div({ class: 'navbar-center' }, [Span({}, 'Center')]),
Div({ class: 'navbar-end' }, [Span({}, 'End')])
]),
Div({ class: 'text-sm font-bold mt-2' }, 'With Shadow'),
Navbar({ class: 'rounded-box shadow-lg' }, [
Div({ class: 'navbar-start' }, [Span({}, 'Logo')]),
Div({ class: 'navbar-end' }, [Button({ class: 'btn btn-sm' }, 'Button')])
]),
Div({ class: 'text-sm font-bold mt-2' }, 'With Background'),
Navbar({ class: 'rounded-box bg-base-300' }, [
Div({ class: 'navbar-start' }, [Span({ class: 'font-bold' }, 'Colored')]),
Div({ class: 'navbar-end' }, [Span({ class: 'text-sm' }, 'Info')])
])
]);
};
$mount(VariantsDemo, variantsTarget);
}
};
initNavbarExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initNavbarExamples);
});
}
})();
</script>

View File

@@ -0,0 +1,740 @@
# Radio
Radio button component with label, tooltip support, and reactive group selection. All radios in the same group share a common `name` attribute for proper HTML semantics.
## Tag
`Radio`
## Props
| Prop | Type | Default | Description |
| :----------- | :--------------------------- | :---------- | :----------------------------------------------- |
| `label` | `string` | `-` | Label text for the radio button |
| `value` | `string \| Signal<string>` | `-` | Selected value signal for the group |
| `radioValue` | `string` | `-` | Value of this radio button |
| `name` | `string` | `-` | Group name (all radios in group should share this) |
| `tooltip` | `string` | `-` | Tooltip text on hover |
| `disabled` | `boolean \| Signal<boolean>` | `false` | Disabled state |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `onclick` | `function` | `-` | Click event handler |
## Live Examples
### Basic Radio Group
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-3"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const selected = $('option1');
return Div({ class: 'flex flex-col gap-3' }, [
Radio({
label: 'Option 1',
name: 'basic-group',
value: selected,
radioValue: 'option1',
onclick: () => selected('option1')
}),
Radio({
label: 'Option 2',
name: 'basic-group',
value: selected,
radioValue: 'option2',
onclick: () => selected('option2')
}),
Radio({
label: 'Option 3',
name: 'basic-group',
value: selected,
radioValue: 'option3',
onclick: () => selected('option3')
}),
Div({ class: 'mt-2 text-sm opacity-70' }, () => `Selected: ${selected()}`)
]);
};
$mount(BasicDemo, '#demo-basic');
```
### With Tooltip
<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-tooltip" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-3"></div>
</div>
</div>
```javascript
const TooltipDemo = () => {
const selected = $('light');
return Div({ class: 'flex flex-col gap-3' }, [
Radio({
label: 'Light mode',
name: 'theme-group',
value: selected,
radioValue: 'light',
tooltip: 'Light theme with white background',
onclick: () => selected('light')
}),
Radio({
label: 'Dark mode',
name: 'theme-group',
value: selected,
radioValue: 'dark',
tooltip: 'Dark theme with black background',
onclick: () => selected('dark')
})
]);
};
$mount(TooltipDemo, '#demo-tooltip');
```
### Disabled State
<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-disabled" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-3"></div>
</div>
</div>
```javascript
const DisabledDemo = () => {
const selected = $('enabled');
return Div({ class: 'flex flex-col gap-3' }, [
Radio({
label: 'Enabled option',
name: 'disabled-group',
value: selected,
radioValue: 'enabled',
onclick: () => selected('enabled')
}),
Radio({
label: 'Disabled option (cannot select)',
name: 'disabled-group',
value: selected,
radioValue: 'disabled',
disabled: true,
onclick: () => selected('disabled')
})
]);
};
$mount(DisabledDemo, '#demo-disabled');
```
### Reactive Preview
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ReactiveDemo = () => {
const size = $('medium');
const color = $('blue');
const sizes = [
{ value: 'small', label: 'Small' },
{ value: 'medium', label: 'Medium' },
{ value: 'large', label: 'Large' }
];
const colors = [
{ value: 'blue', label: 'Blue' },
{ value: 'green', label: 'Green' },
{ value: 'red', label: 'Red' }
];
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'text-sm font-bold' }, 'Select size'),
Div({ class: 'flex gap-4' }, sizes.map(s =>
Radio({
label: s.label,
name: 'size-group',
value: size,
radioValue: s.value,
onclick: () => size(s.value)
})
)),
Div({ class: 'text-sm font-bold mt-2' }, 'Select color'),
Div({ class: 'flex gap-4' }, colors.map(c =>
Radio({
label: c.label,
name: 'color-group',
value: color,
radioValue: c.value,
onclick: () => color(c.value)
})
)),
Div({
class: 'mt-4 p-4 rounded-lg text-center transition-all',
style: () => {
const sizeMap = { small: 'text-sm', medium: 'text-base', large: 'text-lg' };
const colorMap = { blue: '#3b82f6', green: '#10b981', red: '#ef4444' };
return `background-color: ${colorMap[color()]}; color: white; ${sizeMap[size()]}`;
}
}, () => `${size()} ${color()} preview`)
]);
};
$mount(ReactiveDemo, '#demo-reactive');
```
### Payment Method Selection
<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-payment" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const PaymentDemo = () => {
const method = $('credit');
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'text-sm font-bold' }, 'Payment method'),
Div({ class: 'flex flex-col gap-3' }, [
Radio({
label: '💳 Credit Card',
name: 'payment-group',
value: method,
radioValue: 'credit',
onclick: () => method('credit')
}),
Radio({
label: '🏦 Bank Transfer',
name: 'payment-group',
value: method,
radioValue: 'bank',
onclick: () => method('bank')
}),
Radio({
label: '📱 Digital Wallet',
name: 'payment-group',
value: method,
radioValue: 'wallet',
onclick: () => method('wallet')
})
]),
Div({ class: 'alert alert-info mt-2' }, () => {
const messages = {
credit: 'You selected Credit Card. Enter your card details.',
bank: 'You selected Bank Transfer. Use the provided account number.',
wallet: 'You selected Digital Wallet. Scan the QR code to pay.'
};
return messages[method()];
})
]);
};
$mount(PaymentDemo, '#demo-payment');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
const primary = $('primary1');
const secondary = $('secondary1');
const accent = $('accent1');
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'card bg-base-200 p-4' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Primary variant'),
Div({ class: 'flex gap-4' }, [
Radio({
label: 'Option A',
name: 'primary-group',
value: primary,
radioValue: 'primary1',
class: 'radio-primary',
onclick: () => primary('primary1')
}),
Radio({
label: 'Option B',
name: 'primary-group',
value: primary,
radioValue: 'primary2',
class: 'radio-primary',
onclick: () => primary('primary2')
})
])
]),
Div({ class: 'card bg-base-200 p-4' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Secondary variant'),
Div({ class: 'flex gap-4' }, [
Radio({
label: 'Option A',
name: 'secondary-group',
value: secondary,
radioValue: 'secondary1',
class: 'radio-secondary',
onclick: () => secondary('secondary1')
}),
Radio({
label: 'Option B',
name: 'secondary-group',
value: secondary,
radioValue: 'secondary2',
class: 'radio-secondary',
onclick: () => secondary('secondary2')
})
])
]),
Div({ class: 'card bg-base-200 p-4' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Accent variant'),
Div({ class: 'flex gap-4' }, [
Radio({
label: 'Option A',
name: 'accent-group',
value: accent,
radioValue: 'accent1',
class: 'radio-accent',
onclick: () => accent('accent1')
}),
Radio({
label: 'Option B',
name: 'accent-group',
value: accent,
radioValue: 'accent2',
class: 'radio-accent',
onclick: () => accent('accent2')
})
])
])
]);
};
$mount(VariantsDemo, '#demo-variants');
```
### Dynamic Options
<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-dynamic" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const DynamicDemo = () => {
const category = $('cars');
const selected = $('');
const options = {
cars: [
{ value: 'sedan', label: 'Sedan' },
{ value: 'suv', label: 'SUV' },
{ value: 'sports', label: 'Sports' }
],
colors: [
{ value: 'red', label: 'Red' },
{ value: 'blue', label: 'Blue' },
{ value: 'black', label: 'Black' }
]
};
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex gap-4' }, [
Radio({
label: 'Cars',
name: 'category-group',
value: category,
radioValue: 'cars',
onclick: () => {
category('cars');
selected('');
}
}),
Radio({
label: 'Colors',
name: 'category-group',
value: category,
radioValue: 'colors',
onclick: () => {
category('colors');
selected('');
}
})
]),
Div({ class: 'divider my-1' }),
Div({ class: 'text-sm font-bold' }, () => `Select ${category()}`),
Div({ class: 'flex flex-col gap-2' }, () =>
options[category()].map(opt =>
Radio({
label: opt.label,
name: 'dynamic-option-group',
value: selected,
radioValue: opt.value,
onclick: () => selected(opt.value)
})
)
),
() => selected()
? Div({ class: 'alert alert-success mt-2' }, () => `Selected ${category()}: ${selected()}`)
: null
]);
};
$mount(DynamicDemo, '#demo-dynamic');
```
<script>
(function() {
const initRadioExamples = () => {
// 1. Basic Radio Group
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
const selected = $('option1');
return Div({ class: 'flex flex-col gap-3' }, [
Radio({
label: 'Option 1',
name: 'basic-group',
value: selected,
radioValue: 'option1',
onclick: () => selected('option1')
}),
Radio({
label: 'Option 2',
name: 'basic-group',
value: selected,
radioValue: 'option2',
onclick: () => selected('option2')
}),
Radio({
label: 'Option 3',
name: 'basic-group',
value: selected,
radioValue: 'option3',
onclick: () => selected('option3')
}),
Div({ class: 'mt-2 text-sm opacity-70' }, () => `Selected: ${selected()}`)
]);
};
$mount(BasicDemo, basicTarget);
}
// 2. With Tooltip
const tooltipTarget = document.querySelector('#demo-tooltip');
if (tooltipTarget && !tooltipTarget.hasChildNodes()) {
const TooltipDemo = () => {
const selected = $('light');
return Div({ class: 'flex flex-col gap-3' }, [
Radio({
label: 'Light mode',
name: 'theme-group',
value: selected,
radioValue: 'light',
tooltip: 'Light theme with white background',
onclick: () => selected('light')
}),
Radio({
label: 'Dark mode',
name: 'theme-group',
value: selected,
radioValue: 'dark',
tooltip: 'Dark theme with black background',
onclick: () => selected('dark')
})
]);
};
$mount(TooltipDemo, tooltipTarget);
}
// 3. Disabled State
const disabledTarget = document.querySelector('#demo-disabled');
if (disabledTarget && !disabledTarget.hasChildNodes()) {
const DisabledDemo = () => {
const selected = $('enabled');
return Div({ class: 'flex flex-col gap-3' }, [
Radio({
label: 'Enabled option',
name: 'disabled-group',
value: selected,
radioValue: 'enabled',
onclick: () => selected('enabled')
}),
Radio({
label: 'Disabled option (cannot select)',
name: 'disabled-group',
value: selected,
radioValue: 'disabled',
disabled: true,
onclick: () => selected('disabled')
})
]);
};
$mount(DisabledDemo, disabledTarget);
}
// 4. Reactive Preview
const reactiveTarget = document.querySelector('#demo-reactive');
if (reactiveTarget && !reactiveTarget.hasChildNodes()) {
const ReactiveDemo = () => {
const size = $('medium');
const color = $('blue');
const sizes = [
{ value: 'small', label: 'Small' },
{ value: 'medium', label: 'Medium' },
{ value: 'large', label: 'Large' }
];
const colors = [
{ value: 'blue', label: 'Blue' },
{ value: 'green', label: 'Green' },
{ value: 'red', label: 'Red' }
];
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'text-sm font-bold' }, 'Select size'),
Div({ class: 'flex gap-4' }, sizes.map(s =>
Radio({
label: s.label,
name: 'size-group',
value: size,
radioValue: s.value,
onclick: () => size(s.value)
})
)),
Div({ class: 'text-sm font-bold mt-2' }, 'Select color'),
Div({ class: 'flex gap-4' }, colors.map(c =>
Radio({
label: c.label,
name: 'color-group',
value: color,
radioValue: c.value,
onclick: () => color(c.value)
})
)),
Div({
class: 'mt-4 p-4 rounded-lg text-center transition-all',
style: () => {
const sizeMap = { small: 'text-sm', medium: 'text-base', large: 'text-lg' };
const colorMap = { blue: '#3b82f6', green: '#10b981', red: '#ef4444' };
return `background-color: ${colorMap[color()]}; color: white; ${sizeMap[size()]}`;
}
}, () => `${size()} ${color()} preview`)
]);
};
$mount(ReactiveDemo, reactiveTarget);
}
// 5. Payment Method Selection
const paymentTarget = document.querySelector('#demo-payment');
if (paymentTarget && !paymentTarget.hasChildNodes()) {
const PaymentDemo = () => {
const method = $('credit');
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'text-sm font-bold' }, 'Payment method'),
Div({ class: 'flex flex-col gap-3' }, [
Radio({
label: '💳 Credit Card',
name: 'payment-group',
value: method,
radioValue: 'credit',
onclick: () => method('credit')
}),
Radio({
label: '🏦 Bank Transfer',
name: 'payment-group',
value: method,
radioValue: 'bank',
onclick: () => method('bank')
}),
Radio({
label: '📱 Digital Wallet',
name: 'payment-group',
value: method,
radioValue: 'wallet',
onclick: () => method('wallet')
})
]),
Div({ class: 'alert alert-info mt-2' }, () => {
const messages = {
credit: 'You selected Credit Card. Enter your card details.',
bank: 'You selected Bank Transfer. Use the provided account number.',
wallet: 'You selected Digital Wallet. Scan the QR code to pay.'
};
return messages[method()];
})
]);
};
$mount(PaymentDemo, paymentTarget);
}
// 6. All Variants
const variantsTarget = document.querySelector('#demo-variants');
if (variantsTarget && !variantsTarget.hasChildNodes()) {
const VariantsDemo = () => {
const primary = $('primary1');
const secondary = $('secondary1');
const accent = $('accent1');
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'card bg-base-200 p-4' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Primary variant'),
Div({ class: 'flex gap-4' }, [
Radio({
label: 'Option A',
name: 'primary-group',
value: primary,
radioValue: 'primary1',
class: 'radio-primary',
onclick: () => primary('primary1')
}),
Radio({
label: 'Option B',
name: 'primary-group',
value: primary,
radioValue: 'primary2',
class: 'radio-primary',
onclick: () => primary('primary2')
})
])
]),
Div({ class: 'card bg-base-200 p-4' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Secondary variant'),
Div({ class: 'flex gap-4' }, [
Radio({
label: 'Option A',
name: 'secondary-group',
value: secondary,
radioValue: 'secondary1',
class: 'radio-secondary',
onclick: () => secondary('secondary1')
}),
Radio({
label: 'Option B',
name: 'secondary-group',
value: secondary,
radioValue: 'secondary2',
class: 'radio-secondary',
onclick: () => secondary('secondary2')
})
])
]),
Div({ class: 'card bg-base-200 p-4' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Accent variant'),
Div({ class: 'flex gap-4' }, [
Radio({
label: 'Option A',
name: 'accent-group',
value: accent,
radioValue: 'accent1',
class: 'radio-accent',
onclick: () => accent('accent1')
}),
Radio({
label: 'Option B',
name: 'accent-group',
value: accent,
radioValue: 'accent2',
class: 'radio-accent',
onclick: () => accent('accent2')
})
])
])
]);
};
$mount(VariantsDemo, variantsTarget);
}
// 7. Dynamic Options
const dynamicTarget = document.querySelector('#demo-dynamic');
if (dynamicTarget && !dynamicTarget.hasChildNodes()) {
const DynamicDemo = () => {
const category = $('cars');
const selected = $('');
const options = {
cars: [
{ value: 'sedan', label: 'Sedan' },
{ value: 'suv', label: 'SUV' },
{ value: 'sports', label: 'Sports' }
],
colors: [
{ value: 'red', label: 'Red' },
{ value: 'blue', label: 'Blue' },
{ value: 'black', label: 'Black' }
]
};
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex gap-4' }, [
Radio({
label: 'Cars',
name: 'category-group',
value: category,
radioValue: 'cars',
onclick: () => {
category('cars');
selected('');
}
}),
Radio({
label: 'Colors',
name: 'category-group',
value: category,
radioValue: 'colors',
onclick: () => {
category('colors');
selected('');
}
})
]),
Div({ class: 'divider my-1' }),
Div({ class: 'text-sm font-bold' }, () => `Select ${category()}`),
Div({ class: 'flex flex-col gap-2' }, () =>
options[category()].map(opt =>
Radio({
label: opt.label,
name: 'dynamic-option-group',
value: selected,
radioValue: opt.value,
onclick: () => selected(opt.value)
})
)
),
() => selected()
? Div({ class: 'alert alert-success mt-2' }, () => `Selected ${category()}: ${selected()}`)
: null
]);
};
$mount(DynamicDemo, dynamicTarget);
}
};
initRadioExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initRadioExamples);
});
}
})();
</script>

View File

@@ -0,0 +1,536 @@
# Rating
Star rating component with customizable count, icons, and read-only mode.
## Tag
`Rating`
## Props
| Prop | Type | Default | Description |
| :----------- | :--------------------------- | :--------------- | :----------------------------------------------- |
| `value` | `number \| Signal<number>` | `0` | Current rating value |
| `count` | `number \| Signal<number>` | `5` | Number of stars/items |
| `mask` | `string` | `'mask-star'` | Mask shape (mask-star, mask-star-2, mask-heart) |
| `readonly` | `boolean \| Signal<boolean>` | `false` | Disable interaction |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
## Live Examples
### Basic Rating
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const rating = $(3);
return Div({ class: 'flex flex-col gap-2 items-center' }, [
Rating({
value: rating,
count: 5,
onchange: (value) => rating(value)
}),
Div({ class: 'text-sm opacity-70' }, () => `Rating: ${rating()} / 5`)
]);
};
$mount(BasicDemo, '#demo-basic');
```
### Heart Rating
<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-heart" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const HeartDemo = () => {
const rating = $(4);
return Div({ class: 'flex flex-col gap-2 items-center' }, [
Rating({
value: rating,
count: 5,
mask: 'mask-heart',
onchange: (value) => rating(value)
}),
Div({ class: 'text-sm opacity-70' }, () => `${rating()} hearts`)
]);
};
$mount(HeartDemo, '#demo-heart');
```
### Star with Outline
<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-star2" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const Star2Demo = () => {
const rating = $(2);
return Div({ class: 'flex flex-col gap-2 items-center' }, [
Rating({
value: rating,
count: 5,
mask: 'mask-star-2',
onchange: (value) => rating(value)
}),
Div({ class: 'text-sm opacity-70' }, () => `${rating()} stars`)
]);
};
$mount(Star2Demo, '#demo-star2');
```
### Read-only Rating
<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-readonly" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const ReadonlyDemo = () => {
const rating = $(4.5);
return Div({ class: 'flex flex-col gap-2 items-center' }, [
Rating({
value: rating,
count: 5,
readonly: true
}),
Div({ class: 'text-sm opacity-70' }, 'Average rating: 4.5/5 (read-only)')
]);
};
$mount(ReadonlyDemo, '#demo-readonly');
```
### Custom Count
<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-custom" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const CustomDemo = () => {
const rating = $(3);
const count = $(10);
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Div({ class: 'flex items-center gap-4' }, [
Span({ class: 'text-sm' }, 'Number of stars:'),
Input({
type: 'number',
value: count,
class: 'input input-sm w-24',
oninput: (e) => count(parseInt(e.target.value) || 1)
})
]),
Rating({
value: rating,
count: count,
onchange: (value) => rating(value)
}),
Div({ class: 'text-sm opacity-70' }, () => `Rating: ${rating()} / ${count()}`)
]);
};
$mount(CustomDemo, '#demo-custom');
```
### Product Review
<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-review" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ReviewDemo = () => {
const quality = $(4);
const price = $(3);
const support = $(5);
const average = () => Math.round(((quality() + price() + support()) / 3) * 10) / 10;
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'text-lg font-bold' }, 'Product Review'),
Div({ class: 'flex flex-col gap-2' }, [
Div({ class: 'flex justify-between items-center' }, [
Span({ class: 'text-sm w-24' }, 'Quality:'),
Rating({
value: quality,
count: 5,
size: 'sm',
onchange: (v) => quality(v)
})
]),
Div({ class: 'flex justify-between items-center' }, [
Span({ class: 'text-sm w-24' }, 'Price:'),
Rating({
value: price,
count: 5,
size: 'sm',
onchange: (v) => price(v)
})
]),
Div({ class: 'flex justify-between items-center' }, [
Span({ class: 'text-sm w-24' }, 'Support:'),
Rating({
value: support,
count: 5,
size: 'sm',
onchange: (v) => support(v)
})
])
]),
Div({ class: 'divider my-1' }),
Div({ class: 'flex justify-between items-center' }, [
Span({ class: 'font-bold' }, 'Overall:'),
Div({ class: 'text-2xl font-bold text-primary' }, () => average())
])
]);
};
$mount(ReviewDemo, '#demo-review');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-6"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
return Div({ class: 'flex flex-col gap-6' }, [
Div({ class: 'text-center' }, [
Div({ class: 'text-sm mb-2' }, 'Mask Star'),
Rating({ value: $(3), count: 5, mask: 'mask-star' })
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-sm mb-2' }, 'Mask Star 2 (yellow)' ),
Rating({ value: $(4), count: 5, mask: 'mask-star-2', class: 'rating-warning' })
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-sm mb-2' }, 'Mask Heart'),
Rating({ value: $(5), count: 5, mask: 'mask-heart', class: 'rating-error' })
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-sm mb-2' }, 'Half Stars (read-only)'),
Rating({ value: $(3.5), count: 5, readonly: true })
])
]);
};
$mount(VariantsDemo, '#demo-variants');
```
### Interactive Feedback
<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-feedback" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const FeedbackDemo = () => {
const rating = $(0);
const feedback = $(false);
const messages = {
1: 'Very disappointed 😞',
2: 'Could be better 😕',
3: 'Good 👍',
4: 'Very good 😊',
5: 'Excellent! 🎉'
};
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Div({ class: 'text-center' }, [
Div({ class: 'text-sm mb-2' }, 'How was your experience?'),
Rating({
value: rating,
count: 5,
onchange: (value) => {
rating(value);
feedback(true);
if (value >= 4) {
Toast('Thank you for your positive feedback!', 'alert-success', 2000);
} else if (value <= 2) {
Toast('We appreciate your feedback and will improve!', 'alert-warning', 2000);
} else {
Toast('Thanks for your rating!', 'alert-info', 2000);
}
}
})
]),
() => rating() > 0
? Div({ class: 'alert alert-soft text-center' }, [
messages[rating()] || `Rating: ${rating()} stars`
])
: null
]);
};
$mount(FeedbackDemo, '#demo-feedback');
```
<script>
(function() {
const initRatingExamples = () => {
// 1. Basic Rating
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
const rating = $(3);
return Div({ class: 'flex flex-col gap-2 items-center' }, [
Rating({
value: rating,
count: 5,
onchange: (value) => rating(value)
}),
Div({ class: 'text-sm opacity-70' }, () => `Rating: ${rating()} / 5`)
]);
};
$mount(BasicDemo, basicTarget);
}
// 2. Heart Rating
const heartTarget = document.querySelector('#demo-heart');
if (heartTarget && !heartTarget.hasChildNodes()) {
const HeartDemo = () => {
const rating = $(4);
return Div({ class: 'flex flex-col gap-2 items-center' }, [
Rating({
value: rating,
count: 5,
mask: 'mask-heart',
onchange: (value) => rating(value)
}),
Div({ class: 'text-sm opacity-70' }, () => `${rating()} hearts`)
]);
};
$mount(HeartDemo, heartTarget);
}
// 3. Star with Outline
const star2Target = document.querySelector('#demo-star2');
if (star2Target && !star2Target.hasChildNodes()) {
const Star2Demo = () => {
const rating = $(2);
return Div({ class: 'flex flex-col gap-2 items-center' }, [
Rating({
value: rating,
count: 5,
mask: 'mask-star-2',
onchange: (value) => rating(value)
}),
Div({ class: 'text-sm opacity-70' }, () => `${rating()} stars`)
]);
};
$mount(Star2Demo, star2Target);
}
// 4. Read-only Rating
const readonlyTarget = document.querySelector('#demo-readonly');
if (readonlyTarget && !readonlyTarget.hasChildNodes()) {
const ReadonlyDemo = () => {
const rating = $(4.5);
return Div({ class: 'flex flex-col gap-2 items-center' }, [
Rating({
value: rating,
count: 5,
readonly: true
}),
Div({ class: 'text-sm opacity-70' }, 'Average rating: 4.5/5 (read-only)')
]);
};
$mount(ReadonlyDemo, readonlyTarget);
}
// 5. Custom Count
const customTarget = document.querySelector('#demo-custom');
if (customTarget && !customTarget.hasChildNodes()) {
const CustomDemo = () => {
const rating = $(3);
const count = $(10);
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Div({ class: 'flex items-center gap-4' }, [
Span({ class: 'text-sm' }, 'Number of stars:'),
Input({
type: 'number',
value: count,
class: 'input input-sm w-24',
oninput: (e) => count(parseInt(e.target.value) || 1)
})
]),
Rating({
value: rating,
count: count,
onchange: (value) => rating(value)
}),
Div({ class: 'text-sm opacity-70' }, () => `Rating: ${rating()} / ${count()}`)
]);
};
$mount(CustomDemo, customTarget);
}
// 6. Product Review
const reviewTarget = document.querySelector('#demo-review');
if (reviewTarget && !reviewTarget.hasChildNodes()) {
const ReviewDemo = () => {
const quality = $(4);
const price = $(3);
const support = $(5);
const average = () => Math.round(((quality() + price() + support()) / 3) * 10) / 10;
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'text-lg font-bold' }, 'Product Review'),
Div({ class: 'flex flex-col gap-2' }, [
Div({ class: 'flex justify-between items-center' }, [
Span({ class: 'text-sm w-24' }, 'Quality:'),
Rating({
value: quality,
count: 5,
size: 'sm',
onchange: (v) => quality(v)
})
]),
Div({ class: 'flex justify-between items-center' }, [
Span({ class: 'text-sm w-24' }, 'Price:'),
Rating({
value: price,
count: 5,
size: 'sm',
onchange: (v) => price(v)
})
]),
Div({ class: 'flex justify-between items-center' }, [
Span({ class: 'text-sm w-24' }, 'Support:'),
Rating({
value: support,
count: 5,
size: 'sm',
onchange: (v) => support(v)
})
])
]),
Div({ class: 'divider my-1' }),
Div({ class: 'flex justify-between items-center' }, [
Span({ class: 'font-bold' }, 'Overall:'),
Div({ class: 'text-2xl font-bold text-primary' }, () => average())
])
]);
};
$mount(ReviewDemo, reviewTarget);
}
// 7. All Variants
const variantsTarget = document.querySelector('#demo-variants');
if (variantsTarget && !variantsTarget.hasChildNodes()) {
const VariantsDemo = () => {
return Div({ class: 'flex flex-col gap-6' }, [
Div({ class: 'text-center' }, [
Div({ class: 'text-sm mb-2' }, 'Mask Star'),
Rating({ value: $(3), count: 5, mask: 'mask-star' })
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-sm mb-2' }, 'Mask Star 2 (yellow)' ),
Rating({ value: $(4), count: 5, mask: 'mask-star-2', class: 'rating-warning' })
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-sm mb-2' }, 'Mask Heart'),
Rating({ value: $(5), count: 5, mask: 'mask-heart', class: 'rating-error' })
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-sm mb-2' }, 'Half Stars (read-only)'),
Rating({ value: $(3.5), count: 5, readonly: true })
])
]);
};
$mount(VariantsDemo, variantsTarget);
}
// 8. Interactive Feedback
const feedbackTarget = document.querySelector('#demo-feedback');
if (feedbackTarget && !feedbackTarget.hasChildNodes()) {
const FeedbackDemo = () => {
const rating = $(0);
const feedback = $(false);
const messages = {
1: 'Very disappointed 😞',
2: 'Could be better 😕',
3: 'Good 👍',
4: 'Very good 😊',
5: 'Excellent! 🎉'
};
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Div({ class: 'text-center' }, [
Div({ class: 'text-sm mb-2' }, 'How was your experience?'),
Rating({
value: rating,
count: 5,
onchange: (value) => {
rating(value);
feedback(true);
if (value >= 4) {
Toast('Thank you for your positive feedback!', 'alert-success', 2000);
} else if (value <= 2) {
Toast('We appreciate your feedback and will improve!', 'alert-warning', 2000);
} else {
Toast('Thanks for your rating!', 'alert-info', 2000);
}
}
})
]),
() => rating() > 0
? Div({ class: 'alert alert-soft text-center' }, [
messages[rating()] || `Rating: ${rating()} stars`
])
: null
]);
};
$mount(FeedbackDemo, feedbackTarget);
}
};
initRatingExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initRatingExamples);
});
}
})();
</script>

View File

@@ -0,0 +1,366 @@
# Select
Dropdown select component with full DaisyUI styling, reactive options, and form integration.
## Tag
`Select`
## Props
| Prop | Type | Default | Description |
| :----------- | :-------------------------------------- | :------------------ | :----------------------------------------------- |
| `label` | `string` | `-` | Label text above select |
| `options` | `Array<{value: string, label: string}>` | `[]` | Array of options with value and label |
| `value` | `string \| Signal<string>` | `''` | Selected value |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `disabled` | `boolean \| Signal<boolean>` | `false` | Disabled state |
| `onchange` | `function` | `-` | Change event handler |
## Live Examples
### Basic Select
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const selected = $('apple');
return Select({
label: 'Choose a fruit',
options: [
{ value: 'apple', label: '🍎 Apple' },
{ value: 'banana', label: '🍌 Banana' },
{ value: 'orange', label: '🍊 Orange' },
{ value: 'grape', label: '🍇 Grape' }
],
value: selected,
onchange: (e) => selected(e.target.value)
});
};
$mount(BasicDemo, '#demo-basic');
```
### With Reactive Display
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const ReactiveDemo = () => {
const selected = $('small');
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Select({
label: 'Select size',
options: [
{ value: 'small', label: 'Small' },
{ value: 'medium', label: 'Medium' },
{ value: 'large', label: 'Large' }
],
value: selected,
onchange: (e) => selected(e.target.value)
}),
Div({ class: 'alert alert-info' }, [
`You selected: ${selected()}`
])
]);
};
$mount(ReactiveDemo, '#demo-reactive');
```
### Disabled State
<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-disabled" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const DisabledDemo = () => {
return Select({
label: 'Country (disabled)',
options: [
{ value: 'mx', label: 'Mexico' },
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' }
],
value: 'mx',
disabled: true
});
};
$mount(DisabledDemo, '#demo-disabled');
```
### Dynamic Options
<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-dynamic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const DynamicDemo = () => {
const category = $('fruits');
const options = {
fruits: [
{ value: 'apple', label: '🍎 Apple' },
{ value: 'banana', label: '🍌 Banana' }
],
vegetables: [
{ value: 'carrot', label: '🥕 Carrot' },
{ value: 'broccoli', label: '🥦 Broccoli' }
]
};
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Select({
label: 'Category',
options: [
{ value: 'fruits', label: 'Fruits' },
{ value: 'vegetables', label: 'Vegetables' }
],
value: category,
onchange: (e) => category(e.target.value)
}),
Select({
label: 'Item',
options: () => options[category()] || [],
value: $(''),
onchange: (e) => console.log('Selected:', e.target.value)
})
]);
};
$mount(DynamicDemo, '#demo-dynamic');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
const primary = $('option1');
const secondary = $('option2');
const accent = $('');
return Div({ class: 'flex flex-col gap-4' }, [
Select({
label: 'Primary Select',
class: 'select-primary',
options: [
{ value: 'option1', label: 'Option 1' },
{ value: 'option2', label: 'Option 2' },
{ value: 'option3', label: 'Option 3' }
],
value: primary,
onchange: (e) => primary(e.target.value)
}),
Select({
label: 'Secondary Select',
class: 'select-secondary',
options: [
{ value: 'option1', label: 'Option 1' },
{ value: 'option2', label: 'Option 2' }
],
value: secondary,
onchange: (e) => secondary(e.target.value)
}),
Select({
label: 'Ghost Select',
class: 'select-ghost',
options: [
{ value: '', label: 'Select an option' },
{ value: 'opt1', label: 'Option 1' },
{ value: 'opt2', label: 'Option 2' }
],
value: accent,
onchange: (e) => accent(e.target.value)
})
]);
};
$mount(VariantsDemo, '#demo-variants');
```
<script>
(function() {
const initSelectExamples = () => {
// 1. Basic Select
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
const selected = $('apple');
return Select({
label: 'Choose a fruit',
options: [
{ value: 'apple', label: '🍎 Apple' },
{ value: 'banana', label: '🍌 Banana' },
{ value: 'orange', label: '🍊 Orange' },
{ value: 'grape', label: '🍇 Grape' }
],
value: selected,
onchange: (e) => selected(e.target.value)
});
};
$mount(BasicDemo, basicTarget);
}
// 2. Reactive Display
const reactiveTarget = document.querySelector('#demo-reactive');
if (reactiveTarget && !reactiveTarget.hasChildNodes()) {
const ReactiveDemo = () => {
const selected = $('small');
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Select({
label: 'Select size',
options: [
{ value: 'small', label: 'Small' },
{ value: 'medium', label: 'Medium' },
{ value: 'large', label: 'Large' }
],
value: selected,
onchange: (e) => selected(e.target.value)
}),
Div({ class: 'alert alert-info' }, [
`You selected: ${selected()}`
])
]);
};
$mount(ReactiveDemo, reactiveTarget);
}
// 3. Disabled State
const disabledTarget = document.querySelector('#demo-disabled');
if (disabledTarget && !disabledTarget.hasChildNodes()) {
const DisabledDemo = () => {
return Select({
label: 'Country (disabled)',
options: [
{ value: 'mx', label: 'Mexico' },
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' }
],
value: 'mx',
disabled: true
});
};
$mount(DisabledDemo, disabledTarget);
}
// 4. Dynamic Options
const dynamicTarget = document.querySelector('#demo-dynamic');
if (dynamicTarget && !dynamicTarget.hasChildNodes()) {
const DynamicDemo = () => {
const category = $('fruits');
const options = {
fruits: [
{ value: 'apple', label: '🍎 Apple' },
{ value: 'banana', label: '🍌 Banana' }
],
vegetables: [
{ value: 'carrot', label: '🥕 Carrot' },
{ value: 'broccoli', label: '🥦 Broccoli' }
]
};
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Select({
label: 'Category',
options: [
{ value: 'fruits', label: 'Fruits' },
{ value: 'vegetables', label: 'Vegetables' }
],
value: category,
onchange: (e) => category(e.target.value)
}),
Select({
label: 'Item',
options: () => options[category()] || [],
value: $(''),
onchange: (e) => console.log('Selected:', e.target.value)
})
]);
};
$mount(DynamicDemo, dynamicTarget);
}
// 5. All Variants
const variantsTarget = document.querySelector('#demo-variants');
if (variantsTarget && !variantsTarget.hasChildNodes()) {
const VariantsDemo = () => {
const primary = $('option1');
const secondary = $('option2');
const accent = $('');
return Div({ class: 'flex flex-col gap-4' }, [
Select({
label: 'Primary Select',
class: 'select-primary',
options: [
{ value: 'option1', label: 'Option 1' },
{ value: 'option2', label: 'Option 2' },
{ value: 'option3', label: 'Option 3' }
],
value: primary,
onchange: (e) => primary(e.target.value)
}),
Select({
label: 'Secondary Select',
class: 'select-secondary',
options: [
{ value: 'option1', label: 'Option 1' },
{ value: 'option2', label: 'Option 2' }
],
value: secondary,
onchange: (e) => secondary(e.target.value)
}),
Select({
label: 'Ghost Select',
class: 'select-ghost',
options: [
{ value: '', label: 'Select an option' },
{ value: 'opt1', label: 'Option 1' },
{ value: 'opt2', label: 'Option 2' }
],
value: accent,
onchange: (e) => accent(e.target.value)
})
]);
};
$mount(VariantsDemo, variantsTarget);
}
};
initSelectExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initSelectExamples);
});
}
})();
</script>

View File

@@ -0,0 +1,519 @@
# Stack
Stack component for layering multiple elements on top of each other, creating depth and visual hierarchy.
## Tag
`Stack`
## Props
| Prop | Type | Default | Description |
| :----------- | :--------------------------- | :---------- | :----------------------------------------------- |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `children` | `Array<VNode> \| VNode` | `-` | Elements to stack (first is bottom, last is top) |
## Live Examples
### Basic Stack
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
return Stack({ class: 'w-40' }, [
Div({ class: 'bg-primary text-primary-content rounded-lg p-4 shadow-lg' }, 'Layer 1'),
Div({ class: 'bg-secondary text-secondary-content rounded-lg p-4 shadow-lg' }, 'Layer 2'),
Div({ class: 'bg-accent text-accent-content rounded-lg p-4 shadow-lg' }, 'Layer 3')
]);
};
$mount(BasicDemo, '#demo-basic');
```
### Card Stack
<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-cards" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const CardsDemo = () => {
return Stack({ class: 'w-64' }, [
Div({ class: 'card bg-base-100 shadow-xl border border-base-300' }, [
Div({ class: 'card-body p-4' }, [
Span({ class: 'text-sm opacity-70' }, 'Back Card'),
Span({ class: 'font-bold' }, 'Additional info')
])
]),
Div({ class: 'card bg-primary text-primary-content shadow-xl' }, [
Div({ class: 'card-body p-4' }, [
Span({ class: 'text-sm' }, 'Front Card'),
Span({ class: 'font-bold text-lg' }, 'Main Content')
])
])
]);
};
$mount(CardsDemo, '#demo-cards');
```
### Avatar Stack
<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-avatars" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const AvatarsDemo = () => {
return Stack({ class: 'w-32' }, [
Div({ class: 'avatar placeholder' }, [
Div({ class: 'bg-neutral text-neutral-content rounded-full w-16' }, [
Span({}, 'JD')
])
]),
Div({ class: 'avatar placeholder' }, [
Div({ class: 'bg-primary text-primary-content rounded-full w-16' }, [
Span({}, 'JS')
])
]),
Div({ class: 'avatar placeholder' }, [
Div({ class: 'bg-secondary text-secondary-content rounded-full w-16' }, [
Span({}, 'BC')
])
])
]);
};
$mount(AvatarsDemo, '#demo-avatars');
```
### Image Stack
<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-images" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const ImagesDemo = () => {
return Stack({ class: 'w-48' }, [
Div({ class: 'w-full h-32 bg-gradient-to-r from-primary to-secondary rounded-lg shadow-lg' }, [
Div({ class: 'p-2 text-white text-sm' }, 'Background Image')
]),
Div({ class: 'w-full h-32 bg-gradient-to-r from-secondary to-accent rounded-lg shadow-lg translate-x-2 translate-y-2' }, [
Div({ class: 'p-2 text-white text-sm' }, 'Middle Layer')
]),
Div({ class: 'w-full h-32 bg-gradient-to-r from-accent to-primary rounded-lg shadow-lg translate-x-4 translate-y-4 flex items-center justify-center' }, [
Span({ class: 'text-white font-bold' }, 'Top Layer')
])
]);
};
$mount(ImagesDemo, '#demo-images');
```
### Photo Gallery Stack
<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-gallery" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const GalleryDemo = () => {
const photos = [
{ color: 'bg-primary', label: 'Photo 1' },
{ color: 'bg-secondary', label: 'Photo 2' },
{ color: 'bg-accent', label: 'Photo 3' },
{ color: 'bg-info', label: 'Photo 4' }
];
return Stack({ class: 'w-48 cursor-pointer hover:scale-105 transition-transform' }, [
...photos.map((photo, idx) =>
Div({
class: `${photo.color} rounded-lg shadow-lg transition-all`,
style: `transform: translate(${idx * 4}px, ${idx * 4}px); width: 100%; height: 100%;`
}, [
Div({ class: 'p-4 text-white font-bold' }, photo.label)
])
)
]);
};
$mount(GalleryDemo, '#demo-gallery');
```
### Interactive Stack
<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-interactive" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const InteractiveDemo = () => {
const active = $(0);
const colors = ['primary', 'secondary', 'accent', 'info', 'success'];
const labels = ['Home', 'Profile', 'Settings', 'Messages', 'Notifications'];
return Div({ class: 'flex flex-col gap-6 items-center' }, [
Stack({ class: 'w-56' }, colors.map((color, idx) =>
Div({
class: `bg-${color} text-${color}-content rounded-lg p-4 shadow-lg transition-all cursor-pointer ${idx === active() ? 'scale-105 z-10' : ''}`,
style: `transform: translate(${idx * 8}px, ${idx * 8}px);`,
onclick: () => active(idx)
}, [
Div({ class: 'font-bold' }, labels[idx]),
Div({ class: 'text-sm opacity-80' }, `Layer ${idx + 1}`)
])
)),
Div({ class: 'mt-4 text-center' }, [
Span({ class: 'font-bold' }, () => `Active: ${labels[active()]}`),
Div({ class: 'flex gap-2 mt-2' }, colors.map((_, idx) =>
Button({
class: `btn btn-xs ${idx === active() ? 'btn-primary' : 'btn-ghost'}`,
onclick: () => active(idx)
}, `${idx + 1}`)
))
])
]);
};
$mount(InteractiveDemo, '#demo-interactive');
```
### Notification Stack
<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-notifications" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const NotificationsDemo = () => {
const notifications = $([
{ id: 1, message: 'New message from John', type: 'info' },
{ id: 2, message: 'Your order has shipped', type: 'success' },
{ id: 3, message: 'Meeting in 10 minutes', type: 'warning' }
]);
const removeNotification = (id) => {
notifications(notifications().filter(n => n.id !== id));
};
const typeClasses = {
info: 'bg-info text-info-content',
success: 'bg-success text-success-content',
warning: 'bg-warning text-warning-content',
error: 'bg-error text-error-content'
};
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Stack({ class: 'w-80' }, notifications().map((notif, idx) =>
Div({
class: `${typeClasses[notif.type]} rounded-lg p-3 shadow-lg transition-all cursor-pointer`,
style: `transform: translate(${idx * 4}px, ${idx * 4}px);`,
onclick: () => removeNotification(notif.id)
}, [
Div({ class: 'flex justify-between items-center' }, [
Span({ class: 'text-sm' }, notif.message),
Span({ class: 'text-xs opacity-70 cursor-pointer hover:opacity-100' }, '✕')
])
])
)),
notifications().length === 0
? Div({ class: 'alert alert-soft' }, 'No notifications')
: Button({
class: 'btn btn-sm btn-ghost mt-2',
onclick: () => notifications([])
}, 'Clear All')
]);
};
$mount(NotificationsDemo, '#demo-notifications');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-8 justify-center"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
Div({ class: 'text-center' }, [
Div({ class: 'text-sm mb-2' }, 'Small Stack'),
Stack({ class: 'w-24' }, [
Div({ class: 'bg-primary rounded p-2 text-xs' }, '1'),
Div({ class: 'bg-secondary rounded p-2 text-xs' }, '2'),
Div({ class: 'bg-accent rounded p-2 text-xs' }, '3')
])
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-sm mb-2' }, 'Medium Stack'),
Stack({ class: 'w-32' }, [
Div({ class: 'bg-primary rounded p-3' }, 'A'),
Div({ class: 'bg-secondary rounded p-3' }, 'B'),
Div({ class: 'bg-accent rounded p-3' }, 'C')
])
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-sm mb-2' }, 'Large Stack'),
Stack({ class: 'w-40' }, [
Div({ class: 'bg-primary rounded p-4' }, 'X'),
Div({ class: 'bg-secondary rounded p-4' }, 'Y'),
Div({ class: 'bg-accent rounded p-4' }, 'Z')
])
])
]);
};
$mount(VariantsDemo, '#demo-variants');
```
<script>
(function() {
const initStackExamples = () => {
// 1. Basic Stack
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
return Stack({ class: 'w-40' }, [
Div({ class: 'bg-primary text-primary-content rounded-lg p-4 shadow-lg' }, 'Layer 1'),
Div({ class: 'bg-secondary text-secondary-content rounded-lg p-4 shadow-lg' }, 'Layer 2'),
Div({ class: 'bg-accent text-accent-content rounded-lg p-4 shadow-lg' }, 'Layer 3')
]);
};
$mount(BasicDemo, basicTarget);
}
// 2. Card Stack
const cardsTarget = document.querySelector('#demo-cards');
if (cardsTarget && !cardsTarget.hasChildNodes()) {
const CardsDemo = () => {
return Stack({ class: 'w-64' }, [
Div({ class: 'card bg-base-100 shadow-xl border border-base-300' }, [
Div({ class: 'card-body p-4' }, [
Span({ class: 'text-sm opacity-70' }, 'Back Card'),
Span({ class: 'font-bold' }, 'Additional info')
])
]),
Div({ class: 'card bg-primary text-primary-content shadow-xl' }, [
Div({ class: 'card-body p-4' }, [
Span({ class: 'text-sm' }, 'Front Card'),
Span({ class: 'font-bold text-lg' }, 'Main Content')
])
])
]);
};
$mount(CardsDemo, cardsTarget);
}
// 3. Avatar Stack
const avatarsTarget = document.querySelector('#demo-avatars');
if (avatarsTarget && !avatarsTarget.hasChildNodes()) {
const AvatarsDemo = () => {
return Stack({ class: 'w-32' }, [
Div({ class: 'avatar placeholder' }, [
Div({ class: 'bg-neutral text-neutral-content rounded-full w-16' }, [
Span({}, 'JD')
])
]),
Div({ class: 'avatar placeholder' }, [
Div({ class: 'bg-primary text-primary-content rounded-full w-16' }, [
Span({}, 'JS')
])
]),
Div({ class: 'avatar placeholder' }, [
Div({ class: 'bg-secondary text-secondary-content rounded-full w-16' }, [
Span({}, 'BC')
])
])
]);
};
$mount(AvatarsDemo, avatarsTarget);
}
// 4. Image Stack
const imagesTarget = document.querySelector('#demo-images');
if (imagesTarget && !imagesTarget.hasChildNodes()) {
const ImagesDemo = () => {
return Stack({ class: 'w-48' }, [
Div({ class: 'w-full h-32 bg-gradient-to-r from-primary to-secondary rounded-lg shadow-lg' }, [
Div({ class: 'p-2 text-white text-sm' }, 'Background Image')
]),
Div({ class: 'w-full h-32 bg-gradient-to-r from-secondary to-accent rounded-lg shadow-lg translate-x-2 translate-y-2' }, [
Div({ class: 'p-2 text-white text-sm' }, 'Middle Layer')
]),
Div({ class: 'w-full h-32 bg-gradient-to-r from-accent to-primary rounded-lg shadow-lg translate-x-4 translate-y-4 flex items-center justify-center' }, [
Span({ class: 'text-white font-bold' }, 'Top Layer')
])
]);
};
$mount(ImagesDemo, imagesTarget);
}
// 5. Photo Gallery Stack
const galleryTarget = document.querySelector('#demo-gallery');
if (galleryTarget && !galleryTarget.hasChildNodes()) {
const GalleryDemo = () => {
const photos = [
{ color: 'bg-primary', label: 'Photo 1' },
{ color: 'bg-secondary', label: 'Photo 2' },
{ color: 'bg-accent', label: 'Photo 3' },
{ color: 'bg-info', label: 'Photo 4' }
];
return Stack({ class: 'w-48 cursor-pointer hover:scale-105 transition-transform' }, [
...photos.map((photo, idx) =>
Div({
class: `${photo.color} rounded-lg shadow-lg transition-all`,
style: `transform: translate(${idx * 4}px, ${idx * 4}px); width: 100%; height: 100%;`
}, [
Div({ class: 'p-4 text-white font-bold' }, photo.label)
])
)
]);
};
$mount(GalleryDemo, galleryTarget);
}
// 6. Interactive Stack
const interactiveTarget = document.querySelector('#demo-interactive');
if (interactiveTarget && !interactiveTarget.hasChildNodes()) {
const InteractiveDemo = () => {
const active = $(0);
const colors = ['primary', 'secondary', 'accent', 'info', 'success'];
const labels = ['Home', 'Profile', 'Settings', 'Messages', 'Notifications'];
return Div({ class: 'flex flex-col gap-6 items-center' }, [
Stack({ class: 'w-56' }, colors.map((color, idx) =>
Div({
class: `bg-${color} text-${color}-content rounded-lg p-4 shadow-lg transition-all cursor-pointer ${idx === active() ? 'scale-105 z-10' : ''}`,
style: `transform: translate(${idx * 8}px, ${idx * 8}px);`,
onclick: () => active(idx)
}, [
Div({ class: 'font-bold' }, labels[idx]),
Div({ class: 'text-sm opacity-80' }, `Layer ${idx + 1}`)
])
)),
Div({ class: 'mt-4 text-center' }, [
Span({ class: 'font-bold' }, () => `Active: ${labels[active()]}`),
Div({ class: 'flex gap-2 mt-2' }, colors.map((_, idx) =>
Button({
class: `btn btn-xs ${idx === active() ? 'btn-primary' : 'btn-ghost'}`,
onclick: () => active(idx)
}, `${idx + 1}`)
))
])
]);
};
$mount(InteractiveDemo, interactiveTarget);
}
// 7. Notification Stack
const notificationsTarget = document.querySelector('#demo-notifications');
if (notificationsTarget && !notificationsTarget.hasChildNodes()) {
const NotificationsDemo = () => {
const notifications = $([
{ id: 1, message: 'New message from John', type: 'info' },
{ id: 2, message: 'Your order has shipped', type: 'success' },
{ id: 3, message: 'Meeting in 10 minutes', type: 'warning' }
]);
const removeNotification = (id) => {
notifications(notifications().filter(n => n.id !== id));
};
const typeClasses = {
info: 'bg-info text-info-content',
success: 'bg-success text-success-content',
warning: 'bg-warning text-warning-content',
error: 'bg-error text-error-content'
};
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Stack({ class: 'w-80' }, notifications().map((notif, idx) =>
Div({
class: `${typeClasses[notif.type]} rounded-lg p-3 shadow-lg transition-all cursor-pointer`,
style: `transform: translate(${idx * 4}px, ${idx * 4}px);`,
onclick: () => removeNotification(notif.id)
}, [
Div({ class: 'flex justify-between items-center' }, [
Span({ class: 'text-sm' }, notif.message),
Span({ class: 'text-xs opacity-70 cursor-pointer hover:opacity-100' }, '✕')
])
])
)),
notifications().length === 0
? Div({ class: 'alert alert-soft' }, 'No notifications')
: Button({
class: 'btn btn-sm btn-ghost mt-2',
onclick: () => notifications([])
}, 'Clear All')
]);
};
$mount(NotificationsDemo, notificationsTarget);
}
// 8. All Variants
const variantsTarget = document.querySelector('#demo-variants');
if (variantsTarget && !variantsTarget.hasChildNodes()) {
const VariantsDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
Div({ class: 'text-center' }, [
Div({ class: 'text-sm mb-2' }, 'Small Stack'),
Stack({ class: 'w-24' }, [
Div({ class: 'bg-primary rounded p-2 text-xs' }, '1'),
Div({ class: 'bg-secondary rounded p-2 text-xs' }, '2'),
Div({ class: 'bg-accent rounded p-2 text-xs' }, '3')
])
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-sm mb-2' }, 'Medium Stack'),
Stack({ class: 'w-32' }, [
Div({ class: 'bg-primary rounded p-3' }, 'A'),
Div({ class: 'bg-secondary rounded p-3' }, 'B'),
Div({ class: 'bg-accent rounded p-3' }, 'C')
])
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-sm mb-2' }, 'Large Stack'),
Stack({ class: 'w-40' }, [
Div({ class: 'bg-primary rounded p-4' }, 'X'),
Div({ class: 'bg-secondary rounded p-4' }, 'Y'),
Div({ class: 'bg-accent rounded p-4' }, 'Z')
])
])
]);
};
$mount(VariantsDemo, variantsTarget);
}
};
initStackExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initStackExamples);
});
}
})();
</script>

644
docs/components_old/stat.md Normal file
View File

@@ -0,0 +1,644 @@
# Stat
Statistic card component for displaying metrics, counts, and key performance indicators with optional icons and descriptions.
## Tag
`Stat`
## Props
| Prop | Type | Default | Description |
| :----------- | :--------------------------- | :---------- | :----------------------------------------------- |
| `label` | `string \| VNode \| Signal` | `-` | Statistic label/title |
| `value` | `string \| number \| Signal` | `-` | Main statistic value |
| `desc` | `string \| VNode \| Signal` | `-` | Description or trend text |
| `icon` | `string \| VNode \| Signal` | `-` | Icon displayed in the figure area |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
## Live Examples
### Basic Stat
<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-basic" 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 BasicDemo = () => {
return Div({ class: 'grid grid-cols-1 md:grid-cols-3 gap-4' }, [
Stat({
label: 'Total Users',
value: '2,345',
desc: '↗︎ 120 new users this month'
}),
Stat({
label: 'Revenue',
value: '$45,678',
desc: '↘︎ 5% decrease from last month'
}),
Stat({
label: 'Conversion Rate',
value: '3.45%',
desc: '↗︎ 0.5% increase'
})
]);
};
$mount(BasicDemo, '#demo-basic');
```
### With Icons
<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-icons" 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 IconsDemo = () => {
return Div({ class: 'grid grid-cols-1 md:grid-cols-3 gap-4' }, [
Stat({
label: 'Active Users',
value: '1,234',
desc: 'Currently online',
icon: Icons.iconShow
}),
Stat({
label: 'New Orders',
value: '89',
desc: 'Today',
icon: Icons.iconSuccess
}),
Stat({
label: 'Pending Tasks',
value: '23',
desc: 'Need attention',
icon: Icons.iconWarning
})
]);
};
$mount(IconsDemo, '#demo-icons');
```
### Reactive Values
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ReactiveDemo = () => {
const count = $(0);
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'grid grid-cols-1 md:grid-cols-2 gap-4' }, [
Stat({
label: 'Counter',
value: () => count(),
desc: 'Click the button to increase',
icon: Icons.iconInfo
}),
Stat({
label: 'Squared',
value: () => Math.pow(count(), 2),
desc: 'Square of counter',
icon: Icons.iconSuccess
})
]),
Div({ class: 'flex gap-2 justify-center' }, [
Button({
class: 'btn btn-primary',
onclick: () => count(count() + 1)
}, 'Increment'),
Button({
class: 'btn btn-ghost',
onclick: () => count(0)
}, 'Reset')
])
]);
};
$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: Icons.iconSuccess
}),
Stat({
label: 'Bounce Rate',
value: '42%',
desc: Div({ class: 'text-error' }, '↘︎ 3% from last week'),
icon: Icons.iconError
}),
Stat({
label: 'Avg. Session',
value: '4m 32s',
desc: Div({ class: 'text-warning' }, '↗︎ 12 seconds'),
icon: Icons.iconWarning
})
]);
};
$mount(TrendsDemo, '#demo-trends');
```
### Multiple Stats in Row
<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-multiple" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const MultipleDemo = () => {
return Div({ class: 'grid grid-cols-1 md:grid-cols-4 gap-4' }, [
Stat({
label: 'Posts',
value: '1,234',
desc: 'Total content',
icon: Span({ class: 'text-2xl' }, '📝')
}),
Stat({
label: 'Comments',
value: '8,901',
desc: 'Engagement',
icon: Span({ class: 'text-2xl' }, '💬')
}),
Stat({
label: 'Likes',
value: '12,345',
desc: 'Reactions',
icon: Span({ class: 'text-2xl' }, '❤️')
}),
Stat({
label: 'Shares',
value: '456',
desc: 'Viral reach',
icon: Span({ class: 'text-2xl' }, '🔄')
})
]);
};
$mount(MultipleDemo, '#demo-multiple');
```
### Dashboard Example
<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-dashboard" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const DashboardDemo = () => {
const stats = $({
users: 1245,
revenue: 89342,
orders: 342,
satisfaction: 94
});
const updateStats = () => {
stats({
users: stats().users + Math.floor(Math.random() * 50),
revenue: stats().revenue + Math.floor(Math.random() * 1000),
orders: stats().orders + Math.floor(Math.random() * 20),
satisfaction: Math.min(100, stats().satisfaction + Math.floor(Math.random() * 5) - 2)
});
};
return Div({ class: 'flex flex-col gap-6' }, [
Div({ class: 'grid grid-cols-1 md:grid-cols-4 gap-4' }, [
Stat({
label: 'Total Users',
value: () => stats().users.toLocaleString(),
desc: 'Registered users',
icon: Icons.iconShow
}),
Stat({
label: 'Revenue',
value: () => `$${stats().revenue.toLocaleString()}`,
desc: 'This month',
icon: Icons.iconSuccess
}),
Stat({
label: 'Orders',
value: () => stats().orders.toLocaleString(),
desc: 'Completed',
icon: Icons.iconInfo
}),
Stat({
label: 'Satisfaction',
value: () => `${stats().satisfaction}%`,
desc: stats().satisfaction > 90 ? 'Excellent!' : 'Good',
icon: Icons.iconWarning
})
]),
Div({ class: 'flex justify-center' }, [
Button({
class: 'btn btn-primary',
onclick: updateStats
}, 'Refresh Data')
])
]);
};
$mount(DashboardDemo, '#demo-dashboard');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 grid grid-cols-1 md:grid-cols-2 gap-4"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
return Div({ class: 'grid grid-cols-1 md:grid-cols-2 gap-4' }, [
Stat({
label: 'Primary Stat',
value: '1,234',
desc: 'With description',
icon: Icons.iconInfo,
class: 'bg-primary/10 text-primary'
}),
Stat({
label: 'Success Stat',
value: '89%',
desc: 'Success rate',
icon: Icons.iconSuccess,
class: 'bg-success/10 text-success'
}),
Stat({
label: 'Warning Stat',
value: '23',
desc: 'Pending items',
icon: Icons.iconWarning,
class: 'bg-warning/10 text-warning'
}),
Stat({
label: 'Error Stat',
value: '5',
desc: 'Failed attempts',
icon: Icons.iconError,
class: 'bg-error/10 text-error'
})
]);
};
$mount(VariantsDemo, '#demo-variants');
```
### Compact Stats
<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-compact" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-4"></div>
</div>
</div>
```javascript
const CompactDemo = () => {
return Div({ class: 'flex flex-wrap gap-4' }, [
Stat({
label: 'Views',
value: '12.3K',
class: 'stat-compact'
}),
Stat({
label: 'Likes',
value: '2,456',
class: 'stat-compact'
}),
Stat({
label: 'Comments',
value: '345',
class: 'stat-compact'
}),
Stat({
label: 'Shares',
value: '89',
class: 'stat-compact'
})
]);
};
$mount(CompactDemo, '#demo-compact');
```
<script>
(function() {
const initStatExamples = () => {
// 1. Basic Stat
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
return Div({ class: 'grid grid-cols-1 md:grid-cols-3 gap-4' }, [
Stat({
label: 'Total Users',
value: '2,345',
desc: '↗︎ 120 new users this month'
}),
Stat({
label: 'Revenue',
value: '$45,678',
desc: '↘︎ 5% decrease from last month'
}),
Stat({
label: 'Conversion Rate',
value: '3.45%',
desc: '↗︎ 0.5% increase'
})
]);
};
$mount(BasicDemo, basicTarget);
}
// 2. With Icons
const iconsTarget = document.querySelector('#demo-icons');
if (iconsTarget && !iconsTarget.hasChildNodes()) {
const IconsDemo = () => {
return Div({ class: 'grid grid-cols-1 md:grid-cols-3 gap-4' }, [
Stat({
label: 'Active Users',
value: '1,234',
desc: 'Currently online',
icon: Icons.iconShow
}),
Stat({
label: 'New Orders',
value: '89',
desc: 'Today',
icon: Icons.iconSuccess
}),
Stat({
label: 'Pending Tasks',
value: '23',
desc: 'Need attention',
icon: Icons.iconWarning
})
]);
};
$mount(IconsDemo, iconsTarget);
}
// 3. Reactive Values
const reactiveTarget = document.querySelector('#demo-reactive');
if (reactiveTarget && !reactiveTarget.hasChildNodes()) {
const ReactiveDemo = () => {
const count = $(0);
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'grid grid-cols-1 md:grid-cols-2 gap-4' }, [
Stat({
label: 'Counter',
value: () => count(),
desc: 'Click the button to increase',
icon: Icons.iconInfo
}),
Stat({
label: 'Squared',
value: () => Math.pow(count(), 2),
desc: 'Square of counter',
icon: Icons.iconSuccess
})
]),
Div({ class: 'flex gap-2 justify-center' }, [
Button({
class: 'btn btn-primary',
onclick: () => count(count() + 1)
}, 'Increment'),
Button({
class: 'btn btn-ghost',
onclick: () => count(0)
}, 'Reset')
])
]);
};
$mount(ReactiveDemo, reactiveTarget);
}
// 4. With Trend Indicators
const trendsTarget = document.querySelector('#demo-trends');
if (trendsTarget && !trendsTarget.hasChildNodes()) {
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: Icons.iconSuccess
}),
Stat({
label: 'Bounce Rate',
value: '42%',
desc: Div({ class: 'text-error' }, '↘︎ 3% from last week'),
icon: Icons.iconError
}),
Stat({
label: 'Avg. Session',
value: '4m 32s',
desc: Div({ class: 'text-warning' }, '↗︎ 12 seconds'),
icon: Icons.iconWarning
})
]);
};
$mount(TrendsDemo, trendsTarget);
}
// 5. Multiple Stats in Row
const multipleTarget = document.querySelector('#demo-multiple');
if (multipleTarget && !multipleTarget.hasChildNodes()) {
const MultipleDemo = () => {
return Div({ class: 'grid grid-cols-1 md:grid-cols-4 gap-4' }, [
Stat({
label: 'Posts',
value: '1,234',
desc: 'Total content',
icon: Span({ class: 'text-2xl' }, '📝')
}),
Stat({
label: 'Comments',
value: '8,901',
desc: 'Engagement',
icon: Span({ class: 'text-2xl' }, '💬')
}),
Stat({
label: 'Likes',
value: '12,345',
desc: 'Reactions',
icon: Span({ class: 'text-2xl' }, '❤️')
}),
Stat({
label: 'Shares',
value: '456',
desc: 'Viral reach',
icon: Span({ class: 'text-2xl' }, '🔄')
})
]);
};
$mount(MultipleDemo, multipleTarget);
}
// 6. Dashboard Example
const dashboardTarget = document.querySelector('#demo-dashboard');
if (dashboardTarget && !dashboardTarget.hasChildNodes()) {
const DashboardDemo = () => {
const stats = $({
users: 1245,
revenue: 89342,
orders: 342,
satisfaction: 94
});
const updateStats = () => {
stats({
users: stats().users + Math.floor(Math.random() * 50),
revenue: stats().revenue + Math.floor(Math.random() * 1000),
orders: stats().orders + Math.floor(Math.random() * 20),
satisfaction: Math.min(100, stats().satisfaction + Math.floor(Math.random() * 5) - 2)
});
};
return Div({ class: 'flex flex-col gap-6' }, [
Div({ class: 'grid grid-cols-1 md:grid-cols-4 gap-4' }, [
Stat({
label: 'Total Users',
value: () => stats().users.toLocaleString(),
desc: 'Registered users',
icon: Icons.iconShow
}),
Stat({
label: 'Revenue',
value: () => `$${stats().revenue.toLocaleString()}`,
desc: 'This month',
icon: Icons.iconSuccess
}),
Stat({
label: 'Orders',
value: () => stats().orders.toLocaleString(),
desc: 'Completed',
icon: Icons.iconInfo
}),
Stat({
label: 'Satisfaction',
value: () => `${stats().satisfaction}%`,
desc: stats().satisfaction > 90 ? 'Excellent!' : 'Good',
icon: Icons.iconWarning
})
]),
Div({ class: 'flex justify-center' }, [
Button({
class: 'btn btn-primary',
onclick: updateStats
}, 'Refresh Data')
])
]);
};
$mount(DashboardDemo, dashboardTarget);
}
// 7. All Variants
const variantsTarget = document.querySelector('#demo-variants');
if (variantsTarget && !variantsTarget.hasChildNodes()) {
const VariantsDemo = () => {
return Div({ class: 'grid grid-cols-1 md:grid-cols-2 gap-4' }, [
Stat({
label: 'Primary Stat',
value: '1,234',
desc: 'With description',
icon: Icons.iconInfo,
class: 'bg-primary/10 text-primary'
}),
Stat({
label: 'Success Stat',
value: '89%',
desc: 'Success rate',
icon: Icons.iconSuccess,
class: 'bg-success/10 text-success'
}),
Stat({
label: 'Warning Stat',
value: '23',
desc: 'Pending items',
icon: Icons.iconWarning,
class: 'bg-warning/10 text-warning'
}),
Stat({
label: 'Error Stat',
value: '5',
desc: 'Failed attempts',
icon: Icons.iconError,
class: 'bg-error/10 text-error'
})
]);
};
$mount(VariantsDemo, variantsTarget);
}
// 8. Compact Stats
const compactTarget = document.querySelector('#demo-compact');
if (compactTarget && !compactTarget.hasChildNodes()) {
const CompactDemo = () => {
return Div({ class: 'flex flex-wrap gap-4' }, [
Stat({
label: 'Views',
value: '12.3K',
class: 'stat-compact'
}),
Stat({
label: 'Likes',
value: '2,456',
class: 'stat-compact'
}),
Stat({
label: 'Comments',
value: '345',
class: 'stat-compact'
}),
Stat({
label: 'Shares',
value: '89',
class: 'stat-compact'
})
]);
};
$mount(CompactDemo, compactTarget);
}
};
initStatExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initStatExamples);
});
}
})();
</script>

500
docs/components_old/swap.md Normal file
View File

@@ -0,0 +1,500 @@
# Swap
Toggle component that swaps between two states (on/off) with customizable icons or content.
## Tag
`Swap`
## Props
| Prop | Type | Default | Description |
| :----------- | :--------------------------- | :---------- | :----------------------------------------------- |
| `value` | `boolean \| Signal<boolean>` | `false` | Swap state (true = on, false = off) |
| `on` | `string \| VNode` | `-` | Content to show when state is on |
| `off` | `string \| VNode` | `-` | Content to show when state is off |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `onclick` | `function` | `-` | Click event handler |
## Live Examples
### Basic Swap
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const isOn = $(false);
return Swap({
value: isOn,
on: "🌟 ON",
off: "💫 OFF",
onclick: () => isOn(!isOn())
});
};
$mount(BasicDemo, '#demo-basic');
```
### Icon Swap
<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-icons" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const IconsDemo = () => {
const isOn = $(false);
return Swap({
value: isOn,
on: Icons.iconShow,
off: Icons.iconHide,
onclick: () => isOn(!isOn())
});
};
$mount(IconsDemo, '#demo-icons');
```
### Emoji Swap
<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-emoji" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const EmojiDemo = () => {
const isOn = $(false);
return Swap({
value: isOn,
on: "❤️",
off: "🖤",
onclick: () => isOn(!isOn())
});
};
$mount(EmojiDemo, '#demo-emoji');
```
### Custom Content Swap
<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-custom" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const CustomDemo = () => {
const isOn = $(false);
return Swap({
value: isOn,
on: Div({ class: "badge badge-success gap-1" }, ["✅", " Active"]),
off: Div({ class: "badge badge-ghost gap-1" }, ["⭕", " Inactive"]),
onclick: () => isOn(!isOn())
});
};
$mount(CustomDemo, '#demo-custom');
```
### With Reactive State
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const ReactiveDemo = () => {
const isOn = $(false);
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Swap({
value: isOn,
on: Icons.iconShow,
off: Icons.iconHide,
onclick: () => isOn(!isOn())
}),
Div({ class: 'text-center' }, () =>
isOn()
? Div({ class: 'alert alert-success' }, 'Content is visible')
: Div({ class: 'alert alert-soft' }, 'Content is hidden')
)
]);
};
$mount(ReactiveDemo, '#demo-reactive');
```
### Toggle Mode Swap
<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-mode" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4"></div>
</div>
</div>
```javascript
const ModeDemo = () => {
const darkMode = $(false);
const notifications = $(true);
const sound = $(false);
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Div({ class: 'flex justify-between items-center' }, [
Span({}, 'Dark mode'),
Swap({
value: darkMode,
on: "🌙",
off: "☀️",
onclick: () => darkMode(!darkMode())
})
]),
Div({ class: 'flex justify-between items-center' }, [
Span({}, 'Notifications'),
Swap({
value: notifications,
on: "🔔",
off: "🔕",
onclick: () => notifications(!notifications())
})
]),
Div({ class: 'flex justify-between items-center' }, [
Span({}, 'Sound effects'),
Swap({
value: sound,
on: "🔊",
off: "🔇",
onclick: () => sound(!sound())
})
]),
Div({ class: 'mt-2 p-3 rounded-lg', style: () => darkMode() ? 'background: #1f2937; color: white' : 'background: #f3f4f6' }, [
Div({ class: 'text-sm' }, () => `Mode: ${darkMode() ? 'Dark' : 'Light'} | Notifications: ${notifications() ? 'On' : 'Off'} | Sound: ${sound() ? 'On' : 'Off'}`)
])
]);
};
$mount(ModeDemo, '#demo-mode');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-8 justify-center"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center items-center' }, [
Div({ class: 'text-center' }, [
Div({ class: 'text-xs mb-2' }, 'Volume'),
Swap({
value: $(false),
on: "🔊",
off: "🔇"
})
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-xs mb-2' }, 'Like'),
Swap({
value: $(true),
on: "❤️",
off: "🤍"
})
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-xs mb-2' }, 'Star'),
Swap({
value: $(false),
on: "⭐",
off: "☆"
})
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-xs mb-2' }, 'Check'),
Swap({
value: $(true),
on: Icons.iconSuccess,
off: Icons.iconError
})
])
]);
};
$mount(VariantsDemo, '#demo-variants');
```
### Simple Todo Toggle
<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-todo" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const TodoDemo = () => {
const todos = [
{ id: 1, text: 'Complete documentation', completed: $(true) },
{ id: 2, text: 'Review pull requests', completed: $(false) },
{ id: 3, text: 'Deploy to production', completed: $(false) }
];
return Div({ class: 'flex flex-col gap-3' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Todo list'),
...todos.map(todo =>
Div({ class: 'flex items-center justify-between p-2 bg-base-200 rounded-lg' }, [
Span({ class: todo.completed() ? 'line-through opacity-50' : '' }, todo.text),
Swap({
value: todo.completed,
on: Icons.iconSuccess,
off: Icons.iconClose,
onclick: () => todo.completed(!todo.completed())
})
])
),
Div({ class: 'mt-2 text-sm opacity-70' }, () => {
const completed = todos.filter(t => t.completed()).length;
return `${completed} of ${todos.length} tasks completed`;
})
]);
};
$mount(TodoDemo, '#demo-todo');
```
<script>
(function() {
const initSwapExamples = () => {
// 1. Basic Swap
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
const isOn = $(false);
return Swap({
value: isOn,
on: "🌟 ON",
off: "💫 OFF",
onclick: () => isOn(!isOn())
});
};
$mount(BasicDemo, basicTarget);
}
// 2. Icon Swap
const iconsTarget = document.querySelector('#demo-icons');
if (iconsTarget && !iconsTarget.hasChildNodes()) {
const IconsDemo = () => {
const isOn = $(false);
return Swap({
value: isOn,
on: Icons.iconShow,
off: Icons.iconHide,
onclick: () => isOn(!isOn())
});
};
$mount(IconsDemo, iconsTarget);
}
// 3. Emoji Swap
const emojiTarget = document.querySelector('#demo-emoji');
if (emojiTarget && !emojiTarget.hasChildNodes()) {
const EmojiDemo = () => {
const isOn = $(false);
return Swap({
value: isOn,
on: "❤️",
off: "🖤",
onclick: () => isOn(!isOn())
});
};
$mount(EmojiDemo, emojiTarget);
}
// 4. Custom Content Swap
const customTarget = document.querySelector('#demo-custom');
if (customTarget && !customTarget.hasChildNodes()) {
const CustomDemo = () => {
const isOn = $(false);
return Swap({
value: isOn,
on: Div({ class: "badge badge-success gap-1" }, ["✅", " Active"]),
off: Div({ class: "badge badge-ghost gap-1" }, ["⭕", " Inactive"]),
onclick: () => isOn(!isOn())
});
};
$mount(CustomDemo, customTarget);
}
// 5. Reactive State
const reactiveTarget = document.querySelector('#demo-reactive');
if (reactiveTarget && !reactiveTarget.hasChildNodes()) {
const ReactiveDemo = () => {
const isOn = $(false);
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Swap({
value: isOn,
on: Icons.iconShow,
off: Icons.iconHide,
onclick: () => isOn(!isOn())
}),
Div({ class: 'text-center' }, () =>
isOn()
? Div({ class: 'alert alert-success' }, 'Content is visible')
: Div({ class: 'alert alert-soft' }, 'Content is hidden')
)
]);
};
$mount(ReactiveDemo, reactiveTarget);
}
// 6. Toggle Mode Swap
const modeTarget = document.querySelector('#demo-mode');
if (modeTarget && !modeTarget.hasChildNodes()) {
const ModeDemo = () => {
const darkMode = $(false);
const notifications = $(true);
const sound = $(false);
return Div({ class: 'flex flex-col gap-4 w-full' }, [
Div({ class: 'flex justify-between items-center' }, [
Span({}, 'Dark mode'),
Swap({
value: darkMode,
on: "🌙",
off: "☀️",
onclick: () => darkMode(!darkMode())
})
]),
Div({ class: 'flex justify-between items-center' }, [
Span({}, 'Notifications'),
Swap({
value: notifications,
on: "🔔",
off: "🔕",
onclick: () => notifications(!notifications())
})
]),
Div({ class: 'flex justify-between items-center' }, [
Span({}, 'Sound effects'),
Swap({
value: sound,
on: "🔊",
off: "🔇",
onclick: () => sound(!sound())
})
]),
Div({ class: 'mt-2 p-3 rounded-lg', style: () => darkMode() ? 'background: #1f2937; color: white' : 'background: #f3f4f6' }, [
Div({ class: 'text-sm' }, () => `Mode: ${darkMode() ? 'Dark' : 'Light'} | Notifications: ${notifications() ? 'On' : 'Off'} | Sound: ${sound() ? 'On' : 'Off'}`)
])
]);
};
$mount(ModeDemo, modeTarget);
}
// 7. All Variants
const variantsTarget = document.querySelector('#demo-variants');
if (variantsTarget && !variantsTarget.hasChildNodes()) {
const VariantsDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center items-center' }, [
Div({ class: 'text-center' }, [
Div({ class: 'text-xs mb-2' }, 'Volume'),
Swap({
value: $(false),
on: "🔊",
off: "🔇"
})
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-xs mb-2' }, 'Like'),
Swap({
value: $(true),
on: "❤️",
off: "🤍"
})
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-xs mb-2' }, 'Star'),
Swap({
value: $(false),
on: "⭐",
off: "☆"
})
]),
Div({ class: 'text-center' }, [
Div({ class: 'text-xs mb-2' }, 'Check'),
Swap({
value: $(true),
on: Icons.iconSuccess,
off: Icons.iconError
})
])
]);
};
$mount(VariantsDemo, variantsTarget);
}
// 8. Simple Todo Toggle
const todoTarget = document.querySelector('#demo-todo');
if (todoTarget && !todoTarget.hasChildNodes()) {
const TodoDemo = () => {
const todos = [
{ id: 1, text: 'Complete documentation', completed: $(true) },
{ id: 2, text: 'Review pull requests', completed: $(false) },
{ id: 3, text: 'Deploy to production', completed: $(false) }
];
return Div({ class: 'flex flex-col gap-3' }, [
Div({ class: 'text-sm font-bold mb-2' }, 'Todo list'),
...todos.map(todo =>
Div({ class: 'flex items-center justify-between p-2 bg-base-200 rounded-lg' }, [
Span({ class: todo.completed() ? 'line-through opacity-50' : '' }, todo.text),
Swap({
value: todo.completed,
on: Icons.iconSuccess,
off: Icons.iconClose,
onclick: () => todo.completed(!todo.completed())
})
])
),
Div({ class: 'mt-2 text-sm opacity-70' }, () => {
const completed = todos.filter(t => t.completed()).length;
return `${completed} of ${todos.length} tasks completed`;
})
]);
};
$mount(TodoDemo, todoTarget);
}
};
initSwapExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initSwapExamples);
});
}
})();
</script>

View File

@@ -0,0 +1,716 @@
# Table
Data table component with sorting, pagination, zebra stripes, pin rows, and custom cell rendering.
## Tag
`Table`
## Props
| Prop | Type | Default | Description |
| :----------- | :-------------------------------------- | :--------------- | :----------------------------------------------- |
| `items` | `Array \| Signal<Array>` | `[]` | Data array to display |
| `columns` | `Array<{label: string, key?: string, render?: function, class?: string, footer?: string}>` | `[]` | Column definitions |
| `keyFn` | `function` | `(item, idx) => idx` | Unique key function for rows |
| `zebra` | `boolean \| Signal<boolean>` | `false` | Enable zebra striping |
| `pinRows` | `boolean \| Signal<boolean>` | `false` | Pin header rows on scroll |
| `empty` | `string \| VNode` | `'No data'` | Content to show when no data |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
## Live Examples
### Basic Table
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const users = [
{ id: 1, name: 'John Doe', email: 'john@example.com', role: 'Admin' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'User' },
{ id: 3, name: 'Bob Johnson', email: 'bob@example.com', role: 'Editor' }
];
return Table({
items: users,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' },
{ label: 'Email', key: 'email' },
{ label: 'Role', key: 'role' }
]
});
};
$mount(BasicDemo, '#demo-basic');
```
### With Zebra Stripes
<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-zebra" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ZebraDemo = () => {
const products = [
{ id: 1, name: 'Laptop', price: '$999', stock: 15 },
{ id: 2, name: 'Mouse', price: '$29', stock: 42 },
{ id: 3, name: 'Keyboard', price: '$79', stock: 28 },
{ id: 4, name: 'Monitor', price: '$299', stock: 12 }
];
return Table({
items: products,
columns: [
{ label: 'Product', key: 'name' },
{ label: 'Price', key: 'price' },
{ label: 'Stock', key: 'stock' }
],
zebra: true
});
};
$mount(ZebraDemo, '#demo-zebra');
```
### With Custom Cell Rendering
<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-custom" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const CustomDemo = () => {
const orders = [
{ id: 101, customer: 'Alice', amount: 250, status: 'completed' },
{ id: 102, customer: 'Bob', amount: 89, status: 'pending' },
{ id: 103, customer: 'Charlie', amount: 450, status: 'shipped' }
];
return Table({
items: orders,
columns: [
{ label: 'Order ID', key: 'id' },
{ label: 'Customer', key: 'customer' },
{
label: 'Amount',
key: 'amount',
render: (item) => `$${item.amount}`
},
{
label: 'Status',
key: 'status',
render: (item) => {
const statusClass = {
completed: 'badge badge-success',
pending: 'badge badge-warning',
shipped: 'badge badge-info'
};
return Span({ class: statusClass[item.status] }, item.status);
}
}
],
zebra: true
});
};
$mount(CustomDemo, '#demo-custom');
```
### With Footers
<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-footer" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const FooterDemo = () => {
const sales = [
{ month: 'January', revenue: 12500, expenses: 8900 },
{ month: 'February', revenue: 14200, expenses: 9200 },
{ month: 'March', revenue: 16800, expenses: 10100 }
];
const totalRevenue = sales.reduce((sum, item) => sum + item.revenue, 0);
const totalExpenses = sales.reduce((sum, item) => sum + item.expenses, 0);
return Table({
items: sales,
columns: [
{ label: 'Month', key: 'month' },
{
label: 'Revenue',
key: 'revenue',
render: (item) => `$${item.revenue.toLocaleString()}`,
footer: `Total: $${totalRevenue.toLocaleString()}`
},
{
label: 'Expenses',
key: 'expenses',
render: (item) => `$${item.expenses.toLocaleString()}`,
footer: `Total: $${totalExpenses.toLocaleString()}`
},
{
label: 'Profit',
render: (item) => `$${(item.revenue - item.expenses).toLocaleString()}`,
footer: `$${(totalRevenue - totalExpenses).toLocaleString()}`
}
],
zebra: true
});
};
$mount(FooterDemo, '#demo-footer');
```
### Empty State
<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-empty" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const EmptyDemo = () => {
const emptyList = [];
return Table({
items: emptyList,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' }
],
empty: Div({ class: 'flex flex-col items-center gap-2' }, [
Icons.iconInfo,
Span({}, 'No records found')
])
});
};
$mount(EmptyDemo, '#demo-empty');
```
### Reactive Data
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ReactiveDemo = () => {
const filter = $('all');
const tasks = $([
{ id: 1, title: 'Complete documentation', completed: true },
{ id: 2, title: 'Review pull requests', completed: false },
{ id: 3, title: 'Deploy to production', completed: false },
{ id: 4, title: 'Update dependencies', completed: true }
]);
const filteredTasks = () => {
if (filter() === 'completed') {
return tasks().filter(t => t.completed);
} else if (filter() === 'pending') {
return tasks().filter(t => !t.completed);
}
return tasks();
};
const addTask = () => {
const newId = Math.max(...tasks().map(t => t.id), 0) + 1;
tasks([...tasks(), { id: newId, title: `Task ${newId}`, completed: false }]);
};
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex gap-2' }, [
Button({
class: 'btn btn-sm',
onclick: () => filter('all')
}, 'All'),
Button({
class: 'btn btn-sm',
onclick: () => filter('completed')
}, 'Completed'),
Button({
class: 'btn btn-sm',
onclick: () => filter('pending')
}, 'Pending'),
Button({
class: 'btn btn-sm btn-primary',
onclick: addTask
}, 'Add Task')
]),
Table({
items: filteredTasks,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Title', key: 'title' },
{
label: 'Status',
render: (item) => item.completed
? Span({ class: 'badge badge-success' }, '✓ Done')
: Span({ class: 'badge badge-warning' }, '○ Pending')
}
],
zebra: true
})
]);
};
$mount(ReactiveDemo, '#demo-reactive');
```
### With Actions
<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-actions" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ActionsDemo = () => {
const users = $([
{ id: 1, name: 'John Doe', email: 'john@example.com', active: true },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', active: false },
{ id: 3, name: 'Bob Johnson', email: 'bob@example.com', active: true }
]);
const deleteUser = (id) => {
users(users().filter(u => u.id !== id));
Toast('User deleted', 'alert-info', 2000);
};
const toggleActive = (id) => {
users(users().map(u =>
u.id === id ? { ...u, active: !u.active } : u
));
};
return Table({
items: users,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' },
{ label: 'Email', key: 'email' },
{
label: 'Status',
render: (item) => item.active
? Span({ class: 'badge badge-success' }, 'Active')
: Span({ class: 'badge badge-ghost' }, 'Inactive')
},
{
label: 'Actions',
render: (item) => Div({ class: 'flex gap-1' }, [
Button({
class: 'btn btn-xs btn-ghost',
onclick: () => toggleActive(item.id)
}, item.active ? 'Deactivate' : 'Activate'),
Button({
class: 'btn btn-xs btn-error',
onclick: () => deleteUser(item.id)
}, 'Delete')
])
}
],
zebra: true
});
};
$mount(ActionsDemo, '#demo-actions');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-6"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
const data = [
{ id: 1, name: 'Item 1', value: 100 },
{ id: 2, name: 'Item 2', value: 200 },
{ id: 3, name: 'Item 3', value: 300 }
];
return Div({ class: 'flex flex-col gap-6' }, [
Div({ class: 'text-sm font-bold' }, 'Default Table'),
Table({
items: data,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' },
{ label: 'Value', key: 'value' }
]
}),
Div({ class: 'text-sm font-bold mt-4' }, 'Zebra Stripes'),
Table({
items: data,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' },
{ label: 'Value', key: 'value' }
],
zebra: true
}),
Div({ class: 'text-sm font-bold mt-4' }, 'Compact Table'),
Table({
items: data,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' },
{ label: 'Value', key: 'value' }
],
class: 'table-compact'
})
]);
};
$mount(VariantsDemo, '#demo-variants');
```
<script>
(function() {
const initTableExamples = () => {
// 1. Basic Table
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
const users = [
{ id: 1, name: 'John Doe', email: 'john@example.com', role: 'Admin' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'User' },
{ id: 3, name: 'Bob Johnson', email: 'bob@example.com', role: 'Editor' }
];
return Table({
items: users,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' },
{ label: 'Email', key: 'email' },
{ label: 'Role', key: 'role' }
]
});
};
$mount(BasicDemo, basicTarget);
}
// 2. With Zebra Stripes
const zebraTarget = document.querySelector('#demo-zebra');
if (zebraTarget && !zebraTarget.hasChildNodes()) {
const ZebraDemo = () => {
const products = [
{ id: 1, name: 'Laptop', price: '$999', stock: 15 },
{ id: 2, name: 'Mouse', price: '$29', stock: 42 },
{ id: 3, name: 'Keyboard', price: '$79', stock: 28 },
{ id: 4, name: 'Monitor', price: '$299', stock: 12 }
];
return Table({
items: products,
columns: [
{ label: 'Product', key: 'name' },
{ label: 'Price', key: 'price' },
{ label: 'Stock', key: 'stock' }
],
zebra: true
});
};
$mount(ZebraDemo, zebraTarget);
}
// 3. With Custom Cell Rendering
const customTarget = document.querySelector('#demo-custom');
if (customTarget && !customTarget.hasChildNodes()) {
const CustomDemo = () => {
const orders = [
{ id: 101, customer: 'Alice', amount: 250, status: 'completed' },
{ id: 102, customer: 'Bob', amount: 89, status: 'pending' },
{ id: 103, customer: 'Charlie', amount: 450, status: 'shipped' }
];
return Table({
items: orders,
columns: [
{ label: 'Order ID', key: 'id' },
{ label: 'Customer', key: 'customer' },
{
label: 'Amount',
key: 'amount',
render: (item) => `$${item.amount}`
},
{
label: 'Status',
key: 'status',
render: (item) => {
const statusClass = {
completed: 'badge badge-success',
pending: 'badge badge-warning',
shipped: 'badge badge-info'
};
return Span({ class: statusClass[item.status] }, item.status);
}
}
],
zebra: true
});
};
$mount(CustomDemo, customTarget);
}
// 4. With Footers
const footerTarget = document.querySelector('#demo-footer');
if (footerTarget && !footerTarget.hasChildNodes()) {
const FooterDemo = () => {
const sales = [
{ month: 'January', revenue: 12500, expenses: 8900 },
{ month: 'February', revenue: 14200, expenses: 9200 },
{ month: 'March', revenue: 16800, expenses: 10100 }
];
const totalRevenue = sales.reduce((sum, item) => sum + item.revenue, 0);
const totalExpenses = sales.reduce((sum, item) => sum + item.expenses, 0);
return Table({
items: sales,
columns: [
{ label: 'Month', key: 'month' },
{
label: 'Revenue',
key: 'revenue',
render: (item) => `$${item.revenue.toLocaleString()}`,
footer: `Total: $${totalRevenue.toLocaleString()}`
},
{
label: 'Expenses',
key: 'expenses',
render: (item) => `$${item.expenses.toLocaleString()}`,
footer: `Total: $${totalExpenses.toLocaleString()}`
},
{
label: 'Profit',
render: (item) => `$${(item.revenue - item.expenses).toLocaleString()}`,
footer: `$${(totalRevenue - totalExpenses).toLocaleString()}`
}
],
zebra: true
});
};
$mount(FooterDemo, footerTarget);
}
// 5. Empty State
const emptyTarget = document.querySelector('#demo-empty');
if (emptyTarget && !emptyTarget.hasChildNodes()) {
const EmptyDemo = () => {
const emptyList = [];
return Table({
items: emptyList,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' }
],
empty: Div({ class: 'flex flex-col items-center gap-2' }, [
Icons.iconInfo,
Span({}, 'No records found')
])
});
};
$mount(EmptyDemo, emptyTarget);
}
// 6. Reactive Data
const reactiveTarget = document.querySelector('#demo-reactive');
if (reactiveTarget && !reactiveTarget.hasChildNodes()) {
const ReactiveDemo = () => {
const filter = $('all');
const tasks = $([
{ id: 1, title: 'Complete documentation', completed: true },
{ id: 2, title: 'Review pull requests', completed: false },
{ id: 3, title: 'Deploy to production', completed: false },
{ id: 4, title: 'Update dependencies', completed: true }
]);
const filteredTasks = () => {
if (filter() === 'completed') {
return tasks().filter(t => t.completed);
} else if (filter() === 'pending') {
return tasks().filter(t => !t.completed);
}
return tasks();
};
const addTask = () => {
const newId = Math.max(...tasks().map(t => t.id), 0) + 1;
tasks([...tasks(), { id: newId, title: `Task ${newId}`, completed: false }]);
};
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex gap-2' }, [
Button({
class: 'btn btn-sm',
onclick: () => filter('all')
}, 'All'),
Button({
class: 'btn btn-sm',
onclick: () => filter('completed')
}, 'Completed'),
Button({
class: 'btn btn-sm',
onclick: () => filter('pending')
}, 'Pending'),
Button({
class: 'btn btn-sm btn-primary',
onclick: addTask
}, 'Add Task')
]),
Table({
items: filteredTasks,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Title', key: 'title' },
{
label: 'Status',
render: (item) => item.completed
? Span({ class: 'badge badge-success' }, '✓ Done')
: Span({ class: 'badge badge-warning' }, '○ Pending')
}
],
zebra: true
})
]);
};
$mount(ReactiveDemo, reactiveTarget);
}
// 7. With Actions
const actionsTarget = document.querySelector('#demo-actions');
if (actionsTarget && !actionsTarget.hasChildNodes()) {
const ActionsDemo = () => {
const users = $([
{ id: 1, name: 'John Doe', email: 'john@example.com', active: true },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', active: false },
{ id: 3, name: 'Bob Johnson', email: 'bob@example.com', active: true }
]);
const deleteUser = (id) => {
users(users().filter(u => u.id !== id));
Toast('User deleted', 'alert-info', 2000);
};
const toggleActive = (id) => {
users(users().map(u =>
u.id === id ? { ...u, active: !u.active } : u
));
};
return Table({
items: users,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' },
{ label: 'Email', key: 'email' },
{
label: 'Status',
render: (item) => item.active
? Span({ class: 'badge badge-success' }, 'Active')
: Span({ class: 'badge badge-ghost' }, 'Inactive')
},
{
label: 'Actions',
render: (item) => Div({ class: 'flex gap-1' }, [
Button({
class: 'btn btn-xs btn-ghost',
onclick: () => toggleActive(item.id)
}, item.active ? 'Deactivate' : 'Activate'),
Button({
class: 'btn btn-xs btn-error',
onclick: () => deleteUser(item.id)
}, 'Delete')
])
}
],
zebra: true
});
};
$mount(ActionsDemo, actionsTarget);
}
// 8. All Variants
const variantsTarget = document.querySelector('#demo-variants');
if (variantsTarget && !variantsTarget.hasChildNodes()) {
const VariantsDemo = () => {
const data = [
{ id: 1, name: 'Item 1', value: 100 },
{ id: 2, name: 'Item 2', value: 200 },
{ id: 3, name: 'Item 3', value: 300 }
];
return Div({ class: 'flex flex-col gap-6' }, [
Div({ class: 'text-sm font-bold' }, 'Default Table'),
Table({
items: data,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' },
{ label: 'Value', key: 'value' }
]
}),
Div({ class: 'text-sm font-bold mt-4' }, 'Zebra Stripes'),
Table({
items: data,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' },
{ label: 'Value', key: 'value' }
],
zebra: true
}),
Div({ class: 'text-sm font-bold mt-4' }, 'Compact Table'),
Table({
items: data,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' },
{ label: 'Value', key: 'value' }
],
class: 'table-compact'
})
]);
};
$mount(VariantsDemo, variantsTarget);
}
};
initTableExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initTableExamples);
});
}
})();
</script>

677
docs/components_old/tabs.md Normal file
View File

@@ -0,0 +1,677 @@
# Tabs
Tabs component for organizing content into separate panels with tab navigation.
## Tag
`Tabs`
## Props
| Prop | Type | Default | Description |
| :----------- | :-------------------------------------- | :---------- | :----------------------------------------------- |
| `items` | `Array<TabItem> \| Signal<Array>` | `[]` | Array of tab items |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
### TabItem Structure
| Property | Type | Description |
| :---------- | :--------------------------- | :----------------------------------------------- |
| `label` | `string \| VNode` | Tab button label |
| `content` | `VNode \| function` | Content to display when tab is active |
| `active` | `boolean \| Signal<boolean>` | Whether this tab is active (only one per group) |
| `disabled` | `boolean` | Whether tab is disabled |
| `tip` | `string` | Tooltip text for the tab |
| `onclick` | `function` | Click handler (optional, overrides default) |
## Live Examples
### Basic Tabs
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const activeTab = $('tab1');
return Tabs({
items: [
{
label: 'Tab 1',
active: () => activeTab() === 'tab1',
onclick: () => activeTab('tab1'),
content: Div({ class: 'p-4' }, 'Content for Tab 1')
},
{
label: 'Tab 2',
active: () => activeTab() === 'tab2',
onclick: () => activeTab('tab2'),
content: Div({ class: 'p-4' }, 'Content for Tab 2')
},
{
label: 'Tab 3',
active: () => activeTab() === 'tab3',
onclick: () => activeTab('tab3'),
content: Div({ class: 'p-4' }, 'Content for Tab 3')
}
]
});
};
$mount(BasicDemo, '#demo-basic');
```
### With Icons
<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-icons" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const IconsDemo = () => {
const activeTab = $('home');
return Tabs({
items: [
{
label: Span({ class: 'flex items-center gap-2' }, ['🏠', 'Home']),
active: () => activeTab() === 'home',
onclick: () => activeTab('home'),
content: Div({ class: 'p-4' }, 'Welcome to the Home tab!')
},
{
label: Span({ class: 'flex items-center gap-2' }, ['⭐', 'Favorites']),
active: () => activeTab() === 'favorites',
onclick: () => activeTab('favorites'),
content: Div({ class: 'p-4' }, 'Your favorite items appear here.')
},
{
label: Span({ class: 'flex items-center gap-2' }, ['⚙️', 'Settings']),
active: () => activeTab() === 'settings',
onclick: () => activeTab('settings'),
content: Div({ class: 'p-4' }, 'Configure your preferences.')
}
]
});
};
$mount(IconsDemo, '#demo-icons');
```
### With Tooltips
<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-tooltips" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const TooltipsDemo = () => {
const activeTab = $('profile');
return Tabs({
items: [
{
label: 'Profile',
tip: 'View your profile information',
active: () => activeTab() === 'profile',
onclick: () => activeTab('profile'),
content: Div({ class: 'p-4' }, 'Profile information here.')
},
{
label: 'Settings',
tip: 'Adjust your preferences',
active: () => activeTab() === 'settings',
onclick: () => activeTab('settings'),
content: Div({ class: 'p-4' }, 'Settings configuration.')
},
{
label: 'Notifications',
tip: 'Manage notifications',
active: () => activeTab() === 'notifications',
onclick: () => activeTab('notifications'),
content: Div({ class: 'p-4' }, 'Notification settings.')
}
]
});
};
$mount(TooltipsDemo, '#demo-tooltips');
```
### Disabled Tab
<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-disabled" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const DisabledDemo = () => {
const activeTab = $('basic');
return Tabs({
items: [
{
label: 'Basic',
active: () => activeTab() === 'basic',
onclick: () => activeTab('basic'),
content: Div({ class: 'p-4' }, 'Basic features available.')
},
{
label: 'Premium',
disabled: true,
tip: 'Upgrade to access',
content: Div({ class: 'p-4' }, 'Premium content (locked)')
},
{
label: 'Pro',
disabled: true,
tip: 'Coming soon',
content: Div({ class: 'p-4' }, 'Pro features (coming soon)')
}
]
});
};
$mount(DisabledDemo, '#demo-disabled');
```
### Reactive Content
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ReactiveDemo = () => {
const activeTab = $('counter');
const count = $(0);
return Tabs({
items: [
{
label: 'Counter',
active: () => activeTab() === 'counter',
onclick: () => activeTab('counter'),
content: Div({ class: 'p-4 text-center' }, [
Div({ class: 'text-4xl font-bold mb-4' }, () => count()),
Button({
class: 'btn btn-primary',
onclick: () => count(count() + 1)
}, 'Increment')
])
},
{
label: 'Timer',
active: () => activeTab() === 'timer',
onclick: () => activeTab('timer'),
content: Div({ class: 'p-4' }, () => `Current time: ${new Date().toLocaleTimeString()}`)
},
{
label: 'Status',
active: () => activeTab() === 'status',
onclick: () => activeTab('status'),
content: Div({ class: 'p-4' }, () => `Counter value: ${count()}, Last updated: ${new Date().toLocaleTimeString()}`)
}
]
});
};
$mount(ReactiveDemo, '#demo-reactive');
```
### Form Tabs
<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-form" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const FormTabs = () => {
const activeTab = $('personal');
const formData = $({
name: '',
email: '',
address: '',
city: '',
notifications: true,
newsletter: false
});
const updateField = (field, value) => {
formData({ ...formData(), [field]: value });
};
const handleSubmit = () => {
Toast('Form submitted!', 'alert-success', 2000);
console.log(formData());
};
return Div({ class: 'flex flex-col gap-4' }, [
Tabs({
items: [
{
label: 'Personal Info',
active: () => activeTab() === 'personal',
onclick: () => activeTab('personal'),
content: Div({ class: 'p-4 space-y-4' }, [
Input({
label: 'Name',
value: () => formData().name,
placeholder: 'Enter your name',
oninput: (e) => updateField('name', e.target.value)
}),
Input({
label: 'Email',
type: 'email',
value: () => formData().email,
placeholder: 'email@example.com',
oninput: (e) => updateField('email', e.target.value)
})
])
},
{
label: 'Address',
active: () => activeTab() === 'address',
onclick: () => activeTab('address'),
content: Div({ class: 'p-4 space-y-4' }, [
Input({
label: 'Address',
value: () => formData().address,
placeholder: 'Street address',
oninput: (e) => updateField('address', e.target.value)
}),
Input({
label: 'City',
value: () => formData().city,
placeholder: 'City',
oninput: (e) => updateField('city', e.target.value)
})
])
},
{
label: 'Preferences',
active: () => activeTab() === 'prefs',
onclick: () => activeTab('prefs'),
content: Div({ class: 'p-4 space-y-4' }, [
Checkbox({
label: 'Email notifications',
value: () => formData().notifications,
onclick: () => updateField('notifications', !formData().notifications)
}),
Checkbox({
label: 'Newsletter subscription',
value: () => formData().newsletter,
onclick: () => updateField('newsletter', !formData().newsletter)
})
])
}
]
}),
Div({ class: 'flex justify-end mt-4' }, [
Button({
class: 'btn btn-primary',
onclick: handleSubmit
}, 'Submit')
])
]);
};
$mount(FormTabs, '#demo-form');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-6"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
const items = [
{ label: 'Tab 1', content: 'Content 1' },
{ label: 'Tab 2', content: 'Content 2' },
{ label: 'Tab 3', content: 'Content 3' }
].map((tab, idx) => ({
...tab,
active: () => idx === 0,
content: Div({ class: 'p-4' }, tab.content)
}));
return Div({ class: 'flex flex-col gap-6' }, [
Div({ class: 'text-sm font-bold' }, 'Default Tabs'),
Tabs({ items }),
Div({ class: 'text-sm font-bold mt-4' }, 'Boxed Tabs'),
Tabs({ items, class: 'tabs-box' }),
Div({ class: 'text-sm font-bold mt-4' }, 'Lifted Tabs'),
Tabs({ items, class: 'tabs-lifted' })
]);
};
$mount(VariantsDemo, '#demo-variants');
```
<script>
(function() {
const initTabsExamples = () => {
// 1. Basic Tabs
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
const activeTab = $('tab1');
return Tabs({
items: [
{
label: 'Tab 1',
active: () => activeTab() === 'tab1',
onclick: () => activeTab('tab1'),
content: Div({ class: 'p-4' }, 'Content for Tab 1')
},
{
label: 'Tab 2',
active: () => activeTab() === 'tab2',
onclick: () => activeTab('tab2'),
content: Div({ class: 'p-4' }, 'Content for Tab 2')
},
{
label: 'Tab 3',
active: () => activeTab() === 'tab3',
onclick: () => activeTab('tab3'),
content: Div({ class: 'p-4' }, 'Content for Tab 3')
}
]
});
};
$mount(BasicDemo, basicTarget);
}
// 2. With Icons
const iconsTarget = document.querySelector('#demo-icons');
if (iconsTarget && !iconsTarget.hasChildNodes()) {
const IconsDemo = () => {
const activeTab = $('home');
return Tabs({
items: [
{
label: Span({ class: 'flex items-center gap-2' }, ['🏠', 'Home']),
active: () => activeTab() === 'home',
onclick: () => activeTab('home'),
content: Div({ class: 'p-4' }, 'Welcome to the Home tab!')
},
{
label: Span({ class: 'flex items-center gap-2' }, ['⭐', 'Favorites']),
active: () => activeTab() === 'favorites',
onclick: () => activeTab('favorites'),
content: Div({ class: 'p-4' }, 'Your favorite items appear here.')
},
{
label: Span({ class: 'flex items-center gap-2' }, ['⚙️', 'Settings']),
active: () => activeTab() === 'settings',
onclick: () => activeTab('settings'),
content: Div({ class: 'p-4' }, 'Configure your preferences.')
}
]
});
};
$mount(IconsDemo, iconsTarget);
}
// 3. With Tooltips
const tooltipsTarget = document.querySelector('#demo-tooltips');
if (tooltipsTarget && !tooltipsTarget.hasChildNodes()) {
const TooltipsDemo = () => {
const activeTab = $('profile');
return Tabs({
items: [
{
label: 'Profile',
tip: 'View your profile information',
active: () => activeTab() === 'profile',
onclick: () => activeTab('profile'),
content: Div({ class: 'p-4' }, 'Profile information here.')
},
{
label: 'Settings',
tip: 'Adjust your preferences',
active: () => activeTab() === 'settings',
onclick: () => activeTab('settings'),
content: Div({ class: 'p-4' }, 'Settings configuration.')
},
{
label: 'Notifications',
tip: 'Manage notifications',
active: () => activeTab() === 'notifications',
onclick: () => activeTab('notifications'),
content: Div({ class: 'p-4' }, 'Notification settings.')
}
]
});
};
$mount(TooltipsDemo, tooltipsTarget);
}
// 4. Disabled Tab
const disabledTarget = document.querySelector('#demo-disabled');
if (disabledTarget && !disabledTarget.hasChildNodes()) {
const DisabledDemo = () => {
const activeTab = $('basic');
return Tabs({
items: [
{
label: 'Basic',
active: () => activeTab() === 'basic',
onclick: () => activeTab('basic'),
content: Div({ class: 'p-4' }, 'Basic features available.')
},
{
label: 'Premium',
disabled: true,
tip: 'Upgrade to access',
content: Div({ class: 'p-4' }, 'Premium content (locked)')
},
{
label: 'Pro',
disabled: true,
tip: 'Coming soon',
content: Div({ class: 'p-4' }, 'Pro features (coming soon)')
}
]
});
};
$mount(DisabledDemo, disabledTarget);
}
// 5. Reactive Content
const reactiveTarget = document.querySelector('#demo-reactive');
if (reactiveTarget && !reactiveTarget.hasChildNodes()) {
const ReactiveDemo = () => {
const activeTab = $('counter');
const count = $(0);
return Tabs({
items: [
{
label: 'Counter',
active: () => activeTab() === 'counter',
onclick: () => activeTab('counter'),
content: Div({ class: 'p-4 text-center' }, [
Div({ class: 'text-4xl font-bold mb-4' }, () => count()),
Button({
class: 'btn btn-primary',
onclick: () => count(count() + 1)
}, 'Increment')
])
},
{
label: 'Timer',
active: () => activeTab() === 'timer',
onclick: () => activeTab('timer'),
content: Div({ class: 'p-4' }, () => `Current time: ${new Date().toLocaleTimeString()}`)
},
{
label: 'Status',
active: () => activeTab() === 'status',
onclick: () => activeTab('status'),
content: Div({ class: 'p-4' }, () => `Counter value: ${count()}, Last updated: ${new Date().toLocaleTimeString()}`)
}
]
});
};
$mount(ReactiveDemo, reactiveTarget);
}
// 6. Form Tabs
const formTarget = document.querySelector('#demo-form');
if (formTarget && !formTarget.hasChildNodes()) {
const FormTabs = () => {
const activeTab = $('personal');
const formData = $({
name: '',
email: '',
address: '',
city: '',
notifications: true,
newsletter: false
});
const updateField = (field, value) => {
formData({ ...formData(), [field]: value });
};
const handleSubmit = () => {
Toast('Form submitted!', 'alert-success', 2000);
console.log(formData());
};
return Div({ class: 'flex flex-col gap-4' }, [
Tabs({
items: [
{
label: 'Personal Info',
active: () => activeTab() === 'personal',
onclick: () => activeTab('personal'),
content: Div({ class: 'p-4 space-y-4' }, [
Input({
label: 'Name',
value: () => formData().name,
placeholder: 'Enter your name',
oninput: (e) => updateField('name', e.target.value)
}),
Input({
label: 'Email',
type: 'email',
value: () => formData().email,
placeholder: 'email@example.com',
oninput: (e) => updateField('email', e.target.value)
})
])
},
{
label: 'Address',
active: () => activeTab() === 'address',
onclick: () => activeTab('address'),
content: Div({ class: 'p-4 space-y-4' }, [
Input({
label: 'Address',
value: () => formData().address,
placeholder: 'Street address',
oninput: (e) => updateField('address', e.target.value)
}),
Input({
label: 'City',
value: () => formData().city,
placeholder: 'City',
oninput: (e) => updateField('city', e.target.value)
})
])
},
{
label: 'Preferences',
active: () => activeTab() === 'prefs',
onclick: () => activeTab('prefs'),
content: Div({ class: 'p-4 space-y-4' }, [
Checkbox({
label: 'Email notifications',
value: () => formData().notifications,
onclick: () => updateField('notifications', !formData().notifications)
}),
Checkbox({
label: 'Newsletter subscription',
value: () => formData().newsletter,
onclick: () => updateField('newsletter', !formData().newsletter)
})
])
}
]
}),
Div({ class: 'flex justify-end mt-4' }, [
Button({
class: 'btn btn-primary',
onclick: handleSubmit
}, 'Submit')
])
]);
};
$mount(FormTabs, formTarget);
}
// 7. All Variants
const variantsTarget = document.querySelector('#demo-variants');
if (variantsTarget && !variantsTarget.hasChildNodes()) {
const VariantsDemo = () => {
const items = [
{ label: 'Tab 1', content: 'Content 1' },
{ label: 'Tab 2', content: 'Content 2' },
{ label: 'Tab 3', content: 'Content 3' }
].map((tab, idx) => ({
...tab,
active: () => idx === 0,
content: Div({ class: 'p-4' }, tab.content)
}));
return Div({ class: 'flex flex-col gap-6' }, [
Div({ class: 'text-sm font-bold' }, 'Default Tabs'),
Tabs({ items }),
Div({ class: 'text-sm font-bold mt-4' }, 'Boxed Tabs'),
Tabs({ items, class: 'tabs-box' }),
Div({ class: 'text-sm font-bold mt-4' }, 'Lifted Tabs'),
Tabs({ items, class: 'tabs-lifted' })
]);
};
$mount(VariantsDemo, variantsTarget);
}
};
initTabsExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initTabsExamples);
});
}
})();
</script>

View File

@@ -0,0 +1,553 @@
# Timeline
Timeline component for displaying chronological events, steps, or progress with customizable icons and content.
## Tag
`Timeline`
## Props
| Prop | Type | Default | Description |
| :----------- | :-------------------------------------- | :--------------- | :----------------------------------------------- |
| `items` | `Array<TimelineItem> \| Signal` | `[]` | Timeline items to display |
| `vertical` | `boolean \| Signal<boolean>` | `true` | Vertical or horizontal orientation |
| `compact` | `boolean \| Signal<boolean>` | `false` | Compact mode with less padding |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
### TimelineItem Structure
| Property | Type | Description |
| :---------- | :--------------------------- | :----------------------------------------------- |
| `title` | `string \| VNode \| Signal` | Event title or main text |
| `detail` | `string \| VNode \| Signal` | Additional details or description |
| `icon` | `string \| VNode` | Custom icon (overrides type) |
| `type` | `string` | Type: 'success', 'warning', 'error', 'info' |
| `completed` | `boolean` | Whether event is completed (affects connector) |
## Live Examples
### Basic Timeline
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
const events = [
{ title: 'Project Started', detail: 'Initial planning and setup', type: 'info', completed: true },
{ title: 'Design Phase', detail: 'UI/UX design completed', type: 'success', completed: true },
{ title: 'Development', detail: 'Core features implemented', type: 'warning', completed: false },
{ title: 'Testing', detail: 'Quality assurance', type: 'info', completed: false },
{ title: 'Launch', detail: 'Production deployment', type: 'success', completed: false }
];
return Timeline({ items: events });
};
$mount(BasicDemo, '#demo-basic');
```
### Horizontal Timeline
<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-horizontal" class="bg-base-100 p-6 rounded-xl border border-base-300 overflow-x-auto"></div>
</div>
</div>
```javascript
const HorizontalDemo = () => {
const steps = [
{ title: 'Step 1', detail: 'Requirements', type: 'success', completed: true },
{ title: 'Step 2', detail: 'Design', type: 'success', completed: true },
{ title: 'Step 3', detail: 'Development', type: 'warning', completed: false },
{ title: 'Step 4', detail: 'Testing', type: 'info', completed: false },
{ title: 'Step 5', detail: 'Deploy', type: 'info', completed: false }
];
return Timeline({
items: steps,
vertical: false,
class: 'min-w-[600px]'
});
};
$mount(HorizontalDemo, '#demo-horizontal');
```
### Compact Timeline
<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-compact" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const CompactDemo = () => {
const activities = [
{ title: 'User login', detail: '10:30 AM', type: 'success', completed: true },
{ title: 'Viewed dashboard', detail: '10:32 AM', type: 'info', completed: true },
{ title: 'Updated profile', detail: '10:45 AM', type: 'success', completed: true },
{ title: 'Made purchase', detail: '11:00 AM', type: 'warning', completed: false }
];
return Timeline({
items: activities,
compact: true
});
};
$mount(CompactDemo, '#demo-compact');
```
### Custom Icons
<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-icons" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const IconsDemo = () => {
const milestones = [
{ title: 'Kickoff', detail: 'Project kickoff meeting', icon: Icons.iconInfo, completed: true },
{ title: 'MVP', detail: 'Minimum viable product', icon: Icons.iconSuccess, completed: true },
{ title: 'Beta', detail: 'Beta release', icon: Icons.iconWarning, completed: false },
{ title: 'Launch', detail: 'Public launch', icon: Icons.iconShow, completed: false }
];
return Timeline({ items: milestones });
};
$mount(IconsDemo, '#demo-icons');
```
### Reactive Timeline
<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-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ReactiveDemo = () => {
const currentStep = $(2);
const steps = [
{ title: 'Order Placed', detail: 'Your order has been confirmed', type: 'success', completed: true },
{ title: 'Processing', detail: 'Payment verified, preparing shipment', type: 'success', completed: currentStep() > 1 },
{ title: 'Shipped', detail: 'Package on the way', type: 'warning', completed: currentStep() > 2 },
{ title: 'Delivered', detail: 'Arriving soon', type: 'info', completed: currentStep() > 3 }
];
const items = () => steps.map((step, idx) => ({
...step,
completed: idx < currentStep()
}));
const nextStep = () => {
if (currentStep() < steps.length) {
currentStep(currentStep() + 1);
Toast(`Step ${currentStep()}: ${steps[currentStep() - 1].title}`, 'alert-info', 1500);
}
};
const reset = () => {
currentStep(0);
Toast('Order tracking reset', 'alert-warning', 1500);
};
return Div({ class: 'flex flex-col gap-4' }, [
Timeline({ items: items }),
Div({ class: 'flex gap-2 justify-center mt-4' }, [
Button({
class: 'btn btn-primary btn-sm',
onclick: nextStep,
disabled: () => currentStep() >= steps.length
}, 'Next Step'),
Button({
class: 'btn btn-ghost btn-sm',
onclick: reset
}, 'Reset')
])
]);
};
$mount(ReactiveDemo, '#demo-reactive');
```
### Order Status Tracker
<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-order" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const OrderDemo = () => {
const status = $('shipped');
const statusMap = {
pending: { title: 'Order Pending', detail: 'Awaiting confirmation', completed: false, type: 'warning' },
confirmed: { title: 'Order Confirmed', detail: 'Payment received', completed: false, type: 'info' },
processing: { title: 'Processing', detail: 'Preparing your order', completed: false, type: 'info' },
shipped: { title: 'Shipped', detail: 'Package in transit', completed: false, type: 'info' },
delivered: { title: 'Delivered', detail: 'Order completed', completed: false, type: 'success' }
};
const statusOrder = ['pending', 'confirmed', 'processing', 'shipped', 'delivered'];
const currentIndex = statusOrder.indexOf(status());
const items = statusOrder.map((key, idx) => ({
...statusMap[key],
completed: idx < currentIndex
}));
return Div({ class: 'flex flex-col gap-4' }, [
Timeline({ items, compact: true }),
Div({ class: 'flex gap-2 justify-center flex-wrap mt-4' }, statusOrder.map(s =>
Button({
class: `btn btn-xs ${status() === s ? 'btn-primary' : 'btn-ghost'}`,
onclick: () => status(s)
}, statusMap[s].title)
))
]);
};
$mount(OrderDemo, '#demo-order');
```
### Company History
<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-history" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const HistoryDemo = () => {
const milestones = [
{
title: '2015 - Founded',
detail: 'Company started with 3 employees in a small office',
type: 'success',
completed: true
},
{
title: '2017 - First Product',
detail: 'Launched our first software product to market',
type: 'success',
completed: true
},
{
title: '2019 - Series A',
detail: 'Raised $5M in funding, expanded team to 50',
type: 'success',
completed: true
},
{
title: '2022 - Global Expansion',
detail: 'Opened offices in Europe and Asia',
type: 'info',
completed: true
},
{
title: '2024 - AI Integration',
detail: 'Launched AI-powered features',
type: 'warning',
completed: false
},
{
title: '2026 - Future Goals',
detail: 'Aiming for market leadership',
type: 'info',
completed: false
}
];
return Timeline({ items: milestones });
};
$mount(HistoryDemo, '#demo-history');
```
### All Variants
<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-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-8"></div>
</div>
</div>
```javascript
const VariantsDemo = () => {
const sampleItems = [
{ title: 'Event 1', detail: 'Description here', type: 'success', completed: true },
{ title: 'Event 2', detail: 'Description here', type: 'warning', completed: false },
{ title: 'Event 3', detail: 'Description here', type: 'info', completed: false }
];
return Div({ class: 'flex flex-col gap-8' }, [
Div({ class: 'text-sm font-bold' }, 'Vertical Timeline'),
Timeline({ items: sampleItems }),
Div({ class: 'text-sm font-bold mt-4' }, 'Horizontal Timeline'),
Timeline({ items: sampleItems, vertical: false, class: 'min-w-[500px]' }),
Div({ class: 'text-sm font-bold mt-4' }, 'Compact Timeline'),
Timeline({ items: sampleItems, compact: true })
]);
};
$mount(VariantsDemo, '#demo-variants');
```
<script>
(function() {
const initTimelineExamples = () => {
// 1. Basic Timeline
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
const events = [
{ title: 'Project Started', detail: 'Initial planning and setup', type: 'info', completed: true },
{ title: 'Design Phase', detail: 'UI/UX design completed', type: 'success', completed: true },
{ title: 'Development', detail: 'Core features implemented', type: 'warning', completed: false },
{ title: 'Testing', detail: 'Quality assurance', type: 'info', completed: false },
{ title: 'Launch', detail: 'Production deployment', type: 'success', completed: false }
];
return Timeline({ items: events });
};
$mount(BasicDemo, basicTarget);
}
// 2. Horizontal Timeline
const horizontalTarget = document.querySelector('#demo-horizontal');
if (horizontalTarget && !horizontalTarget.hasChildNodes()) {
const HorizontalDemo = () => {
const steps = [
{ title: 'Step 1', detail: 'Requirements', type: 'success', completed: true },
{ title: 'Step 2', detail: 'Design', type: 'success', completed: true },
{ title: 'Step 3', detail: 'Development', type: 'warning', completed: false },
{ title: 'Step 4', detail: 'Testing', type: 'info', completed: false },
{ title: 'Step 5', detail: 'Deploy', type: 'info', completed: false }
];
return Timeline({
items: steps,
vertical: false,
class: 'min-w-[600px]'
});
};
$mount(HorizontalDemo, horizontalTarget);
}
// 3. Compact Timeline
const compactTarget = document.querySelector('#demo-compact');
if (compactTarget && !compactTarget.hasChildNodes()) {
const CompactDemo = () => {
const activities = [
{ title: 'User login', detail: '10:30 AM', type: 'success', completed: true },
{ title: 'Viewed dashboard', detail: '10:32 AM', type: 'info', completed: true },
{ title: 'Updated profile', detail: '10:45 AM', type: 'success', completed: true },
{ title: 'Made purchase', detail: '11:00 AM', type: 'warning', completed: false }
];
return Timeline({
items: activities,
compact: true
});
};
$mount(CompactDemo, compactTarget);
}
// 4. Custom Icons
const iconsTarget = document.querySelector('#demo-icons');
if (iconsTarget && !iconsTarget.hasChildNodes()) {
const IconsDemo = () => {
const milestones = [
{ title: 'Kickoff', detail: 'Project kickoff meeting', icon: Icons.iconInfo, completed: true },
{ title: 'MVP', detail: 'Minimum viable product', icon: Icons.iconSuccess, completed: true },
{ title: 'Beta', detail: 'Beta release', icon: Icons.iconWarning, completed: false },
{ title: 'Launch', detail: 'Public launch', icon: Icons.iconShow, completed: false }
];
return Timeline({ items: milestones });
};
$mount(IconsDemo, iconsTarget);
}
// 5. Reactive Timeline
const reactiveTarget = document.querySelector('#demo-reactive');
if (reactiveTarget && !reactiveTarget.hasChildNodes()) {
const ReactiveDemo = () => {
const currentStep = $(2);
const steps = [
{ title: 'Order Placed', detail: 'Your order has been confirmed', type: 'success', completed: true },
{ title: 'Processing', detail: 'Payment verified, preparing shipment', type: 'success', completed: currentStep() > 1 },
{ title: 'Shipped', detail: 'Package on the way', type: 'warning', completed: currentStep() > 2 },
{ title: 'Delivered', detail: 'Arriving soon', type: 'info', completed: currentStep() > 3 }
];
const items = () => steps.map((step, idx) => ({
...step,
completed: idx < currentStep()
}));
const nextStep = () => {
if (currentStep() < steps.length) {
currentStep(currentStep() + 1);
Toast(`Step ${currentStep()}: ${steps[currentStep() - 1].title}`, 'alert-info', 1500);
}
};
const reset = () => {
currentStep(0);
Toast('Order tracking reset', 'alert-warning', 1500);
};
return Div({ class: 'flex flex-col gap-4' }, [
Timeline({ items: items }),
Div({ class: 'flex gap-2 justify-center mt-4' }, [
Button({
class: 'btn btn-primary btn-sm',
onclick: nextStep,
disabled: () => currentStep() >= steps.length
}, 'Next Step'),
Button({
class: 'btn btn-ghost btn-sm',
onclick: reset
}, 'Reset')
])
]);
};
$mount(ReactiveDemo, reactiveTarget);
}
// 6. Order Status Tracker
const orderTarget = document.querySelector('#demo-order');
if (orderTarget && !orderTarget.hasChildNodes()) {
const OrderDemo = () => {
const status = $('shipped');
const statusMap = {
pending: { title: 'Order Pending', detail: 'Awaiting confirmation', completed: false, type: 'warning' },
confirmed: { title: 'Order Confirmed', detail: 'Payment received', completed: false, type: 'info' },
processing: { title: 'Processing', detail: 'Preparing your order', completed: false, type: 'info' },
shipped: { title: 'Shipped', detail: 'Package in transit', completed: false, type: 'info' },
delivered: { title: 'Delivered', detail: 'Order completed', completed: false, type: 'success' }
};
const statusOrder = ['pending', 'confirmed', 'processing', 'shipped', 'delivered'];
const currentIndex = statusOrder.indexOf(status());
const items = statusOrder.map((key, idx) => ({
...statusMap[key],
completed: idx < currentIndex
}));
return Div({ class: 'flex flex-col gap-4' }, [
Timeline({ items, compact: true }),
Div({ class: 'flex gap-2 justify-center flex-wrap mt-4' }, statusOrder.map(s =>
Button({
class: `btn btn-xs ${status() === s ? 'btn-primary' : 'btn-ghost'}`,
onclick: () => status(s)
}, statusMap[s].title)
))
]);
};
$mount(OrderDemo, orderTarget);
}
// 7. Company History
const historyTarget = document.querySelector('#demo-history');
if (historyTarget && !historyTarget.hasChildNodes()) {
const HistoryDemo = () => {
const milestones = [
{
title: '2015 - Founded',
detail: 'Company started with 3 employees in a small office',
type: 'success',
completed: true
},
{
title: '2017 - First Product',
detail: 'Launched our first software product to market',
type: 'success',
completed: true
},
{
title: '2019 - Series A',
detail: 'Raised $5M in funding, expanded team to 50',
type: 'success',
completed: true
},
{
title: '2022 - Global Expansion',
detail: 'Opened offices in Europe and Asia',
type: 'info',
completed: true
},
{
title: '2024 - AI Integration',
detail: 'Launched AI-powered features',
type: 'warning',
completed: false
},
{
title: '2026 - Future Goals',
detail: 'Aiming for market leadership',
type: 'info',
completed: false
}
];
return Timeline({ items: milestones });
};
$mount(HistoryDemo, historyTarget);
}
// 8. All Variants
const variantsTarget = document.querySelector('#demo-variants');
if (variantsTarget && !variantsTarget.hasChildNodes()) {
const VariantsDemo = () => {
const sampleItems = [
{ title: 'Event 1', detail: 'Description here', type: 'success', completed: true },
{ title: 'Event 2', detail: 'Description here', type: 'warning', completed: false },
{ title: 'Event 3', detail: 'Description here', type: 'info', completed: false }
];
return Div({ class: 'flex flex-col gap-8' }, [
Div({ class: 'text-sm font-bold' }, 'Vertical Timeline'),
Timeline({ items: sampleItems }),
Div({ class: 'text-sm font-bold mt-4' }, 'Horizontal Timeline'),
Timeline({ items: sampleItems, vertical: false, class: 'min-w-[500px]' }),
Div({ class: 'text-sm font-bold mt-4' }, 'Compact Timeline'),
Timeline({ items: sampleItems, compact: true })
]);
};
$mount(VariantsDemo, variantsTarget);
}
};
initTimelineExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initTimelineExamples);
});
}
})();
</script>

View File

@@ -0,0 +1,630 @@
# Toast
Toast notification utility for displaying temporary messages that automatically dismiss after a specified duration. Can be used programmatically.
## Function
`Toast(message, type = 'alert-info', duration = 3500)`
| Param | Type | Default | Description |
| :--------- | :--------------------------- | :--------------- | :----------------------------------------------- |
| `message` | `string \| VNode` | `-` | Message content to display |
| `type` | `string` | `'alert-info'` | Alert type: 'alert-info', 'alert-success', 'alert-warning', 'alert-error' |
| `duration` | `number` | `3500` | Auto-dismiss duration in milliseconds |
## Live Examples
### Basic Toast
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-2 justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
return Div({ class: 'flex flex-wrap gap-2 justify-center' }, [
Button({
class: 'btn btn-info',
onclick: () => Toast('This is an info message', 'alert-info', 3000)
}, 'Info Toast'),
Button({
class: 'btn btn-success',
onclick: () => Toast('Operation successful!', 'alert-success', 3000)
}, 'Success Toast'),
Button({
class: 'btn btn-warning',
onclick: () => Toast('Please check your input', 'alert-warning', 3000)
}, 'Warning Toast'),
Button({
class: 'btn btn-error',
onclick: () => Toast('An error occurred', 'alert-error', 3000)
}, 'Error Toast')
]);
};
$mount(BasicDemo, '#demo-basic');
```
### Different Durations
<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-duration" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-2 justify-center"></div>
</div>
</div>
```javascript
const DurationDemo = () => {
return Div({ class: 'flex flex-wrap gap-2 justify-center' }, [
Button({
class: 'btn btn-sm',
onclick: () => Toast('Short (1s)', 'alert-info', 1000)
}, '1 Second'),
Button({
class: 'btn btn-sm',
onclick: () => Toast('Normal (3s)', 'alert-success', 3000)
}, '3 Seconds'),
Button({
class: 'btn btn-sm',
onclick: () => Toast('Long (5s)', 'alert-warning', 5000)
}, '5 Seconds'),
Button({
class: 'btn btn-sm',
onclick: () => Toast('Very Long (8s)', 'alert-error', 8000)
}, '8 Seconds')
]);
};
$mount(DurationDemo, '#demo-duration');
```
### Interactive Toast
<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-interactive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const InteractiveDemo = () => {
const count = $(0);
const showRandomToast = () => {
const types = ['alert-info', 'alert-success', 'alert-warning', 'alert-error'];
const messages = [
'You clicked the button!',
'Action completed successfully',
'Processing your request...',
'Something interesting happened'
];
const randomType = types[Math.floor(Math.random() * types.length)];
const randomMessage = messages[Math.floor(Math.random() * messages.length)];
count(count() + 1);
Toast(`${randomMessage} (${count()})`, randomType, 2000);
};
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Button({
class: 'btn btn-primary',
onclick: showRandomToast
}, 'Show Random Toast'),
Div({ class: 'text-sm opacity-70' }, () => `Toasts shown: ${count()}`)
]);
};
$mount(InteractiveDemo, '#demo-interactive');
```
### Form Validation Toast
<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-form" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const FormToastDemo = () => {
const email = $('');
const password = $('');
const handleSubmit = () => {
if (!email()) {
Toast('Please enter your email', 'alert-warning', 3000);
return;
}
if (!email().includes('@')) {
Toast('Please enter a valid email address', 'alert-error', 3000);
return;
}
if (!password()) {
Toast('Please enter your password', 'alert-warning', 3000);
return;
}
if (password().length < 6) {
Toast('Password must be at least 6 characters', 'alert-error', 3000);
return;
}
Toast('Login successful! Redirecting...', 'alert-success', 2000);
};
return Div({ class: 'flex flex-col gap-4 max-w-md mx-auto' }, [
Div({ class: 'text-lg font-bold text-center' }, 'Login Form'),
Input({
label: 'Email',
type: 'email',
value: email,
placeholder: 'user@example.com',
oninput: (e) => email(e.target.value)
}),
Input({
label: 'Password',
type: 'password',
value: password,
placeholder: 'Enter password',
oninput: (e) => password(e.target.value)
}),
Button({
class: 'btn btn-primary',
onclick: handleSubmit
}, 'Login')
]);
};
$mount(FormToastDemo, '#demo-form');
```
### Success Feedback
<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-feedback" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const FeedbackDemo = () => {
const items = $([
{ id: 1, name: 'Item 1', saved: false },
{ id: 2, name: 'Item 2', saved: false },
{ id: 3, name: 'Item 3', saved: false }
]);
const saveItem = (id) => {
items(items().map(item =>
item.id === id ? { ...item, saved: true } : item
));
Toast(`Item ${id} saved successfully!`, 'alert-success', 2000);
};
const saveAll = () => {
items(items().map(item => ({ ...item, saved: true })));
Toast('All items saved!', 'alert-success', 2000);
};
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex justify-between items-center' }, [
Span({ class: 'font-bold' }, 'Items to Save'),
Button({
class: 'btn btn-sm btn-primary',
onclick: saveAll
}, 'Save All')
]),
Div({ class: 'flex flex-col gap-2' }, items().map(item =>
Div({ class: 'flex justify-between items-center p-3 bg-base-200 rounded-lg' }, [
Span({}, item.name),
item.saved
? Span({ class: 'badge badge-success' }, '✓ Saved')
: Button({
class: 'btn btn-xs btn-primary',
onclick: () => saveItem(item.id)
}, 'Save')
])
))
]);
};
$mount(FeedbackDemo, '#demo-feedback');
```
### Error Handling
<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-error" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ErrorDemo = () => {
const simulateApiCall = () => {
const success = Math.random() > 0.3;
if (success) {
Toast('Data loaded successfully!', 'alert-success', 2000);
} else {
Toast('Failed to load data. Please try again.', 'alert-error', 3000);
}
};
const simulateNetworkError = () => {
Toast('Network error: Unable to connect to server', 'alert-error', 4000);
};
const simulateTimeout = () => {
Toast('Request timeout (5s). Please check your connection.', 'alert-warning', 4000);
};
return Div({ class: 'flex flex-wrap gap-3 justify-center' }, [
Button({
class: 'btn btn-primary',
onclick: simulateApiCall
}, 'Simulate API Call'),
Button({
class: 'btn btn-error',
onclick: simulateNetworkError
}, 'Network Error'),
Button({
class: 'btn btn-warning',
onclick: simulateTimeout
}, 'Timeout')
]);
};
$mount(ErrorDemo, '#demo-error');
```
### Custom Messages
<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-custom" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-2 justify-center"></div>
</div>
</div>
```javascript
const CustomDemo = () => {
const showCustomToast = (type, message) => {
Toast(message, type, 3000);
};
return Div({ class: 'flex flex-wrap gap-2 justify-center' }, [
Button({
class: 'btn btn-info',
onclick: () => showCustomToast('alert-info', '📧 New email received from john@example.com')
}, 'Email'),
Button({
class: 'btn btn-success',
onclick: () => showCustomToast('alert-success', '💰 Payment of $49.99 completed')
}, 'Payment'),
Button({
class: 'btn btn-warning',
onclick: () => showCustomToast('alert-warning', '⚠️ Your session will expire in 5 minutes')
}, 'Session Warning'),
Button({
class: 'btn btn-error',
onclick: () => showCustomToast('alert-error', '🔒 Failed login attempt detected')
}, 'Security Alert')
]);
};
$mount(CustomDemo, '#demo-custom');
```
### Multiple Toasts
<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-multiple" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const MultipleDemo = () => {
const showMultipleToasts = () => {
Toast('First message', 'alert-info', 3000);
setTimeout(() => Toast('Second message', 'alert-success', 3000), 500);
setTimeout(() => Toast('Third message', 'alert-warning', 3000), 1000);
setTimeout(() => Toast('Fourth message', 'alert-error', 3000), 1500);
};
return Div({ class: 'flex justify-center' }, [
Button({
class: 'btn btn-primary',
onclick: showMultipleToasts
}, 'Show Multiple Toasts')
]);
};
$mount(MultipleDemo, '#demo-multiple');
```
<script>
(function() {
const initToastExamples = () => {
// 1. Basic Toast
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
return Div({ class: 'flex flex-wrap gap-2 justify-center' }, [
Button({
class: 'btn btn-info',
onclick: () => Toast('This is an info message', 'alert-info', 3000)
}, 'Info Toast'),
Button({
class: 'btn btn-success',
onclick: () => Toast('Operation successful!', 'alert-success', 3000)
}, 'Success Toast'),
Button({
class: 'btn btn-warning',
onclick: () => Toast('Please check your input', 'alert-warning', 3000)
}, 'Warning Toast'),
Button({
class: 'btn btn-error',
onclick: () => Toast('An error occurred', 'alert-error', 3000)
}, 'Error Toast')
]);
};
$mount(BasicDemo, basicTarget);
}
// 2. Different Durations
const durationTarget = document.querySelector('#demo-duration');
if (durationTarget && !durationTarget.hasChildNodes()) {
const DurationDemo = () => {
return Div({ class: 'flex flex-wrap gap-2 justify-center' }, [
Button({
class: 'btn btn-sm',
onclick: () => Toast('Short (1s)', 'alert-info', 1000)
}, '1 Second'),
Button({
class: 'btn btn-sm',
onclick: () => Toast('Normal (3s)', 'alert-success', 3000)
}, '3 Seconds'),
Button({
class: 'btn btn-sm',
onclick: () => Toast('Long (5s)', 'alert-warning', 5000)
}, '5 Seconds'),
Button({
class: 'btn btn-sm',
onclick: () => Toast('Very Long (8s)', 'alert-error', 8000)
}, '8 Seconds')
]);
};
$mount(DurationDemo, durationTarget);
}
// 3. Interactive Toast
const interactiveTarget = document.querySelector('#demo-interactive');
if (interactiveTarget && !interactiveTarget.hasChildNodes()) {
const InteractiveDemo = () => {
const count = $(0);
const showRandomToast = () => {
const types = ['alert-info', 'alert-success', 'alert-warning', 'alert-error'];
const messages = [
'You clicked the button!',
'Action completed successfully',
'Processing your request...',
'Something interesting happened'
];
const randomType = types[Math.floor(Math.random() * types.length)];
const randomMessage = messages[Math.floor(Math.random() * messages.length)];
count(count() + 1);
Toast(`${randomMessage} (${count()})`, randomType, 2000);
};
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Button({
class: 'btn btn-primary',
onclick: showRandomToast
}, 'Show Random Toast'),
Div({ class: 'text-sm opacity-70' }, () => `Toasts shown: ${count()}`)
]);
};
$mount(InteractiveDemo, interactiveTarget);
}
// 4. Form Validation Toast
const formTarget = document.querySelector('#demo-form');
if (formTarget && !formTarget.hasChildNodes()) {
const FormToastDemo = () => {
const email = $('');
const password = $('');
const handleSubmit = () => {
if (!email()) {
Toast('Please enter your email', 'alert-warning', 3000);
return;
}
if (!email().includes('@')) {
Toast('Please enter a valid email address', 'alert-error', 3000);
return;
}
if (!password()) {
Toast('Please enter your password', 'alert-warning', 3000);
return;
}
if (password().length < 6) {
Toast('Password must be at least 6 characters', 'alert-error', 3000);
return;
}
Toast('Login successful! Redirecting...', 'alert-success', 2000);
};
return Div({ class: 'flex flex-col gap-4 max-w-md mx-auto' }, [
Div({ class: 'text-lg font-bold text-center' }, 'Login Form'),
Input({
label: 'Email',
type: 'email',
value: email,
placeholder: 'user@example.com',
oninput: (e) => email(e.target.value)
}),
Input({
label: 'Password',
type: 'password',
value: password,
placeholder: 'Enter password',
oninput: (e) => password(e.target.value)
}),
Button({
class: 'btn btn-primary',
onclick: handleSubmit
}, 'Login')
]);
};
$mount(FormToastDemo, formTarget);
}
// 5. Success Feedback
const feedbackTarget = document.querySelector('#demo-feedback');
if (feedbackTarget && !feedbackTarget.hasChildNodes()) {
const FeedbackDemo = () => {
const items = $([
{ id: 1, name: 'Item 1', saved: false },
{ id: 2, name: 'Item 2', saved: false },
{ id: 3, name: 'Item 3', saved: false }
]);
const saveItem = (id) => {
items(items().map(item =>
item.id === id ? { ...item, saved: true } : item
));
Toast(`Item ${id} saved successfully!`, 'alert-success', 2000);
};
const saveAll = () => {
items(items().map(item => ({ ...item, saved: true })));
Toast('All items saved!', 'alert-success', 2000);
};
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex justify-between items-center' }, [
Span({ class: 'font-bold' }, 'Items to Save'),
Button({
class: 'btn btn-sm btn-primary',
onclick: saveAll
}, 'Save All')
]),
Div({ class: 'flex flex-col gap-2' }, items().map(item =>
Div({ class: 'flex justify-between items-center p-3 bg-base-200 rounded-lg' }, [
Span({}, item.name),
item.saved
? Span({ class: 'badge badge-success' }, '✓ Saved')
: Button({
class: 'btn btn-xs btn-primary',
onclick: () => saveItem(item.id)
}, 'Save')
])
))
]);
};
$mount(FeedbackDemo, feedbackTarget);
}
// 6. Error Handling
const errorTarget = document.querySelector('#demo-error');
if (errorTarget && !errorTarget.hasChildNodes()) {
const ErrorDemo = () => {
const simulateApiCall = () => {
const success = Math.random() > 0.3;
if (success) {
Toast('Data loaded successfully!', 'alert-success', 2000);
} else {
Toast('Failed to load data. Please try again.', 'alert-error', 3000);
}
};
const simulateNetworkError = () => {
Toast('Network error: Unable to connect to server', 'alert-error', 4000);
};
const simulateTimeout = () => {
Toast('Request timeout (5s). Please check your connection.', 'alert-warning', 4000);
};
return Div({ class: 'flex flex-wrap gap-3 justify-center' }, [
Button({
class: 'btn btn-primary',
onclick: simulateApiCall
}, 'Simulate API Call'),
Button({
class: 'btn btn-error',
onclick: simulateNetworkError
}, 'Network Error'),
Button({
class: 'btn btn-warning',
onclick: simulateTimeout
}, 'Timeout')
]);
};
$mount(ErrorDemo, errorTarget);
}
// 7. Custom Messages
const customTarget = document.querySelector('#demo-custom');
if (customTarget && !customTarget.hasChildNodes()) {
const CustomDemo = () => {
const showCustomToast = (type, message) => {
Toast(message, type, 3000);
};
return Div({ class: 'flex flex-wrap gap-2 justify-center' }, [
Button({
class: 'btn btn-info',
onclick: () => showCustomToast('alert-info', '📧 New email received from john@example.com')
}, 'Email'),
Button({
class: 'btn btn-success',
onclick: () => showCustomToast('alert-success', '💰 Payment of $49.99 completed')
}, 'Payment'),
Button({
class: 'btn btn-warning',
onclick: () => showCustomToast('alert-warning', '⚠️ Your session will expire in 5 minutes')
}, 'Session Warning'),
Button({
class: 'btn btn-error',
onclick: () => showCustomToast('alert-error', '🔒 Failed login attempt detected')
}, 'Security Alert')
]);
};
$mount(CustomDemo, customTarget);
}
// 8. Multiple Toasts
const multipleTarget = document.querySelector('#demo-multiple');
if (multipleTarget && !multipleTarget.hasChildNodes()) {
const MultipleDemo = () => {
const showMultipleToasts = () => {
Toast('First message', 'alert-info', 3000);
setTimeout(() => Toast('Second message', 'alert-success', 3000), 500);
setTimeout(() => Toast('Third message', 'alert-warning', 3000), 1000);
setTimeout(() => Toast('Fourth message', 'alert-error', 3000), 1500);
};
return Div({ class: 'flex justify-center' }, [
Button({
class: 'btn btn-primary',
onclick: showMultipleToasts
}, 'Show Multiple Toasts')
]);
};
$mount(MultipleDemo, multipleTarget);
}
};
initToastExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initToastExamples);
});
}
})();
</script>

View File

@@ -0,0 +1,554 @@
# Tooltip
Tooltip component for displaying helpful hints and additional information on hover.
## Tag
`Tooltip`
## Props
| Prop | Type | Default | Description |
| :----------- | :--------------------------- | :---------- | :----------------------------------------------- |
| `tip` | `string \| VNode \| Signal` | `-` | Tooltip content to display on hover |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
| `children` | `VNode` | `-` | Element to attach the tooltip to |
## Live Examples
### Basic Tooltip
<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-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-4 justify-center"></div>
</div>
</div>
```javascript
const BasicDemo = () => {
return Div({ class: 'flex flex-wrap gap-4 justify-center' }, [
Tooltip({ tip: 'This is a tooltip' }, [
Button({ class: 'btn btn-primary' }, 'Hover me')
]),
Tooltip({ tip: 'Tooltips can be placed on any element' }, [
Span({ class: 'text-sm cursor-help border-b border-dashed' }, 'Help text')
]),
Tooltip({ tip: 'Icons can also have tooltips' }, [
Icons.iconInfo
])
]);
};
$mount(BasicDemo, '#demo-basic');
```
### Tooltip Positions
<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-positions" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-8 justify-center"></div>
</div>
</div>
```javascript
const PositionsDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
Tooltip({ tip: 'Top tooltip', class: 'tooltip-top' }, [
Button({ class: 'btn btn-sm' }, 'Top')
]),
Tooltip({ tip: 'Bottom tooltip', class: 'tooltip-bottom' }, [
Button({ class: 'btn btn-sm' }, 'Bottom')
]),
Tooltip({ tip: 'Left tooltip', class: 'tooltip-left' }, [
Button({ class: 'btn btn-sm' }, 'Left')
]),
Tooltip({ tip: 'Right tooltip', class: 'tooltip-right' }, [
Button({ class: 'btn btn-sm' }, 'Right')
])
]);
};
$mount(PositionsDemo, '#demo-positions');
```
### Tooltip with Icons
<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-icons" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-8 justify-center"></div>
</div>
</div>
```javascript
const IconsDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
Tooltip({ tip: 'Save document' }, [
Button({ class: 'btn btn-ghost btn-circle' }, '💾')
]),
Tooltip({ tip: 'Edit item' }, [
Button({ class: 'btn btn-ghost btn-circle' }, '✏️')
]),
Tooltip({ tip: 'Delete permanently' }, [
Button({ class: 'btn btn-ghost btn-circle text-error' }, '🗑️')
]),
Tooltip({ tip: 'Settings' }, [
Button({ class: 'btn btn-ghost btn-circle' }, '⚙️')
])
]);
};
$mount(IconsDemo, '#demo-icons');
```
### Form Field Tooltips
<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-form" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const FormDemo = () => {
const username = $('');
const email = $('');
return Div({ class: 'flex flex-col gap-4 max-w-md mx-auto' }, [
Div({ class: 'flex items-center gap-2' }, [
Input({
label: 'Username',
value: username,
placeholder: 'Choose a username',
oninput: (e) => username(e.target.value)
}),
Tooltip({ tip: 'Must be at least 3 characters, letters and numbers only' }, [
Span({ class: 'cursor-help text-info' }, '?')
])
]),
Div({ class: 'flex items-center gap-2' }, [
Input({
label: 'Email',
type: 'email',
value: email,
placeholder: 'Enter your email',
oninput: (e) => email(e.target.value)
}),
Tooltip({ tip: 'We\'ll never share your email with anyone' }, [
Span({ class: 'cursor-help text-info' }, '?')
])
])
]);
};
$mount(FormDemo, '#demo-form');
```
### Interactive Tooltip
<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-interactive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const InteractiveDemo = () => {
const showTip = $(false);
const tooltipText = $('Hover over the button!');
const updateTooltip = (text) => {
tooltipText(text);
setTimeout(() => {
tooltipText('Hover over the button!');
}, 2000);
};
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Tooltip({ tip: () => tooltipText() }, [
Button({
class: 'btn btn-primary btn-lg',
onclick: () => Toast('Button clicked!', 'alert-info', 2000)
}, 'Interactive Button')
]),
Div({ class: 'flex gap-2 flex-wrap justify-center mt-4' }, [
Button({
class: 'btn btn-xs',
onclick: () => updateTooltip('You clicked the button!')
}, 'Change Tooltip'),
Button({
class: 'btn btn-xs',
onclick: () => updateTooltip('Try hovering now!')
}, 'Change Again')
])
]);
};
$mount(InteractiveDemo, '#demo-interactive');
```
### Rich Tooltip Content
<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-rich" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-4 justify-center"></div>
</div>
</div>
```javascript
const RichDemo = () => {
return Div({ class: 'flex flex-wrap gap-4 justify-center' }, [
Tooltip({
tip: Div({ class: 'text-left p-1' }, [
Div({ class: 'font-bold' }, 'User Info'),
Div({ class: 'text-xs' }, 'John Doe'),
Div({ class: 'text-xs' }, 'john@example.com'),
Div({ class: 'badge badge-xs badge-primary mt-1' }, 'Admin')
])
}, [
Button({ class: 'btn btn-outline' }, 'User Profile')
]),
Tooltip({
tip: Div({ class: 'text-left p-1' }, [
Div({ class: 'font-bold flex items-center gap-1' }, [
Icons.iconWarning,
Span({}, 'System Status')
]),
Div({ class: 'text-xs' }, 'All systems operational'),
Div({ class: 'text-xs text-success' }, '✓ 99.9% uptime')
])
}, [
Button({ class: 'btn btn-outline' }, 'System Status')
])
]);
};
$mount(RichDemo, '#demo-rich');
```
### Color Variants
<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-colors" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-4 justify-center"></div>
</div>
</div>
```javascript
const ColorsDemo = () => {
return Div({ class: 'flex flex-wrap gap-4 justify-center' }, [
Tooltip({ tip: 'Primary tooltip', class: 'tooltip-primary' }, [
Button({ class: 'btn btn-primary btn-sm' }, 'Primary')
]),
Tooltip({ tip: 'Secondary tooltip', class: 'tooltip-secondary' }, [
Button({ class: 'btn btn-secondary btn-sm' }, 'Secondary')
]),
Tooltip({ tip: 'Accent tooltip', class: 'tooltip-accent' }, [
Button({ class: 'btn btn-accent btn-sm' }, 'Accent')
]),
Tooltip({ tip: 'Info tooltip', class: 'tooltip-info' }, [
Button({ class: 'btn btn-info btn-sm' }, 'Info')
]),
Tooltip({ tip: 'Success tooltip', class: 'tooltip-success' }, [
Button({ class: 'btn btn-success btn-sm' }, 'Success')
]),
Tooltip({ tip: 'Warning tooltip', class: 'tooltip-warning' }, [
Button({ class: 'btn btn-warning btn-sm' }, 'Warning')
]),
Tooltip({ tip: 'Error tooltip', class: 'tooltip-error' }, [
Button({ class: 'btn btn-error btn-sm' }, 'Error')
])
]);
};
$mount(ColorsDemo, '#demo-colors');
```
### All Tooltip Positions
<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-all-positions" class="bg-base-100 p-6 rounded-xl border border-base-300 grid grid-cols-3 gap-4 justify-items-center"></div>
</div>
</div>
```javascript
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' }, [
Button({ class: 'btn btn-sm w-24' }, 'Top')
])
]),
Div({ class: 'col-start-1 row-start-2' }, [
Tooltip({ tip: 'Left tooltip', class: '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' }, [
Button({ class: 'btn btn-sm w-24' }, 'Right')
])
]),
Div({ class: 'col-start-2 row-start-3' }, [
Tooltip({ tip: 'Bottom tooltip', class: 'tooltip-bottom' }, [
Button({ class: 'btn btn-sm w-24' }, 'Bottom')
])
])
]);
};
$mount(AllPositionsDemo, '#demo-all-positions');
```
<script>
(function() {
const initTooltipExamples = () => {
// 1. Basic Tooltip
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
return Div({ class: 'flex flex-wrap gap-4 justify-center' }, [
Tooltip({ tip: 'This is a tooltip' }, [
Button({ class: 'btn btn-primary' }, 'Hover me')
]),
Tooltip({ tip: 'Tooltips can be placed on any element' }, [
Span({ class: 'text-sm cursor-help border-b border-dashed' }, 'Help text')
]),
Tooltip({ tip: 'Icons can also have tooltips' }, [
Icons.iconInfo
])
]);
};
$mount(BasicDemo, basicTarget);
}
// 2. Tooltip Positions
const positionsTarget = document.querySelector('#demo-positions');
if (positionsTarget && !positionsTarget.hasChildNodes()) {
const PositionsDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
Tooltip({ tip: 'Top tooltip', class: 'tooltip-top' }, [
Button({ class: 'btn btn-sm' }, 'Top')
]),
Tooltip({ tip: 'Bottom tooltip', class: 'tooltip-bottom' }, [
Button({ class: 'btn btn-sm' }, 'Bottom')
]),
Tooltip({ tip: 'Left tooltip', class: 'tooltip-left' }, [
Button({ class: 'btn btn-sm' }, 'Left')
]),
Tooltip({ tip: 'Right tooltip', class: 'tooltip-right' }, [
Button({ class: 'btn btn-sm' }, 'Right')
])
]);
};
$mount(PositionsDemo, positionsTarget);
}
// 3. Tooltip with Icons
const iconsTarget = document.querySelector('#demo-icons');
if (iconsTarget && !iconsTarget.hasChildNodes()) {
const IconsDemo = () => {
return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
Tooltip({ tip: 'Save document' }, [
Button({ class: 'btn btn-ghost btn-circle' }, '💾')
]),
Tooltip({ tip: 'Edit item' }, [
Button({ class: 'btn btn-ghost btn-circle' }, '✏️')
]),
Tooltip({ tip: 'Delete permanently' }, [
Button({ class: 'btn btn-ghost btn-circle text-error' }, '🗑️')
]),
Tooltip({ tip: 'Settings' }, [
Button({ class: 'btn btn-ghost btn-circle' }, '⚙️')
])
]);
};
$mount(IconsDemo, iconsTarget);
}
// 4. Form Field Tooltips
const formTarget = document.querySelector('#demo-form');
if (formTarget && !formTarget.hasChildNodes()) {
const FormDemo = () => {
const username = $('');
const email = $('');
return Div({ class: 'flex flex-col gap-4 max-w-md mx-auto' }, [
Div({ class: 'flex items-center gap-2' }, [
Input({
label: 'Username',
value: username,
placeholder: 'Choose a username',
oninput: (e) => username(e.target.value)
}),
Tooltip({ tip: 'Must be at least 3 characters, letters and numbers only' }, [
Span({ class: 'cursor-help text-info' }, '?')
])
]),
Div({ class: 'flex items-center gap-2' }, [
Input({
label: 'Email',
type: 'email',
value: email,
placeholder: 'Enter your email',
oninput: (e) => email(e.target.value)
}),
Tooltip({ tip: 'We\'ll never share your email with anyone' }, [
Span({ class: 'cursor-help text-info' }, '?')
])
])
]);
};
$mount(FormDemo, formTarget);
}
// 5. Interactive Tooltip
const interactiveTarget = document.querySelector('#demo-interactive');
if (interactiveTarget && !interactiveTarget.hasChildNodes()) {
const InteractiveDemo = () => {
const showTip = $(false);
const tooltipText = $('Hover over the button!');
const updateTooltip = (text) => {
tooltipText(text);
setTimeout(() => {
tooltipText('Hover over the button!');
}, 2000);
};
return Div({ class: 'flex flex-col gap-4 items-center' }, [
Tooltip({ tip: () => tooltipText() }, [
Button({
class: 'btn btn-primary btn-lg',
onclick: () => Toast('Button clicked!', 'alert-info', 2000)
}, 'Interactive Button')
]),
Div({ class: 'flex gap-2 flex-wrap justify-center mt-4' }, [
Button({
class: 'btn btn-xs',
onclick: () => updateTooltip('You clicked the button!')
}, 'Change Tooltip'),
Button({
class: 'btn btn-xs',
onclick: () => updateTooltip('Try hovering now!')
}, 'Change Again')
])
]);
};
$mount(InteractiveDemo, interactiveTarget);
}
// 6. Rich Tooltip Content
const richTarget = document.querySelector('#demo-rich');
if (richTarget && !richTarget.hasChildNodes()) {
const RichDemo = () => {
return Div({ class: 'flex flex-wrap gap-4 justify-center' }, [
Tooltip({
tip: Div({ class: 'text-left p-1' }, [
Div({ class: 'font-bold' }, 'User Info'),
Div({ class: 'text-xs' }, 'John Doe'),
Div({ class: 'text-xs' }, 'john@example.com'),
Div({ class: 'badge badge-xs badge-primary mt-1' }, 'Admin')
])
}, [
Button({ class: 'btn btn-outline' }, 'User Profile')
]),
Tooltip({
tip: Div({ class: 'text-left p-1' }, [
Div({ class: 'font-bold flex items-center gap-1' }, [
Icons.iconWarning,
Span({}, 'System Status')
]),
Div({ class: 'text-xs' }, 'All systems operational'),
Div({ class: 'text-xs text-success' }, '✓ 99.9% uptime')
])
}, [
Button({ class: 'btn btn-outline' }, 'System Status')
])
]);
};
$mount(RichDemo, richTarget);
}
// 7. Color Variants
const colorsTarget = document.querySelector('#demo-colors');
if (colorsTarget && !colorsTarget.hasChildNodes()) {
const ColorsDemo = () => {
return Div({ class: 'flex flex-wrap gap-4 justify-center' }, [
Tooltip({ tip: 'Primary tooltip', class: 'tooltip-primary' }, [
Button({ class: 'btn btn-primary btn-sm' }, 'Primary')
]),
Tooltip({ tip: 'Secondary tooltip', class: 'tooltip-secondary' }, [
Button({ class: 'btn btn-secondary btn-sm' }, 'Secondary')
]),
Tooltip({ tip: 'Accent tooltip', class: 'tooltip-accent' }, [
Button({ class: 'btn btn-accent btn-sm' }, 'Accent')
]),
Tooltip({ tip: 'Info tooltip', class: 'tooltip-info' }, [
Button({ class: 'btn btn-info btn-sm' }, 'Info')
]),
Tooltip({ tip: 'Success tooltip', class: 'tooltip-success' }, [
Button({ class: 'btn btn-success btn-sm' }, 'Success')
]),
Tooltip({ tip: 'Warning tooltip', class: 'tooltip-warning' }, [
Button({ class: 'btn btn-warning btn-sm' }, 'Warning')
]),
Tooltip({ tip: 'Error tooltip', class: 'tooltip-error' }, [
Button({ class: 'btn btn-error btn-sm' }, 'Error')
])
]);
};
$mount(ColorsDemo, colorsTarget);
}
// 8. All Tooltip Positions
const allPositionsTarget = document.querySelector('#demo-all-positions');
if (allPositionsTarget && !allPositionsTarget.hasChildNodes()) {
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' }, [
Button({ class: 'btn btn-sm w-24' }, 'Top')
])
]),
Div({ class: 'col-start-1 row-start-2' }, [
Tooltip({ tip: 'Left tooltip', class: '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' }, [
Button({ class: 'btn btn-sm w-24' }, 'Right')
])
]),
Div({ class: 'col-start-2 row-start-3' }, [
Tooltip({ tip: 'Bottom tooltip', class: 'tooltip-bottom' }, [
Button({ class: 'btn btn-sm w-24' }, 'Bottom')
])
])
]);
};
$mount(AllPositionsDemo, allPositionsTarget);
}
};
initTooltipExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initTooltipExamples);
});
}
})();
</script>

View File

@@ -1,42 +1,77 @@
<!DOCTYPE html> <!doctype html>
<html lang="es"> <html lang="es">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<title>SigPro-UI Docs</title> <title>SigPro-UI Docs</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify/lib/themes/vue.css"> <link
rel="stylesheet"
href="//cdn.jsdelivr.net/npm/docsify/lib/themes/vue.css"
/>
<link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css" /> <link
href="./sigpro.css"
rel="stylesheet"
type="text/css"
/>
<!-- <link
href="https://cdn.jsdelivr.net/npm/daisyui@5"
rel="stylesheet"
type="text/css"
/> -->
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script> <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<style>
/* Personaliza los callouts si quieres */
.markdown-section .callout {
margin: 1.5rem 0;
}
</style>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
<script> <script>
window.$docsify = { window.$docsify = {
name: 'SigPro-UI', name: "SigPro-UI",
repo: '', repo: "",
loadSidebar: true, loadSidebar: true,
subMaxLevel: 3,
sidebarDisplayLevel: 1, sidebarDisplayLevel: 1,
executeScript: true, executeScript: true,
copyCode: { copyCode: {
buttonText: '<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>', buttonText: '<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>',
errorText: 'Error', errorText: "Error",
successText: '<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><polyline points="20 6 9 17 4 12"></polyline></svg>' successText: '<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><polyline points="20 6 9 17 4 12"></polyline></svg>',
},
plugins: [
function(hook, vm) {
hook.doneEach(function() {
// Buscamos los bloques de código JS que queremos ejecutar
const codeBlocks = document.querySelectorAll('pre[data-lang="javascript"] code');
codeBlocks.forEach(code => {
// Usamos un bloque try/catch por si el código del Markdown tiene errores
try {
// Ejecutamos el código.
// Como tu librería ya puso $, $mount, Button, etc. en 'window',
// el código los encontrará directamente.
const runDemo = new Function(code.innerText);
runDemo();
} catch (err) {
console.error("Error en la demo de SigPro:", err);
} }
});
});
} }
]
};
</script> </script>
<script type="module"> <script src="//cdn.jsdelivr.net/npm/docsify@4.13.0/lib/docsify.min.js"></script>
import * as sigpro from 'https://cdn.jsdelivr.net/npm/sigpro@latest/+esm';
import * as sigproui from 'https://cdn.jsdelivr.net/npm/sigpro-ui@latest/+esm';
window.sigpro = sigpro;
window.sigproui = sigproui;
</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>
<script src="./sigpro-ui.min.js"></script>
</body> </body>
</html> </html>

View File

@@ -2,81 +2,61 @@
Follow these steps to integrate **SigPro-UI** into your project. Follow these steps to integrate **SigPro-UI** into your project.
## Prerequisites ---
Make sure your project already has:
- [SigPro Core](https://www.npmjs.com/package/sigpro) installed. !> **📘 Core Concepts**
- [Tailwind CSS v4](https://tailwindcss.com/) configured. **Note:** SigPro-UI now includes SigPro core internally. No need to install SigPro separately.
- [daisyUI v5](https://daisyui.com/) installed. 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.
---
## 1.Install the package ## 1. Install the package
Choose your preferred package manager: Choose your preferred package manager:
```bash ```bash
npm install sigpro-ui npm install sigpro-ui
# or
pnpm add sigpro-ui
# or
bun add sigpro-ui
# or
yarn add sigpro-ui
``` ```
## 2.Configure CSS ## 2. Import and use in your app
Add the following to your main CSS file (e.g., `app.css`): ### ESM / Module usage
```css
@import "tailwindcss";
@plugin "daisyui";
```
> This enables Tailwind CSS v4 + daisyUI v5 styles for all SigPro-UI components.
## 3.Import and initialize in your app
Create or update your `main.js` (or `index.js`):
```javascript ```javascript
// Import required modules // Import everything from sigpro-ui (includes sigpro core)
import { $, $mount } from "sigpro"; import { $, $mount, Button, Alert, Input, tt } from "sigpro-ui";
import { UI } from "sigpro-ui"; import "sigpro-ui/css";
// Import your CSS (adjust the path if needed) // Create your app
import "./app.css"; const App = () => {
const count = $(0);
// Import your main App component return $html('div', { class: 'p-8 max-w-md mx-auto' }, [
import { App } from "./App.js"; $html('h1', { class: 'text-2xl font-bold mb-4' }, 'SigProUI Demo'),
// Initialize SigPro-UI (English locale) Input({
UI("en"); placeholder: 'Enter your name...'
}),
Button({
class: 'btn-primary mt-4',
onclick: () => count(count() + 1)
}, () => `Clicks: ${count()}`),
Alert({
type: 'success',
message: () => `Welcome to SigProUI!`
})
]);
};
// Mount your app // Mount your app
$mount(App, "#app"); $mount(App, "#app");
``` ```
## 4.Create your first component ### CDN Usage (no build step)
Example `App.js`: Simply add the script tag and start using SigProUI:
```javascript
import { Div, Button, Alert } from "sigpro-ui";
export const App = () => {
return Div({ class: "p-4" }, [
Button({
class: "btn-primary",
onclick: () => Alert("Hello SigPro-UI!")
}, "Click Me")
]);
};
```
## Optional: Use CDN (no build step)
If you prefer not to use a build step, you can import directly from a CDN:
```html ```html
<!DOCTYPE html> <!DOCTYPE html>
@@ -84,46 +64,115 @@ If you prefer not to use a build step, you can import directly from a CDN:
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<script type="importmap"> <title>SigProUI Demo</title>
{ <script src="https://unpkg.com/sigpro-ui@latest/dist/sigpro-ui.min.js"></script>
"imports": { <link href="https://unpkg.com/sigpro-ui@latest/css/sigpro-ui.min.css" rel="stylesheet">
"sigpro": "https://esm.run/sigpro",
"sigpro-ui": "https://cdn.jsdelivr.net/npm/sigpro-ui@latest/+esm"
}
}
</script>
<style> <style>
@import "tailwindcss"; body { padding: 2rem; }
@plugin "daisyui";
</style> </style>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
<script type="module">
import { $, $mount } from "sigpro";
import { UI, Div, Button, Alert } from "sigpro-ui";
UI("en"); <script>
// All functions are available directly in window
// No need to import anything!
const App = () => Div({ class: "p-4" }, [ const { $, $mount, Button, Input, Alert } = window;
Button({ class: "btn-primary", onclick: () => Alert("Hello!") }, "Click")
const App = () => {
const name = $('');
const count = $(0);
return $html('div', { class: 'max-w-md mx-auto p-4' }, [
$html('h1', { class: 'text-2xl font-bold mb-4' }, 'SigProUI Demo'),
Input({
value: name,
placeholder: 'Enter your name...'
}),
Button({
class: 'btn-primary mt-4',
onclick: () => count(count() + 1)
}, () => `Clicks: ${count()}`),
Alert({
type: 'success',
message: () => name() ? `Hello ${name()}!` : 'Welcome to SigProUI!'
})
]); ]);
};
$mount(App, "#app"); $mount(App, '#app');
</script> </script>
</body> </body>
</html> </html>
``` ```
## What's included?
When you install SigProUI, you get:
### SigPro Core Functions
- `$()` - Reactive signals
- `$watch()` - Watch reactive dependencies
- `$html()` - Create HTML elements with reactivity
- `$if()` - Conditional rendering
- `$for()` - List rendering
- `$router()` - Hash-based routing
- `$mount()` - Mount components to DOM
>For more information about SigPro Core visit official Docs [SigPro Docs](https://natxocc.github.io/sigpro/#/)
### UI Components
- `Button()` - Styled button with DaisyUI
- `Input()` - Form input with floating labels
- `Alert()` - Alert messages
- `Modal()` - Modal dialogs
- `Table()` - Data tables
- `Tabs()` - Tabbed interfaces
- And 30+ more components!
### Utilities
- `tt()` - i18n translation function
## Language Support
SigProUI includes built-in i18n with Spanish and English:
```javascript
import { tt, Locale } from 'sigpro-ui';
// Change locale (default is 'es')
Locale('en');
// Use translations
const closeButton = Button({}, tt('close')());
const searchPlaceholder = Input({ placeholder: tt('search')() });
```
## TypeScript Support
SigProUI includes TypeScript definitions. Import types as needed:
```typescript
import { Button, Input, type ButtonProps, type InputProps } from 'sigpro-ui';
const MyButton: React.FC<ButtonProps> = (props) => {
return Button(props, 'Click me');
};
```
## Troubleshooting ## Troubleshooting
| Problem | Solution | | Problem | Solution |
| :--- | :--- | | :--- | :--- |
| Components don't look styled | Check that `app.css` is imported and contains the Tailwind + daisyUI directives. | | Components don't look styled | Make sure you're loading css 'sigpro.css' (either via import or CDN)|
| `UI is not defined` | Make sure you call `UI()` **before** using any component like `Button`, `Input`, etc. | | `$ is not defined` | SigProUI includes sigpro core - no need to install separately. Make sure you're importing from 'sigpro-ui' |
| Locale not working | Verify you passed a valid locale (`"es"` or `"en"`) to `UI()`. | | `Button is not defined` | In CDN mode, all components are in window. Use `window.Button` or destructure from window |
| Build errors with Tailwind v4 | Ensure Tailwind CSS v4 and daisyUI v5 are correctly installed and configured. | | Build errors | Ensure you're using a modern bundler that supports ESM |
| CDN components not working | The CDN version exposes everything globally. Use `const { $, Button } = window;` or call directly |
| Locale not working | Set locale with `Locale('en')` before using translations |
**Happy coding!** 🎉 **Happy coding!** 🎉

View File

@@ -1,17 +1,13 @@
# SigPro-UI Quick Reference # SigPro-UI Quick Reference
**Version:** daisyUI v5 + Tailwind v4 Plugin
**Status:** Active / WIP **Status:** Active / WIP
> **Prerequisites:** Tailwind CSS v4+ and DaisyUI v5+ installed.
## Global Initialization ## Global Initialization
```javascript ```javascript
import { UI } from 'sigpro-ui'; import "sigpro-ui";
import "sigpro-ui/css";
// Injects all components into window and sets default language
UI('en'); // 'es' or 'en'
// All components (Button, Input, Table, Toast, etc.) are now globally available. // All components (Button, Input, Table, Toast, etc.) are now globally available.
``` ```
@@ -23,14 +19,13 @@ UI('en'); // 'es' or 'en'
| Component | Purpose | Basic Example | | Component | Purpose | Basic Example |
| :--- | :--- | :--- | | :--- | :--- | :--- |
| **Button** | Styled button with DaisyUI | `Button({ class: "btn-primary" }, "Submit")` | | **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 })` | | **Select** | Dropdown selection menu | `Select({ options: ["Admin", "User"], value: $role })` |
| **Checkbox** | Binary toggle (boolean) | `Checkbox({ label: "Active", checked: $isActive })` | | **Checkbox** | Binary toggle (boolean) | `Checkbox({ label: "Active", checked: $isActive })` |
| **Table** | Data grid with column rendering | `Table({ items: $data, columns: [...] })` | | **Table** | Data grid with column rendering | `Table({ items: $data, columns: [...] })` |
| **Modal** | Overlay dialog controlled by a Signal | `Modal({ open: $show, title: "Alert" }, "Message")` | | **Modal** | Overlay dialog controlled by a Signal | `Modal({ open: $show, title: "Alert" }, "Message")` |
| **Badge** | Small status indicator or tag | `Badge({ class: "badge-outline" }, "Beta")` | | **Badge** | Small status indicator or tag | `Badge({ class: "badge-outline" }, "Beta")` |
| **Alert** | Contextual notification | `Alert({ type: "info" }, "Update available")` | | **Alert** | Contextual notification | `Alert({ type: "info" }, "Update available")` |
| **Loading** | Loading indicators (spinner, dots) | `Loading({ show: $isLoading })` |
| **Dropdown** | Contextual overlay menu | `Dropdown({ label: "Menu" }, [Link1, Link2])` | | **Dropdown** | Contextual overlay menu | `Dropdown({ label: "Menu" }, [Link1, Link2])` |
| **Tabs** | Reactive tab-based navigation | `Tabs({ items: ["Home", "Settings"], active: $index })` | | **Tabs** | Reactive tab-based navigation | `Tabs({ items: ["Home", "Settings"], active: $index })` |
| **Stat** | Statistical data block (KPIs) | `Stat({ label: "Sales", value: "$400" })` | | **Stat** | Statistical data block (KPIs) | `Stat({ label: "Sales", value: "$400" })` |
@@ -42,7 +37,7 @@ UI('en'); // 'es' or 'en'
| Component | Description | Example | | 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 })` | | **Select** | Dropdown selector | `Select({ label: "Role", options: ["Admin", "User"], value: $role })` |
| **Autocomplete** | Searchable dropdown with filtering | `Autocomplete({ label: "Country", options: countryList, value: $country })` | | **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 })` | | **Datepicker** | Date picker (single or range mode) | `Datepicker({ label: "Date", value: $date, range: false })` |
@@ -55,6 +50,36 @@ UI('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 ## Data Display
| Component | Description | Example | | Component | Description | Example |
@@ -65,7 +90,7 @@ UI('en'); // 'es' or 'en'
| **Stat** | Statistical data blocks (KPIs) | `Stat({ label: "Total", value: "1.2k", desc: "Monthly" })` | | **Stat** | Statistical data blocks (KPIs) | `Stat({ label: "Total", value: "1.2k", desc: "Monthly" })` |
| **Timeline** | Vertical/horizontal timeline | `Timeline({ items: [{ title: "Step 1", detail: "Completed" }] })` | | **Timeline** | Vertical/horizontal timeline | `Timeline({ items: [{ title: "Step 1", detail: "Completed" }] })` |
| **Stack** | Stacked elements | `Stack({}, [Card1, Card2, Card3])` | | **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(...))` |
--- ---
@@ -76,8 +101,7 @@ UI('en'); // 'es' or 'en'
| **Alert** | Inline contextual notification | `Alert({ type: "success" }, "Changes saved!")` | | **Alert** | Inline contextual notification | `Alert({ type: "success" }, "Changes saved!")` |
| **Modal** | Dialog overlay | `Modal({ open: $isOpen, title: "Confirm" }, "Are you sure?")` | | **Modal** | Dialog overlay | `Modal({ open: $isOpen, title: "Confirm" }, "Are you sure?")` |
| **Toast** | Floating notification (auto-stacking) | `Toast("Action completed", "alert-info", 3000)` | | **Toast** | Floating notification (auto-stacking) | `Toast("Action completed", "alert-info", 3000)` |
| **Loading** | Full-screen or inline loading indicator | `Loading({ show: $isLoading })` | | **Tooltip** | Hover tooltip wrapper | `Tooltip({ tip: "Help text", ui: "tooltip-top" }, Button(...))` |
| **Tooltip** | Hover tooltip wrapper | `Tooltip({ tip: "Help text" }, Button(...))` |
--- ---
@@ -100,7 +124,7 @@ UI('en'); // 'es' or 'en'
| Component | Description | Example | | Component | Description | Example |
| :--- | :--- | :--- | | :--- | :--- | :--- |
| **Fab** | Floating Action Button with actions | `Fab({ icon: "+", actions: [{ label: "Add", onclick: add }] })` | | **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(...))` |
--- ---
@@ -110,12 +134,11 @@ Built-in locale system with Spanish and English support.
```javascript ```javascript
// Set global UI language // Set global UI language
Locale("en"); // or "es" Locale("en");
// Get translated string (returns a reactive signal) // Get translated string (returns a reactive signal)
const closeText = tt("close"); // "Close" or "Cerrar" const closeText = tt("close"); // "Close" or "Cerrar"
// Available keys: close, confirm, cancel, search, loading, nodata
``` ```
--- ---
@@ -124,7 +147,7 @@ const closeText = tt("close"); // "Close" or "Cerrar"
1. **Atomic Reactivity**: Components accepting `value` or `checked` bind directly to **SigPro Signals**. Updates happen instantly without full page re-renders. 1. **Atomic Reactivity**: Components accepting `value` or `checked` bind directly to **SigPro Signals**. Updates happen instantly without full page re-renders.
2. **Tailwind v4 + DaisyUI v5**: Pass any Tailwind utility class via the `class` attribute to override default DaisyUI styling. 2. **DaisyUI v5**: Pass any daisyUI styling via the `class` attribute.
```javascript ```javascript
Button({ class: "btn-primary btn-sm rounded-full shadow-lg" }, "Click") Button({ class: "btn-primary btn-sm rounded-full shadow-lg" }, "Click")
@@ -142,16 +165,16 @@ const closeText = tt("close"); // "Close" or "Cerrar"
```javascript ```javascript
const name = $(""); const name = $("");
const error = $(null);
Input({ Input({
label: "Full Name",
value: name, value: name,
error: error, placeholder: "Name",
oninput: (e) => { validate: (value) => {
name(e.target.value); if (!value) return "Name is required";
error(e.target.value.length < 3 ? "Name too short" : null); if (value.length < 3) return "Name too short";
} return "";
},
oninput: (e) => name(e.target.value)
}) })
``` ```
@@ -160,7 +183,6 @@ Input({
```javascript ```javascript
const userId = $("123"); const userId = $("123");
const userData = $(null); const userData = $(null);
const loading = $(false);
$watch(userId, async (id) => { $watch(userId, async (id) => {
loading(true); loading(true);
@@ -169,7 +191,6 @@ $watch(userId, async (id) => {
}); });
// In template // In template
Loading({ show: loading });
$if(() => userData(), () => Alert({ type: "success" }, userData()?.name)) $if(() => userData(), () => Alert({ type: "success" }, userData()?.name))
``` ```
@@ -193,14 +214,14 @@ Modal({
| Component | Key Props | | Component | Key Props |
| :--- | :--- | | :--- | :--- |
| `Button` | `class`, `disabled`, `loading`, `badge`, `tooltip`, `icon` | | `Button` | `class`, `disabled`, `loading`, `icon` |
| `Input` | `label`, `value`, `error`, `type`, `placeholder`, `disabled`, `tip` | | `Input` | `value`, `validate`, `type`, `placeholder`, `icon`, `disabled` |
| `Select` | `label`, `options`, `value`, `disabled` | | `Select` | `label`, `options`, `value`, `disabled` |
| `Modal` | `open`, `title`, `buttons` | | `Modal` | `open`, `title`, `buttons` |
| `Table` | `items`, `columns`, `zebra`, `pinRows`, `empty` | | `Table` | `items`, `columns`, `zebra`, `pinRows`, `empty` |
| `Alert` | `type` (info/success/warning/error), `soft`, `actions` | | `Alert` | `type` (info/success/warning/error), `soft`, `actions` |
| `Toast` | `message`, `type`, `duration` | | `Toast` | `message`, `type`, `duration` |
| `Loading` | `show` |
| `Datepicker` | `value`, `range`, `label`, `placeholder` | | `Datepicker` | `value`, `range`, `label`, `placeholder` |
| `Autocomplete` | `options`, `value`, `onSelect`, `label` | | `Autocomplete` | `options`, `value`, `onSelect`, `label` |
| `Indicator` | `value` (function that returns number/string) |
| `Tooltip` | `tip`, `ui` (tooltip-top/bottom/left/right) |

7
docs/sigpro-ui.min.js vendored Normal file

File diff suppressed because one or more lines are too long

2
docs/sigpro.css Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +1,25 @@
// /plugins/index.js // index.js
export * from './sigpro-ui/sigpro-ui.js'; import './src/sigpro.js';
// import './src/css/sigpro.css'; // No importes CSS en JS
import * as Components from './src/components/index.js';
// import * as Icons from './src/core/icons.js'; // ELIMINAR
import * as Utils from './src/core/utils.js';
import { tt } from './src/core/i18n.js';
export * from './src/components/index.js';
// export * from './src/core/icons.js'; // ELIMINAR
export * from './src/core/utils.js';
export { tt };
if (typeof window !== 'undefined') {
Object.entries(Components).forEach(([name, component]) => {
window[name] = component;
});
// window.Icons = Icons; // ELIMINAR
window.Utils = Utils;
window.tt = tt;
window.SigProUI = { ...Components, Utils, tt };
console.log("🎨 SigProUI ready");
}

View File

@@ -1,20 +1,25 @@
{ {
"name": "sigpro-ui", "name": "sigpro-ui",
"version": "1.0.0", "version": "1.1.1",
"type": "module",
"license": "MIT",
"main": "./index.js", "main": "./index.js",
"module": "./index.js", "module": "./index.js",
"exports": { "devDependencies": {
".": "./index.js" "@iconify/json": "^2.2.458",
"@iconify/tailwind4": "^1.2.3",
"@tailwindcss/cli": "^4.0.0",
"daisyui": "^5.5.19",
"tailwindcss": "^4.2.2"
},
"exports": {
".": {
"import": "./index.js",
"script": "./dist/sigpro-ui.js"
},
"./css": {
"import": "./css/index.js",
"default": "./css/index.js"
}
}, },
"files": [
"index.js",
"sigpro-ui",
"vite",
"README.md",
"LICENSE"
],
"homepage": "https://natxocc.github.io/sigpro-ui/", "homepage": "https://natxocc.github.io/sigpro-ui/",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -23,19 +28,25 @@
"bugs": { "bugs": {
"url": "https://github.com/natxocc/sigpro-ui/issues" "url": "https://github.com/natxocc/sigpro-ui/issues"
}, },
"files": [
"index.js",
"dist",
"css",
"README.md",
"LICENSE"
],
"jsdelivr": "./dist/sigpro-ui.min.js",
"license": "MIT",
"scripts": { "scripts": {
"build:cssmin": "./node_modules/.bin/tailwindcss -i ./src/css/sigpro.css -o ./css/sigpro.min.css --content './src/**/*.js' --minify",
"build:css": "./node_modules/.bin/tailwindcss -i ./src/css/sigpro.css -o ./css/sigpro.css --content './src/**/*.js'",
"build:cssdocs": "./node_modules/.bin/tailwindcss -i ./src/css/sigpro.css -o ./docs/sigpro.css --content './src/**/*.js' --minify",
"build:js": "bun build ./index.js --bundle --outfile=./dist/sigpro-ui.js --format=iife --global-name=SigProUI && bun build ./index.js --bundle --outfile=./dist/sigpro-ui.min.js --format=iife --global-name=SigProUI --minify",
"build:jsdocs": "bun build ./index.js --bundle --outfile=./docs/sigpro-ui.min.js --format=iife --global-name=SigProUI --minify",
"build": "bun run build:css && bun run build:js && bun run build:jsdocs && bun run build:cssdocs && bun run build:cssmin",
"prepublishOnly": "bun run build",
"docs": "bun x serve docs" "docs": "bun x serve docs"
}, },
"keywords": [ "type": "module",
"signals", "unpkg": "./dist/sigpro-ui.min.js"
"reactive",
"sigpro",
"sigpro components",
"UI",
"web-components",
"vanilla-js",
"reactive-programming",
"signals-library",
"fine-grained-reactivity"
]
} }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
// components/Accordion.js
import { $html } from "../sigpro.js";
import { ui, val } from "../core/utils.js";
/**
* Accordion component
*
* daisyUI classes used:
* - collapse, collapse-arrow, collapse-plus, collapse-title, collapse-content
* - collapse-open, collapse-close
* - bg-base-200, bg-base-100, bg-primary, bg-secondary
* - mb-2, mt-2, rounded-box
*/
export const Accordion = (props, children) => {
const { class: className, title, name, open, ...rest } = props;
return $html("div", {
...rest,
class: ui('collapse collapse-arrow bg-base-200 mb-2', className),
}, [
$html("input", {
type: name ? "radio" : "checkbox",
name: name,
checked: val(open),
}),
$html("div", { class: "collapse-title text-xl font-medium" }, title),
$html("div", { class: "collapse-content" }, children),
]);
};

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

@@ -0,0 +1,42 @@
// components/Alert.js
import { $html } from "../sigpro.js";
import { ui, getIcon } from "../core/utils.js";
/**
* Alert component
*
* daisyUI classes used:
* - alert, alert-info, alert-success, alert-warning, alert-error
* - alert-soft, alert-outline, alert-dash
*/
export const Alert = (props, children) => {
const { class: className, actions, type = 'info', soft = true, ...rest } = props;
const iconMap = {
info: "icon-[lucide--info]",
success: "icon-[lucide--check-circle]",
warning: "icon-[lucide--alert-triangle]",
error: "icon-[lucide--alert-circle]",
};
// Build the complete class string
const typeClass = `alert-${type}`;
const softClass = soft ? 'alert-soft' : '';
const allClasses = [typeClass, softClass, className].filter(Boolean).join(' ');
const content = children || props.message;
return $html("div", {
...rest,
role: "alert",
class: ui('alert', allClasses),
}, () => [
getIcon(iconMap[type]),
$html("div", { class: "flex-1" }, [
$html("span", {}, [typeof content === "function" ? content() : content])
]),
actions ? $html("div", { class: "flex-none" }, [
typeof actions === "function" ? actions() : actions
]) : null,
].filter(Boolean));
};

View File

@@ -0,0 +1,106 @@
// components/Autocomplete.js
import { $, $html, $for } from "../sigpro.js";
import { val } from "../core/utils.js";
import { tt } from "../core/i18n.js";
import { Input } from "./Input.js";
/**
* Autocomplete component
*
* daisyUI classes used:
* - input, input-bordered, input-primary, input-secondary
* - menu, menu-dropdown, menu-dropdown-show
* - bg-base-100, rounded-box, shadow-xl, border, border-base-300
* - absolute, left-0, w-full, mt-1, p-2, max-h-60, overflow-y-auto
* - z-50, active, bg-primary, text-primary-content
*/
export const Autocomplete = (props) => {
const { class: className, items = [], 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(items) || [];
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,
class: className,
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" }, tt("nodata")())),
],
),
]);
};

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

@@ -0,0 +1,21 @@
// components/Badge.js
import { $html } from "../sigpro.js";
import { ui } from "../core/utils.js";
/**
* Badge component
*
* daisyUI classes used:
* - badge, badge-primary, badge-secondary, badge-accent
* - badge-info, badge-success, badge-warning, badge-error
* - badge-outline, badge-soft, badge-dash
* - badge-xs, badge-sm, badge-md, badge-lg
*/
export const Badge = (props, children) => {
const { class: className, ...rest } = props;
return $html("span", {
...rest,
class: ui('badge', className),
}, children);
};

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

@@ -0,0 +1,30 @@
// components/Button.js
import { $html } from "../sigpro.js";
import { ui, val, getIcon } from "../core/utils.js";
/**
* Button component
*
* daisyUI classes used:
* - btn, btn-primary, btn-secondary, btn-accent, btn-ghost
* - btn-info, btn-success, btn-warning, btn-error, btn-neutral
* - btn-xs, btn-sm, btn-md, btn-lg, btn-xl
* - btn-outline, btn-soft, btn-dash, btn-link
* - btn-circle, btn-square, btn-wide, btn-block
* - btn-active, btn-disabled
*/
export const Button = (props, children) => {
const { class: className, loading, icon, ...rest } = props;
const iconEl = getIcon(icon);
return $html("button", {
...rest,
class: ui('btn', className),
disabled: () => val(loading) || val(props.disabled),
}, () => [
val(loading) && $html("span", { class: "loading loading-spinner" }),
iconEl,
children
].filter(Boolean));
};

View File

@@ -0,0 +1,32 @@
// components/Checkbox.js
import { $html } from "../sigpro.js";
import { val, ui } from "../core/utils.js";
/**
* Checkbox component
*
* daisyUI classes used:
* - checkbox, checkbox-primary, checkbox-secondary, checkbox-accent
* - checkbox-info, checkbox-success, checkbox-warning, checkbox-error
* - checkbox-xs, checkbox-sm, checkbox-md, checkbox-lg
* - toggle, toggle-primary, toggle-secondary, toggle-accent
* - toggle-xs, toggle-sm, toggle-md, toggle-lg
* - label, label-text, cursor-pointer
*/
export const Checkbox = (props) => {
const { class: className, value, tooltip, toggle, label, ...rest } = props;
const checkEl = $html("input", {
...rest,
type: "checkbox",
class: () => ui(val(toggle) ? "toggle" : "checkbox", className),
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,92 @@
// components/Colorpicker.js
import { $, $html, $if } from "../sigpro.js";
import { val, ui } from "../core/utils.js";
/**
* Colorpicker component
*
* daisyUI classes used:
* - btn, btn-primary, btn-secondary, btn-accent, btn-ghost
* - bg-base-100, border, border-base-300, shadow-sm, shadow-2xl
* - rounded-box, rounded-sm, fixed, inset-0
* - z-50, z-110, absolute, left-0, mt-2, p-3, w-64
* - grid, grid-cols-8, gap-1, ring, ring-offset-1, ring-primary
* - scale-110, transition-all, hover:scale-125, active:scale-95
*/
export const Colorpicker = (props) => {
const { class: className, 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: ui('relative w-fit', className) }, [
$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,258 @@
// components/Datepicker.js
import { $, $html, $if } from "../sigpro.js";
import { val, ui, getIcon } from "../core/utils.js";
import { Input } from "./Input.js";
/**
* Datepicker component
*
* daisyUI classes used:
* - input, input-bordered, input-primary
* - btn, btn-ghost, btn-xs, btn-circle
* - bg-base-100, border, border-base-300, shadow-2xl, rounded-box
* - absolute, left-0, mt-2, p-4, w-80, z-100, z-90
* - grid, grid-cols-7, gap-1, text-center
* - ring, ring-primary, ring-inset, font-black
* - range, range-xs
* - tooltip, tooltip-top, tooltip-bottom
*/
export const Datepicker = (props) => {
const { class: className, 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: ui('relative w-full', className) }, [
Input({
label,
placeholder: placeholder || (isRangeMode() ? "Seleccionar rango..." : "Seleccionar fecha..."),
value: displayValue,
readonly: true,
icon: getIcon("icon-[lucide--calendar]"),
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) },
getIcon("icon-[lucide--chevrons-left]")
),
$html("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(-1) },
getIcon("icon-[lucide--chevron-left]")
),
]),
$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) },
getIcon("icon-[lucide--chevron-right]")
),
$html("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(1) },
getIcon("icon-[lucide--chevrons-right]")
),
]),
]),
$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) })),
]);
};

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

@@ -0,0 +1,46 @@
// components/Drawer.js
import { $html } from "../sigpro.js";
import { ui } from "../core/utils.js";
/**
* Drawer component
*
* daisyUI classes used:
* - drawer, drawer-toggle, drawer-content, drawer-side, drawer-overlay
* - bg-base-200, w-80, min-h-full
*/
export const Drawer = (props, children) => {
const { class: className, id, open, side, content, ...rest } = props;
const drawerId = id || `drawer-${Math.random().toString(36).slice(2, 9)}`;
return $html("div", {
...rest,
class: ui('drawer', className),
}, [
$html("input", {
id: drawerId,
type: "checkbox",
class: "drawer-toggle",
checked: () => typeof open === "function" ? open() : open,
onchange: (e) => {
if (typeof open === "function") open(e.target.checked);
}
}),
$html("div", { class: "drawer-content" }, [
typeof content === "function" ? content() : content
]),
$html("div", { class: "drawer-side" }, [
$html("label", {
for: drawerId,
class: "drawer-overlay",
onclick: () => {
if (typeof open === "function") open(false);
}
}),
$html("div", { class: "min-h-full bg-base-200 w-80" }, [
typeof side === "function" ? side() : side
])
])
]);
};

View File

@@ -0,0 +1,78 @@
// components/Dropdown.js
// import { $html, $for, $watch } from "../sigpro.js";
import { ui } from "../core/utils.js";
/**
* Dropdown component - Solo soporta menús (items)
*
* daisyUI classes used:
* - dropdown, dropdown-content, dropdown-end, dropdown-top, dropdown-bottom
* - menu, btn
* - bg-base-100, shadow, rounded-box, border
* - z-50, p-2, w-52
* - m-1, flex, items-center, gap-2
*/
let currentOpen = null;
if (typeof window !== 'undefined' && !window.__dropdownHandlerRegistered) {
window.addEventListener('click', (e) => {
if (currentOpen && !currentOpen.contains(e.target)) {
currentOpen.open = false;
currentOpen = null;
}
});
window.__dropdownHandlerRegistered = true;
}
export const Dropdown = (props) => {
const { class: className, label, icon, items, ...rest } = props;
return $html("details", {
...rest,
class: ui('dropdown', className),
}, [
$html("summary", {
class: "btn m-1 flex items-center gap-2 list-none cursor-pointer",
style: "display: inline-flex;",
onclick: (e) => {
const details = e.currentTarget.closest('details');
if (currentOpen && currentOpen !== details) {
currentOpen.open = false;
}
setTimeout(() => {
currentOpen = details.open ? details : null;
}, 0);
}
}, [
() => icon ? (typeof icon === "function" ? icon() : icon) : null,
() => label ? (typeof label === "function" ? label() : label) : null
]),
$html("ul", {
tabindex: "-1",
class: "dropdown-content z-[50] menu p-2 shadow bg-base-100 rounded-box w-52 border border-base-300"
}, [
() => {
const currentItems = typeof items === "function" ? items() : (items || []);
return currentItems.map(item =>
$html("li", {}, [
$html("a", {
class: item.class || "",
onclick: (e) => {
if (item.onclick) item.onclick(e);
const details = e.currentTarget.closest('details');
if (details) {
details.open = false;
if (currentOpen === details) currentOpen = null;
}
}
}, [
item.icon ? $html("span", {}, item.icon) : null,
$html("span", {}, item.label)
])
])
);
}
])
]);
};

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

@@ -0,0 +1,58 @@
// components/Fab.js
import { $html } from "../sigpro.js";
import { val, ui, getIcon } from "../core/utils.js";
/**
* Fab (Floating Action Button) component
*
* daisyUI classes used:
* - btn, btn-lg, btn-circle, btn-primary
* - shadow-2xl, shadow-lg
* - badge, badge-ghost, shadow-sm, whitespace-nowrap
* - absolute, flex, flex-col-reverse, items-end, gap-3
* - z-100, transition-all, duration-300
* - bottom-6, right-6, top-6, left-6
*/
export const Fab = (props) => {
const { class: className, icon, label, actions = [], position = "bottom-6 right-6", ...rest } = props;
return $html(
"div",
{
...rest,
class: ui(`fab absolute ${position} flex flex-col-reverse items-end gap-3 z-[100]`, className),
},
[
$html(
"div",
{
tabindex: 0,
role: "button",
class: "btn btn-lg btn-circle btn-primary shadow-2xl",
},
[
icon ? getIcon(icon) : null,
!icon && label ? label : null
],
),
...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 ? getIcon(act.icon) : act.text || ""],
),
]),
),
],
);
};

View File

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

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

@@ -0,0 +1,130 @@
// components/Fileinput.js
import { $, $html, $if, $for } from "../sigpro.js";
import { ui, getIcon } from "../core/utils.js";
/**
* Fileinput component
*
* daisyUI classes used:
* - fieldset, w-full, p-0
* - tooltip, tooltip-top, before:z-50, after:z-50
* - relative, flex, items-center, justify-between, w-full, h-12, px-4
* - border-2, border-dashed, rounded-lg, cursor-pointer, transition-all, duration-200
* - border-primary, bg-primary/10, border-base-content/20, bg-base-100, hover:bg-base-200
* - text-sm, opacity-70, truncate, grow, text-left
* - text-[10px], opacity-40, shrink-0
* - text-error, text-[10px], mt-1, px-1, font-medium
* - mt-2, space-y-1
* - flex, items-center, justify-between, p-1.5, pl-3, text-xs, bg-base-200/50, rounded-md, border, border-base-300
* - gap-2, truncate, opacity-50, font-medium, max-w-[200px]
* - btn, btn-ghost, btn-xs, btn-circle
*/
export const Fileinput = (props) => {
const { class: className, tooltip, max = 2, accept = "*", onSelect, ...rest } = 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", { ...rest, class: ui('fieldset w-full p-0', className) }, [
$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" }, [
getIcon("icon-[lucide--upload]"),
$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);
},
},
[getIcon("icon-[lucide--x]")]
),
]),
(file) => file.name + file.lastModified,
),
]),
),
]);
};

View File

@@ -0,0 +1,28 @@
// components/Indicator.js
import { $html } from "../sigpro.js";
import { ui } from "../core/utils.js";
/**
* Indicator component
*
* daisyUI classes used:
* - indicator, indicator-item
* - badge, badge-primary, badge-secondary, badge-accent
* - badge-info, badge-success, badge-warning, badge-error
* - badge-outline, badge-soft, badge-dash
* - badge-xs, badge-sm, badge-md, badge-lg
*/
export const Indicator = (props, children) => {
const { value, class: className, ...rest } = props;
return $html("div", {
...rest,
class: "indicator"
}, () => [
// El indicador debe ir PRIMERO (antes que el children)
value ? $html("span", {
class: ui('indicator-item badge', className)
}, () => typeof value === "function" ? value() : value) : null,
children
].filter(Boolean));
};

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

@@ -0,0 +1,110 @@
// components/Input.js
import { $, $html, $watch } from "../sigpro.js";
import { val, ui, getIcon } from "../core/utils.js";
/**
* Input component - Solo el input con ícono integrado a la izquierda
*
* daisyUI classes used:
* - input, input-bordered, input-ghost, input-primary, input-secondary
* - input-accent, input-info, input-success, input-warning, input-error
* - input-xs, input-sm, input-md, input-lg
* - btn, btn-ghost, btn-xs, btn-sm, btn-md, btn-circle, opacity-50, hover:opacity-100
*/
export const Input = (props) => {
const {
class: className,
value,
type = "text",
icon,
oninput,
placeholder,
disabled,
size,
validate,
...rest
} = props;
const isPassword = type === "password";
const visible = $(false);
const errorMsg = $(null);
const iconMap = {
text: "icon-[lucide--text]",
password: "icon-[lucide--lock]",
date: "icon-[lucide--calendar]",
number: "icon-[lucide--hash]",
email: "icon-[lucide--mail]",
search: "icon-[lucide--search]",
tel: "icon-[lucide--phone]",
url: "icon-[lucide--link]",
};
const leftIcon = icon ? getIcon(icon) : (iconMap[type] ? getIcon(iconMap[type]) : null);
const getPasswordIcon = () => getIcon(visible() ? "icon-[lucide--eye-off]" : "icon-[lucide--eye]");
const paddingLeft = leftIcon ? "pl-10" : "";
const paddingRight = isPassword ? "pr-10" : "";
const buttonSize = () => {
if (className?.includes('input-xs')) return 'btn-xs';
if (className?.includes('input-sm')) return 'btn-sm';
if (className?.includes('input-lg')) return 'btn-lg';
return 'btn-md';
};
const handleInput = (e) => {
const newValue = e.target.value;
if (validate) {
const result = validate(newValue);
errorMsg(result || null);
}
oninput?.(e);
};
const hasError = () => errorMsg() && errorMsg() !== '';
const inputClasses = () => {
let classes = `input w-full ${paddingLeft} ${paddingRight}`;
if (className) classes += ` ${className}`;
if (hasError()) classes += ' input-error';
return classes.trim();
};
const inputElement = $html("input", {
...rest,
type: () => (isPassword ? (visible() ? "text" : "password") : type),
placeholder: placeholder || " ",
class: inputClasses,
value: value,
oninput: handleInput,
disabled: () => val(disabled),
"aria-invalid": () => hasError() ? "true" : "false",
});
return $html(
"div",
{ class: "relative w-full" },
() => [
inputElement,
leftIcon ? $html("div", {
class: "absolute left-3 inset-y-0 flex items-center pointer-events-none text-base-content/60",
}, leftIcon) : null,
isPassword ? $html("button", {
type: "button",
class: ui(
"absolute right-3 inset-y-0 flex items-center",
"btn btn-ghost btn-circle opacity-50 hover:opacity-100",
buttonSize()
),
onclick: (e) => {
e.preventDefault();
visible(!visible());
}
}, () => getPasswordIcon()) : null,
$html("div", {
class: "text-error text-xs mt-1 px-3 absolute -bottom-5 left-0",
}, () => hasError() ? errorMsg() : null),
]
);
};

28
src/components/Label.js Normal file
View File

@@ -0,0 +1,28 @@
// components/Label.js
import { $, $html } from "../sigpro.js";
import { ui, val } from "../core/utils.js";
/**
* Label component
*
* daisyUI classes used:
* - label
* - floating-label
*/
export const Label = (props) => {
const { children, value, floating = false, error, required, class: className, ...rest } = props;
if (floating) {
return $html("label", { class: ui("floating-label w-full", className), ...rest }, () => [
value ? $html("span", {}, value) : null,
children,
error ? $html("span", { class: "text-error text-xs" }, val(error)) : null,
]);
}
return $html("label", { class: ui("input w-full", className), ...rest }, () => [
value ? $html("span", { class: "label" }, value) : null,
children,
error ? $html("span", { class: "text-error text-xs" }, val(error)) : null,
]);
};

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

@@ -0,0 +1,27 @@
// components/List.js
import { $html, $if, $for } from "../sigpro.js";
import { ui, val } from "../core/utils.js";
/**
* List component
*
* daisyUI classes used:
* - list, list-row, list-bullet, list-image, list-none
* - bg-base-100, rounded-box, shadow-md
* - p-4, pb-2, text-xs, opacity-60
* - flex, items-center, gap-2
*/
export const List = (props) => {
const { class: className, items, header, render, keyFn = (item, index) => item.id ?? index, ...rest } = props;
const listItems = $for(
items,
(item, index) => $html("li", { class: "list-row" }, [render(item, index)]),
keyFn
);
return $html("ul", {
...rest,
class: ui('list bg-base-100 rounded-box shadow-md', className),
}, header ? [$if(header, () => $html("li", { class: "p-4 pb-2 text-xs opacity-60" }, [val(header)])), listItems] : listItems);
};

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

@@ -0,0 +1,36 @@
// components/Menu.js
import { $html, $for } from "../sigpro.js";
import { val, ui } from "../core/utils.js";
/**
* Menu component
*
* daisyUI classes used:
* - menu, menu-dropdown, menu-dropdown-show
* - bg-base-200, rounded-box
* - details, summary, ul, li, a
* - mr-2, active
*/
export const Menu = (props) => {
const { class: className, items, ...rest } = 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", { ...rest, class: ui('menu bg-base-200 rounded-box', className) }, renderItems(items));
};

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

@@ -0,0 +1,65 @@
// components/Modal.js
import { $html, $watch } from "../sigpro.js";
import { ui } from "../core/utils.js";
import { tt } from "../core/i18n.js";
import { Button } from "./Button.js";
/**
* Modal component
*
* daisyUI classes used:
* - modal, modal-box, modal-action, modal-backdrop
* - modal-open, modal-middle, modal-top, modal-bottom
* - text-lg, font-bold, mb-4, py-2
* - flex, gap-2
*/
export const Modal = (props, children) => {
const { class: className, title, buttons, open, ...rest } = props;
let dialogElement = null;
const handleOpen = () => {
const isOpen = typeof open === "function" ? open() : open;
if (!dialogElement) return;
if (isOpen) {
if (!dialogElement.open) dialogElement.showModal();
} else {
if (dialogElement.open) dialogElement.close();
}
};
$watch(() => handleOpen());
const close = () => {
if (typeof open === "function") open(false);
};
return $html("dialog", {
...rest,
ref: (el) => {
dialogElement = el;
if (el) handleOpen();
},
class: ui('modal', className),
onclose: close,
oncancel: close
}, [
$html("div", { class: "modal-box" }, [
title ? $html("h3", { class: "text-lg font-bold mb-4" }, () =>
typeof title === "function" ? title() : title
) : null,
$html("div", { class: "py-2" }, [
typeof children === "function" ? children() : children
]),
$html("div", { class: "modal-action" }, [
$html("form", { method: "dialog", class: "flex gap-2" }, [
...(Array.isArray(buttons) ? buttons : [buttons]).filter(Boolean),
Button({ type: "submit" }, tt("close")())
])
])
]),
$html("form", { method: "dialog", class: "modal-backdrop" }, [
$html("button", {}, "close")
])
]);
};

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

@@ -0,0 +1,16 @@
// components/Navbar.js
import { $html } from "../sigpro.js";
import { ui } from "../core/utils.js";
/**
* Navbar component
*
* daisyUI classes used:
* - navbar, navbar-start, navbar-center, navbar-end
* - bg-base-100, shadow-sm, px-4
*/
export const Navbar = (props, children) => {
const { class: className, ...rest } = props;
return $html("div", { ...rest, class: ui('navbar bg-base-100 shadow-sm px-4', className) }, children);
};

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

@@ -0,0 +1,37 @@
// components/Radio.js
import { $html } from "../sigpro.js";
import { val, ui } from "../core/utils.js";
/**
* Radio component
*
* daisyUI classes used:
* - radio, radio-primary, radio-secondary, radio-accent
* - radio-info, radio-success, radio-warning, radio-error
* - radio-xs, radio-sm, radio-md, radio-lg
* - label, label-text, cursor-pointer, justify-start, gap-3
* - tooltip, tooltip-top, tooltip-bottom, tooltip-left, tooltip-right
*/
export const Radio = (props) => {
const { class: className, label, tooltip, value, inputValue, name, ...rest } = props;
const radioEl = $html("input", {
...rest,
type: "radio",
name: name,
class: ui('radio', className),
checked: () => val(value) === inputValue,
onclick: () => {
if (typeof value === "function") value(inputValue);
},
});
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;
};

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

@@ -0,0 +1,34 @@
// components/Range.js
import { $html } from "../sigpro.js";
import { val, ui } from "../core/utils.js";
/**
* Range component
*
* daisyUI classes used:
* - range, range-primary, range-secondary, range-accent
* - range-info, range-success, range-warning, range-error
* - range-xs, range-sm, range-md, range-lg
* - label-text, flex, flex-col, gap-2
* - tooltip, tooltip-top, tooltip-bottom, tooltip-left, tooltip-right
*/
export const Range = (props) => {
const {class: className, label, tooltip, value, ...rest } = props;
const rangeEl = $html("input", {
...rest,
type: "range",
class: ui('range', className),
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;
};

Some files were not shown because too many files have changed in this diff Show More