Update Docs

This commit is contained in:
2026-03-31 19:28:26 +02:00
parent 3619239b9d
commit 80f1be6f07
3 changed files with 1087 additions and 8 deletions

View File

@@ -1,6 +1,6 @@
# Radio # Radio
Radio button component with label, tooltip support, and reactive group selection. Radio button component with label, tooltip support, and reactive group selection. All radios in the same group share a common `name` attribute for proper HTML semantics.
## Tag ## Tag
@@ -11,8 +11,9 @@ Radio button component with label, tooltip support, and reactive group selection
| Prop | Type | Default | Description | | Prop | Type | Default | Description |
| :----------- | :--------------------------- | :---------- | :----------------------------------------------- | | :----------- | :--------------------------- | :---------- | :----------------------------------------------- |
| `label` | `string` | `-` | Label text for the radio button | | `label` | `string` | `-` | Label text for the radio button |
| `value` | `string \| Signal<string>` | `-` | Selected value (for group) | | `value` | `string \| Signal<string>` | `-` | Selected value signal for the group |
| `radioValue` | `string` | `-` | Value of this radio button | | `radioValue` | `string` | `-` | Value of this radio button |
| `name` | `string` | `-` | Group name (all radios in group should share this) |
| `tooltip` | `string` | `-` | Tooltip text on hover | | `tooltip` | `string` | `-` | Tooltip text on hover |
| `disabled` | `boolean \| Signal<boolean>` | `false` | Disabled state | | `disabled` | `boolean \| Signal<boolean>` | `false` | Disabled state |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) | | `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
@@ -36,18 +37,21 @@ const BasicDemo = () => {
return Div({ class: 'flex flex-col gap-3' }, [ return Div({ class: 'flex flex-col gap-3' }, [
Radio({ Radio({
label: 'Option 1', label: 'Option 1',
name: 'basic-group',
value: selected, value: selected,
radioValue: 'option1', radioValue: 'option1',
onclick: () => selected('option1') onclick: () => selected('option1')
}), }),
Radio({ Radio({
label: 'Option 2', label: 'Option 2',
name: 'basic-group',
value: selected, value: selected,
radioValue: 'option2', radioValue: 'option2',
onclick: () => selected('option2') onclick: () => selected('option2')
}), }),
Radio({ Radio({
label: 'Option 3', label: 'Option 3',
name: 'basic-group',
value: selected, value: selected,
radioValue: 'option3', radioValue: 'option3',
onclick: () => selected('option3') onclick: () => selected('option3')
@@ -74,6 +78,7 @@ const TooltipDemo = () => {
return Div({ class: 'flex flex-col gap-3' }, [ return Div({ class: 'flex flex-col gap-3' }, [
Radio({ Radio({
label: 'Light mode', label: 'Light mode',
name: 'theme-group',
value: selected, value: selected,
radioValue: 'light', radioValue: 'light',
tooltip: 'Light theme with white background', tooltip: 'Light theme with white background',
@@ -81,6 +86,7 @@ const TooltipDemo = () => {
}), }),
Radio({ Radio({
label: 'Dark mode', label: 'Dark mode',
name: 'theme-group',
value: selected, value: selected,
radioValue: 'dark', radioValue: 'dark',
tooltip: 'Dark theme with black background', tooltip: 'Dark theme with black background',
@@ -107,12 +113,14 @@ const DisabledDemo = () => {
return Div({ class: 'flex flex-col gap-3' }, [ return Div({ class: 'flex flex-col gap-3' }, [
Radio({ Radio({
label: 'Enabled option', label: 'Enabled option',
name: 'disabled-group',
value: selected, value: selected,
radioValue: 'enabled', radioValue: 'enabled',
onclick: () => selected('enabled') onclick: () => selected('enabled')
}), }),
Radio({ Radio({
label: 'Disabled option (cannot select)', label: 'Disabled option (cannot select)',
name: 'disabled-group',
value: selected, value: selected,
radioValue: 'disabled', radioValue: 'disabled',
disabled: true, disabled: true,
@@ -144,9 +152,9 @@ const ReactiveDemo = () => {
]; ];
const colors = [ const colors = [
{ value: 'blue', label: 'Blue', class: 'text-blue-600' }, { value: 'blue', label: 'Blue' },
{ value: 'green', label: 'Green', class: 'text-green-600' }, { value: 'green', label: 'Green' },
{ value: 'red', label: 'Red', class: 'text-red-600' } { value: 'red', label: 'Red' }
]; ];
return Div({ class: 'flex flex-col gap-4' }, [ return Div({ class: 'flex flex-col gap-4' }, [
@@ -154,6 +162,7 @@ const ReactiveDemo = () => {
Div({ class: 'flex gap-4' }, sizes.map(s => Div({ class: 'flex gap-4' }, sizes.map(s =>
Radio({ Radio({
label: s.label, label: s.label,
name: 'size-group',
value: size, value: size,
radioValue: s.value, radioValue: s.value,
onclick: () => size(s.value) onclick: () => size(s.value)
@@ -163,6 +172,7 @@ const ReactiveDemo = () => {
Div({ class: 'flex gap-4' }, colors.map(c => Div({ class: 'flex gap-4' }, colors.map(c =>
Radio({ Radio({
label: c.label, label: c.label,
name: 'color-group',
value: color, value: color,
radioValue: c.value, radioValue: c.value,
onclick: () => color(c.value) onclick: () => color(c.value)
@@ -199,18 +209,21 @@ const PaymentDemo = () => {
Div({ class: 'flex flex-col gap-3' }, [ Div({ class: 'flex flex-col gap-3' }, [
Radio({ Radio({
label: '💳 Credit Card', label: '💳 Credit Card',
name: 'payment-group',
value: method, value: method,
radioValue: 'credit', radioValue: 'credit',
onclick: () => method('credit') onclick: () => method('credit')
}), }),
Radio({ Radio({
label: '🏦 Bank Transfer', label: '🏦 Bank Transfer',
name: 'payment-group',
value: method, value: method,
radioValue: 'bank', radioValue: 'bank',
onclick: () => method('bank') onclick: () => method('bank')
}), }),
Radio({ Radio({
label: '📱 Digital Wallet', label: '📱 Digital Wallet',
name: 'payment-group',
value: method, value: method,
radioValue: 'wallet', radioValue: 'wallet',
onclick: () => method('wallet') onclick: () => method('wallet')
@@ -250,6 +263,7 @@ const VariantsDemo = () => {
Div({ class: 'flex gap-4' }, [ Div({ class: 'flex gap-4' }, [
Radio({ Radio({
label: 'Option A', label: 'Option A',
name: 'primary-group',
value: primary, value: primary,
radioValue: 'primary1', radioValue: 'primary1',
class: 'radio-primary', class: 'radio-primary',
@@ -257,6 +271,7 @@ const VariantsDemo = () => {
}), }),
Radio({ Radio({
label: 'Option B', label: 'Option B',
name: 'primary-group',
value: primary, value: primary,
radioValue: 'primary2', radioValue: 'primary2',
class: 'radio-primary', class: 'radio-primary',
@@ -269,6 +284,7 @@ const VariantsDemo = () => {
Div({ class: 'flex gap-4' }, [ Div({ class: 'flex gap-4' }, [
Radio({ Radio({
label: 'Option A', label: 'Option A',
name: 'secondary-group',
value: secondary, value: secondary,
radioValue: 'secondary1', radioValue: 'secondary1',
class: 'radio-secondary', class: 'radio-secondary',
@@ -276,6 +292,7 @@ const VariantsDemo = () => {
}), }),
Radio({ Radio({
label: 'Option B', label: 'Option B',
name: 'secondary-group',
value: secondary, value: secondary,
radioValue: 'secondary2', radioValue: 'secondary2',
class: 'radio-secondary', class: 'radio-secondary',
@@ -288,6 +305,7 @@ const VariantsDemo = () => {
Div({ class: 'flex gap-4' }, [ Div({ class: 'flex gap-4' }, [
Radio({ Radio({
label: 'Option A', label: 'Option A',
name: 'accent-group',
value: accent, value: accent,
radioValue: 'accent1', radioValue: 'accent1',
class: 'radio-accent', class: 'radio-accent',
@@ -295,6 +313,7 @@ const VariantsDemo = () => {
}), }),
Radio({ Radio({
label: 'Option B', label: 'Option B',
name: 'accent-group',
value: accent, value: accent,
radioValue: 'accent2', radioValue: 'accent2',
class: 'radio-accent', class: 'radio-accent',
@@ -338,6 +357,7 @@ const DynamicDemo = () => {
Div({ class: 'flex gap-4' }, [ Div({ class: 'flex gap-4' }, [
Radio({ Radio({
label: 'Cars', label: 'Cars',
name: 'category-group',
value: category, value: category,
radioValue: 'cars', radioValue: 'cars',
onclick: () => { onclick: () => {
@@ -347,6 +367,7 @@ const DynamicDemo = () => {
}), }),
Radio({ Radio({
label: 'Colors', label: 'Colors',
name: 'category-group',
value: category, value: category,
radioValue: 'colors', radioValue: 'colors',
onclick: () => { onclick: () => {
@@ -361,6 +382,7 @@ const DynamicDemo = () => {
options[category()].map(opt => options[category()].map(opt =>
Radio({ Radio({
label: opt.label, label: opt.label,
name: 'dynamic-option-group',
value: selected, value: selected,
radioValue: opt.value, radioValue: opt.value,
onclick: () => selected(opt.value) onclick: () => selected(opt.value)
@@ -388,18 +410,21 @@ $mount(DynamicDemo, '#demo-dynamic');
return Div({ class: 'flex flex-col gap-3' }, [ return Div({ class: 'flex flex-col gap-3' }, [
Radio({ Radio({
label: 'Option 1', label: 'Option 1',
name: 'basic-group',
value: selected, value: selected,
radioValue: 'option1', radioValue: 'option1',
onclick: () => selected('option1') onclick: () => selected('option1')
}), }),
Radio({ Radio({
label: 'Option 2', label: 'Option 2',
name: 'basic-group',
value: selected, value: selected,
radioValue: 'option2', radioValue: 'option2',
onclick: () => selected('option2') onclick: () => selected('option2')
}), }),
Radio({ Radio({
label: 'Option 3', label: 'Option 3',
name: 'basic-group',
value: selected, value: selected,
radioValue: 'option3', radioValue: 'option3',
onclick: () => selected('option3') onclick: () => selected('option3')
@@ -419,6 +444,7 @@ $mount(DynamicDemo, '#demo-dynamic');
return Div({ class: 'flex flex-col gap-3' }, [ return Div({ class: 'flex flex-col gap-3' }, [
Radio({ Radio({
label: 'Light mode', label: 'Light mode',
name: 'theme-group',
value: selected, value: selected,
radioValue: 'light', radioValue: 'light',
tooltip: 'Light theme with white background', tooltip: 'Light theme with white background',
@@ -426,6 +452,7 @@ $mount(DynamicDemo, '#demo-dynamic');
}), }),
Radio({ Radio({
label: 'Dark mode', label: 'Dark mode',
name: 'theme-group',
value: selected, value: selected,
radioValue: 'dark', radioValue: 'dark',
tooltip: 'Dark theme with black background', tooltip: 'Dark theme with black background',
@@ -445,12 +472,14 @@ $mount(DynamicDemo, '#demo-dynamic');
return Div({ class: 'flex flex-col gap-3' }, [ return Div({ class: 'flex flex-col gap-3' }, [
Radio({ Radio({
label: 'Enabled option', label: 'Enabled option',
name: 'disabled-group',
value: selected, value: selected,
radioValue: 'enabled', radioValue: 'enabled',
onclick: () => selected('enabled') onclick: () => selected('enabled')
}), }),
Radio({ Radio({
label: 'Disabled option (cannot select)', label: 'Disabled option (cannot select)',
name: 'disabled-group',
value: selected, value: selected,
radioValue: 'disabled', radioValue: 'disabled',
disabled: true, disabled: true,
@@ -475,9 +504,9 @@ $mount(DynamicDemo, '#demo-dynamic');
]; ];
const colors = [ const colors = [
{ value: 'blue', label: 'Blue', class: 'text-blue-600' }, { value: 'blue', label: 'Blue' },
{ value: 'green', label: 'Green', class: 'text-green-600' }, { value: 'green', label: 'Green' },
{ value: 'red', label: 'Red', class: 'text-red-600' } { value: 'red', label: 'Red' }
]; ];
return Div({ class: 'flex flex-col gap-4' }, [ return Div({ class: 'flex flex-col gap-4' }, [
@@ -485,6 +514,7 @@ $mount(DynamicDemo, '#demo-dynamic');
Div({ class: 'flex gap-4' }, sizes.map(s => Div({ class: 'flex gap-4' }, sizes.map(s =>
Radio({ Radio({
label: s.label, label: s.label,
name: 'size-group',
value: size, value: size,
radioValue: s.value, radioValue: s.value,
onclick: () => size(s.value) onclick: () => size(s.value)
@@ -494,6 +524,7 @@ $mount(DynamicDemo, '#demo-dynamic');
Div({ class: 'flex gap-4' }, colors.map(c => Div({ class: 'flex gap-4' }, colors.map(c =>
Radio({ Radio({
label: c.label, label: c.label,
name: 'color-group',
value: color, value: color,
radioValue: c.value, radioValue: c.value,
onclick: () => color(c.value) onclick: () => color(c.value)
@@ -523,18 +554,21 @@ $mount(DynamicDemo, '#demo-dynamic');
Div({ class: 'flex flex-col gap-3' }, [ Div({ class: 'flex flex-col gap-3' }, [
Radio({ Radio({
label: '💳 Credit Card', label: '💳 Credit Card',
name: 'payment-group',
value: method, value: method,
radioValue: 'credit', radioValue: 'credit',
onclick: () => method('credit') onclick: () => method('credit')
}), }),
Radio({ Radio({
label: '🏦 Bank Transfer', label: '🏦 Bank Transfer',
name: 'payment-group',
value: method, value: method,
radioValue: 'bank', radioValue: 'bank',
onclick: () => method('bank') onclick: () => method('bank')
}), }),
Radio({ Radio({
label: '📱 Digital Wallet', label: '📱 Digital Wallet',
name: 'payment-group',
value: method, value: method,
radioValue: 'wallet', radioValue: 'wallet',
onclick: () => method('wallet') onclick: () => method('wallet')
@@ -567,6 +601,7 @@ $mount(DynamicDemo, '#demo-dynamic');
Div({ class: 'flex gap-4' }, [ Div({ class: 'flex gap-4' }, [
Radio({ Radio({
label: 'Option A', label: 'Option A',
name: 'primary-group',
value: primary, value: primary,
radioValue: 'primary1', radioValue: 'primary1',
class: 'radio-primary', class: 'radio-primary',
@@ -574,6 +609,7 @@ $mount(DynamicDemo, '#demo-dynamic');
}), }),
Radio({ Radio({
label: 'Option B', label: 'Option B',
name: 'primary-group',
value: primary, value: primary,
radioValue: 'primary2', radioValue: 'primary2',
class: 'radio-primary', class: 'radio-primary',
@@ -586,6 +622,7 @@ $mount(DynamicDemo, '#demo-dynamic');
Div({ class: 'flex gap-4' }, [ Div({ class: 'flex gap-4' }, [
Radio({ Radio({
label: 'Option A', label: 'Option A',
name: 'secondary-group',
value: secondary, value: secondary,
radioValue: 'secondary1', radioValue: 'secondary1',
class: 'radio-secondary', class: 'radio-secondary',
@@ -593,6 +630,7 @@ $mount(DynamicDemo, '#demo-dynamic');
}), }),
Radio({ Radio({
label: 'Option B', label: 'Option B',
name: 'secondary-group',
value: secondary, value: secondary,
radioValue: 'secondary2', radioValue: 'secondary2',
class: 'radio-secondary', class: 'radio-secondary',
@@ -605,6 +643,7 @@ $mount(DynamicDemo, '#demo-dynamic');
Div({ class: 'flex gap-4' }, [ Div({ class: 'flex gap-4' }, [
Radio({ Radio({
label: 'Option A', label: 'Option A',
name: 'accent-group',
value: accent, value: accent,
radioValue: 'accent1', radioValue: 'accent1',
class: 'radio-accent', class: 'radio-accent',
@@ -612,6 +651,7 @@ $mount(DynamicDemo, '#demo-dynamic');
}), }),
Radio({ Radio({
label: 'Option B', label: 'Option B',
name: 'accent-group',
value: accent, value: accent,
radioValue: 'accent2', radioValue: 'accent2',
class: 'radio-accent', class: 'radio-accent',
@@ -648,6 +688,7 @@ $mount(DynamicDemo, '#demo-dynamic');
Div({ class: 'flex gap-4' }, [ Div({ class: 'flex gap-4' }, [
Radio({ Radio({
label: 'Cars', label: 'Cars',
name: 'category-group',
value: category, value: category,
radioValue: 'cars', radioValue: 'cars',
onclick: () => { onclick: () => {
@@ -657,6 +698,7 @@ $mount(DynamicDemo, '#demo-dynamic');
}), }),
Radio({ Radio({
label: 'Colors', label: 'Colors',
name: 'category-group',
value: category, value: category,
radioValue: 'colors', radioValue: 'colors',
onclick: () => { onclick: () => {
@@ -671,6 +713,7 @@ $mount(DynamicDemo, '#demo-dynamic');
options[category()].map(opt => options[category()].map(opt =>
Radio({ Radio({
label: opt.label, label: opt.label,
name: 'dynamic-option-group',
value: selected, value: selected,
radioValue: opt.value, radioValue: opt.value,
onclick: () => selected(opt.value) onclick: () => selected(opt.value)

View File

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

View File

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