refactor: change name to fieldName to allow easier slotProps binding
This commit is contained in:
parent
0cd3371e4a
commit
9162c7464c
|
|
@ -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 }]),
|
||||
username: z
|
||||
.string({
|
||||
required_error: 'Username is required.',
|
||||
})
|
||||
.min(2, {
|
||||
message: 'Username must be at least 2 characters.',
|
||||
}),
|
||||
|
||||
subObject: z.object({
|
||||
subField: z.string().optional().default('Sub Field'),
|
||||
numberField: z.number().optional().default(10),
|
||||
password: z
|
||||
.string({
|
||||
required_error: 'Password is required.',
|
||||
})
|
||||
.min(8, {
|
||||
message: 'Password must be at least 8 characters.',
|
||||
}),
|
||||
|
||||
subSubObject: z
|
||||
.object({
|
||||
subSubField: z.string().default('Sub Sub Field'),
|
||||
})
|
||||
.describe('Sub Sub Object Description'),
|
||||
}),
|
||||
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?',
|
||||
favouriteNumber: {
|
||||
description: 'Your favourite number between 1 and 10.',
|
||||
},
|
||||
vegetarian: {
|
||||
label: 'Are you a vegetarian?',
|
||||
description:
|
||||
'Setting this to true will remove non-vegetarian food options.',
|
||||
acceptTerms: {
|
||||
label: 'Accept terms and conditions.',
|
||||
inputProps: {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
mealOptions: {
|
||||
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user