feat: add vee-validate

This commit is contained in:
sadeghbarati 2023-09-25 15:45:23 +03:30
parent a92b15deee
commit d4bb455a44
11 changed files with 1109 additions and 921 deletions

View File

@ -13,16 +13,18 @@
}, },
"dependencies": { "dependencies": {
"@morev/vue-transitions": "^2.3.6", "@morev/vue-transitions": "^2.3.6",
"@tanstack/vue-table": "^8.9.9", "@tanstack/vue-table": "^8.10.1",
"@unovis/ts": "^1.2.1", "@unovis/ts": "^1.2.1",
"@unovis/vue": "1.3.0-alpha.3", "@unovis/vue": "1.3.0-alpha.3",
"@vee-validate/zod": "^4.11.7",
"@vueuse/core": "^10.4.1", "@vueuse/core": "^10.4.1",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.0.0", "clsx": "^2.0.0",
"date-fns": "^2.30.0", "date-fns": "^2.30.0",
"lucide-vue-next": "^0.276.0", "lucide-vue-next": "^0.276.0",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"v-calendar": "^3.0.3", "v-calendar": "^3.1.0",
"vee-validate": "4.11.7",
"vue": "^3.3.4", "vue": "^3.3.4",
"vue-wrap-balancer": "^1.1.3", "vue-wrap-balancer": "^1.1.3",
"zod": "^3.22.2" "zod": "^3.22.2"
@ -38,17 +40,17 @@
"@vitejs/plugin-vue-jsx": "^3.0.2", "@vitejs/plugin-vue-jsx": "^3.0.2",
"@vue/compiler-core": "^3.3.4", "@vue/compiler-core": "^3.3.4",
"@vue/compiler-dom": "^3.3.4", "@vue/compiler-dom": "^3.3.4",
"autoprefixer": "^10.4.15", "autoprefixer": "^10.4.16",
"lodash.template": "^4.5.0", "lodash.template": "^4.5.0",
"radix-vue": "^0.2.3", "radix-vue": "^0.3.0",
"rimraf": "^5.0.1", "rimraf": "^5.0.1",
"tailwind-merge": "^1.14.0", "tailwind-merge": "^1.14.0",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.3.3",
"tsx": "^3.12.10", "tsx": "^3.13.0",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"unplugin-icons": "^0.17.0", "unplugin-icons": "^0.17.0",
"vite": "^4.4.9", "vite": "^4.4.9",
"vitepress": "^1.0.0-rc.13", "vitepress": "^1.0.0-rc.13",
"vue-tsc": "^1.8.11" "vue-tsc": "^1.8.13"
} }
} }

View File

