refactor: update

- add new-york style
- off eslint import/first rule
- use `useId` from radix-vue
This commit is contained in:
Sadegh Barati 2023-09-29 01:33:22 +03:30
parent 76be3fb332
commit 23a3d2ac9c
19 changed files with 390 additions and 225 deletions

View File

@ -12,5 +12,6 @@ module.exports = {
'no-console': 'warn', 'no-console': 'warn',
'no-tabs': 'off', 'no-tabs': 'off',
'no-invalid-character': 'off', 'no-invalid-character': 'off',
'import/first': 'off',
}, },
} }

View File

@ -58,14 +58,11 @@ const accountFormSchema = toTypedSchema(z.object({
}), }),
})) }))
// type AccountFormValues = z.infer<typeof accountFormSchema>
// const errors = ref<z.ZodFormattedError<AccountFormValues> | null>(null)
const filterFunction = (list: typeof languages, search: string) => list.filter(i => i.value.toLowerCase().includes(search.toLowerCase())) const filterFunction = (list: typeof languages, search: string) => list.filter(i => i.value.toLowerCase().includes(search.toLowerCase()))
// https://github.com/logaretm/vee-validate/issues/3521 // https://github.com/logaretm/vee-validate/issues/3521
// https://github.com/logaretm/vee-validate/discussions/3571 // https://github.com/logaretm/vee-validate/discussions/3571
async function handleSubmit(values: any) { async function onSubmit(values: any) {
console.log('Form submitted!', values) console.log('Form submitted!', values)
} }
</script> </script>
@ -83,7 +80,7 @@ async function handleSubmit(values: any) {
<Form <Form
v-slot="{ v-slot="{
setValues, setValues,
}" :validation-schema="accountFormSchema" class="space-y-8" @submit="handleSubmit" }" :validation-schema="accountFormSchema" class="space-y-8" @submit="onSubmit"
> >
<FormField v-slot="{ componentField }" name="name"> <FormField v-slot="{ componentField }" name="name">
<FormItem> <FormItem>

View File

@ -1,11 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'
import { useForm } from 'vee-validate' import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod' import { toTypedSchema } from '@vee-validate/zod'
import * as z from 'zod' import * as z from 'zod'
import { cn } from '@/lib/utils'
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/default/ui/form' import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/default/ui/form'
import { Separator } from '@/lib/registry/new-york/ui/separator' import { Separator } from '@/lib/registry/new-york/ui/separator'
import { RadioGroup, RadioGroupItem } from '@/lib/registry/default/ui/radio-group' import { RadioGroup, RadioGroupItem } from '@/lib/registry/default/ui/radio-group'
import { import {
@ -28,16 +26,16 @@ const appearanceFormSchema = toTypedSchema(z.object({
}), }),
})) }))
const { handleSubmit, resetForm } = useForm({ const { handleSubmit } = useForm({
validationSchema: appearanceFormSchema, validationSchema: appearanceFormSchema,
initialValues: { initialValues: {
theme: 'dark', theme: 'light',
font: 'inter', font: 'inter',
}, },
}) })
const onSubmit = handleSubmit((values, { resetForm }) => { const onSubmit = handleSubmit((values) => {
console.log(values) console.log('Form submitted!', values)
}) })
</script> </script>
@ -84,7 +82,7 @@ const onSubmit = handleSubmit((values, { resetForm }) => {
</FormItem> </FormItem>
</FormField> </FormField>
<FormField v-slot="{ componentField, value }" type="radio" name="theme"> <FormField v-slot="{ componentField }" type="radio" name="theme">
<FormItem class-name="space-y-1"> <FormItem class-name="space-y-1">
<FormLabel>Theme</FormLabel> <FormLabel>Theme</FormLabel>
<FormDescription> <FormDescription>
@ -95,7 +93,6 @@ const onSubmit = handleSubmit((values, { resetForm }) => {
<RadioGroup <RadioGroup
class="grid max-w-md grid-cols-2 gap-8 pt-2" class="grid max-w-md grid-cols-2 gap-8 pt-2"
v-bind="componentField" v-bind="componentField"
:default-value="value"
> >
<FormItem> <FormItem>
<FormLabel class="[&:has([data-state=checked])>div]:border-primary"> <FormLabel class="[&:has([data-state=checked])>div]:border-primary">

View File

@ -1,17 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
import * as z from 'zod' import * as z from 'zod'
import { cn } from '@/lib/utils'
import { Label } from '@/lib/registry/new-york/ui/label' import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/default/ui/form'
import { Separator } from '@/lib/registry/new-york/ui/separator' import { Separator } from '@/lib/registry/new-york/ui/separator'
import { Checkbox } from '@/lib/registry/new-york/ui/checkbox' import { Checkbox } from '@/lib/registry/new-york/ui/checkbox'
import { Button } from '@/lib/registry/new-york/ui/button' import { Button } from '@/lib/registry/new-york/ui/button'
const displayForm = ref({
items: ['recents', 'home'],
})
const items = [ const items = [
{ {
id: 'recents', id: 'recents',
@ -43,25 +39,22 @@ const items = [
}, },
] as const ] as const
const displayFormSchema = z.object({ const displayFormSchema = toTypedSchema(z.object({
items: z.array(z.string()).refine(value => value.some(item => item), { items: z.array(z.string()).refine(value => value.some(item => item), {
message: 'You have to select at least one item.', message: 'You have to select at least one item.',
}), }),
}))
const { handleSubmit } = useForm({
validationSchema: displayFormSchema,
initialValues: {
items: ['recents', 'home'],
},
}) })
type DisplayFormValues = z.infer<typeof displayFormSchema> const onSubmit = handleSubmit((values) => {
const errors = ref<z.ZodFormattedError<DisplayFormValues> | null>(null) console.log('Form submitted!', values)
})
async function handleSubmit() {
const result = displayFormSchema.safeParse(displayForm.value)
if (!result.success) {
errors.value = result.error.format()
console.log(errors.value)
return
}
console.log('Form submitted!', displayForm.value)
}
</script> </script>
<template> <template>
@ -74,21 +67,37 @@ async function handleSubmit() {
</p> </p>
</div> </div>
<Separator /> <Separator />
<form @submit.prevent="handleSubmit"> <form @submit="onSubmit">
<div class="mb-4"> <FormField name="items">
<Label for="sidebar" :class="cn('text-md', errors?.items && 'text-destructive')"> <FormItem>
Sidebar <div class="mb-4">
</Label> <FormLabel class="text-base">
<span class="text-xs text-muted-foreground"> Sidebar
Select the items you want to display in the sidebar. </FormLabel>
</span> <FormDescription>
</div> Select the items you want to display in the sidebar.
<div v-for="item in items" :key="item.id" class="pb-1"> </FormDescription>
<div class="flex flex-row items-center space-x-3 space-y-0"> </div>
<Checkbox :id="item.id" :checked="displayForm.items.includes(item.id)" @change="displayForm.items.includes(item.id) ? displayForm.items.splice(displayForm.items.indexOf(item.id), 1) : displayForm.items.push(item.id)" />
<Label :for="item.id">{{ item.label }}</Label> <FormField v-for="item in items" v-slot="{ value, handleChange }" :key="item.id" name="items">
</div> <FormItem :key="item.id" class="flex flex-row items-start space-x-3 space-y-0">
</div> <FormControl>
<Checkbox
:checked="value.includes(item.id)"
@update:checked="(checked) => {
handleChange(checked ? [...value, item.id] : value.filter(id => id !== item.id))
}"
/>
</FormControl>
<FormLabel class="font-normal">
{{ item.label }}
</FormLabel>
</FormItem>
</FormField>
<FormMessage />
</FormItem>
</FormField>
<div class="flex justify-start mt-4"> <div class="flex justify-start mt-4">
<Button type="submit"> <Button type="submit">
Update display Update display

View File

@ -1,25 +1,16 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
import * as z from 'zod' import * as z from 'zod'
import { cn } from '@/lib/utils'
import { Label } from '@/lib/registry/new-york/ui/label' import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/default/ui/form'
import { Separator } from '@/lib/registry/new-york/ui/separator' import { Separator } from '@/lib/registry/new-york/ui/separator'
import { RadioGroup, RadioGroupItem } from '@/lib/registry/new-york/ui/radio-group' import { RadioGroup, RadioGroupItem } from '@/lib/registry/new-york/ui/radio-group'
import { Switch } from '@/lib/registry/new-york/ui/switch' import { Switch } from '@/lib/registry/new-york/ui/switch'
import { Checkbox } from '@/lib/registry/new-york/ui/checkbox' import { Checkbox } from '@/lib/registry/new-york/ui/checkbox'
import { Button } from '@/lib/registry/new-york/ui/button' import { Button } from '@/lib/registry/new-york/ui/button'
const notificationsForm = ref({ const notificationsFormSchema = toTypedSchema(z.object({
type: '',
mobile: false,
communication_emails: false,
social_emails: true,
marketing_emails: false,
security_emails: true,
})
const notificationsFormSchema = z.object({
type: z.enum(['all', 'mentions', 'none'], { type: z.enum(['all', 'mentions', 'none'], {
required_error: 'You need to select a notification type.', required_error: 'You need to select a notification type.',
}), }),
@ -28,21 +19,21 @@ const notificationsFormSchema = z.object({
social_emails: z.boolean().default(false).optional(), social_emails: z.boolean().default(false).optional(),
marketing_emails: z.boolean().default(false).optional(), marketing_emails: z.boolean().default(false).optional(),
security_emails: z.boolean(), security_emails: z.boolean(),
}))
const { handleSubmit } = useForm({
validationSchema: notificationsFormSchema,
initialValues: {
communication_emails: false,
marketing_emails: false,
social_emails: true,
security_emails: true,
},
}) })
type notificationsFormValues = z.infer<typeof notificationsFormSchema> const onSubmit = handleSubmit((values, { resetForm }) => {
const errors = ref<z.ZodFormattedError<notificationsFormValues> | null>(null) console.log('Form submitted!', values)
})
async function handleSubmit() {
const result = notificationsFormSchema.safeParse(notificationsForm.value)
if (!result.success) {
errors.value = result.error.format()
console.log(errors.value)
return
}
console.log('Form submitted!', notificationsForm.value)
}
</script> </script>
<template> <template>
@ -55,117 +46,149 @@ async function handleSubmit() {
</p> </p>
</div> </div>
<Separator /> <Separator />
<form class="space-y-8" @submit.prevent="handleSubmit"> <form class="space-y-8" @submit="onSubmit">
<div class="grid gap-2"> <FormField v-slot="{ componentField }" type="radio" name="type">
<Label for="font" :class="cn('text-sm', errors?.type && 'text-destructive')"> <FormItem class="space-y-3">
Notify me about... <FormLabel>Notify me about...</FormLabel>
</Label> <FormControl>
<RadioGroup <RadioGroup
v-model="notificationsForm.type" class="flex flex-col space-y-1"
default-value="all" v-bind="componentField"
class="flex flex-col space-y-1" >
> <FormItem class="flex items-center space-x-3 space-y-0">
<div class="flex items-center space-x-3 space-y-0"> <FormControl>
<RadioGroupItem id="all" value="all" /> <RadioGroupItem value="all" />
<Label for="all">All new messages</Label> </FormControl>
</div> <FormLabel class="font-normal">
<div class="flex items-center space-x-3 space-y-0"> All new messages
<RadioGroupItem id="mentions" value="mentions" /> </FormLabel>
<Label for="mentions">Direct messages and mentions</Label> </FormItem>
</div> <FormItem class="flex items-center space-x-3 space-y-0">
<div class="flex items-center space-x-3 space-y-0"> <FormControl>
<RadioGroupItem id="none" value="none" /> <RadioGroupItem value="mentions" />
<Label for="none">Nothing</Label> </FormControl>
</div> <FormLabel class="font-normal">
<div v-if="errors?.type" class="text-sm text-destructive"> Direct messages and mentions
<span v-for="error in errors.type._errors" :key="error">{{ error }}</span> </FormLabel>
</div> </FormItem>
</RadioGroup> <FormItem class="flex items-center space-x-3 space-y-0">
</div> <FormControl>
<RadioGroupItem value="none" />
</FormControl>
<FormLabel class="font-normal">
Nothing
</FormLabel>
</FormItem>
</RadioGroup>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<div class="grid gap-2"> <div>
<h3 class="mb-4 text-lg font-medium"> <h3 class="mb-4 text-lg font-medium">
Email Notifications Email Notifications
</h3> </h3>
<div class="space-y-4"> <div class="space-y-4">
<div class="flex flex-row items-center justify-between rounded-lg border p-4"> <FormField v-slot="{ handleChange, value }" type="checkbox" name="communication_emails">
<div class="space-y-0.5"> <FormItem class="flex flex-row items-center justify-between rounded-lg border p-4">
<Label class="text-base" for="communication_emails"> <div class="space-y-0.5">
Communication emails <FormLabel class="text-base">
</Label> Communication emails
<span class="text-xs text-muted-foreground"> </FormLabel>
Receive emails about your account activity. <FormDescription>
</span> Receive emails about your account activity.
</div> </FormDescription>
<Switch </div>
id="communication_emails" <FormControl>
v-model:checked="notificationsForm.communication_emails" <Switch
:checked="value"
@update:checked="handleChange"
/>
</FormControl>
</FormItem>
</FormField>
<FormField v-slot="{ handleChange, value }" type="checkbox" name="marketing_emails">
<FormItem class="flex flex-row items-center justify-between rounded-lg border p-4">
<div class="space-y-0.5">
<FormLabel class="text-base">
Marketing emails
</FormLabel>
<FormDescription>
Receive emails about new products, features, and more.
</FormDescription>
</div>
<FormControl>
<Switch
:checked="value"
@update:checked="handleChange"
/>
</FormControl>
</FormItem>
</FormField>
<FormField v-slot="{ handleChange, value }" type="checkbox" name="social_emails">
<FormItem class="flex flex-row items-center justify-between rounded-lg border p-4">
<div class="space-y-0.5">
<FormLabel class="text-base">
Social emails
</FormLabel>
<FormDescription>
Receive emails for friend requests, follows, and more.
</FormDescription>
</div>
<FormControl>
<Switch
:checked="value"
@update:checked="handleChange"
/>
</FormControl>
</FormItem>
</FormField>
<FormField v-slot="{ handleChange, value }" type="checkbox" name="security_emails">
<FormItem class="flex flex-row items-center justify-between rounded-lg border p-4">
<div class="space-y-0.5">
<FormLabel class="text-base">
Security emails
</FormLabel>
<FormDescription>
Receive emails about your account activity and security.
</FormDescription>
</div>
<FormControl>
<Switch
:checked="value"
@update:checked="handleChange"
/>
</FormControl>
</FormItem>
</FormField>
</div>
</div>
<FormField v-slot="{ handleChange, value }" type="checkbox" name="mobile">
<FormItem class="flex flex-row items-start space-x-3 space-y-0">
<FormControl>
<Checkbox
:checked="value"
@update:checked="handleChange"
/> />
</div> </FormControl>
</div> <div class="space-y-1 leading-none">
<div class="flex flex-row items-center justify-between rounded-lg border p-4"> <FormLabel>
<div class="space-y-0.5">
<Label class="text-base" for="marketing_emails">
Marketing emails
</Label>
<span class="text-xs text-muted-foreground">
Receive emails about new products, features, and more.
</span>
</div>
<Switch
id="marketing_emails"
v-model:checked="notificationsForm.marketing_emails"
/>
</div>
<div class="flex flex-row items-center justify-between rounded-lg border p-4">
<div class="space-y-0.5">
<Label class="text-base" for="social_emails">
Social emails
</Label>
<span class="text-xs text-muted-foreground">
Receive emails for friend requests, follows, and more.
</span>
</div>
<Switch
id="social_emails"
v-model:checked="notificationsForm.social_emails"
/>
</div>
<div class="flex flex-row items-center justify-between rounded-lg border p-4">
<div class="space-y-0.5">
<Label class="text-base" for="security_emails">
Security emails
</Label>
<span class="text-xs text-muted-foreground">
Receive emails about your account activity and security.
</span>
</div>
<Switch
id="security_emails"
v-model:checked="notificationsForm.security_emails"
disabled
/>
</div>
</div>
<div class="grid gap-2">
<div class="flex flex-row items-start space-x-3 space-y-0">
<Checkbox
id="mobile"
v-model:checked="notificationsForm.mobile"
/>
<div>
<Label for="mobile">
Use different settings for my mobile devices Use different settings for my mobile devices
</Label> </FormLabel>
<span class="text-xs text-muted-foreground"> <FormDescription>
You can manage your mobile notifications in the {{ " " }} You can manage your mobile notifications in the{" "}
<a href="/examples/forms">mobile settings</a> page. <a href="/examples/forms">
</span> mobile settings
</a> page.
</FormDescription>
</div> </div>
</div> </FormItem>
</div> </FormField>
<div class="flex justify-start"> <div class="flex justify-start">
<Button type="submit"> <Button type="submit">

View File

@ -57,8 +57,8 @@ const { handleSubmit, resetForm } = useForm({
}, },
}) })
const onSubmit = handleSubmit((values, { resetForm }) => { const onSubmit = handleSubmit((values) => {
console.log(values) console.log('Form submitted!', values)
}) })
</script> </script>
@ -89,20 +89,21 @@ const onSubmit = handleSubmit((values, { resetForm }) => {
<FormField v-slot="{ componentField }" name="email"> <FormField v-slot="{ componentField }" name="email">
<FormItem> <FormItem>
<FormLabel>Email</FormLabel> <FormLabel>Email</FormLabel>
<FormControl>
<Select v-bind="componentField"> <Select v-bind="componentField">
<FormControl>
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Select an email" /> <SelectValue placeholder="Select an email" />
</SelectTrigger> </SelectTrigger>
<SelectContent> </FormControl>
<SelectGroup> <SelectContent>
<SelectItem v-for="email in verifiedEmails" :key="email" :value="email"> <SelectGroup>
{{ email }} <SelectItem v-for="email in verifiedEmails" :key="email" :value="email">
</SelectItem> {{ email }}
</SelectGroup> </SelectItem>
</SelectContent> </SelectGroup>
</Select> </SelectContent>
</FormControl> </Select>
<FormDescription> <FormDescription>
You can manage verified email addresses in your email settings. You can manage verified email addresses in your email settings.
</FormDescription> </FormDescription>

View File

@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Slot } from 'radix-vue' import { Slot } from 'radix-vue'
import { useFormField } from './FormItem.vue' import { useFormField } from './useFormField'
const { error, formItemId, formDescriptionId, formMessageId } = useFormField() const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
</script> </script>

View File

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useFormField } from './FormItem.vue' import { useFormField } from './useFormField'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
const { formDescriptionId } = useFormField() const { formDescriptionId } = useFormField()

View File

@ -1,36 +1,17 @@
<script lang="ts"> <script lang="ts">
import { FieldContextKey, useFieldError, useIsFieldDirty, useIsFieldTouched, useIsFieldValid } from 'vee-validate' import { type InjectionKey } from 'vue'
import { inject } from 'vue'
export function useFormField() { export const FORMI_TEM_INJECTION_KEY
const fieldContext = inject(FieldContextKey) = Symbol() as InjectionKey<string>
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>
<script lang="ts" setup> <script lang="ts" setup>
// eslint-disable-next-line import/first import { provide } from 'vue'
import { useId } from 'radix-vue'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
const id = useId()
provide(FORMI_TEM_INJECTION_KEY, id)
</script> </script>
<template> <template>

View File

@ -1,11 +1,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Label, type LabelProps } from 'radix-vue' import { Label, type LabelProps } from 'radix-vue'
import { useFormField } from './FormItem.vue' import { useFormField } from './useFormField'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
const props = defineProps<LabelProps>() const props = defineProps<LabelProps>()
const { error, id, formItemId } = useFormField() const { error, formItemId } = useFormField()
</script> </script>
<template> <template>

View File

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

View File

@ -0,0 +1,30 @@
import { FieldContextKey, useFieldError, useIsFieldDirty, useIsFieldTouched, useIsFieldValid } from 'vee-validate'
import { inject } from 'vue'
import { FORMI_TEM_INJECTION_KEY } from './FormItem.vue'
export function useFormField() {
const fieldContext = inject(FieldContextKey)
const fieldItemContext = inject(FORMI_TEM_INJECTION_KEY)
const fieldState = {
valid: useIsFieldValid(),
isDirty: useIsFieldDirty(),
isTouched: useIsFieldTouched(),
error: useFieldError(),
}
if (!fieldContext)
throw new Error('useFormField should be used within <FormField>')
const { name } = fieldContext
const id = fieldItemContext
return {
id,
name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
}
}

View File

@ -0,0 +1,16 @@
<script lang="ts" setup>
import { Slot } from 'radix-vue'
import { useFormField } from './useFormField'
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,15 @@
<script lang="ts" setup>
import { useFormField } from './useFormField'
import { cn } from '@/lib/utils'
const { formDescriptionId } = useFormField()
</script>
<template>
<p
:id="formDescriptionId"
:class="cn('text-[0.8rem] text-muted-foreground', $attrs.class ?? '')"
>
<slot />
</p>
</template>

View File

@ -0,0 +1,21 @@
<script lang="ts">
import { type InjectionKey } from 'vue'
export const FORMI_TEM_INJECTION_KEY
= Symbol() as InjectionKey<string>
</script>
<script lang="ts" setup>
import { provide } from 'vue'
import { useId } from 'radix-vue'
import { cn } from '@/lib/utils'
const id = useId()
provide(FORMI_TEM_INJECTION_KEY, id)
</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 './useFormField'
import { cn } from '@/lib/utils'
const props = defineProps<LabelProps>()
const { error, formItemId } = useFormField()
</script>
<template>
<Label
:class="cn(
'block text-sm tracking-tight font-medium text-foreground text-left',
error && 'text-destructive',
$attrs.class ?? '',
)"
:for="formItemId"
>
<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 './useFormField'
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,6 @@
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'
export { default as FormDescription } from './FormDescription.vue'

View File

@ -0,0 +1,30 @@
import { FieldContextKey, useFieldError, useIsFieldDirty, useIsFieldTouched, useIsFieldValid } from 'vee-validate'
import { inject } from 'vue'
import { FORMI_TEM_INJECTION_KEY } from './FormItem.vue'
export function useFormField() {
const fieldContext = inject(FieldContextKey)
const fieldItemContext = inject(FORMI_TEM_INJECTION_KEY)
const fieldState = {
valid: useIsFieldValid(),
isDirty: useIsFieldDirty(),
isTouched: useIsFieldTouched(),
error: useFieldError(),
}
if (!fieldContext)
throw new Error('useFormField should be used within <FormField>')
const { name } = fieldContext
const id = fieldItemContext
return {
id,
name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
}
}