Docs
This commit is contained in:
537
docs/components/badge.md
Normal file
537
docs/components/badge.md
Normal 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' }, [
|
||||
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, 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>
|
||||
0
docs/components/indicator.md
Normal file
0
docs/components/indicator.md
Normal file
601
docs/components/list.md
Normal file
601
docs/components/list.md
Normal 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>
|
||||
519
docs/components/stack.md
Normal file
519
docs/components/stack.md
Normal 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/stat.md
Normal file
644
docs/components/stat.md
Normal 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>
|
||||
716
docs/components/table.md
Normal file
716
docs/components/table.md
Normal 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>
|
||||
553
docs/components/timeline.md
Normal file
553
docs/components/timeline.md
Normal 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>
|
||||
Reference in New Issue
Block a user