@ -2,10 +2,13 @@
import { ref } from 'vue' import { ref } from 'vue'
import * as z from 'zod' import * as z from 'zod'
import { format } from 'date-fns' import { format } from 'date-fns'
import { toTypedSchema } from '@vee-validate/zod'
import { configure } from 'vee-validate'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import RadixIconsCalendar from '~icons/radix-icons/calendar' import RadixIconsCalendar from '~icons/radix-icons/calendar'
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/default/ui/form'
import { Input } from '@/lib/registry/new-york/ui/input' import { Input } from '@/lib/registry/new-york/ui/input'
import { Label } from '@/lib/registry/new-york/ui/label' import { Label } from '@/lib/registry/new-york/ui/label'
import { Separator } from '@/lib/registry/new-york/ui/separator' import { Separator } from '@/lib/registry/new-york/ui/separator'
@ -25,6 +28,14 @@ import {
} from '@/lib/registry/default/ui/popover' } from '@/lib/registry/default/ui/popover'
import { Calendar } from '@/lib/registry/new-york/ui/calendar' import { Calendar } from '@/lib/registry/new-york/ui/calendar'
configure({
bails: true,
validateOnBlur: false,
validateOnChange: false,
validateOnInput: true,
validateOnModelUpdate: false,
})
const accountForm = ref({ const accountForm = ref({
name: '', name: '',
dob: null, dob: null,
@ -43,35 +54,38 @@ const languages = [
{ label: 'Chinese', value: 'zh' }, { label: 'Chinese', value: 'zh' },
] as const ] as const
const accountFormSchema = z.object({ const accountFormSchema = toTypedSchema(z.object({
name: z // name: z
.string() // .string()
.min(2, { // .min(2, {
message: 'Name must be at least 2 characters.', // message: 'Name must be at least 2 characters.',
}) // })
.max(30, { // .max(30, {
message: 'Name must not be longer than 30 characters.', // message: 'Name must not be longer than 30 characters.',
}), // }),
dob: z.date({ // dob: z.date({
required_error: 'A date of birth is required.', // required_error: 'A date of birth is required.',
}), // }),
language: z.string().nonempty({ // language: z.string().nonempty({
// message: 'Please select a language.',
// }),
example: z.string().nonempty({
message: 'Please select a language.', message: 'Please select a language.',
}), }).min(5),
}) }))
type AccountFormValues = z.infer<typeof accountFormSchema> // type AccountFormValues = z.infer<typeof accountFormSchema>
const errors = ref<z.ZodFormattedError<AccountFormValues> | null>(null) // const errors = ref<z.ZodFormattedError<AccountFormValues> | null>(null)
async function handleSubmit() { async function handleSubmit() {
const result = accountFormSchema.safeParse(accountForm.value) // const result = accountFormSchema.safeParse(accountForm.value)
if (!result.success) { // if (!result.success) {
errors.value = result.error.format() // errors.value = result.error.format()
console.log(errors.value) // console.log(errors.value)
return // return
} // }
console.log('Form submitted!', accountForm.value) // console.log('Form submitted!', accountForm.value)
} }
</script> </script>
@ -85,8 +99,22 @@ async function handleSubmit() {
</p> </p>
</div> </div>
<Separator /> <Separator />
<form class="space-y-8" @submit.prevent="handleSubmit"> <Form :validation-schema="accountFormSchema" class="space-y-8" @submit="handleSubmit">
<div class="grid gap-2"> <FormField v-slot="{ field }" name="example" type="password">
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input placeholder="Your name" v-bind="field" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<Label for="dob">
Date of Birth
</Label>
<!-- <div class="grid gap-2">
<Label for="name" :class="cn('text-sm', errors?.name && 'text-destructive')"> <Label for="name" :class="cn('text-sm', errors?.name && 'text-destructive')">
Name Name
</Label> </Label>
@ -97,11 +125,9 @@ async function handleSubmit() {
<div v-if="errors?.name" class="text-sm text-destructive"> <div v-if="errors?.name" class="text-sm text-destructive">
<span v-for="error in errors.name._errors" :key="error">{{ error }}</span> <span v-for="error in errors.name._errors" :key="error">{{ error }}</span>
</div> </div>
</div> </div> -->
<div class="grid gap-2"> <!-- <div class="grid gap-2">
<Label for="dob" :class="cn('text-sm', errors?.dob && 'text-destructive')">
Date of Birth
</Label>
<Popover> <Popover>
<PopoverTrigger as-child> <PopoverTrigger as-child>
<Button <Button
@ -148,11 +174,11 @@ async function handleSubmit() {
<div v-if="errors?.language" class="text-sm text-destructive"> <div v-if="errors?.language" class="text-sm text-destructive">
<span v-for="error in errors.language._errors" :key="error">{{ error }}</span> <span v-for="error in errors.language._errors" :key="error">{{ error }}</span>
</div> </div>
</div> </div> -->
<div class="flex justify-start"> <div class="flex justify-start">
<Button type="submit"> <Button type="submit">
Update account Update account
</Button> </Button>
</div> </div>
</form> </Form>
</template> </template>

View File

@ -0,0 +1,16 @@
<script lang="ts" setup>
import Slot from './Slot.vue'
import { useFormField } from './FormItem.vue'
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
</script>
<template>
<Slot
:id="formItemId"
:aria-describedby="!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`"
:aria-invalid="!!error"
>
<slot />
</Slot>
</template>

View File

@ -0,0 +1,40 @@
<script lang="ts">
import { FieldContextKey, useFieldError, useIsFieldDirty, useIsFieldTouched, useIsFieldValid } from 'vee-validate'
import { inject } from 'vue'
export function useFormField() {
const fieldContext = inject(FieldContextKey)
const fieldState = {
valid: useIsFieldValid(),
isDirty: useIsFieldDirty(),
isTouched: useIsFieldTouched(),
error: useFieldError(),
}
if (!fieldContext)
throw new Error('useFormField should be used within <FormField>')
const { id, name } = fieldContext
return {
id,
name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
}
}
</script>
<script lang="ts" setup>
// eslint-disable-next-line import/first
import { cn } from '@/lib/utils'
</script>
<template>
<div :class="cn('space-y-2', $attrs.class ?? '')">
<slot />
</div>
</template>

View File

@ -0,0 +1,22 @@
<script lang="ts" setup>
import { Label, type LabelProps } from 'radix-vue'
import { useFormField } from './FormItem.vue'
import { cn } from '@/lib/utils'
const props = defineProps<LabelProps>()
const { error, id } = useFormField()
</script>
<template>
<Label
:class="cn(
'block text-sm tracking-tight font-medium text-foreground text-left',
error && 'text-destructive',
$attrs.class ?? '',
)"
:for="`${id}-form-item`"
>
<slot />
</Label>
</template>

View File

@ -0,0 +1,16 @@
<script lang="ts" setup>
import { ErrorMessage } from 'vee-validate'
import { toValue } from 'vue'
import { useFormField } from './FormItem.vue'
const { name, formMessageId } = useFormField()
</script>
<template>
<ErrorMessage
:id="formMessageId"
as="p"
:name="toValue(name)"
class="text-[0.8rem] font-medium text-destructive"
/>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts"></script>
<template>
<component
:is="
// @ts-ignore we can ignore this as we have validated the existence of 1 child in Primitive.vue
$slots.default?.()[0].children?.[0]
"
v-bind="$attrs"
/>
</template>

View File

@ -0,0 +1,5 @@
export { Form, Field as FormField } from 'vee-validate'
export { default as FormItem } from './FormItem.vue'
export { default as FormLabel } from './FormLabel.vue'
export { default as FormControl } from './FormControl.vue'
export { default as FormMessage } from './FormMessage.vue'

View File

@ -2,7 +2,7 @@
"name": "shadcn-vue", "name": "shadcn-vue",
"version": "0.1.9", "version": "0.1.9",
"private": true, "private": true,
"packageManager": "pnpm@8.7.5", "packageManager": "pnpm@8.7.6",
"license": "MIT", "license": "MIT",
"repository": "radix-vue/shadcn-vue", "repository": "radix-vue/shadcn-vue",
"workspaces": [ "workspaces": [
@ -29,12 +29,12 @@
"bumpp": "^9.2.0" "bumpp": "^9.2.0"
}, },
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "^0.41.3", "@antfu/eslint-config": "^0.43.1",
"@commitlint/cli": "^17.7.1", "@commitlint/cli": "^17.7.1",
"@commitlint/config-conventional": "^17.7.0", "@commitlint/config-conventional": "^17.7.0",
"eslint": "^8.49.0", "eslint": "^8.50.0",
"lint-staged": "^14.0.1", "lint-staged": "^14.0.1",
"pnpm": "^8.7.5", "pnpm": "^8.7.6",
"simple-git-hooks": "^2.9.0", "simple-git-hooks": "^2.9.0",
"taze": "^0.11.2", "taze": "^0.11.2",
"typescript": "^5.2.2", "typescript": "^5.2.2",

File diff suppressed because it is too large Load Diff