mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-20 23:11:43 +01:00
docs(form): update (#2167)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
This commit is contained in:
@@ -21,8 +21,26 @@ const props = withDefaults(defineProps<{
|
||||
* @defaultValue true
|
||||
*/
|
||||
preview?: boolean
|
||||
|
||||
/**
|
||||
* Whether to show the source code
|
||||
* @defaultValue true
|
||||
*/
|
||||
source?: boolean
|
||||
|
||||
/**
|
||||
* A list of variable props to link to the component.
|
||||
*/
|
||||
options?: Array<{
|
||||
name: string
|
||||
label: string
|
||||
items: any[]
|
||||
default: any
|
||||
multiple: boolean
|
||||
}>
|
||||
}>(), {
|
||||
preview: true
|
||||
preview: true,
|
||||
source: true
|
||||
})
|
||||
|
||||
const { $prettier } = useNuxtApp()
|
||||
@@ -71,16 +89,48 @@ const { data: ast } = await useAsyncData(`component-example-${camelName}`, async
|
||||
|
||||
return parseMarkdown(formatted)
|
||||
}, { watch: [code] })
|
||||
|
||||
const optionsValues = ref(props.options?.reduce((acc, option) => {
|
||||
if (option.name) {
|
||||
acc[option.name] = option.default
|
||||
}
|
||||
return acc
|
||||
}, {}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="my-5">
|
||||
<div v-if="preview">
|
||||
<div class="flex border border-b-0 border-gray-300 dark:border-gray-700 relative p-4 rounded-t-md z-[1]" :class="[props.class]">
|
||||
<component :is="camelName" v-bind="componentProps" />
|
||||
<div class="border border-gray-300 dark:border-gray-700 relative z-[1]" :class="[props.class, { 'border-b-0 rounded-t-md': props.source, 'rounded-md': !props.source }]">
|
||||
<div v-if="props.options?.length" class="flex gap-4 p-4 border-b border-gray-300 dark:border-gray-700">
|
||||
<UFormField
|
||||
v-for="option in props.options"
|
||||
:key="option.name"
|
||||
:label="option.label"
|
||||
:name="option.name"
|
||||
size="sm"
|
||||
class="inline-flex ring ring-gray-300 dark:ring-gray-700 rounded"
|
||||
:ui="{
|
||||
wrapper: 'bg-gray-50 dark:bg-gray-800/50 rounded-l flex border-r border-gray-300 dark:border-gray-700',
|
||||
label: 'text-gray-500 dark:text-gray-400 px-2 py-1.5',
|
||||
container: 'mt-0'
|
||||
}"
|
||||
>
|
||||
<USelectMenu
|
||||
v-model="optionsValues[option.name]"
|
||||
:items="option.items"
|
||||
class="rounded rounded-l-none w-40"
|
||||
multiple
|
||||
:ui="{ itemLeadingChip: 'size-2' }"
|
||||
/>
|
||||
</UFormField>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<component :is="camelName" v-bind="{ ...componentProps, ...optionsValues }" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MDCRenderer v-if="ast" :body="ast.body" :data="ast.data" class="[&_pre]:!rounded-t-none [&_div.my-5]:!mt-0" />
|
||||
<MDCRenderer v-if="ast && props.source" :body="ast.body" :data="ast.data" class="[&_pre]:!rounded-t-none [&_div.my-5]:!mt-0" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormError, FormSubmitEvent } from '#ui/types'
|
||||
|
||||
const state = reactive({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
const validate = (state: any): FormError[] => {
|
||||
const errors = []
|
||||
if (!state.email) errors.push({ name: 'email', message: 'Required' })
|
||||
if (!state.password) errors.push({ name: 'password', message: 'Required' })
|
||||
return errors
|
||||
}
|
||||
|
||||
const toast = useToast()
|
||||
async function onSubmit(event: FormSubmitEvent<any>) {
|
||||
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'green' })
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm :validate="validate" :state="state" class="space-y-4" @submit="onSubmit">
|
||||
<UFormField label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField label="Password" name="password">
|
||||
<UInput v-model="state.password" type="password" />
|
||||
</UFormField>
|
||||
|
||||
<UButton type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</UForm>
|
||||
</template>
|
||||
@@ -0,0 +1,115 @@
|
||||
<script setup lang="ts">
|
||||
import { z } from 'zod'
|
||||
import type { FormSubmitEvent, Form } from '@nuxt/ui'
|
||||
|
||||
const schema = z.object({
|
||||
input: z.string().min(10),
|
||||
inputMenu: z.any().refine(option => option?.value === 'option-2', {
|
||||
message: 'Select Option 2'
|
||||
}),
|
||||
inputMenuMultiple: z.any().refine(values => !!values?.find((option: any) => option.value === 'option-2'), {
|
||||
message: 'Include Option 2'
|
||||
}),
|
||||
textarea: z.string().min(10),
|
||||
select: z.string().refine(value => value === 'option-2', {
|
||||
message: 'Select Option 2'
|
||||
}),
|
||||
selectMenu: z.any().refine(option => option?.value === 'option-2', {
|
||||
message: 'Select Option 2'
|
||||
}),
|
||||
selectMenuMultiple: z.any().refine(values => !!values?.find((option: any) => option.value === 'option-2'), {
|
||||
message: 'Include Option 2'
|
||||
}),
|
||||
switch: z.boolean().refine(value => value === true, {
|
||||
message: 'Toggle me'
|
||||
}),
|
||||
checkbox: z.boolean().refine(value => value === true, {
|
||||
message: 'Check me'
|
||||
}),
|
||||
radioGroup: z.string().refine(value => value === 'option-2', {
|
||||
message: 'Select Option 2'
|
||||
}),
|
||||
slider: z.number().max(20, { message: 'Must be less than 20' })
|
||||
})
|
||||
|
||||
type Schema = z.output<typeof schema>
|
||||
|
||||
const state = reactive<Partial<Schema>>({})
|
||||
const form = ref<Form<Schema>>()
|
||||
|
||||
const items = [
|
||||
{ label: 'Option 1', value: 'option-1' },
|
||||
{ label: 'Option 2', value: 'option-2' },
|
||||
{ label: 'Option 3', value: 'option-3' }
|
||||
]
|
||||
|
||||
const toast = useToast()
|
||||
async function onSubmit(event: FormSubmitEvent<any>) {
|
||||
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'green' })
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm ref="form" :state="state" :schema="schema" @submit="onSubmit">
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<UFormField label="Input" name="input">
|
||||
<UInput v-model="state.input" placeholder="john@lennon.com" class="w-40" />
|
||||
</UFormField>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<UFormField name="switch">
|
||||
<USwitch v-model="state.switch" label="Switch me" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField name="checkbox">
|
||||
<UCheckbox v-model="state.checkbox" label="Check me" />
|
||||
</UFormField>
|
||||
</div>
|
||||
|
||||
<UFormField name="slider" label="Slider">
|
||||
<USlider v-model="state.slider" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField name="select" label="Select">
|
||||
<USelect v-model="state.select" :items="items" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField name="selectMenu" label="Select Menu">
|
||||
<USelectMenu v-model="state.selectMenu" :items="items" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField name="selectMenuMultiple" label="Select Menu (Multiple)">
|
||||
<USelectMenu v-model="state.selectMenuMultiple" multiple :items="items" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField name="inputMenu" label="Input Menu">
|
||||
<UInputMenu v-model="state.inputMenu" :items="items" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField name="inputMenuMultiple" label="Input Menu (Multiple)">
|
||||
<UInputMenu v-model="state.inputMenuMultiple" multiple :items="items" />
|
||||
</UFormField>
|
||||
|
||||
<span />
|
||||
|
||||
<UFormField label="Textarea" name="textarea">
|
||||
<UTextarea v-model="state.textarea" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField name="radioGroup">
|
||||
<URadioGroup v-model="state.radioGroup" legend="Radio group" :items="items" />
|
||||
</UFormField>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 mt-8">
|
||||
<UButton color="gray" type="submit" :disabled="form?.disabled">
|
||||
Submit
|
||||
</UButton>
|
||||
|
||||
<UButton color="gray" variant="outline" :disabled="form?.disabled" @click="form?.clear()">
|
||||
Clear
|
||||
</UButton>
|
||||
</div>
|
||||
</UForm>
|
||||
</template>
|
||||
38
docs/app/components/content/examples/form/FormExampleJoi.vue
Normal file
38
docs/app/components/content/examples/form/FormExampleJoi.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
import Joi from 'joi'
|
||||
import type { FormSubmitEvent } from '#ui/types'
|
||||
|
||||
const schema = Joi.object({
|
||||
email: Joi.string().required(),
|
||||
password: Joi.string()
|
||||
.min(8)
|
||||
.required()
|
||||
})
|
||||
|
||||
const state = reactive({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
const toast = useToast()
|
||||
async function onSubmit(event: FormSubmitEvent<any>) {
|
||||
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'green' })
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
|
||||
<UFormField label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField label="Password" name="password">
|
||||
<UInput v-model="state.password" type="password" />
|
||||
</UFormField>
|
||||
|
||||
<UButton type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</UForm>
|
||||
</template>
|
||||
@@ -0,0 +1,54 @@
|
||||
<script setup lang="ts">
|
||||
import { z } from 'zod'
|
||||
import type { FormSubmitEvent } from '@nuxt/ui'
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string().min(2),
|
||||
news: z.boolean()
|
||||
})
|
||||
|
||||
type Schema = z.output<typeof schema>
|
||||
|
||||
const nestedSchema = z.object({
|
||||
email: z.string().email()
|
||||
})
|
||||
|
||||
type NestedSchema = z.output<typeof nestedSchema>
|
||||
|
||||
const state = reactive<Partial<Schema & NestedSchema>>({ })
|
||||
|
||||
const toast = useToast()
|
||||
async function onSubmit(event: FormSubmitEvent<any>) {
|
||||
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'green' })
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
:state="state"
|
||||
:schema="schema"
|
||||
class="gap-4 flex flex-col w-60"
|
||||
@submit="(event) => onSubmit(event)"
|
||||
>
|
||||
<UFormField label="Name" name="name">
|
||||
<UInput v-model="state.name" placeholder="John Lennon" />
|
||||
</UFormField>
|
||||
|
||||
<div>
|
||||
<UCheckbox v-model="state.news" name="news" label="Register to our newsletter" />
|
||||
</div>
|
||||
|
||||
<UForm v-if="state.news" :state="state" :schema="nestedSchema">
|
||||
<UFormField label="Email" name="email">
|
||||
<UInput v-model="state.email" placeholder="john@lennon.com" />
|
||||
</UFormField>
|
||||
</UForm>
|
||||
|
||||
<div>
|
||||
<UButton type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</div>
|
||||
</UForm>
|
||||
</template>
|
||||
@@ -0,0 +1,79 @@
|
||||
<script setup lang="ts">
|
||||
import { z } from 'zod'
|
||||
import type { FormSubmitEvent } from '@nuxt/ui'
|
||||
|
||||
const schema = z.object({
|
||||
customer: z.string().min(2)
|
||||
})
|
||||
|
||||
type Schema = z.output<typeof schema>
|
||||
|
||||
const itemSchema = z.object({
|
||||
description: z.string().min(1),
|
||||
price: z.number().min(0)
|
||||
})
|
||||
|
||||
type ItemSchema = z.output<typeof itemSchema>
|
||||
|
||||
const state = reactive<Partial<Schema & { items: Partial<ItemSchema>[] }>>({
|
||||
items: [{}]
|
||||
})
|
||||
|
||||
function addItem() {
|
||||
if (!state.items) {
|
||||
state.items = []
|
||||
}
|
||||
state.items.push({})
|
||||
}
|
||||
|
||||
function removeItem() {
|
||||
if (state.items) {
|
||||
state.items.pop()
|
||||
}
|
||||
}
|
||||
const formItemRef = ref()
|
||||
|
||||
const toast = useToast()
|
||||
async function onSubmit(event: FormSubmitEvent<any>) {
|
||||
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'green' })
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm
|
||||
ref="formItemRef"
|
||||
:state="state"
|
||||
:schema="schema"
|
||||
class="gap-4 flex flex-col w-60"
|
||||
@submit="onSubmit"
|
||||
>
|
||||
<UFormField label="Customer" name="customer">
|
||||
<UInput v-model="state.customer" placeholder="Wonka Industries" />
|
||||
</UFormField>
|
||||
|
||||
<UForm v-for="item, count in state.items" :key="count" :state="item" :schema="itemSchema" class="flex gap-2">
|
||||
<UFormField :label="!count ? 'Description' : undefined" name="description">
|
||||
<UInput v-model="item.description" />
|
||||
</UFormField>
|
||||
<UFormField :label="!count ? 'Price' : undefined" name="price" class="w-20">
|
||||
<UInput v-model="item.price" type="number" />
|
||||
</UFormField>
|
||||
</UForm>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<UButton color="gray" variant="subtle" size="sm" @click="addItem()">
|
||||
Add Item
|
||||
</UButton>
|
||||
|
||||
<UButton color="gray" variant="ghost" size="sm" @click="removeItem()">
|
||||
Remove Item
|
||||
</UButton>
|
||||
</div>
|
||||
<div>
|
||||
<UButton type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</div>
|
||||
</UForm>
|
||||
</template>
|
||||
@@ -0,0 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormError, FormErrorEvent, FormSubmitEvent } from '#ui/types'
|
||||
|
||||
const state = reactive({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
const validate = (state: any): FormError[] => {
|
||||
const errors = []
|
||||
if (!state.email) errors.push({ name: 'email', message: 'Required' })
|
||||
if (!state.password) errors.push({ name: 'password', message: 'Required' })
|
||||
return errors
|
||||
}
|
||||
|
||||
const toast = useToast()
|
||||
async function onSubmit(event: FormSubmitEvent<any>) {
|
||||
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'green' })
|
||||
console.log(event.data)
|
||||
}
|
||||
|
||||
async function onError(event: FormErrorEvent) {
|
||||
const element = document.getElementById(event.errors[0].id)
|
||||
element?.focus()
|
||||
element?.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm :validate="validate" :state="state" class="space-y-4" @submit="onSubmit" @error="onError">
|
||||
<UFormField label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField label="Password" name="password">
|
||||
<UInput v-model="state.password" type="password" />
|
||||
</UFormField>
|
||||
|
||||
<UButton type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</UForm>
|
||||
</template>
|
||||
@@ -0,0 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
import * as v from 'valibot'
|
||||
import type { FormSubmitEvent } from '#ui/types'
|
||||
|
||||
const schema = v.object({
|
||||
email: v.pipe(v.string(), v.email('Invalid email')),
|
||||
password: v.pipe(v.string(), v.minLength(8, 'Must be at least 8 characters'))
|
||||
})
|
||||
|
||||
type Schema = v.InferOutput<typeof schema>
|
||||
|
||||
const state = reactive({
|
||||
email: '',
|
||||
password: ''
|
||||
})
|
||||
|
||||
const toast = useToast()
|
||||
async function onSubmit(event: FormSubmitEvent<Schema>) {
|
||||
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'green' })
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm :schema="v.safeParser(schema)" :state="state" class="space-y-4" @submit="onSubmit">
|
||||
<UFormField label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField label="Password" name="password">
|
||||
<UInput v-model="state.password" type="password" />
|
||||
</UFormField>
|
||||
|
||||
<UButton type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</UForm>
|
||||
</template>
|
||||
40
docs/app/components/content/examples/form/FormExampleYup.vue
Normal file
40
docs/app/components/content/examples/form/FormExampleYup.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
import { object, string, type InferType } from 'yup'
|
||||
import type { FormSubmitEvent } from '#ui/types'
|
||||
|
||||
const schema = object({
|
||||
email: string().email('Invalid email').required('Required'),
|
||||
password: string()
|
||||
.min(8, 'Must be at least 8 characters')
|
||||
.required('Required')
|
||||
})
|
||||
|
||||
type Schema = InferType<typeof schema>
|
||||
|
||||
const state = reactive({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
const toast = useToast()
|
||||
async function onSubmit(event: FormSubmitEvent<Schema>) {
|
||||
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'green' })
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
|
||||
<UFormField label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField label="Password" name="password">
|
||||
<UInput v-model="state.password" type="password" />
|
||||
</UFormField>
|
||||
|
||||
<UButton type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</UForm>
|
||||
</template>
|
||||
38
docs/app/components/content/examples/form/FormExampleZod.vue
Normal file
38
docs/app/components/content/examples/form/FormExampleZod.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
import { z } from 'zod'
|
||||
import type { FormSubmitEvent } from '#ui/types'
|
||||
|
||||
const schema = z.object({
|
||||
email: z.string().email('Invalid email'),
|
||||
password: z.string().min(8, 'Must be at least 8 characters')
|
||||
})
|
||||
|
||||
type Schema = z.output<typeof schema>
|
||||
|
||||
const state = reactive({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
const toast = useToast()
|
||||
async function onSubmit(event: FormSubmitEvent<Schema>) {
|
||||
toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'green' })
|
||||
console.log(event.data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
|
||||
<UFormField label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField label="Password" name="password">
|
||||
<UInput v-model="state.password" type="password" />
|
||||
</UFormField>
|
||||
|
||||
<UButton type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</UForm>
|
||||
</template>
|
||||
17
docs/content/2.composables/use-form-field.md
Normal file
17
docs/content/2.composables/use-form-field.md
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: useFormField
|
||||
description: 'A composable to integrate custom inputs with the Form component'
|
||||
navigation:
|
||||
badge:
|
||||
label: Todo
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Use the auto-imported `useFormField` composable to integrate custom inputs with a [Form](/components/form).
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const { inputId, emitFormBlur, emitFormInput, emitFormChange } = useFormField()
|
||||
</script>
|
||||
```
|
||||
@@ -1,17 +1,155 @@
|
||||
---
|
||||
description: A form element that provides validation and submission handling.
|
||||
description: A form component with built-in validation and submission handling.
|
||||
links:
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Form.vue
|
||||
navigation:
|
||||
badge:
|
||||
label: Todo
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
## Examples
|
||||
Use the Form component to validate form data using schema libraries such as [Zod](https://github.com/colinhacks/zod), [Yup](https://github.com/jquense/yup), [Joi](https://github.com/hapijs/joi), [Valibot](https://github.com/fabian-hiller/valibot), or your own validation logic.
|
||||
|
||||
It works with the [FormField](/components/form-field) component to display error messages around form elements automatically.
|
||||
|
||||
### Schema Validation
|
||||
|
||||
It requires two props:
|
||||
- `state` - a reactive object holding the form's state.
|
||||
- `schema` - a schema object from a validation library like [Zod](https://github.com/colinhacks/zod), [Yup](https://github.com/jquense/yup), [Joi](https://github.com/hapijs/joi) or [Valibot](https://github.com/fabian-hiller/valibot).
|
||||
|
||||
::warning
|
||||
**No validation library is included** by default, ensure you **install the one you need**.
|
||||
::
|
||||
|
||||
::tabs
|
||||
::component-example{label="Zod"}
|
||||
---
|
||||
name: 'form-example-zod'
|
||||
props:
|
||||
class: 'w-60'
|
||||
---
|
||||
::
|
||||
|
||||
::component-example{label="Yup"}
|
||||
---
|
||||
name: 'form-example-yup'
|
||||
props:
|
||||
class: 'w-60'
|
||||
---
|
||||
::
|
||||
|
||||
::component-example{label="Joi"}
|
||||
---
|
||||
name: 'form-example-joi'
|
||||
props:
|
||||
class: 'w-60'
|
||||
---
|
||||
::
|
||||
|
||||
::component-example{label="Valibot"}
|
||||
---
|
||||
name: 'form-example-valibot'
|
||||
props:
|
||||
class: 'w-60'
|
||||
---
|
||||
::
|
||||
::
|
||||
|
||||
Errors are reported directly to the [FormField](/components/form-field) component based on the `name` prop. This means the validation rules defined for the `email` attribute in your schema will be applied to `<FormField name="email">`{lang="vue"}.
|
||||
|
||||
Nested validation rules are handled using dot notation. For example, a rule like `{ user: z.object({ email: z.string() }) }`{lang="ts"} will be applied to `<FormField name="user.email">`{lang="vue"}.
|
||||
|
||||
### Custom Validation
|
||||
|
||||
Use the `validate` prop to apply your own validation logic.
|
||||
|
||||
The validation function must return a list of errors with the following attributes:
|
||||
- `message` - the error message to display.
|
||||
- `name` - the `name` of the `FormField` to send the error to.
|
||||
|
||||
::tip
|
||||
It can be used alongside the `schema` prop to handle complex use cases.
|
||||
::
|
||||
|
||||
::component-example
|
||||
---
|
||||
name: 'form-example-basic'
|
||||
props:
|
||||
class: 'w-60'
|
||||
---
|
||||
::
|
||||
|
||||
### Input Events
|
||||
|
||||
The Form component automatically triggers validation when an input emits an `input`, `change`, or `blur` event.
|
||||
- Validation on `input` occurs **as you type**.
|
||||
- Validation on `change` occurs when you **commit to a value**.
|
||||
- Validation on `blur` happens when an input **loses focus**.
|
||||
|
||||
You can control when validation happens this using the `validate-on` prop.
|
||||
|
||||
::component-example{label="Default"}
|
||||
---
|
||||
source: false
|
||||
name: 'form-example-elements'
|
||||
options:
|
||||
- name: 'validate-on'
|
||||
label: 'validate-on'
|
||||
items:
|
||||
- 'input'
|
||||
- 'change'
|
||||
- 'blur'
|
||||
default:
|
||||
- 'input'
|
||||
- 'change'
|
||||
- 'blur'
|
||||
multiple: true
|
||||
---
|
||||
::
|
||||
|
||||
::tip
|
||||
You can use the [useFormField](/composables/use-form-field) composable to implement this inside your own components.
|
||||
::
|
||||
|
||||
### Error Event
|
||||
|
||||
You can listen to the `@error` event to handle errors. This event is triggered when the form is submitted and contains an array of `FormError` objects with the following fields:
|
||||
|
||||
- `id` - the input's `id`.
|
||||
- `name` - the `name` of the `FormField`
|
||||
- `message` - the error message to display.
|
||||
|
||||
Here's an example that focuses the first input element with an error after the form is submitted:
|
||||
|
||||
::component-example
|
||||
---
|
||||
name: 'form-example-on-error'
|
||||
collapse: true
|
||||
props:
|
||||
class: 'w-60'
|
||||
---
|
||||
::
|
||||
|
||||
### Nesting Forms
|
||||
|
||||
Nesting form components allows you to manage complex data structures, such as lists or conditional fields, more efficiently.
|
||||
|
||||
For example, it can be used to dynamically add fields based on user's input:
|
||||
::component-example
|
||||
---
|
||||
collapse: true
|
||||
name: 'form-example-nested'
|
||||
---
|
||||
::
|
||||
|
||||
Or to validate list inputs:
|
||||
::component-example
|
||||
---
|
||||
collapse: true
|
||||
name: 'form-example-nested-list'
|
||||
---
|
||||
::
|
||||
|
||||
## API
|
||||
|
||||
|
||||
@@ -147,7 +147,8 @@ async function _validate(opts: { name?: string | string[], silent?: boolean, nes
|
||||
errors.value = await getErrors()
|
||||
}
|
||||
|
||||
const childErrors = nestedValidatePromises ? await Promise.all(nestedValidatePromises) : []
|
||||
const childErrors = (await Promise.all(nestedValidatePromises)).filter(val => val)
|
||||
|
||||
if (errors.value.length + childErrors.length > 0) {
|
||||
if (opts.silent) return false
|
||||
throw new FormValidationException(formId, errors.value, childErrors)
|
||||
@@ -173,7 +174,6 @@ async function onSubmit(payload: Event) {
|
||||
errors: error.errors,
|
||||
childrens: error.childrens
|
||||
}
|
||||
|
||||
emits('error', errorEvent)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user