feat: typing for nested config

This commit is contained in:
zernonia 2024-04-18 14:38:19 +08:00
parent 9f37b12c21
commit 49ad6c85e4
12 changed files with 118 additions and 36 deletions

View File

@ -146,7 +146,6 @@ const onSubmit = handleSubmit((values) => {
placeholder: 'Choose a color',
},
},
marshmallows: {
component: 'radio',
},
@ -158,7 +157,15 @@ const onSubmit = handleSubmit((values) => {
},
subObject: {
subField: {
label: 'custom labvel',
description: '123',
},
subSubObject: {
subSubField: {
label: 'sub suuuub',
},
},
},
}"
@submit="onSubmit"
@ -175,6 +182,10 @@ const onSubmit = handleSubmit((values) => {
</div>
</template>
<template #subObject="componentField">
<AutoFormField v-bind="{ ...componentField, name: 'subObject' }" />
</template>
<Button type="submit">
Submit
</Button>

View File

@ -1,15 +1,13 @@
<script setup lang="ts" generic="T extends ZodRawShape">
<script setup lang="ts" generic="U extends ZodRawShape, T extends ZodObject<U>">
import { computed } from 'vue'
import type { ZodAny, ZodObject, ZodRawShape } from 'zod'
import type { ZodAny, ZodObject, ZodRawShape, z } from 'zod'
import { getBaseType, getDefaultValueInZodStack } from './utils'
import type { Config } from './interface'
import type { Config, ConfigItem } from './interface'
import AutoFormField from './AutoFormField.vue'
const props = defineProps<{
schema: ZodObject<T>
fieldConfig?: {
[key in keyof T]?: Config
}
schema: T
fieldConfig?: Config<z.infer<T>>
}>()
const shapes = computed(() => {
@ -48,16 +46,14 @@ const shapes = computed(() => {
<slot
:shape="shape"
:name="key.toString()"
:config="fieldConfig?.[key as keyof typeof fieldConfig] as Config"
:config="fieldConfig?.[key as keyof typeof fieldConfig] as ConfigItem"
>
<AutoFormField
:config="fieldConfig?.[key as keyof typeof fieldConfig] as Config"
:config="fieldConfig?.[key as keyof typeof fieldConfig] as ConfigItem"
:name="key.toString()"
:shape="shape"
/>
</slot>
<!-- {{ shape.schema }} -->
</template>
<slot :shapes="shapes" />

View File

@ -1,7 +1,6 @@
<script setup lang="ts">
<script setup lang="ts" generic="U extends ZodAny">
import type { ZodAny } from 'zod'
import type { Config } from './interface'
import type { Config, ConfigItem } from './interface'
import { DEFAULT_ZOD_HANDLERS, INPUT_COMPONENTS } from './constant'
defineProps<{
@ -12,16 +11,21 @@ defineProps<{
options?: any[]
schema?: ZodAny
}
config?: Config
config?: ConfigItem | Config<U>
}>()
function isValidConfig(config: any): config is ConfigItem {
return !!config?.component
}
</script>
<template>
<component
:is="INPUT_COMPONENTS[config?.component ?? DEFAULT_ZOD_HANDLERS[shape.type]] "
:is="isValidConfig(config) ? INPUT_COMPONENTS[config.component!] : INPUT_COMPONENTS[DEFAULT_ZOD_HANDLERS[shape.type]] "
:name="name"
:required="shape.required"
:options="shape.options"
:schema="shape.schema"
:config="config"
>
<slot />

View File

@ -4,6 +4,7 @@ import FieldEnum from './fields/Enum.vue'
import FieldFile from './fields/File.vue'
import FieldInput from './fields/Input.vue'
import FieldNumber from './fields/Number.vue'
import FieldObject from './fields/Object.vue'
export const INPUT_COMPONENTS = {
date: FieldDate,
@ -15,6 +16,7 @@ export const INPUT_COMPONENTS = {
number: FieldNumber,
string: FieldInput,
file: FieldFile,
object: FieldObject,
}
/**
@ -30,4 +32,5 @@ export const DEFAULT_ZOD_HANDLERS: {
ZodEnum: 'select',
ZodNativeEnum: 'select',
ZodNumber: 'number',
ZodObject: 'object',
}

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import { beautifyObjectName } from '../utils'
import type { Config } from '../interface'
import type { ConfigItem } from '../interface'
import AutoFormLabel from '../AutoFormLabel.vue'
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/new-york/ui/form'
import { Switch } from '@/lib/registry/new-york/ui/switch'
@ -9,7 +9,7 @@ import { Checkbox } from '@/lib/registry/new-york/ui/checkbox'
defineProps<{
name: string
required?: boolean
config?: Config
config?: ConfigItem
}>()
</script>

View File

@ -2,8 +2,8 @@
import { DateFormatter, getLocalTimeZone } from '@internationalized/date'
import { CalendarIcon } from '@radix-icons/vue'
import { beautifyObjectName } from '../utils'
import type { Config } from '../interface'
import AutoFormLabel from '../AutoFormLabel.vue'
import type { ConfigItem } from '../interface'
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/new-york/ui/form'
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
@ -14,7 +14,7 @@ import { cn } from '@/lib/utils'
defineProps<{
name: string
required?: boolean
config?: Config
config?: ConfigItem
}>()
const df = new DateFormatter('en-US', {

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed } from 'vue'
import { beautifyObjectName } from '../utils'
import type { Config } from '../interface'
import type { ConfigItem } from '../interface'
import AutoFormLabel from '../AutoFormLabel.vue'
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/new-york/ui/form'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/lib/registry/new-york/ui/select'
@ -12,7 +12,7 @@ const props = defineProps<{
name: string
required?: boolean
options?: string[]
config?: Config
config?: ConfigItem
}>()
const computedOptions = computed(() => props.config?.enumProps?.options || props.options)

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import { beautifyObjectName } from '../utils'
import type { Config } from '../interface'
import type { ConfigItem } 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'
@ -8,7 +8,7 @@ import { Input } from '@/lib/registry/new-york/ui/input'
defineProps<{
name: string
required?: boolean
config?: Config
config?: ConfigItem
}>()
async function parseFileAsString(file: File | undefined): Promise<string> {

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import { beautifyObjectName } from '../utils'
import type { Config } from '../interface'
import type { Config, ConfigItem } from '../interface'
import AutoFormLabel from '../AutoFormLabel.vue'
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/new-york/ui/form'
import { Input } from '@/lib/registry/new-york/ui/input'
@ -9,7 +9,7 @@ import { Textarea } from '@/lib/registry/new-york/ui/textarea'
defineProps<{
name: string
required?: boolean
config?: Config
config?: ConfigItem
}>()
</script>

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import { beautifyObjectName } from '../utils'
import type { Config } from '../interface'
import type { Config, ConfigItem } 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'
@ -12,7 +12,7 @@ defineOptions({
defineProps<{
name: string
required?: boolean
config?: Config
config?: ConfigItem
}>()
</script>

View File

@ -0,0 +1,68 @@
<script setup lang="ts" generic="T extends ZodRawShape">
import type { ZodAny, ZodObject, ZodRawShape } from 'zod'
import { computed } from 'vue'
import type { Config, ConfigItem } from '../interface'
import { beautifyObjectName, getBaseSchema, getBaseType, getDefaultValueInZodStack } from '../utils'
import AutoFormField from '../AutoFormField.vue'
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/lib/registry/new-york/ui/accordion'
const props = defineProps<{
name: string
required?: boolean
config?: Config<T>
schema?: ZodObject<T>
}>()
const shapes = computed(() => {
// @ts-expect-error ignore {} not assignable to object
const val: {
[key in keyof T]: {
type: string
default: any
required?: boolean
options?: string[]
schema?: ZodAny
}
} = {}
if (!props.schema)
return
const shape = getBaseSchema(props.schema)?.shape
if (!shape)
return
Object.keys(shape).forEach((name) => {
const item = shape[name] as ZodAny
let options = 'values' in item._def ? item._def.values as string[] : undefined
if (!Array.isArray(options) && typeof options === 'object')
options = Object.values(options)
val[name as keyof T] = {
type: getBaseType(item),
default: getDefaultValueInZodStack(item),
options,
required: !['ZodOptional', 'ZodNullable'].includes(item._def.typeName),
schema: item,
}
})
return val
})
</script>
<template>
<Accordion type="multiple" class="w-full" collapsible>
<AccordionItem :value="name" class="border-none">
<AccordionTrigger class="text-base">
{{ beautifyObjectName(name) }}
</AccordionTrigger>
<AccordionContent class="p-2 space-y-5">
<template v-for="(shape, key) in shapes" :key="key">
<AutoFormField
:config="config?.[key as keyof typeof config] as ConfigItem"
:name="key.toString()"
:shape="shape"
/>
</template>
</AccordionContent>
</AccordionItem>
</Accordion>
</template>

View File

@ -1,8 +1,8 @@
import type { InputHTMLAttributes, SelectHTMLAttributes } from 'vue'
import type * as z from 'zod'
import type { z } from 'zod'
import type { INPUT_COMPONENTS } from './constant'
export interface Config {
export interface ConfigItem {
label?: string
description?: string
component?: keyof typeof INPUT_COMPONENTS
@ -10,11 +10,11 @@ export interface Config {
enumProps?: SelectHTMLAttributes & { options?: any[] }
}
export type FieldConfig<SchemaType extends z.infer<z.ZodObject<any, any>>> = {
// If SchemaType.key is an object, create a nested FieldConfig, otherwise FieldConfigItem
export type Config<SchemaType extends object> = {
// If SchemaType.key is an object, create a nested Config, otherwise ConfigItem
[Key in keyof SchemaType]?: SchemaType[Key] extends object
? FieldConfig<z.infer<SchemaType[Key]>>
: FieldConfig<z.infer<SchemaType[Key]>> // TODO;
? Config<SchemaType[Key]>
: ConfigItem;
}
export enum DependencyType {