Files
tabler/shared/ui/FormGroup.astro
T

81 lines
2.8 KiB
Plaintext

---
// Form group: label (.form-label) + control slot + optional hint (.form-text).
// When `for` is omitted and associate is true (default), the control is nested
// inside the label for implicit association. Set associate={false} for groups
// with multiple controls.
import FormText from './FormText.astro'
interface Props {
label?: string
/** .required asterisk on the label — also set required on the control */
required?: boolean
/** .form-label-description at the inline end of the label */
description?: string
/** .form-text under the control */
hint?: string
/** for attribute of the label — must match the slotted control's id */
for?: string
/** extra classes on the label (e.g. visually-hidden) */
labelClass?: string
/** wrapper classes; replaces the default mb-3 */
class?: string
/** marks the group invalid — pair with aria-invalid and .is-invalid on the control */
invalid?: boolean
/** error message shown under the control (.invalid-feedback) */
error?: string
/** nest the control in the label when `for` is omitted (default true) */
associate?: boolean
}
const { label, required, description, hint, for: htmlFor, labelClass, class: className = 'mb-3', invalid, error, associate = true } = Astro.props
// Hint/error ids exist only when the caller provides `for` — an auto-derived
// id is unreferenceable by the slotted control anyway, and label-based slugs
// collide between same-label groups on one page.
const hintId = hint && htmlFor ? `${htmlFor}-hint` : undefined
const errorId = error && htmlFor ? `${htmlFor}-error` : undefined
const wrapLabel = Boolean(label && !htmlFor && associate)
---
<div class:list={[className, invalid && 'has-validation']}>
{
label && htmlFor && (
<label class:list={['form-label', required && 'required', labelClass]} for={htmlFor}>
{label}
{required && <span class="visually-hidden"> (required)</span>}
{description && <span class="form-label-description">{description}</span>}
</label>
)
}
{
label && !htmlFor && !associate && (
<div class:list={['form-label', required && 'required', labelClass]}>
{label}
{description && <span class="form-label-description">{description}</span>}
</div>
)
}
{
wrapLabel ? (
<label class="d-block">
<span class:list={['form-label', required && 'required', labelClass]}>
{label}
{required && <span class="visually-hidden"> (required)</span>}
{description && <span class="form-label-description">{description}</span>}
</span>
<slot />
</label>
) : (
<slot />
)
}
{
error && (
<div class="invalid-feedback d-block" id={errorId} role="alert">
{error}
</div>
)
}
{hint && <FormText id={hintId}>{hint}</FormText>}
</div>