refactor: change name to fieldName to allow easier slotProps binding

This commit is contained in:
zernonia 2024-04-19 23:58:43 +08:00
parent 0cd3371e4a
commit 9162c7464c
12 changed files with 148 additions and 98 deletions

View File

@ -9,29 +9,77 @@ import { toast } from '@/lib/registry/new-york/ui/toast'
import type { Config } from '@/lib/registry/new-york/ui/auto-form'
import { AutoForm, AutoFormField } from '@/lib/registry/new-york/ui/auto-form'
enum Sports {
Football = 'Football/Soccer',
Basketball = 'Basketball',
Baseball = 'Baseball',
Hockey = 'Hockey (Ice)',
None = 'I don\'t like sports',
}
const schema = z.object({
age: z.number().default(20),
parentsAllowed: z.boolean().optional(),
vegetarian: z.boolean().default(true),
mealOptions: z.enum(['Pasta', 'Salad', 'Beef Wellington']).optional(),
invitedGuests: z
.array(
z.object({
name: z.string(),
age: z.coerce.number(),
}),
).default([{ name: '123', age: 0 }]),
subObject: z.object({
subField: z.string().optional().default('Sub Field'),
numberField: z.number().optional().default(10),
subSubObject: z
.object({
subSubField: z.string().default('Sub Sub Field'),
username: z
.string({
required_error: 'Username is required.',
})
.describe('Sub Sub Object Description'),
.min(2, {
message: 'Username must be at least 2 characters.',
}),
password: z
.string({
required_error: 'Password is required.',
})
.min(8, {
message: 'Password must be at least 8 characters.',
}),
favouriteNumber: z.coerce
.number({
invalid_type_error: 'Favourite number must be a number.',
})
.min(1, {
message: 'Favourite number must be at least 1.',
})
.max(10, {
message: 'Favourite number must be at most 10.',
})
.default(1)
.optional(),
acceptTerms: z
.boolean()
.refine(value => value, {
message: 'You must accept the terms and conditions.',
path: ['acceptTerms'],
}),
sendMeMails: z.boolean().optional(),
birthday: z.coerce.date().optional(),
color: z.enum(['red', 'green', 'blue']).optional(),
// Another enum example
marshmallows: z
.enum(['not many', 'a few', 'a lot', 'too many']),
// Native enum example
sports: z.nativeEnum(Sports).describe('What is your favourite sport?'),
bio: z
.string()
.min(10, {
message: 'Bio must be at least 10 characters.',
})
.max(160, {
message: 'Bio must not be longer than 30 characters.',
})
.optional(),
customParent: z.string().optional(),
file: z.string().optional(),
})
function onSubmit(values: Record<string, any>) {
@ -47,55 +95,56 @@ function onSubmit(values: Record<string, any>) {
class="w-2/3 space-y-6"
:schema="schema"
:field-config="{
age: {
description:
'Setting this below 18 will require parents consent.',
password: {
label: 'Your secure password',
inputProps: {
type: 'password',
placeholder: '••••••••',
},
parentsAllowed: {
label: 'Did your parents allow you to register?',
},
vegetarian: {
label: 'Are you a vegetarian?',
description:
'Setting this to true will remove non-vegetarian food options.',
favouriteNumber: {
description: 'Your favourite number between 1 and 10.',
},
mealOptions: {
acceptTerms: {
label: 'Accept terms and conditions.',
inputProps: {
required: true,
},
},
birthday: {
description: 'We need your birthday to send you a gift.',
},
sendMeMails: {
component: 'switch',
},
bio: {
component: 'textarea',
},
marshmallows: {
label: 'How many marshmallows fit in your mouth?',
component: 'radio',
},
file: {
label: 'Text file',
component: 'file',
},
}"
:dependencies="[
{
// 'age' hides 'parentsAllowed' when the age is 18 or older
sourceField: 'age',
type: DependencyType.HIDES,
targetField: 'parentsAllowed',
when: (age) => age >= 18,
},
{
// 'vegetarian' checkbox hides the 'Beef Wellington' option from 'mealOptions'
// if its not already selected
sourceField: 'vegetarian',
type: DependencyType.SETS_OPTIONS,
targetField: 'mealOptions',
when: (vegetarian, mealOption) =>
vegetarian && mealOption !== 'Beef Wellington',
options: ['Pasta', 'Salad'],
},
{
sourceField: 'age',
type: DependencyType.HIDES,
targetField: 'invitedGuests.age' as any,
when: (age) => age >= 18,
},
{
sourceField: 'age' as any,
type: DependencyType.HIDES,
targetField: 'subObject.subSubObject' as any,
when: (age) => age >= 18,
},
]"
@submit="onSubmit"
>
<template #acceptTerms="slotProps">
<AutoFormField v-bind="slotProps" />
<div class="!mt-2 text-sm">
I agree to the <button class="text-primary underline">
terms and conditions
</button>.
</div>
</template>
<Button type="submit">
Submit
</Button>

View File

@ -72,11 +72,12 @@ const formComponentProps = computed(() => {
<slot
:shape="shape"
:name="key.toString() as keyof z.infer<T>"
:field-name="key.toString()"
:config="fieldConfig?.[key as keyof typeof fieldConfig] as ConfigItem"
>
<AutoFormField
:config="fieldConfig?.[key as keyof typeof fieldConfig] as ConfigItem"
:name="key.toString()"
:field-name="key.toString()"
:shape="shape"
/>
</slot>

View File

@ -6,7 +6,7 @@ import { DEFAULT_ZOD_HANDLERS, INPUT_COMPONENTS } from './constant'
import useDependencies from './dependencies'
const props = defineProps<{
name: string
fieldName: string
shape: Shape
label?: string
config?: ConfigItem | Config<U>
@ -22,14 +22,14 @@ const delegatedProps = computed(() => {
return undefined
})
const { isDisabled, isHidden, isRequired, overrideOptions } = useDependencies(props.name)
const { isDisabled, isHidden, isRequired, overrideOptions } = useDependencies(props.fieldName)
</script>
<template>
<component
:is="isValidConfig(config) ? INPUT_COMPONENTS[config.component!] : INPUT_COMPONENTS[DEFAULT_ZOD_HANDLERS[shape.type]] "
v-if="!isHidden"
:name="name"
:field-name="fieldName"
:label="label"
:required="isRequired || shape.required"
:options="overrideOptions || shape.options"

View File

@ -13,14 +13,14 @@ import { Separator } from '@/lib/registry/new-york/ui/separator'
import { FormMessage } from '@/lib/registry/new-york/ui/form'
const props = defineProps<{
name: string
fieldName: string
required?: boolean
config?: Config<T>
schema?: z.ZodArray<T>
disabled?: boolean
}>()
const fieldContext = useField(props.name)
const fieldContext = useField(props.fieldName)
function isZodArray(
item: z.ZodArray<any> | z.ZodDefault<any>,
@ -56,13 +56,13 @@ provide(FieldContextKey, fieldContext)
</script>
<template>
<FieldArray v-slot="{ fields, remove, push }" as="section" :name="name">
<FieldArray v-slot="{ fields, remove, push }" as="section" :name="fieldName">
<slot v-bind="props">
<Accordion type="multiple" class="w-full" collapsible :disabled="disabled">
<AccordionItem :value="name" class="border-none">
<AccordionItem :value="fieldName" class="border-none">
<AccordionTrigger>
<AutoFormLabel class="text-base" :required="required">
{{ schema?.description || beautifyObjectName(name) }}
{{ schema?.description || beautifyObjectName(fieldName) }}
</AutoFormLabel>
</AccordionTrigger>
@ -70,8 +70,8 @@ provide(FieldContextKey, fieldContext)
<template v-for="(field, index) of fields" :key="field.key">
<div class="mb-4 p-[1px]">
<AutoFormField
:name="`${name}[${index}]`"
:label="name"
:field-name="`${fieldName}[${index}]`"
:label="fieldName"
:shape="itemShape!"
:config="config as ConfigItem"
/>

View File

@ -1,9 +1,9 @@
<script setup lang="ts">
import { computed } from 'vue'
import { beautifyObjectName } from '../utils'
import type { ConfigItem, FieldProps } from '../interface'
import type { FieldProps } from '../interface'
import AutoFormLabel from '../AutoFormLabel.vue'
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/new-york/ui/form'
import { FormControl, FormDescription, FormField, FormItem, FormMessage } from '@/lib/registry/new-york/ui/form'
import { Switch } from '@/lib/registry/new-york/ui/switch'
import { Checkbox } from '@/lib/registry/new-york/ui/checkbox'
@ -13,7 +13,7 @@ const booleanComponent = computed(() => props.config?.component === 'switch' ? S
</script>
<template>
<FormField v-slot="slotProps" :name="name">
<FormField v-slot="slotProps" :name="fieldName">
<FormItem>
<div class="space-y-0 mb-3 flex items-center gap-3">
<FormControl>
@ -28,7 +28,7 @@ const booleanComponent = computed(() => props.config?.component === 'switch' ? S
</slot>
</FormControl>
<AutoFormLabel v-if="!config?.hideLabel" :required="required">
{{ config?.label || beautifyObjectName(label ?? name) }}
{{ config?.label || beautifyObjectName(label ?? fieldName) }}
</AutoFormLabel>
</div>

View File

@ -3,8 +3,8 @@ import { DateFormatter, getLocalTimeZone } from '@internationalized/date'
import { CalendarIcon } from '@radix-icons/vue'
import { beautifyObjectName } from '../utils'
import AutoFormLabel from '../AutoFormLabel.vue'
import type { ConfigItem, FieldProps } from '../interface'
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/new-york/ui/form'
import type { FieldProps } from '../interface'
import { FormControl, FormDescription, FormField, FormItem, FormMessage } from '@/lib/registry/new-york/ui/form'
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
import { Button } from '@/lib/registry/new-york/ui/button'
@ -19,10 +19,10 @@ const df = new DateFormatter('en-US', {
</script>
<template>
<FormField v-slot="slotProps" :name="name">
<FormField v-slot="slotProps" :name="fieldName">
<FormItem>
<AutoFormLabel v-if="!config?.hideLabel" :required="required">
{{ config?.label || beautifyObjectName(label ?? name) }}
{{ config?.label || beautifyObjectName(label ?? fieldName) }}
</AutoFormLabel>
<FormControl>
<slot v-bind="slotProps">

View File

@ -3,7 +3,7 @@ import { computed } from 'vue'
import { beautifyObjectName } from '../utils'
import type { FieldProps } from '../interface'
import AutoFormLabel from '../AutoFormLabel.vue'
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/new-york/ui/form'
import { FormControl, FormDescription, FormField, FormItem, FormMessage } from '@/lib/registry/new-york/ui/form'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/lib/registry/new-york/ui/select'
import { Label } from '@/lib/registry/new-york/ui/label'
import { RadioGroup, RadioGroupItem } from '@/lib/registry/new-york/ui/radio-group'
@ -16,10 +16,10 @@ const computedOptions = computed(() => props.config?.enumProps?.options || props
</script>
<template>
<FormField v-slot="slotProps" :name="name">
<FormField v-slot="slotProps" :name="fieldName">
<FormItem>
<AutoFormLabel v-if="!config?.hideLabel" :required="required">
{{ config?.label || beautifyObjectName(label ?? name) }}
{{ config?.label || beautifyObjectName(label ?? fieldName) }}
</AutoFormLabel>
<FormControl>
<slot v-bind="slotProps">

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import { beautifyObjectName } from '../utils'
import type { ConfigItem, FieldProps } from '../interface'
import type { FieldProps } from '../interface'
import AutoFormLabel from '../AutoFormLabel.vue'
import { FormControl, FormDescription, FormField, FormItem, FormMessage } from '@/lib/registry/new-york/ui/form'
import { Input } from '@/lib/registry/new-york/ui/input'
@ -24,10 +24,10 @@ async function parseFileAsString(file: File | undefined): Promise<string> {
</script>
<template>
<FormField v-slot="slotProps" :name="name">
<FormField v-slot="slotProps" :name="fieldName">
<FormItem v-bind="$attrs">
<AutoFormLabel v-if="!config?.hideLabel" :required="required">
{{ config?.label || beautifyObjectName(label ?? name) }}
{{ config?.label || beautifyObjectName(label ?? fieldName) }}
</AutoFormLabel>
<FormControl>
<slot v-bind="slotProps">

View File

@ -1,9 +1,9 @@
<script setup lang="ts">
import { computed } from 'vue'
import { beautifyObjectName } from '../utils'
import type { Config, ConfigItem, FieldProps } from '../interface'
import type { FieldProps } from '../interface'
import AutoFormLabel from '../AutoFormLabel.vue'
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/new-york/ui/form'
import { FormControl, FormDescription, FormField, FormItem, FormMessage } from '@/lib/registry/new-york/ui/form'
import { Input } from '@/lib/registry/new-york/ui/input'
import { Textarea } from '@/lib/registry/new-york/ui/textarea'
@ -12,10 +12,10 @@ const inputComponent = computed(() => props.config?.component === 'textarea' ? T
</script>
<template>
<FormField v-slot="slotProps" :name="name">
<FormField v-slot="slotProps" :name="fieldName">
<FormItem v-bind="$attrs">
<AutoFormLabel v-if="!config?.hideLabel" :required="required">
{{ config?.label || beautifyObjectName(label ?? name) }}
{{ config?.label || beautifyObjectName(label ?? fieldName) }}
</AutoFormLabel>
<FormControl>
<slot v-bind="slotProps">

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import { beautifyObjectName } from '../utils'
import type { Config, ConfigItem, FieldProps } from '../interface'
import type { FieldProps } from '../interface'
import AutoFormLabel from '../AutoFormLabel.vue'
import { FormControl, FormDescription, FormField, FormItem, FormMessage } from '@/lib/registry/new-york/ui/form'
import { Input } from '@/lib/registry/new-york/ui/input'
@ -13,10 +13,10 @@ defineProps<FieldProps>()
</script>
<template>
<FormField v-slot="slotProps" :name="name">
<FormField v-slot="slotProps" :name="fieldName">
<FormItem>
<AutoFormLabel v-if="!config?.hideLabel" :required="required">
{{ config?.label || beautifyObjectName(label ?? name) }}
{{ config?.label || beautifyObjectName(label ?? fieldName) }}
</AutoFormLabel>
<FormControl>
<slot v-bind="slotProps">

View File

@ -7,7 +7,7 @@ import AutoFormField from '../AutoFormField.vue'
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/lib/registry/new-york/ui/accordion'
const props = defineProps<{
name: string
fieldName: string
required?: boolean
config?: Config<T>
schema?: ZodObject<T>
@ -45,15 +45,15 @@ const shapes = computed(() => {
<section>
<slot v-bind="props">
<Accordion type="multiple" class="w-full" collapsible :disabled="disabled">
<AccordionItem :value="name" class="border-none">
<AccordionItem :value="fieldName" class="border-none">
<AccordionTrigger class="text-base">
{{ schema?.description || beautifyObjectName(name) }}
{{ schema?.description || beautifyObjectName(fieldName) }}
</AccordionTrigger>
<AccordionContent class="p-[1px] space-y-5">
<template v-for="(shape, key) in shapes" :key="key">
<AutoFormField
:config="config?.[key as keyof typeof config] as ConfigItem"
:name="`${name}.${key.toString()}`"
:field-name="`${fieldName}.${key.toString()}`"
:label="key.toString()"
:shape="shape"
/>

View File

@ -3,7 +3,7 @@ import type { ZodAny, z } from 'zod'
import type { INPUT_COMPONENTS } from './constant'
export interface FieldProps {
name: string
fieldName: string
label?: string
required?: boolean
config?: ConfigItem