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-tabs': '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()))
// https://github.com/logaretm/vee-validate/issues/3521
// https://github.com/logaretm/vee-validate/discussions/3571
async function handleSubmit(values: any) {
async function onSubmit(values: any) {
console.log('Form submitted!', values)
}
</script>
@ -83,7 +80,7 @@ async function handleSubmit(values: any) {
<Form
v-slot="{
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">
<FormItem>

View File

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

View File

@ -1,17 +1,13 @@
<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 { 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 { Checkbox } from '@/lib/registry/new-york/ui/checkbox'
import { Button } from '@/lib/registry/new-york/ui/button'
const displayForm = ref({
items: ['recents', 'home'],
})
const items = [
{
id: 'recents',
@ -43,25 +39,22 @@ const items = [
},
] as const
const displayFormSchema = z.object({
const displayFormSchema = toTypedSchema(z.object({
items: z.array(z.string()).refine(value => value.some(item => 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 errors = ref<z.ZodFormattedError<DisplayFormValues> | null>(null)
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)
}
const onSubmit = handleSubmit((values) => {
console.log('Form submitted!', values)
})
</script>
<template>
@ -74,21 +67,37 @@ async function handleSubmit() {
</p>
</div>
<Separator />
<form @submit.prevent="handleSubmit">
<form @submit="onSubmit">
<FormField name="items">
<FormItem>
<div class="mb-4">
<Label for="sidebar" :class="cn('text-md', errors?.items && 'text-destructive')">
<FormLabel class="text-base">
Sidebar
</Label>
<span class="text-xs text-muted-foreground">
</FormLabel>
<FormDescription>
Select the items you want to display in the sidebar.
</span>
</div>
<div v-for="item in items" :key="item.id" class="pb-1">
<div class="flex flex-row items-center space-x-3 space-y-0">
<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>
</div>
</FormDescription>
</div>
<FormField v-for="item in items" v-slot="{ value, handleChange }" :key="item.id" name="items">
<FormItem :key="item.id" class="flex flex-row items-start space-x-3 space-y-0">
<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">
<Button type="submit">
Update display

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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