docs: add form.md
- change TabPreview.vue to showcase way of using vee-validate
This commit is contained in:
parent
68337c5250
commit
854cb8f20c
|
|
@ -3,10 +3,14 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/lib/registry/default
|
|||
|
||||
const props = withDefaults(defineProps<{
|
||||
name: string
|
||||
names?: string[]
|
||||
align?: 'center' | 'start' | 'end'
|
||||
sfcTsCode?: string
|
||||
sfcTsHtml?: string
|
||||
}>(), { align: 'center' })
|
||||
}>(), {
|
||||
align: 'center',
|
||||
names: () => ['CLI', 'Manual'],
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -15,24 +19,17 @@ const props = withDefaults(defineProps<{
|
|||
<div class="flex items-center justify-between pb-3">
|
||||
<TabsList class="w-full justify-start rounded-none border-b bg-transparent p-0">
|
||||
<TabsTrigger
|
||||
value="CLI"
|
||||
v-for="(tab, index) in props.names"
|
||||
:key="index"
|
||||
:value="tab"
|
||||
class="relative h-9 rounded-none border-b-2 border-b-transparent bg-transparent px-4 pb-3 pt-2 font-semibold text-muted-foreground shadow-none transition-none data-[state=active]:border-b-primary data-[state=active]:text-foreground data-[state=active]:shadow-none"
|
||||
>
|
||||
CLI
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="Manual"
|
||||
class="relative h-9 rounded-none border-b-2 border-b-transparent bg-transparent px-4 pb-3 pt-2 font-semibold text-muted-foreground shadow-none transition-none data-[state=active]:border-b-primary data-[state=active]:text-foreground data-[state=active]:shadow-none"
|
||||
>
|
||||
Manual
|
||||
{{ tab }}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
<TabsContent value="CLI" class="relative space-y-10">
|
||||
<slot name="CLI" />
|
||||
</TabsContent>
|
||||
<TabsContent value="Manual">
|
||||
<slot name="Manual" />
|
||||
<TabsContent v-for="(tab, index) in props.names" :key="index" :value="tab" class="relative space-y-10">
|
||||
<slot :name="tab" />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -217,9 +217,8 @@ export const docsConfig: DocsConfig = {
|
|||
},
|
||||
{
|
||||
title: 'Form',
|
||||
href: '#',
|
||||
label: 'Soon',
|
||||
disabled: true,
|
||||
href: '/docs/components/form',
|
||||
label: 'New',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
|
|
@ -352,10 +351,10 @@ export const examples: Example[] = [
|
|||
code: 'https://github.com/radix-vue/shadcn-vue/tree/dev/apps/www/src/examples/cards',
|
||||
},
|
||||
// {
|
||||
// name: "Tasks",
|
||||
// href: "/examples/tasks",
|
||||
// label: "New",
|
||||
// code: "https://github.com/radix-vue/shadcn-vue/tree/dev/apps/www/src/examples/tasks"
|
||||
// name: "Tasks",
|
||||
// href: "/examples/tasks",
|
||||
// label: "New",
|
||||
// code: "https://github.com/radix-vue/shadcn-vue/tree/dev/apps/www/src/examples/tasks"
|
||||
// },
|
||||
{
|
||||
name: 'Playground',
|
||||
|
|
|
|||
318
apps/www/src/content/docs/components/form.md
Normal file
318
apps/www/src/content/docs/components/form.md
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
---
|
||||
title: VeeValidate
|
||||
description: Building forms with VeeValidate and Zod.
|
||||
---
|
||||
|
||||
Forms are tricky. They are one of the most common things you'll build in a web application, but also one of the most complex.
|
||||
|
||||
Well-designed HTML forms are:
|
||||
|
||||
- Well-structured and semantically correct.
|
||||
- Easy to use and navigate (keyboard).
|
||||
- Accessible with ARIA attributes and proper labels.
|
||||
- Has support for client and server side validation.
|
||||
- Well-styled and consistent with the rest of the application.
|
||||
|
||||
In this guide, we will take a look at building forms with [`vee-validate`](https://vee-validate.logaretm.com/v4/) and [`zod`](https://zod.dev). We're going to use a `<FormField>` component to compose accessible forms using Radix Vue components.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
The `<Form />` component is a wrapper around the `vee-validate` library. It provides a few things:
|
||||
|
||||
|
||||
- Composable components for building forms.
|
||||
- A `<FormField />` component for building controlled form fields.
|
||||
- Form validation using `zod`.
|
||||
- Applies the correct `aria` attributes to form fields based on states, handle unqiue IDs
|
||||
- Built to work with all Radix Vue components.
|
||||
- Bring your own schema library. We use `zod` but you can use any other supported schema validation you want, like [`yup`](https://github.com/jquense/yup) or [`valibot`](https://valibot.dev/).
|
||||
- **You have full control over the markup and styling.**
|
||||
|
||||
[`vee-validate`](https://vee-validate.logaretm.com/v4/) makes use of two flavors to add validation to your forms.
|
||||
- Composition API
|
||||
- Higher-order components (HOC)
|
||||
|
||||
## Anatomy
|
||||
|
||||
```vue
|
||||
<Form>
|
||||
<FormField v-slot="{ ... }">
|
||||
<FormItem>
|
||||
<FormLabel />
|
||||
<FormControl>
|
||||
<!-- any Form Input component or native input elements -->
|
||||
</FormControl>
|
||||
<FormDescription />
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Form>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
<TabPreview name="Component" :names="['Component', 'Native']">
|
||||
<template #Component>
|
||||
|
||||
#### Input Component
|
||||
|
||||
```vue
|
||||
<FormField v-slot="{ componentField }">
|
||||
<FormItem>
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="shadcn" v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormDescription />
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
```
|
||||
|
||||
</template>
|
||||
|
||||
<template #Native>
|
||||
|
||||
#### Native input element
|
||||
|
||||
```vue
|
||||
<FormField v-slot="{ field }">
|
||||
<FormItem>
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormControl>
|
||||
<input placeholder="shadcn" v-bind="field" />
|
||||
</FormControl>
|
||||
<FormDescription />
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
```
|
||||
|
||||
</template>
|
||||
</TabPreview>
|
||||
|
||||
## Installation
|
||||
|
||||
<TabPreview name="CLI">
|
||||
<template #CLI>
|
||||
|
||||
```bash
|
||||
npx shadcn-vue@latest add form
|
||||
```
|
||||
</template>
|
||||
|
||||
<template #Manual>
|
||||
|
||||
<Steps>
|
||||
|
||||
### Install the following dependency:
|
||||
|
||||
```bash
|
||||
npm install radix-vue vee-validate @vee-validate/zod zod
|
||||
```
|
||||
|
||||
### Copy and paste the following codes into your project:
|
||||
|
||||
`index.ts`
|
||||
|
||||
<<< @/lib/registry/default/ui/form/index.ts
|
||||
|
||||
`FormItem.vue`
|
||||
|
||||
<<< @/lib/registry/default/ui/form/FormItem.vue
|
||||
|
||||
`FormLabel.vue`
|
||||
|
||||
<<< @/lib/registry/default/ui/form/FormLabel.vue
|
||||
|
||||
`FormControl.vue`
|
||||
|
||||
<<< @/lib/registry/default/ui/form/FormControl.vue
|
||||
|
||||
`FormMessage.vue`
|
||||
|
||||
<<< @/lib/registry/default/ui/form/FormMessage.vue
|
||||
|
||||
`FormDescription.vue`
|
||||
|
||||
<<< @/lib/registry/default/ui/form/FormDescription.vue
|
||||
|
||||
### Update the import paths to match your project setup.
|
||||
|
||||
</Steps>
|
||||
|
||||
</template>
|
||||
</TabPreview>
|
||||
|
||||
## Usage
|
||||
|
||||
<Steps>
|
||||
|
||||
### Create a form schema
|
||||
|
||||
Define the shape of your form using a Zod schema. You can read more about using Zod in the [Zod documentation](https://zod.dev).
|
||||
|
||||
Use `@vee-validate/zod` to integrate Zod schema validation with `vee-validate`
|
||||
|
||||
`toTypedSchema` also makes the form values and submitted values typed automatically and caters for both input and output types of that schema.
|
||||
|
||||
```vue showLineNumbers {2-3,5-7}
|
||||
<script setup lang="ts">
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import * as z from 'zod'
|
||||
|
||||
const formSchema = toTypedSchema(z.object({
|
||||
username: z.string().min(2).max(50),
|
||||
}))
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
### Define a form
|
||||
|
||||
Use the `useForm` composable from `vee-validate` or use `<Form />` component to create a from.
|
||||
|
||||
|
||||
<TabPreview name="Composition" :names="['Composition', 'Component']">
|
||||
<template #Composition>
|
||||
|
||||
```vue showLineNumbers {2,19-21}
|
||||
<script setup lang="ts">
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import * as z from 'zod'
|
||||
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage
|
||||
} from '@/components/ui/form'
|
||||
|
||||
const formSchema = toTypedSchema(z.object({
|
||||
username: z.string().min(2).max(50),
|
||||
}))
|
||||
|
||||
const form = useForm({
|
||||
validationSchema: formSchema,
|
||||
})
|
||||
|
||||
const onSubmit = form.handleSubmit((values) => {
|
||||
console.log('Form submitted!', values)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form @submit="onSubmit">
|
||||
...
|
||||
</Form>
|
||||
</template>
|
||||
```
|
||||
|
||||
</template>
|
||||
|
||||
<template #Component>
|
||||
|
||||
```vue showLineNumbers {5,24-26}
|
||||
<script setup lang="ts">
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import * as z from 'zod'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage
|
||||
} from '@/components/ui/form'
|
||||
|
||||
const formSchema = toTypedSchema(z.object({
|
||||
username: z.string().min(2).max(50),
|
||||
}))
|
||||
|
||||
function onSubmit(values) {
|
||||
console.log('Form submitted!', values)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form :validation-schema="formSchema" @submit="onSubmit">
|
||||
...
|
||||
</Form>
|
||||
</template>
|
||||
```
|
||||
|
||||
</template>
|
||||
</TabPreview>
|
||||
|
||||
### Build your form
|
||||
|
||||
Based on last step we can either use `<Form />` component or `useForm` composable
|
||||
`useForm` is recommended cause values are typed automatically
|
||||
|
||||
```vue showLineNumbers {2}
|
||||
<script setup lang="ts">
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import * as z from 'zod'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form'
|
||||
import { Input } from '@/components/ui/input'
|
||||
|
||||
const formSchema = toTypedSchema(z.object({
|
||||
username: z.string().min(2).max(50),
|
||||
}))
|
||||
|
||||
const form = useForm({
|
||||
validationSchema: formSchema,
|
||||
})
|
||||
|
||||
const onSubmit = form.handleSubmit((values) => {
|
||||
console.log('Form submitted!', values)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form @submit="onSubmit">
|
||||
<FormField v-slot="{ componentField }" name="username">
|
||||
<FormItem>
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="text" placeholder="shadcn" v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
This is your public display name.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<Button type="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Form>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Done
|
||||
|
||||
That's it. You now have a fully accessible form that is type-safe with client-side validation.
|
||||
|
||||
<ComponentPreview
|
||||
name="InputForm"
|
||||
className="[&_[role=tablist]]:hidden [&>div>div:first-child]:hidden"
|
||||
/>
|
||||
|
||||
</Steps>
|
||||
48
apps/www/src/lib/registry/default/example/InputForm.vue
Normal file
48
apps/www/src/lib/registry/default/example/InputForm.vue
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<script setup lang="ts">
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import * as z from 'zod'
|
||||
|
||||
import { Button } from '@/lib/registry/default/ui/button'
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/lib/registry/default/ui/form'
|
||||
import { Input } from '@/lib/registry/default/ui/input'
|
||||
|
||||
const formSchema = toTypedSchema(z.object({
|
||||
username: z.string().min(2).max(50),
|
||||
}))
|
||||
|
||||
const { handleSubmit } = useForm({
|
||||
validationSchema: formSchema,
|
||||
})
|
||||
|
||||
const onSubmit = handleSubmit((values) => {
|
||||
console.log('Form submitted!', values)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form class="w-2/3 space-y-6" @submit="onSubmit">
|
||||
<FormField v-slot="{ componentField }" name="username">
|
||||
<FormItem>
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="text" placeholder="shadcn" v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
This is your public display name.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<Button type="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Form>
|
||||
</template>
|
||||
48
apps/www/src/lib/registry/new-york/example/InputForm.vue
Normal file
48
apps/www/src/lib/registry/new-york/example/InputForm.vue
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<script setup lang="ts">
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import * as z from 'zod'
|
||||
|
||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/lib/registry/new-york/ui/form'
|
||||
import { Input } from '@/lib/registry/new-york/ui/input'
|
||||
|
||||
const formSchema = toTypedSchema(z.object({
|
||||
username: z.string().min(2).max(50),
|
||||
}))
|
||||
|
||||
const { isFieldDirty, handleSubmit } = useForm({
|
||||
validationSchema: formSchema,
|
||||
})
|
||||
|
||||
const onSubmit = handleSubmit((values) => {
|
||||
console.log('Form submitted!', values)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form class="w-2/3 space-y-6" @submit="onSubmit">
|
||||
<FormField v-slot="{ componentField }" name="username" :validate-on-blur="!isFieldDirty">
|
||||
<FormItem>
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="text" placeholder="shadcn" v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
This is your public display name.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<Button type="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Form>
|
||||
</template>
|
||||
Loading…
Reference in New Issue
Block a user