Update Docs
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user