docs: add form.md

- change TabPreview.vue to showcase way of using vee-validate
This commit is contained in:
sadeghbarati 2023-10-02 13:19:45 +03:30
parent 68337c5250
commit 854cb8f20c
5 changed files with 431 additions and 21 deletions

View File

@ -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>

View File

@ -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',

View 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>

View 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>

View 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>