feat: add vee-validate
This commit is contained in:
parent
a92b15deee
commit
d4bb455a44
|
|
@ -13,16 +13,18 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@morev/vue-transitions": "^2.3.6",
|
||||
"@tanstack/vue-table": "^8.9.9",
|
||||
"@tanstack/vue-table": "^8.10.1",
|
||||
"@unovis/ts": "^1.2.1",
|
||||
"@unovis/vue": "1.3.0-alpha.3",
|
||||
"@vee-validate/zod": "^4.11.7",
|
||||
"@vueuse/core": "^10.4.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"lucide-vue-next": "^0.276.0",
|
||||
"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-wrap-balancer": "^1.1.3",
|
||||
"zod": "^3.22.2"
|
||||
|
|
@ -38,17 +40,17 @@
|
|||
"@vitejs/plugin-vue-jsx": "^3.0.2",
|
||||
"@vue/compiler-core": "^3.3.4",
|
||||
"@vue/compiler-dom": "^3.3.4",
|
||||
"autoprefixer": "^10.4.15",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"lodash.template": "^4.5.0",
|
||||
"radix-vue": "^0.2.3",
|
||||
"radix-vue": "^0.3.0",
|
||||
"rimraf": "^5.0.1",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"tsx": "^3.12.10",
|
||||
"tsx": "^3.13.0",
|
||||
"typescript": "^5.2.2",
|
||||
"unplugin-icons": "^0.17.0",
|
||||
"vite": "^4.4.9",
|
||||
"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 * as z from 'zod'
|
||||
import { format } from 'date-fns'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { configure } from 'vee-validate'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
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 { Label } from '@/lib/registry/new-york/ui/label'
|
||||
import { Separator } from '@/lib/registry/new-york/ui/separator'
|
||||
|
|
@ -25,6 +28,14 @@ import {
|
|||
} from '@/lib/registry/default/ui/popover'
|
||||
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
||||
|
||||
configure({
|
||||
bails: true,
|
||||
validateOnBlur: false,
|
||||
validateOnChange: false,
|
||||
validateOnInput: true,
|
||||
validateOnModelUpdate: false,
|
||||
})
|
||||
|
||||
const accountForm = ref({
|
||||
name: '',
|
||||
dob: null,
|
||||
|
|
@ -43,35 +54,38 @@ const languages = [
|
|||
{ label: 'Chinese', value: 'zh' },
|
||||
] as const
|
||||
|
||||
const accountFormSchema = z.object({
|
||||
name: z
|
||||
.string()
|
||||
.min(2, {
|
||||
message: 'Name must be at least 2 characters.',
|
||||
})
|
||||
.max(30, {
|
||||
message: 'Name must not be longer than 30 characters.',
|
||||
}),
|
||||
dob: z.date({
|
||||
required_error: 'A date of birth is required.',
|
||||
}),
|
||||
language: z.string().nonempty({
|
||||
const accountFormSchema = toTypedSchema(z.object({
|
||||
// name: z
|
||||
// .string()
|
||||
// .min(2, {
|
||||
// message: 'Name must be at least 2 characters.',
|
||||
// })
|
||||
// .max(30, {
|
||||
// message: 'Name must not be longer than 30 characters.',
|
||||
// }),
|
||||
// dob: z.date({
|
||||
// required_error: 'A date of birth is required.',
|
||||
// }),
|
||||
// language: z.string().nonempty({
|
||||
// message: 'Please select a language.',
|
||||
// }),
|
||||
example: z.string().nonempty({
|
||||
message: 'Please select a language.',
|
||||
}),
|
||||
})
|
||||
}).min(5),
|
||||
}))
|
||||
|
||||
type AccountFormValues = z.infer<typeof accountFormSchema>
|
||||
const errors = ref<z.ZodFormattedError<AccountFormValues> | null>(null)
|
||||
// type AccountFormValues = z.infer<typeof accountFormSchema>
|
||||
// const errors = ref<z.ZodFormattedError<AccountFormValues> | null>(null)
|
||||
|
||||
async function handleSubmit() {
|
||||
const result = accountFormSchema.safeParse(accountForm.value)
|
||||
if (!result.success) {
|
||||
errors.value = result.error.format()
|
||||
console.log(errors.value)
|
||||
return
|
||||
}
|
||||
// const result = accountFormSchema.safeParse(accountForm.value)
|
||||
// if (!result.success) {
|
||||
// errors.value = result.error.format()
|
||||
// console.log(errors.value)
|
||||
// return
|
||||
// }
|
||||
|
||||
console.log('Form submitted!', accountForm.value)
|
||||
// console.log('Form submitted!', accountForm.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -85,8 +99,22 @@ async function handleSubmit() {
|
|||
</p>
|
||||
</div>
|
||||
<Separator />
|
||||
<form class="space-y-8" @submit.prevent="handleSubmit">
|
||||
<div class="grid gap-2">
|
||||
<Form :validation-schema="accountFormSchema" class="space-y-8" @submit="handleSubmit">
|
||||
<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')">
|
||||
Name
|
||||
</Label>
|
||||
|
|
@ -97,11 +125,9 @@ async function handleSubmit() {
|
|||
<div v-if="errors?.name" class="text-sm text-destructive">
|
||||
<span v-for="error in errors.name._errors" :key="error">{{ error }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="dob" :class="cn('text-sm', errors?.dob && 'text-destructive')">
|
||||
Date of Birth
|
||||
</Label>
|
||||
</div> -->
|
||||
<!-- <div class="grid gap-2">
|
||||
|
||||
<Popover>
|
||||
<PopoverTrigger as-child>
|
||||
<Button
|
||||
|
|
@ -148,11 +174,11 @@ async function handleSubmit() {
|
|||
<div v-if="errors?.language" class="text-sm text-destructive">
|
||||
<span v-for="error in errors.language._errors" :key="error">{{ error }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="flex justify-start">
|
||||
<Button type="submit">
|
||||
Update account
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</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",
|
||||
"version": "0.1.9",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@8.7.5",
|
||||
"packageManager": "pnpm@8.7.6",
|
||||
"license": "MIT",
|
||||
"repository": "radix-vue/shadcn-vue",
|
||||
"workspaces": [
|
||||
|
|
@ -29,12 +29,12 @@
|
|||
"bumpp": "^9.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^0.41.3",
|
||||
"@antfu/eslint-config": "^0.43.1",
|
||||
"@commitlint/cli": "^17.7.1",
|
||||
"@commitlint/config-conventional": "^17.7.0",
|
||||
"eslint": "^8.49.0",
|
||||
"eslint": "^8.50.0",
|
||||
"lint-staged": "^14.0.1",
|
||||
"pnpm": "^8.7.5",
|
||||
"pnpm": "^8.7.6",
|
||||
"simple-git-hooks": "^2.9.0",
|
||||
"taze": "^0.11.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