๐ข Form group
Basic
NFormGroup
- a wrapper component for NInput
, NTextarea
, Select
, and other form components. It provides a label, description, hint, message, status and other features.
for
attribute to the label and id
attribute to the input. If you want to override this behavior, you can define for
and id
attributes manually.Required
required
- adds *
to the label.
Description
description
- displays description text.
Hint
hint
- displays hint text.
Message
message
- displays message text. Useful in combination with status
prop.
We'll never share your email with anyone else.
Status
status
- changes the status of the form group. Useful for displaying validation status.
Possible values:
info
,success
,warning
,error
.
status
prop, the message
prop and the child component status
prop are automatically updated.Your username is available.
This information will be visible to other users.
Your email is invalid
Your password is weak.
Counter
counter.value
- displays counter text, useful for displaying the number of characters in the input.
counter.max
- the maximum number of characters.
Username has no length limit
Slots
Name | Description |
---|---|
default | The default slot of the form group, refer Basic section. |
top | The top section of the form group. |
bottom | The bottom section of the form group. |
label | The label slot of the form group. |
description | The description slot of the form group. |
hint | The hint slot of the form group. |
message | The message slot of the form group. |
counter | The counter slot of the form group. |
Props
import type { HTMLAttributes } from 'vue'
import type { NLabelProps } from './label'
export interface NFormGroupProps extends NLabelProps {
class?: HTMLAttributes['class']
/**
* Update the form group status.
*
* @default null
*/
status?: 'info' | 'success' | 'warning' | 'error'
/**
* Add a required indicator to the form group.
*
* @default false
*/
required?: boolean
/**
* Manually set the id attribute.
*
* By default, the id attribute is generated randomly for accessibility reasons.
*
* @default randomId
* @example
* id="email"
*/
id?: string
/**
* Label for the form group.
*
* @example
* label="Email"
*/
label?: string
/**
* Display `hint` message for the form group.
*
* @example
* hint="Enter your email address"
*/
hint?: any
/**
* Display `Description` message for the form group.
*
* @example
* description="We will never share your email with anyone else."
*/
description?: any
/**
* Display `Message` for the form group.
* Useful for displaying validation errors.
*
* @example
* message="Email is required"
*/
message?: any
/**
* Display `counter` for the form group.
* Useful for displaying character count.
*
* @example
* counter="{ value: 0, max: 100 }"
*/
counter?: {
value: number
max?: number
}
/**
* `UnaUI` preset configuration
*
* @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/form-group.ts
*/
una?: {
formGroup?: HTMLAttributes['class']
formGroupTopWrapper?: HTMLAttributes['class']
formGroupTopWrapperInner?: HTMLAttributes['class']
formGroupBottomWrapper?: HTMLAttributes['class']
formGroupCounterWrapper?: HTMLAttributes['class']
formGroupMessageWrapper?: HTMLAttributes['class']
formGroupLabelWrapper?: HTMLAttributes['class']
formGroupLabel?: HTMLAttributes['class']
formGroupDescription?: HTMLAttributes['class']
formGroupHint?: HTMLAttributes['class']
formGroupMessage?: HTMLAttributes['class']
formGroupLabelRequired?: HTMLAttributes['class']
}
}
Presets
type FormGroupPrefix = 'form-group'
export const staticFormGroup: Record<`${FormGroupPrefix}-${string}` | FormGroupPrefix, string> = {
// base
'form-group': 'space-y-2 flex flex-col',
'form-group-description': 'text-sm leading-6 text-$c-gray-500',
'form-group-hint': 'text-sm leading-6 text-$c-gray-500',
'form-group-message': 'text-sm transition-all duration-1000 ease-in-out',
// wrappers
'form-group-top-wrapper': 'flex flex-col',
'form-group-top-wrapper-inner': 'flex justify-between items-end space-x-1.5',
'form-group-bottom-wrapper': 'flex space-x-1.5 justify-between items-start',
'form-group-message-wrapper': '',
// label
'form-group-label-wrapper': 'flex',
'form-group-label': 'block text-sm leading-6 font-medium text-$c-gray-900',
'form-group-label-required': 'after:content-[\'*\'] after:ms-0.5 after:text-error',
// counter
'form-group-counter-wrapper': 'text-sm',
'form-group-counter-error': 'text-error',
'form-group-counter-current': 'text-$c-gray-900',
'form-group-counter-separator': 'text-$c-gray-500',
'form-group-counter-max': 'text-$c-gray-500',
}
export const formGroup = [
staticFormGroup,
]
Component
<script setup lang="ts">
import type { NFormGroupProps } from '../../types'
import { computed } from 'vue'
import { cn, randomId } from '../../utils'
import Label from '../elements/Label.vue'
import NFormGroupDefaultSlot from '../slots/FormGroupDefault'
const props = defineProps<NFormGroupProps>()
const id = computed(() => props.id ?? randomId('form-group'))
const statusClassVariants = computed(() => {
const text = {
info: 'text-info',
success: 'text-success',
warning: 'text-warning',
error: 'text-error',
default: 'text-muted',
}
return text[props.status ?? 'default']
})
</script>
<template>
<div
:class="cn(
'form-group',
props.class,
una?.formGroup,
)"
>
<slot name="top">
<div
form-group="message-wrapper"
:class="una?.formGroupMessageWrapper"
>
<div
v-if="label || hint || description"
form-group="top-wrapper"
:class="una?.formGroupTopWrapper"
>
<div
v-if="label || hint"
form-group="top-wrapper-inner"
:class="una?.formGroupTopWrapperInner"
>
<slot name="label">
<Label
:for="props.for ?? id"
>
<div
form-group="label-wrapper"
:class="una?.formGroupLabelWrapper"
>
<span
form-group="label"
:class="una?.formGroupLabel"
>
{{ label }}
</span>
<span
v-if="required"
form-group="label-required"
:class="una?.formGroupLabelRequired"
/>
</div>
</Label>
</slot>
<slot name="hint">
<span
v-if="hint"
form-group="hint"
:class="una?.formGroupHint"
>
{{ hint }}
</span>
</slot>
</div>
<slot name="description">
<span
v-if="description"
form-group="description"
:class="una?.formGroupDescription"
>
{{ description }}
</span>
</slot>
</div>
</div>
</slot>
<NFormGroupDefaultSlot
:id="id"
:status="status"
>
<slot />
</NFormGroupDefaultSlot>
<slot name="bottom">
<div
v-if="message || counter"
form-group="bottom-wrapper"
:class="[
{ 'justify-end': !message && counter },
una?.formGroupBottomWrapper,
]"
>
<slot name="message">
<div
v-if="message"
form-group="message-wrapper"
:class="una?.formGroupMessageWrapper"
>
<p
form-group="message"
:class="[
una?.formGroupMessage,
statusClassVariants,
]"
>
{{ message }}
</p>
</div>
</slot>
<slot name="counter">
<div
v-if="counter"
form-group="counter-wrapper"
:class="una?.formGroupCounterWrapper"
>
<span
:class="`${counter?.value >= (counter?.max || 0) && counter?.max
? 'form-group-counter-error'
: 'form-group-counter-current'}`"
>
{{ counter?.value }}
</span>
<span v-if="counter?.max" form-group="counter-separator">/</span>
<span v-if="counter?.max" form-group="counter-max">{{ counter?.max }}</span>
</div>
</slot>
</div>
</slot>
</div>
</template>