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