feat: add vee-validate
This commit is contained in:
parent
a92b15deee
commit
d4bb455a44
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
16
apps/www/src/lib/registry/default/ui/form/FormControl.vue
Normal file
16
apps/www/src/lib/registry/default/ui/form/FormControl.vue
Normal 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>
|
||||||
40
apps/www/src/lib/registry/default/ui/form/FormItem.vue
Normal file
40
apps/www/src/lib/registry/default/ui/form/FormItem.vue
Normal 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>
|
||||||
22
apps/www/src/lib/registry/default/ui/form/FormLabel.vue
Normal file
22
apps/www/src/lib/registry/default/ui/form/FormLabel.vue
Normal 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>
|
||||||
16
apps/www/src/lib/registry/default/ui/form/FormMessage.vue
Normal file
16
apps/www/src/lib/registry/default/ui/form/FormMessage.vue
Normal 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>
|
||||||
11
apps/www/src/lib/registry/default/ui/form/Slot.vue
Normal file
11
apps/www/src/lib/registry/default/ui/form/Slot.vue
Normal 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>
|
||||||
5
apps/www/src/lib/registry/default/ui/form/index.ts
Normal file
5
apps/www/src/lib/registry/default/ui/form/index.ts
Normal 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'
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
1806
pnpm-lock.yaml
1806
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user