feat: typing for nested config
This commit is contained in:
parent
9f37b12c21
commit
49ad6c85e4
|
|
@ -146,7 +146,6 @@ const onSubmit = handleSubmit((values) => {
|
||||||
placeholder: 'Choose a color',
|
placeholder: 'Choose a color',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
marshmallows: {
|
marshmallows: {
|
||||||
component: 'radio',
|
component: 'radio',
|
||||||
},
|
},
|
||||||
|
|
@ -158,7 +157,15 @@ const onSubmit = handleSubmit((values) => {
|
||||||
},
|
},
|
||||||
|
|
||||||
subObject: {
|
subObject: {
|
||||||
|
subField: {
|
||||||
|
label: 'custom labvel',
|
||||||
|
description: '123',
|
||||||
|
},
|
||||||
|
subSubObject: {
|
||||||
|
subSubField: {
|
||||||
|
label: 'sub suuuub',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}"
|
}"
|
||||||
@submit="onSubmit"
|
@submit="onSubmit"
|
||||||
|
|
@ -175,6 +182,10 @@ const onSubmit = handleSubmit((values) => {
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template #subObject="componentField">
|
||||||
|
<AutoFormField v-bind="{ ...componentField, name: 'subObject' }" />
|
||||||
|
</template>
|
||||||
|
|
||||||
<Button type="submit">
|
<Button type="submit">
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -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 { computed } from 'vue'
|
||||||
import type { ZodAny, ZodObject, ZodRawShape } from 'zod'
|
import type { ZodAny, ZodObject, ZodRawShape, z } from 'zod'
|
||||||
import { getBaseType, getDefaultValueInZodStack } from './utils'
|
import { getBaseType, getDefaultValueInZodStack } from './utils'
|
||||||
import type { Config } from './interface'
|
import type { Config, ConfigItem } from './interface'
|
||||||
import AutoFormField from './AutoFormField.vue'
|
import AutoFormField from './AutoFormField.vue'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
schema: ZodObject<T>
|
schema: T
|
||||||
fieldConfig?: {
|
fieldConfig?: Config<z.infer<T>>
|
||||||
[key in keyof T]?: Config
|
|
||||||
}
|
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const shapes = computed(() => {
|
const shapes = computed(() => {
|
||||||
|
|
@ -48,16 +46,14 @@ const shapes = computed(() => {
|
||||||
<slot
|
<slot
|
||||||
:shape="shape"
|
:shape="shape"
|
||||||
:name="key.toString()"
|
:name="key.toString()"
|
||||||
:config="fieldConfig?.[key as keyof typeof fieldConfig] as Config"
|
:config="fieldConfig?.[key as keyof typeof fieldConfig] as ConfigItem"
|
||||||
>
|
>
|
||||||
<AutoFormField
|
<AutoFormField
|
||||||
:config="fieldConfig?.[key as keyof typeof fieldConfig] as Config"
|
:config="fieldConfig?.[key as keyof typeof fieldConfig] as ConfigItem"
|
||||||
:name="key.toString()"
|
:name="key.toString()"
|
||||||
:shape="shape"
|
:shape="shape"
|
||||||
/>
|
/>
|
||||||
</slot>
|
</slot>
|
||||||
|
|
||||||
<!-- {{ shape.schema }} -->
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<slot :shapes="shapes" />
|
<slot :shapes="shapes" />
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts" generic="U extends ZodAny">
|
||||||
import type { ZodAny } from 'zod'
|
import type { ZodAny } from 'zod'
|
||||||
|
import type { Config, ConfigItem } from './interface'
|
||||||
import type { Config } from './interface'
|
|
||||||
import { DEFAULT_ZOD_HANDLERS, INPUT_COMPONENTS } from './constant'
|
import { DEFAULT_ZOD_HANDLERS, INPUT_COMPONENTS } from './constant'
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|
@ -12,16 +11,21 @@ defineProps<{
|
||||||
options?: any[]
|
options?: any[]
|
||||||
schema?: ZodAny
|
schema?: ZodAny
|
||||||
}
|
}
|
||||||
config?: Config
|
config?: ConfigItem | Config<U>
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
function isValidConfig(config: any): config is ConfigItem {
|
||||||
|
return !!config?.component
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<component
|
<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"
|
:name="name"
|
||||||
:required="shape.required"
|
:required="shape.required"
|
||||||
:options="shape.options"
|
:options="shape.options"
|
||||||
|
:schema="shape.schema"
|
||||||
:config="config"
|
:config="config"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import FieldEnum from './fields/Enum.vue'
|
||||||
import FieldFile from './fields/File.vue'
|
import FieldFile from './fields/File.vue'
|
||||||
import FieldInput from './fields/Input.vue'
|
import FieldInput from './fields/Input.vue'
|
||||||
import FieldNumber from './fields/Number.vue'
|
import FieldNumber from './fields/Number.vue'
|
||||||
|
import FieldObject from './fields/Object.vue'
|
||||||
|
|
||||||
export const INPUT_COMPONENTS = {
|
export const INPUT_COMPONENTS = {
|
||||||
date: FieldDate,
|
date: FieldDate,
|
||||||
|
|
@ -15,6 +16,7 @@ export const INPUT_COMPONENTS = {
|
||||||
number: FieldNumber,
|
number: FieldNumber,
|
||||||
string: FieldInput,
|
string: FieldInput,
|
||||||
file: FieldFile,
|
file: FieldFile,
|
||||||
|
object: FieldObject,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -30,4 +32,5 @@ export const DEFAULT_ZOD_HANDLERS: {
|
||||||
ZodEnum: 'select',
|
ZodEnum: 'select',
|
||||||
ZodNativeEnum: 'select',
|
ZodNativeEnum: 'select',
|
||||||
ZodNumber: 'number',
|
ZodNumber: 'number',
|
||||||
|
ZodObject: 'object',
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { beautifyObjectName } from '../utils'
|
import { beautifyObjectName } from '../utils'
|
||||||
import type { Config } from '../interface'
|
import type { ConfigItem } from '../interface'
|
||||||
import AutoFormLabel from '../AutoFormLabel.vue'
|
import AutoFormLabel from '../AutoFormLabel.vue'
|
||||||
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/new-york/ui/form'
|
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/new-york/ui/form'
|
||||||
import { Switch } from '@/lib/registry/new-york/ui/switch'
|
import { Switch } from '@/lib/registry/new-york/ui/switch'
|
||||||
|
|
@ -9,7 +9,7 @@ import { Checkbox } from '@/lib/registry/new-york/ui/checkbox'
|
||||||
defineProps<{
|
defineProps<{
|
||||||
name: string
|
name: string
|
||||||
required?: boolean
|
required?: boolean
|
||||||
config?: Config
|
config?: ConfigItem
|
||||||
}>()
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
import { DateFormatter, getLocalTimeZone } from '@internationalized/date'
|
import { DateFormatter, getLocalTimeZone } from '@internationalized/date'
|
||||||
import { CalendarIcon } from '@radix-icons/vue'
|
import { CalendarIcon } from '@radix-icons/vue'
|
||||||
import { beautifyObjectName } from '../utils'
|
import { beautifyObjectName } from '../utils'
|
||||||
import type { Config } from '../interface'
|
|
||||||
import AutoFormLabel from '../AutoFormLabel.vue'
|
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 { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/new-york/ui/form'
|
||||||
|
|
||||||
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
||||||
|
|
@ -14,7 +14,7 @@ import { cn } from '@/lib/utils'
|
||||||
defineProps<{
|
defineProps<{
|
||||||
name: string
|
name: string
|
||||||
required?: boolean
|
required?: boolean
|
||||||
config?: Config
|
config?: ConfigItem
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const df = new DateFormatter('en-US', {
|
const df = new DateFormatter('en-US', {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { beautifyObjectName } from '../utils'
|
import { beautifyObjectName } from '../utils'
|
||||||
import type { Config } from '../interface'
|
import type { ConfigItem } from '../interface'
|
||||||
import AutoFormLabel from '../AutoFormLabel.vue'
|
import AutoFormLabel from '../AutoFormLabel.vue'
|
||||||
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/new-york/ui/form'
|
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'
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/lib/registry/new-york/ui/select'
|
||||||
|
|
@ -12,7 +12,7 @@ const props = defineProps<{
|
||||||
name: string
|
name: string
|
||||||
required?: boolean
|
required?: boolean
|
||||||
options?: string[]
|
options?: string[]
|
||||||
config?: Config
|
config?: ConfigItem
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const computedOptions = computed(() => props.config?.enumProps?.options || props.options)
|
const computedOptions = computed(() => props.config?.enumProps?.options || props.options)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { beautifyObjectName } from '../utils'
|
import { beautifyObjectName } from '../utils'
|
||||||
import type { Config } from '../interface'
|
import type { ConfigItem } from '../interface'
|
||||||
import AutoFormLabel from '../AutoFormLabel.vue'
|
import AutoFormLabel from '../AutoFormLabel.vue'
|
||||||
import { FormControl, FormDescription, FormField, FormItem, 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 { Input } from '@/lib/registry/new-york/ui/input'
|
||||||
|
|
@ -8,7 +8,7 @@ import { Input } from '@/lib/registry/new-york/ui/input'
|
||||||
defineProps<{
|
defineProps<{
|
||||||
name: string
|
name: string
|
||||||
required?: boolean
|
required?: boolean
|
||||||
config?: Config
|
config?: ConfigItem
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
async function parseFileAsString(file: File | undefined): Promise<string> {
|
async function parseFileAsString(file: File | undefined): Promise<string> {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { beautifyObjectName } from '../utils'
|
import { beautifyObjectName } from '../utils'
|
||||||
import type { Config } from '../interface'
|
import type { Config, ConfigItem } from '../interface'
|
||||||
import AutoFormLabel from '../AutoFormLabel.vue'
|
import AutoFormLabel from '../AutoFormLabel.vue'
|
||||||
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/new-york/ui/form'
|
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/new-york/ui/form'
|
||||||
import { Input } from '@/lib/registry/new-york/ui/input'
|
import { Input } from '@/lib/registry/new-york/ui/input'
|
||||||
|
|
@ -9,7 +9,7 @@ import { Textarea } from '@/lib/registry/new-york/ui/textarea'
|
||||||
defineProps<{
|
defineProps<{
|
||||||
name: string
|
name: string
|
||||||
required?: boolean
|
required?: boolean
|
||||||
config?: Config
|
config?: ConfigItem
|
||||||
}>()
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { beautifyObjectName } from '../utils'
|
import { beautifyObjectName } from '../utils'
|
||||||
import type { Config } from '../interface'
|
import type { Config, ConfigItem } from '../interface'
|
||||||
import AutoFormLabel from '../AutoFormLabel.vue'
|
import AutoFormLabel from '../AutoFormLabel.vue'
|
||||||
import { FormControl, FormDescription, FormField, FormItem, 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 { Input } from '@/lib/registry/new-york/ui/input'
|
||||||
|
|
@ -12,7 +12,7 @@ defineOptions({
|
||||||
defineProps<{
|
defineProps<{
|
||||||
name: string
|
name: string
|
||||||
required?: boolean
|
required?: boolean
|
||||||
config?: Config
|
config?: ConfigItem
|
||||||
}>()
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import type { InputHTMLAttributes, SelectHTMLAttributes } from 'vue'
|
import type { InputHTMLAttributes, SelectHTMLAttributes } from 'vue'
|
||||||
import type * as z from 'zod'
|
import type { z } from 'zod'
|
||||||
import type { INPUT_COMPONENTS } from './constant'
|
import type { INPUT_COMPONENTS } from './constant'
|
||||||
|
|
||||||
export interface Config {
|
export interface ConfigItem {
|
||||||
label?: string
|
label?: string
|
||||||
description?: string
|
description?: string
|
||||||
component?: keyof typeof INPUT_COMPONENTS
|
component?: keyof typeof INPUT_COMPONENTS
|
||||||
|
|
@ -10,11 +10,11 @@ export interface Config {
|
||||||
enumProps?: SelectHTMLAttributes & { options?: any[] }
|
enumProps?: SelectHTMLAttributes & { options?: any[] }
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FieldConfig<SchemaType extends z.infer<z.ZodObject<any, any>>> = {
|
export type Config<SchemaType extends object> = {
|
||||||
// If SchemaType.key is an object, create a nested FieldConfig, otherwise FieldConfigItem
|
// If SchemaType.key is an object, create a nested Config, otherwise ConfigItem
|
||||||
[Key in keyof SchemaType]?: SchemaType[Key] extends object
|
[Key in keyof SchemaType]?: SchemaType[Key] extends object
|
||||||
? FieldConfig<z.infer<SchemaType[Key]>>
|
? Config<SchemaType[Key]>
|
||||||
: FieldConfig<z.infer<SchemaType[Key]>> // TODO;
|
: ConfigItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum DependencyType {
|
export enum DependencyType {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user