Docs
This commit is contained in:
521
docs/components/alert.md
Normal file
521
docs/components/alert.md
Normal file
@@ -0,0 +1,521 @@
|
|||||||
|
# Alert
|
||||||
|
|
||||||
|
Alert component for displaying contextual messages, notifications, and feedback with different severity levels. Supports soft and solid variants.
|
||||||
|
|
||||||
|
## Tag
|
||||||
|
|
||||||
|
`Alert`
|
||||||
|
|
||||||
|
## Props
|
||||||
|
|
||||||
|
| Prop | Type | Default | Description |
|
||||||
|
| :----------- | :--------------------------- | :---------- | :----------------------------------------------- |
|
||||||
|
| `type` | `string` | `'info'` | Alert type: 'info', 'success', 'warning', 'error' |
|
||||||
|
| `soft` | `boolean \| Signal<boolean>` | `true` | Use soft variant (subtle background) |
|
||||||
|
| `actions` | `VNode \| function` | `-` | Optional action buttons or content |
|
||||||
|
| `message` | `string \| VNode \| Signal` | `-` | Alert message content |
|
||||||
|
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
|
||||||
|
| `children` | `string \| VNode` | `-` | Alert content (alternative to `message`) |
|
||||||
|
|
||||||
|
## Live Examples
|
||||||
|
|
||||||
|
### Basic Alerts
|
||||||
|
|
||||||
|
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||||||
|
<div id="demo-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-3"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const BasicDemo = () => {
|
||||||
|
return Div({ class: 'flex flex-col gap-3' }, [
|
||||||
|
Alert({ type: 'info', message: 'This is an informational message.' }),
|
||||||
|
Alert({ type: 'success', message: 'Operation completed successfully!' }),
|
||||||
|
Alert({ type: 'warning', message: 'Please review your input before proceeding.' }),
|
||||||
|
Alert({ type: 'error', message: 'An error occurred while processing your request.' })
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(BasicDemo, '#demo-basic');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Soft vs Solid Variants
|
||||||
|
|
||||||
|
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||||||
|
<div id="demo-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-3"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const VariantsDemo = () => {
|
||||||
|
return Div({ class: 'flex flex-col gap-3' }, [
|
||||||
|
Alert({ type: 'info', soft: true, message: 'Soft info alert (default)' }),
|
||||||
|
Alert({ type: 'info', soft: false, message: 'Solid info alert' }),
|
||||||
|
Alert({ type: 'success', soft: true, message: 'Soft success alert' }),
|
||||||
|
Alert({ type: 'success', soft: false, message: 'Solid success alert' })
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(VariantsDemo, '#demo-variants');
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Actions
|
||||||
|
|
||||||
|
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||||||
|
<div id="demo-actions" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-3"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const ActionsDemo = () => {
|
||||||
|
const showUndo = $(false);
|
||||||
|
const deletedItem = $(null);
|
||||||
|
|
||||||
|
const deleteItem = (item) => {
|
||||||
|
deletedItem(item);
|
||||||
|
showUndo(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
if (showUndo()) {
|
||||||
|
showUndo(false);
|
||||||
|
Toast('Item permanently deleted', 'alert-info', 2000);
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const undoDelete = () => {
|
||||||
|
showUndo(false);
|
||||||
|
Toast(`Restored: ${deletedItem()}`, 'alert-success', 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
return Div({ class: 'flex flex-col gap-4' }, [
|
||||||
|
Div({ class: 'flex gap-2' }, [
|
||||||
|
Button({ class: 'btn btn-sm', onclick: () => deleteItem('Document A') }, 'Delete Document A'),
|
||||||
|
Button({ class: 'btn btn-sm', onclick: () => deleteItem('Document B') }, 'Delete Document B')
|
||||||
|
]),
|
||||||
|
() => showUndo() ? Alert({
|
||||||
|
type: 'warning',
|
||||||
|
soft: true,
|
||||||
|
message: `Deleted: ${deletedItem()}`,
|
||||||
|
actions: Button({
|
||||||
|
class: 'btn btn-sm btn-primary',
|
||||||
|
onclick: undoDelete
|
||||||
|
}, 'Undo')
|
||||||
|
}) : null
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(ActionsDemo, '#demo-actions');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dismissible Alert
|
||||||
|
|
||||||
|
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||||||
|
<div id="demo-dismissible" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-3"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const DismissibleDemo = () => {
|
||||||
|
const visible = $(true);
|
||||||
|
|
||||||
|
return Div({ class: 'flex flex-col gap-3' }, [
|
||||||
|
() => visible() ? Alert({
|
||||||
|
type: 'info',
|
||||||
|
message: 'This alert can be dismissed. Click the X button to close.',
|
||||||
|
actions: Button({
|
||||||
|
class: 'btn btn-xs btn-circle btn-ghost',
|
||||||
|
onclick: () => visible(false)
|
||||||
|
}, '✕')
|
||||||
|
}) : null,
|
||||||
|
() => !visible() ? Button({
|
||||||
|
class: 'btn btn-sm btn-ghost',
|
||||||
|
onclick: () => visible(true)
|
||||||
|
}, 'Show Alert') : null
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(DismissibleDemo, '#demo-dismissible');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reactive Alert
|
||||||
|
|
||||||
|
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||||||
|
<div id="demo-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const ReactiveDemo = () => {
|
||||||
|
const email = $('');
|
||||||
|
const error = $('');
|
||||||
|
|
||||||
|
const validateEmail = (value) => {
|
||||||
|
email(value);
|
||||||
|
if (value && !value.includes('@')) {
|
||||||
|
error('Please enter a valid email address');
|
||||||
|
} else {
|
||||||
|
error('');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Div({ class: 'flex flex-col gap-4' }, [
|
||||||
|
Input({
|
||||||
|
label: 'Email Address',
|
||||||
|
placeholder: 'Enter your email',
|
||||||
|
value: email,
|
||||||
|
oninput: (e) => validateEmail(e.target.value)
|
||||||
|
}),
|
||||||
|
() => error() ? Alert({ type: 'error', message: error() }) : null,
|
||||||
|
() => email() && !error() ? Alert({
|
||||||
|
type: 'success',
|
||||||
|
message: `Valid email: ${email()}`
|
||||||
|
}) : null
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(ReactiveDemo, '#demo-reactive');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Form Validation
|
||||||
|
|
||||||
|
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||||||
|
<div id="demo-form" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const FormDemo = () => {
|
||||||
|
const name = $('');
|
||||||
|
const email = $('');
|
||||||
|
const submitted = $(false);
|
||||||
|
const errors = $({ name: '', email: '' });
|
||||||
|
|
||||||
|
const validate = () => {
|
||||||
|
const newErrors = {
|
||||||
|
name: name().trim() ? '' : 'Name is required',
|
||||||
|
email: email().trim() ? (email().includes('@') ? '' : 'Invalid email') : 'Email is required'
|
||||||
|
};
|
||||||
|
errors(newErrors);
|
||||||
|
return !newErrors.name && !newErrors.email;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (validate()) {
|
||||||
|
submitted(true);
|
||||||
|
setTimeout(() => submitted(false), 3000);
|
||||||
|
Toast('Form submitted successfully!', 'alert-success', 2000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Div({ class: 'flex flex-col gap-4' }, [
|
||||||
|
Div({ class: 'text-lg font-bold' }, 'Contact Form'),
|
||||||
|
Input({
|
||||||
|
label: 'Name',
|
||||||
|
value: name,
|
||||||
|
error: () => errors().name,
|
||||||
|
oninput: (e) => {
|
||||||
|
name(e.target.value);
|
||||||
|
validate();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Input({
|
||||||
|
label: 'Email',
|
||||||
|
value: email,
|
||||||
|
error: () => errors().email,
|
||||||
|
oninput: (e) => {
|
||||||
|
email(e.target.value);
|
||||||
|
validate();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Button({ class: 'btn btn-primary', onclick: handleSubmit }, 'Submit'),
|
||||||
|
() => submitted() ? Alert({
|
||||||
|
type: 'success',
|
||||||
|
message: 'Thank you! We will contact you soon.'
|
||||||
|
}) : null,
|
||||||
|
() => (errors().name || errors().email) ? Alert({
|
||||||
|
type: 'error',
|
||||||
|
message: 'Please fix the errors above before submitting.'
|
||||||
|
}) : null
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(FormDemo, '#demo-form');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Icon Alerts
|
||||||
|
|
||||||
|
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||||||
|
<div id="demo-icons" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-3"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const IconsDemo = () => {
|
||||||
|
return Div({ class: 'flex flex-col gap-3' }, [
|
||||||
|
Alert({ type: 'info', message: 'Information alert with custom icon' }),
|
||||||
|
Alert({ type: 'success', message: 'Success alert with custom icon' }),
|
||||||
|
Alert({ type: 'warning', message: 'Warning alert with custom icon' }),
|
||||||
|
Alert({ type: 'error', message: 'Error alert with custom icon' })
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(IconsDemo, '#demo-icons');
|
||||||
|
```
|
||||||
|
|
||||||
|
### All Types
|
||||||
|
|
||||||
|
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||||||
|
<div id="demo-all" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-3"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const AllTypesDemo = () => {
|
||||||
|
return Div({ class: 'flex flex-col gap-3' }, [
|
||||||
|
Alert({ type: 'info', message: 'ℹ️ This is an info alert' }),
|
||||||
|
Alert({ type: 'success', message: '✅ This is a success alert' }),
|
||||||
|
Alert({ type: 'warning', message: '⚠️ This is a warning alert' }),
|
||||||
|
Alert({ type: 'error', message: '❌ This is an error alert' })
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(AllTypesDemo, '#demo-all');
|
||||||
|
```
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
const initAlertExamples = () => {
|
||||||
|
|
||||||
|
// 1. Basic Alerts
|
||||||
|
const basicTarget = document.querySelector('#demo-basic');
|
||||||
|
if (basicTarget && !basicTarget.hasChildNodes()) {
|
||||||
|
const BasicDemo = () => {
|
||||||
|
return Div({ class: 'flex flex-col gap-3' }, [
|
||||||
|
Alert({ type: 'info', message: 'This is an informational message.' }),
|
||||||
|
Alert({ type: 'success', message: 'Operation completed successfully!' }),
|
||||||
|
Alert({ type: 'warning', message: 'Please review your input before proceeding.' }),
|
||||||
|
Alert({ type: 'error', message: 'An error occurred while processing your request.' })
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(BasicDemo, basicTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Soft vs Solid Variants
|
||||||
|
const variantsTarget = document.querySelector('#demo-variants');
|
||||||
|
if (variantsTarget && !variantsTarget.hasChildNodes()) {
|
||||||
|
const VariantsDemo = () => {
|
||||||
|
return Div({ class: 'flex flex-col gap-3' }, [
|
||||||
|
Alert({ type: 'info', soft: true, message: 'Soft info alert (default)' }),
|
||||||
|
Alert({ type: 'info', soft: false, message: 'Solid info alert' }),
|
||||||
|
Alert({ type: 'success', soft: true, message: 'Soft success alert' }),
|
||||||
|
Alert({ type: 'success', soft: false, message: 'Solid success alert' })
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(VariantsDemo, variantsTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. With Actions
|
||||||
|
const actionsTarget = document.querySelector('#demo-actions');
|
||||||
|
if (actionsTarget && !actionsTarget.hasChildNodes()) {
|
||||||
|
const ActionsDemo = () => {
|
||||||
|
const showUndo = $(false);
|
||||||
|
const deletedItem = $(null);
|
||||||
|
|
||||||
|
const deleteItem = (item) => {
|
||||||
|
deletedItem(item);
|
||||||
|
showUndo(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
if (showUndo()) {
|
||||||
|
showUndo(false);
|
||||||
|
Toast('Item permanently deleted', 'alert-info', 2000);
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const undoDelete = () => {
|
||||||
|
showUndo(false);
|
||||||
|
Toast(`Restored: ${deletedItem()}`, 'alert-success', 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
return Div({ class: 'flex flex-col gap-4' }, [
|
||||||
|
Div({ class: 'flex gap-2' }, [
|
||||||
|
Button({ class: 'btn btn-sm', onclick: () => deleteItem('Document A') }, 'Delete Document A'),
|
||||||
|
Button({ class: 'btn btn-sm', onclick: () => deleteItem('Document B') }, 'Delete Document B')
|
||||||
|
]),
|
||||||
|
() => showUndo() ? Alert({
|
||||||
|
type: 'warning',
|
||||||
|
soft: true,
|
||||||
|
message: `Deleted: ${deletedItem()}`,
|
||||||
|
actions: Button({
|
||||||
|
class: 'btn btn-sm btn-primary',
|
||||||
|
onclick: undoDelete
|
||||||
|
}, 'Undo')
|
||||||
|
}) : null
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(ActionsDemo, actionsTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Dismissible Alert
|
||||||
|
const dismissibleTarget = document.querySelector('#demo-dismissible');
|
||||||
|
if (dismissibleTarget && !dismissibleTarget.hasChildNodes()) {
|
||||||
|
const DismissibleDemo = () => {
|
||||||
|
const visible = $(true);
|
||||||
|
|
||||||
|
return Div({ class: 'flex flex-col gap-3' }, [
|
||||||
|
() => visible() ? Alert({
|
||||||
|
type: 'info',
|
||||||
|
message: 'This alert can be dismissed. Click the X button to close.',
|
||||||
|
actions: Button({
|
||||||
|
class: 'btn btn-xs btn-circle btn-ghost',
|
||||||
|
onclick: () => visible(false)
|
||||||
|
}, '✕')
|
||||||
|
}) : null,
|
||||||
|
() => !visible() ? Button({
|
||||||
|
class: 'btn btn-sm btn-ghost',
|
||||||
|
onclick: () => visible(true)
|
||||||
|
}, 'Show Alert') : null
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(DismissibleDemo, dismissibleTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Reactive Alert
|
||||||
|
const reactiveTarget = document.querySelector('#demo-reactive');
|
||||||
|
if (reactiveTarget && !reactiveTarget.hasChildNodes()) {
|
||||||
|
const ReactiveDemo = () => {
|
||||||
|
const email = $('');
|
||||||
|
const error = $('');
|
||||||
|
|
||||||
|
const validateEmail = (value) => {
|
||||||
|
email(value);
|
||||||
|
if (value && !value.includes('@')) {
|
||||||
|
error('Please enter a valid email address');
|
||||||
|
} else {
|
||||||
|
error('');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Div({ class: 'flex flex-col gap-4' }, [
|
||||||
|
Input({
|
||||||
|
label: 'Email Address',
|
||||||
|
placeholder: 'Enter your email',
|
||||||
|
value: email,
|
||||||
|
oninput: (e) => validateEmail(e.target.value)
|
||||||
|
}),
|
||||||
|
() => error() ? Alert({ type: 'error', message: error() }) : null,
|
||||||
|
() => email() && !error() ? Alert({
|
||||||
|
type: 'success',
|
||||||
|
message: `Valid email: ${email()}`
|
||||||
|
}) : null
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(ReactiveDemo, reactiveTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Form Validation
|
||||||
|
const formTarget = document.querySelector('#demo-form');
|
||||||
|
if (formTarget && !formTarget.hasChildNodes()) {
|
||||||
|
const FormDemo = () => {
|
||||||
|
const name = $('');
|
||||||
|
const email = $('');
|
||||||
|
const submitted = $(false);
|
||||||
|
const errors = $({ name: '', email: '' });
|
||||||
|
|
||||||
|
const validate = () => {
|
||||||
|
const newErrors = {
|
||||||
|
name: name().trim() ? '' : 'Name is required',
|
||||||
|
email: email().trim() ? (email().includes('@') ? '' : 'Invalid email') : 'Email is required'
|
||||||
|
};
|
||||||
|
errors(newErrors);
|
||||||
|
return !newErrors.name && !newErrors.email;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (validate()) {
|
||||||
|
submitted(true);
|
||||||
|
setTimeout(() => submitted(false), 3000);
|
||||||
|
Toast('Form submitted successfully!', 'alert-success', 2000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Div({ class: 'flex flex-col gap-4' }, [
|
||||||
|
Div({ class: 'text-lg font-bold' }, 'Contact Form'),
|
||||||
|
Input({
|
||||||
|
label: 'Name',
|
||||||
|
value: name,
|
||||||
|
error: () => errors().name,
|
||||||
|
oninput: (e) => {
|
||||||
|
name(e.target.value);
|
||||||
|
validate();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Input({
|
||||||
|
label: 'Email',
|
||||||
|
value: email,
|
||||||
|
error: () => errors().email,
|
||||||
|
oninput: (e) => {
|
||||||
|
email(e.target.value);
|
||||||
|
validate();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Button({ class: 'btn btn-primary', onclick: handleSubmit }, 'Submit'),
|
||||||
|
() => submitted() ? Alert({
|
||||||
|
type: 'success',
|
||||||
|
message: 'Thank you! We will contact you soon.'
|
||||||
|
}) : null,
|
||||||
|
() => (errors().name || errors().email) ? Alert({
|
||||||
|
type: 'error',
|
||||||
|
message: 'Please fix the errors above before submitting.'
|
||||||
|
}) : null
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(FormDemo, formTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Icon Alerts
|
||||||
|
const iconsTarget = document.querySelector('#demo-icons');
|
||||||
|
if (iconsTarget && !iconsTarget.hasChildNodes()) {
|
||||||
|
const IconsDemo = () => {
|
||||||
|
return Div({ class: 'flex flex-col gap-3' }, [
|
||||||
|
Alert({ type: 'info', message: 'Information alert with custom icon' }),
|
||||||
|
Alert({ type: 'success', message: 'Success alert with custom icon' }),
|
||||||
|
Alert({ type: 'warning', message: 'Warning alert with custom icon' }),
|
||||||
|
Alert({ type: 'error', message: 'Error alert with custom icon' })
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(IconsDemo, iconsTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. All Types
|
||||||
|
const allTarget = document.querySelector('#demo-all');
|
||||||
|
if (allTarget && !allTarget.hasChildNodes()) {
|
||||||
|
const AllTypesDemo = () => {
|
||||||
|
return Div({ class: 'flex flex-col gap-3' }, [
|
||||||
|
Alert({ type: 'info', message: 'ℹ️ This is an info alert' }),
|
||||||
|
Alert({ type: 'success', message: '✅ This is a success alert' }),
|
||||||
|
Alert({ type: 'warning', message: '⚠️ This is a warning alert' }),
|
||||||
|
Alert({ type: 'error', message: '❌ This is an error alert' })
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(AllTypesDemo, allTarget);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
initAlertExamples();
|
||||||
|
|
||||||
|
if (window.$docsify) {
|
||||||
|
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
|
||||||
|
hook.doneEach(initAlertExamples);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
@@ -382,11 +382,11 @@ $mount(InlineDemo, '#demo-inline');
|
|||||||
const IconsDemo = () => {
|
const IconsDemo = () => {
|
||||||
return Div({ class: 'flex flex-wrap gap-2' }, [
|
return Div({ class: 'flex flex-wrap gap-2' }, [
|
||||||
Badge({ class: 'gap-1' }, [
|
Badge({ class: 'gap-1' }, [
|
||||||
Icons.iconSuccess,
|
Img({src: Icons.iconSuccess}),
|
||||||
Span({}, 'Success')
|
Span({}, 'Success')
|
||||||
]),
|
]),
|
||||||
Badge({ class: 'gap-1 badge-warning' }, [
|
Badge({ class: 'gap-1 badge-warning' }, [
|
||||||
Icons.iconWarning,
|
Img({src: Icons.iconWarning}),
|
||||||
Span({}, 'Warning')
|
Span({}, 'Warning')
|
||||||
]),
|
]),
|
||||||
Badge({ class: 'gap-1 badge-error' }, [
|
Badge({ class: 'gap-1 badge-error' }, [
|
||||||
|
|||||||
@@ -0,0 +1,556 @@
|
|||||||
|
# Indicator
|
||||||
|
|
||||||
|
Indicator component for adding badges, status markers, or notifications to elements. Perfect for showing counts, online status, or alerts.
|
||||||
|
|
||||||
|
## Tag
|
||||||
|
|
||||||
|
`Indicator`
|
||||||
|
|
||||||
|
## Props
|
||||||
|
|
||||||
|
| Prop | Type | Default | Description |
|
||||||
|
| :----------- | :--------------------------- | :---------- | :----------------------------------------------- |
|
||||||
|
| `badge` | `string \| VNode \| Signal` | `-` | Content to display as indicator |
|
||||||
|
| `badgeClass` | `string` | `''` | Additional CSS classes for the badge |
|
||||||
|
| `class` | `string` | `''` | Additional CSS classes for the container |
|
||||||
|
| `children` | `VNode` | `-` | Element to attach the indicator to |
|
||||||
|
|
||||||
|
## Live Examples
|
||||||
|
|
||||||
|
### Basic Indicator
|
||||||
|
|
||||||
|
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||||||
|
<div id="demo-basic" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-8 justify-center"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const BasicDemo = () => {
|
||||||
|
return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
|
||||||
|
Indicator({ badge: '3', badgeClass: 'badge-primary' }, [
|
||||||
|
Div({ class: 'w-16 h-16 bg-base-300 rounded-lg flex items-center justify-center' }, '📦')
|
||||||
|
]),
|
||||||
|
Indicator({ badge: '99+', badgeClass: 'badge-secondary' }, [
|
||||||
|
Div({ class: 'w-16 h-16 bg-base-300 rounded-lg flex items-center justify-center' }, '🔔')
|
||||||
|
]),
|
||||||
|
Indicator({ badge: 'New', badgeClass: 'badge-accent' }, [
|
||||||
|
Div({ class: 'w-16 h-16 bg-base-300 rounded-lg flex items-center justify-center' }, '✨')
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(BasicDemo, '#demo-basic');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Online Status Indicator
|
||||||
|
|
||||||
|
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||||||
|
<div id="demo-status" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-8 justify-center"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const StatusDemo = () => {
|
||||||
|
return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
|
||||||
|
Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
|
||||||
|
Div({ class: 'avatar placeholder' }, [
|
||||||
|
Div({ class: 'bg-neutral text-neutral-content rounded-full w-12 h-12 flex items-center justify-center' }, 'JD')
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
Indicator({ badge: '●', badgeClass: 'badge-warning badge-xs' }, [
|
||||||
|
Div({ class: 'avatar placeholder' }, [
|
||||||
|
Div({ class: 'bg-neutral text-neutral-content rounded-full w-12 h-12 flex items-center justify-center' }, 'JS')
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
Indicator({ badge: '●', badgeClass: 'badge-error badge-xs' }, [
|
||||||
|
Div({ class: 'avatar placeholder' }, [
|
||||||
|
Div({ class: 'bg-neutral text-neutral-content rounded-full w-12 h-12 flex items-center justify-center' }, 'BC')
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(StatusDemo, '#demo-status');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reactive Counter
|
||||||
|
|
||||||
|
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||||||
|
<div id="demo-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-4 items-center"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const ReactiveDemo = () => {
|
||||||
|
const count = $(0);
|
||||||
|
|
||||||
|
return Div({ class: 'flex flex-col gap-4 items-center' }, [
|
||||||
|
Indicator({
|
||||||
|
badge: () => count() > 0 ? count() : null,
|
||||||
|
badgeClass: 'badge-primary'
|
||||||
|
}, [
|
||||||
|
Button({
|
||||||
|
class: 'btn btn-lg btn-primary',
|
||||||
|
onclick: () => count(count() + 1)
|
||||||
|
}, 'Notifications')
|
||||||
|
]),
|
||||||
|
Div({ class: 'flex gap-2' }, [
|
||||||
|
Button({
|
||||||
|
class: 'btn btn-sm',
|
||||||
|
onclick: () => count(Math.max(0, count() - 1))
|
||||||
|
}, 'Decrease'),
|
||||||
|
Button({
|
||||||
|
class: 'btn btn-sm btn-ghost',
|
||||||
|
onclick: () => count(0)
|
||||||
|
}, 'Clear')
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(ReactiveDemo, '#demo-reactive');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Shopping Cart
|
||||||
|
|
||||||
|
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||||||
|
<div id="demo-cart" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const CartDemo = () => {
|
||||||
|
const cart = $([
|
||||||
|
{ id: 1, name: 'Product 1', price: 29 },
|
||||||
|
{ id: 2, name: 'Product 2', price: 49 }
|
||||||
|
]);
|
||||||
|
|
||||||
|
const addItem = () => {
|
||||||
|
const newId = Math.max(...cart().map(i => i.id), 0) + 1;
|
||||||
|
cart([...cart(), { id: newId, name: `Product ${newId}`, price: Math.floor(Math.random() * 100) + 10 }]);
|
||||||
|
Toast('Item added to cart', 'alert-success', 1500);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeItem = (id) => {
|
||||||
|
cart(cart().filter(item => item.id !== id));
|
||||||
|
Toast('Item removed', 'alert-info', 1500);
|
||||||
|
};
|
||||||
|
|
||||||
|
const total = () => cart().reduce((sum, item) => sum + item.price, 0);
|
||||||
|
|
||||||
|
return Div({ class: 'flex flex-col gap-4' }, [
|
||||||
|
Div({ class: 'flex justify-between items-center' }, [
|
||||||
|
Indicator({
|
||||||
|
badge: () => cart().length,
|
||||||
|
badgeClass: 'badge-primary'
|
||||||
|
}, [
|
||||||
|
Button({ class: 'btn', onclick: addItem }, '🛒 Add to Cart')
|
||||||
|
]),
|
||||||
|
Span({ class: 'text-lg font-bold' }, () => `Total: $${total()}`)
|
||||||
|
]),
|
||||||
|
cart().length === 0
|
||||||
|
? Div({ class: 'alert alert-soft text-center' }, 'Cart is empty')
|
||||||
|
: Div({ class: 'flex flex-col gap-2' }, cart().map(item =>
|
||||||
|
Div({ class: 'flex justify-between items-center p-2 bg-base-200 rounded-lg' }, [
|
||||||
|
Span({}, item.name),
|
||||||
|
Div({ class: 'flex gap-2 items-center' }, [
|
||||||
|
Span({ class: 'text-sm font-bold' }, `$${item.price}`),
|
||||||
|
Button({
|
||||||
|
class: 'btn btn-xs btn-ghost btn-circle',
|
||||||
|
onclick: () => removeItem(item.id)
|
||||||
|
}, '✕')
|
||||||
|
])
|
||||||
|
])
|
||||||
|
))
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(CartDemo, '#demo-cart');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Email Inbox
|
||||||
|
|
||||||
|
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||||||
|
<div id="demo-inbox" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const InboxDemo = () => {
|
||||||
|
const unread = $(3);
|
||||||
|
const messages = $([
|
||||||
|
{ id: 1, from: 'john@example.com', subject: 'Meeting tomorrow', read: false },
|
||||||
|
{ id: 2, from: 'jane@example.com', subject: 'Project update', read: false },
|
||||||
|
{ id: 3, from: 'bob@example.com', subject: 'Question about design', read: false },
|
||||||
|
{ id: 4, from: 'alice@example.com', subject: 'Weekly report', read: true }
|
||||||
|
]);
|
||||||
|
|
||||||
|
const markAsRead = (id) => {
|
||||||
|
const msg = messages().find(m => m.id === id);
|
||||||
|
if (!msg.read) {
|
||||||
|
msg.read = true;
|
||||||
|
messages([...messages()]);
|
||||||
|
unread(unread() - 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Div({ class: 'flex flex-col gap-4' }, [
|
||||||
|
Div({ class: 'flex justify-between items-center' }, [
|
||||||
|
Indicator({
|
||||||
|
badge: () => unread(),
|
||||||
|
badgeClass: 'badge-primary'
|
||||||
|
}, [
|
||||||
|
Span({ class: 'text-lg font-bold' }, 'Inbox')
|
||||||
|
]),
|
||||||
|
Button({
|
||||||
|
class: 'btn btn-sm btn-ghost',
|
||||||
|
onclick: () => {
|
||||||
|
messages().forEach(m => m.read = true);
|
||||||
|
messages([...messages()]);
|
||||||
|
unread(0);
|
||||||
|
}
|
||||||
|
}, 'Mark all read')
|
||||||
|
]),
|
||||||
|
Div({ class: 'flex flex-col gap-2' }, messages().map(msg =>
|
||||||
|
Div({
|
||||||
|
class: `p-3 rounded-lg cursor-pointer transition-all ${msg.read ? 'bg-base-200 opacity-60' : 'bg-primary/10 border-l-4 border-primary'}`,
|
||||||
|
onclick: () => markAsRead(msg.id)
|
||||||
|
}, [
|
||||||
|
Div({ class: 'font-medium' }, msg.from),
|
||||||
|
Div({ class: 'text-sm' }, msg.subject),
|
||||||
|
!msg.read && Span({ class: 'badge badge-xs badge-primary mt-1' }, 'New')
|
||||||
|
])
|
||||||
|
))
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(InboxDemo, '#demo-inbox');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Position
|
||||||
|
|
||||||
|
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||||||
|
<div id="demo-positions" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-8 justify-center"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const PositionsDemo = () => {
|
||||||
|
return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
|
||||||
|
Div({ class: 'text-center' }, [
|
||||||
|
Div({ class: 'text-xs mb-2' }, 'Top-Left'),
|
||||||
|
Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
|
||||||
|
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg' })
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
Div({ class: 'text-center' }, [
|
||||||
|
Div({ class: 'text-xs mb-2' }, 'Top-Right'),
|
||||||
|
Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
|
||||||
|
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg' })
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
Div({ class: 'text-center' }, [
|
||||||
|
Div({ class: 'text-xs mb-2' }, 'Bottom-Left'),
|
||||||
|
Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
|
||||||
|
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg' })
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
Div({ class: 'text-center' }, [
|
||||||
|
Div({ class: 'text-xs mb-2' }, 'Bottom-Right'),
|
||||||
|
Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
|
||||||
|
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg' })
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(PositionsDemo, '#demo-positions');
|
||||||
|
```
|
||||||
|
|
||||||
|
### All Variants
|
||||||
|
|
||||||
|
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||||||
|
<div id="demo-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-wrap gap-8 justify-center"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const VariantsDemo = () => {
|
||||||
|
return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
|
||||||
|
Indicator({ badge: '3', badgeClass: 'badge-primary badge-sm' }, [
|
||||||
|
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg flex items-center justify-center' }, '📧')
|
||||||
|
]),
|
||||||
|
Indicator({ badge: '99+', badgeClass: 'badge-secondary badge-md' }, [
|
||||||
|
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg flex items-center justify-center' }, '🔔')
|
||||||
|
]),
|
||||||
|
Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
|
||||||
|
Div({ class: 'avatar' }, [
|
||||||
|
Div({ class: 'w-10 h-10 rounded-full bg-primary' })
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
Indicator({ badge: '!', badgeClass: 'badge-error badge-sm' }, [
|
||||||
|
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg flex items-center justify-center' }, '⚠️')
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(VariantsDemo, '#demo-variants');
|
||||||
|
```
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
const initIndicatorExamples = () => {
|
||||||
|
|
||||||
|
// 1. Basic Indicator
|
||||||
|
const basicTarget = document.querySelector('#demo-basic');
|
||||||
|
if (basicTarget && !basicTarget.hasChildNodes()) {
|
||||||
|
const BasicDemo = () => {
|
||||||
|
return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
|
||||||
|
Indicator({ badge: '3', badgeClass: 'badge-primary' }, [
|
||||||
|
Div({ class: 'w-16 h-16 bg-base-300 rounded-lg flex items-center justify-center' }, '📦')
|
||||||
|
]),
|
||||||
|
Indicator({ badge: '99+', badgeClass: 'badge-secondary' }, [
|
||||||
|
Div({ class: 'w-16 h-16 bg-base-300 rounded-lg flex items-center justify-center' }, '🔔')
|
||||||
|
]),
|
||||||
|
Indicator({ badge: 'New', badgeClass: 'badge-accent' }, [
|
||||||
|
Div({ class: 'w-16 h-16 bg-base-300 rounded-lg flex items-center justify-center' }, '✨')
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(BasicDemo, basicTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Online Status Indicator
|
||||||
|
const statusTarget = document.querySelector('#demo-status');
|
||||||
|
if (statusTarget && !statusTarget.hasChildNodes()) {
|
||||||
|
const StatusDemo = () => {
|
||||||
|
return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
|
||||||
|
Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
|
||||||
|
Div({ class: 'avatar placeholder' }, [
|
||||||
|
Div({ class: 'bg-neutral text-neutral-content rounded-full w-12 h-12 flex items-center justify-center' }, 'JD')
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
Indicator({ badge: '●', badgeClass: 'badge-warning badge-xs' }, [
|
||||||
|
Div({ class: 'avatar placeholder' }, [
|
||||||
|
Div({ class: 'bg-neutral text-neutral-content rounded-full w-12 h-12 flex items-center justify-center' }, 'JS')
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
Indicator({ badge: '●', badgeClass: 'badge-error badge-xs' }, [
|
||||||
|
Div({ class: 'avatar placeholder' }, [
|
||||||
|
Div({ class: 'bg-neutral text-neutral-content rounded-full w-12 h-12 flex items-center justify-center' }, 'BC')
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(StatusDemo, statusTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Reactive Counter
|
||||||
|
const reactiveTarget = document.querySelector('#demo-reactive');
|
||||||
|
if (reactiveTarget && !reactiveTarget.hasChildNodes()) {
|
||||||
|
const ReactiveDemo = () => {
|
||||||
|
const count = $(0);
|
||||||
|
|
||||||
|
return Div({ class: 'flex flex-col gap-4 items-center' }, [
|
||||||
|
Indicator({
|
||||||
|
badge: () => count() > 0 ? count() : null,
|
||||||
|
badgeClass: 'badge-primary'
|
||||||
|
}, [
|
||||||
|
Button({
|
||||||
|
class: 'btn btn-lg btn-primary',
|
||||||
|
onclick: () => count(count() + 1)
|
||||||
|
}, 'Notifications')
|
||||||
|
]),
|
||||||
|
Div({ class: 'flex gap-2' }, [
|
||||||
|
Button({
|
||||||
|
class: 'btn btn-sm',
|
||||||
|
onclick: () => count(Math.max(0, count() - 1))
|
||||||
|
}, 'Decrease'),
|
||||||
|
Button({
|
||||||
|
class: 'btn btn-sm btn-ghost',
|
||||||
|
onclick: () => count(0)
|
||||||
|
}, 'Clear')
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(ReactiveDemo, reactiveTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Shopping Cart
|
||||||
|
const cartTarget = document.querySelector('#demo-cart');
|
||||||
|
if (cartTarget && !cartTarget.hasChildNodes()) {
|
||||||
|
const CartDemo = () => {
|
||||||
|
const cart = $([
|
||||||
|
{ id: 1, name: 'Product 1', price: 29 },
|
||||||
|
{ id: 2, name: 'Product 2', price: 49 }
|
||||||
|
]);
|
||||||
|
|
||||||
|
const addItem = () => {
|
||||||
|
const newId = Math.max(...cart().map(i => i.id), 0) + 1;
|
||||||
|
cart([...cart(), { id: newId, name: `Product ${newId}`, price: Math.floor(Math.random() * 100) + 10 }]);
|
||||||
|
Toast('Item added to cart', 'alert-success', 1500);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeItem = (id) => {
|
||||||
|
cart(cart().filter(item => item.id !== id));
|
||||||
|
Toast('Item removed', 'alert-info', 1500);
|
||||||
|
};
|
||||||
|
|
||||||
|
const total = () => cart().reduce((sum, item) => sum + item.price, 0);
|
||||||
|
|
||||||
|
return Div({ class: 'flex flex-col gap-4' }, [
|
||||||
|
Div({ class: 'flex justify-between items-center' }, [
|
||||||
|
Indicator({
|
||||||
|
badge: () => cart().length,
|
||||||
|
badgeClass: 'badge-primary'
|
||||||
|
}, [
|
||||||
|
Button({ class: 'btn', onclick: addItem }, '🛒 Add to Cart')
|
||||||
|
]),
|
||||||
|
Span({ class: 'text-lg font-bold' }, () => `Total: $${total()}`)
|
||||||
|
]),
|
||||||
|
cart().length === 0
|
||||||
|
? Div({ class: 'alert alert-soft text-center' }, 'Cart is empty')
|
||||||
|
: Div({ class: 'flex flex-col gap-2' }, cart().map(item =>
|
||||||
|
Div({ class: 'flex justify-between items-center p-2 bg-base-200 rounded-lg' }, [
|
||||||
|
Span({}, item.name),
|
||||||
|
Div({ class: 'flex gap-2 items-center' }, [
|
||||||
|
Span({ class: 'text-sm font-bold' }, `$${item.price}`),
|
||||||
|
Button({
|
||||||
|
class: 'btn btn-xs btn-ghost btn-circle',
|
||||||
|
onclick: () => removeItem(item.id)
|
||||||
|
}, '✕')
|
||||||
|
])
|
||||||
|
])
|
||||||
|
))
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(CartDemo, cartTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Email Inbox
|
||||||
|
const inboxTarget = document.querySelector('#demo-inbox');
|
||||||
|
if (inboxTarget && !inboxTarget.hasChildNodes()) {
|
||||||
|
const InboxDemo = () => {
|
||||||
|
const unread = $(3);
|
||||||
|
const messages = $([
|
||||||
|
{ id: 1, from: 'john@example.com', subject: 'Meeting tomorrow', read: false },
|
||||||
|
{ id: 2, from: 'jane@example.com', subject: 'Project update', read: false },
|
||||||
|
{ id: 3, from: 'bob@example.com', subject: 'Question about design', read: false },
|
||||||
|
{ id: 4, from: 'alice@example.com', subject: 'Weekly report', read: true }
|
||||||
|
]);
|
||||||
|
|
||||||
|
const markAsRead = (id) => {
|
||||||
|
const msg = messages().find(m => m.id === id);
|
||||||
|
if (!msg.read) {
|
||||||
|
msg.read = true;
|
||||||
|
messages([...messages()]);
|
||||||
|
unread(unread() - 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Div({ class: 'flex flex-col gap-4' }, [
|
||||||
|
Div({ class: 'flex justify-between items-center' }, [
|
||||||
|
Indicator({
|
||||||
|
badge: () => unread(),
|
||||||
|
badgeClass: 'badge-primary'
|
||||||
|
}, [
|
||||||
|
Span({ class: 'text-lg font-bold' }, 'Inbox')
|
||||||
|
]),
|
||||||
|
Button({
|
||||||
|
class: 'btn btn-sm btn-ghost',
|
||||||
|
onclick: () => {
|
||||||
|
messages().forEach(m => m.read = true);
|
||||||
|
messages([...messages()]);
|
||||||
|
unread(0);
|
||||||
|
}
|
||||||
|
}, 'Mark all read')
|
||||||
|
]),
|
||||||
|
Div({ class: 'flex flex-col gap-2' }, messages().map(msg =>
|
||||||
|
Div({
|
||||||
|
class: `p-3 rounded-lg cursor-pointer transition-all ${msg.read ? 'bg-base-200 opacity-60' : 'bg-primary/10 border-l-4 border-primary'}`,
|
||||||
|
onclick: () => markAsRead(msg.id)
|
||||||
|
}, [
|
||||||
|
Div({ class: 'font-medium' }, msg.from),
|
||||||
|
Div({ class: 'text-sm' }, msg.subject),
|
||||||
|
!msg.read && Span({ class: 'badge badge-xs badge-primary mt-1' }, 'New')
|
||||||
|
])
|
||||||
|
))
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(InboxDemo, inboxTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Custom Position
|
||||||
|
const positionsTarget = document.querySelector('#demo-positions');
|
||||||
|
if (positionsTarget && !positionsTarget.hasChildNodes()) {
|
||||||
|
const PositionsDemo = () => {
|
||||||
|
return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
|
||||||
|
Div({ class: 'text-center' }, [
|
||||||
|
Div({ class: 'text-xs mb-2' }, 'Top-Left'),
|
||||||
|
Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
|
||||||
|
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg' })
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
Div({ class: 'text-center' }, [
|
||||||
|
Div({ class: 'text-xs mb-2' }, 'Top-Right'),
|
||||||
|
Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
|
||||||
|
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg' })
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
Div({ class: 'text-center' }, [
|
||||||
|
Div({ class: 'text-xs mb-2' }, 'Bottom-Left'),
|
||||||
|
Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
|
||||||
|
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg' })
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
Div({ class: 'text-center' }, [
|
||||||
|
Div({ class: 'text-xs mb-2' }, 'Bottom-Right'),
|
||||||
|
Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
|
||||||
|
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg' })
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(PositionsDemo, positionsTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. All Variants
|
||||||
|
const variantsTarget = document.querySelector('#demo-variants');
|
||||||
|
if (variantsTarget && !variantsTarget.hasChildNodes()) {
|
||||||
|
const VariantsDemo = () => {
|
||||||
|
return Div({ class: 'flex flex-wrap gap-8 justify-center' }, [
|
||||||
|
Indicator({ badge: '3', badgeClass: 'badge-primary badge-sm' }, [
|
||||||
|
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg flex items-center justify-center' }, '📧')
|
||||||
|
]),
|
||||||
|
Indicator({ badge: '99+', badgeClass: 'badge-secondary badge-md' }, [
|
||||||
|
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg flex items-center justify-center' }, '🔔')
|
||||||
|
]),
|
||||||
|
Indicator({ badge: '●', badgeClass: 'badge-success badge-xs' }, [
|
||||||
|
Div({ class: 'avatar' }, [
|
||||||
|
Div({ class: 'w-10 h-10 rounded-full bg-primary' })
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
Indicator({ badge: '!', badgeClass: 'badge-error badge-sm' }, [
|
||||||
|
Div({ class: 'w-12 h-12 bg-base-300 rounded-lg flex items-center justify-center' }, '⚠️')
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
$mount(VariantsDemo, variantsTarget);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
initIndicatorExamples();
|
||||||
|
|
||||||
|
if (window.$docsify) {
|
||||||
|
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
|
||||||
|
hook.doneEach(initIndicatorExamples);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -39,8 +39,9 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="https://unpkg.com/sigpro"></script>
|
<!-- <script src="https://unpkg.com/sigpro"></script> -->
|
||||||
<script src="https://cdn.jsdelivr.net/gh/natxocc/sigpro-ui@main/dist/sigpro-ui.umd.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/gh/natxocc/sigpro@main/dist/sigpro.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/gh/natxocc/sigpro-ui@main/dist/sigpro-ui.umd.js"></script>
|
||||||
<!-- <script src="https://unpkg.com/sigpro-ui"></script> -->
|
<!-- <script src="https://unpkg.com/sigpro-ui"></script> -->
|
||||||
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
|
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
|
||||||
<script src="//cdn.jsdelivr.net/npm/docsify-copy-code/dist/docsify-copy-code.min.js"></script>
|
<script src="//cdn.jsdelivr.net/npm/docsify-copy-code/dist/docsify-copy-code.min.js"></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user