feat: export field component, expose more slotprops
This commit is contained in:
parent
318d4c8643
commit
7370383fbe
|
|
@ -1,13 +1,12 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h } from 'vue'
|
import * as z from 'zod'
|
||||||
|
import { h, reactive } from 'vue'
|
||||||
import { useForm } from 'vee-validate'
|
import { useForm } from 'vee-validate'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import * as z from 'zod'
|
|
||||||
|
|
||||||
import AutoFormField from '../ui/auto-form/AutoFormField.vue'
|
|
||||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
import { toast } from '@/lib/registry/new-york/ui/toast'
|
import { toast } from '@/lib/registry/new-york/ui/toast'
|
||||||
import { AutoForm, getBaseSchema, getBaseType } from '@/lib/registry/new-york/ui/auto-form'
|
import type { Config } from '@/lib/registry/new-york/ui/auto-form'
|
||||||
|
import { AutoForm, AutoFormField } from '@/lib/registry/new-york/ui/auto-form'
|
||||||
|
|
||||||
enum Sports {
|
enum Sports {
|
||||||
Football = 'Football/Soccer',
|
Football = 'Football/Soccer',
|
||||||
|
|
@ -115,59 +114,63 @@ const onSubmit = handleSubmit((values) => {
|
||||||
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const config
|
||||||
|
= reactive({
|
||||||
|
username: {
|
||||||
|
label: '123',
|
||||||
|
description: 'Test description',
|
||||||
|
inputProps: {
|
||||||
|
id: '123',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
inputProps: {
|
||||||
|
id: '345',
|
||||||
|
type: 'password',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
acceptTerms: {
|
||||||
|
description: 'this is custom',
|
||||||
|
},
|
||||||
|
sendMeMails: {
|
||||||
|
component: 'switch',
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
enumProps: {
|
||||||
|
options: ['red', 'green', 'blue'],
|
||||||
|
placeholder: 'Choose a color',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
marshmallows: {
|
||||||
|
component: 'radio',
|
||||||
|
},
|
||||||
|
bio: {
|
||||||
|
component: 'textarea',
|
||||||
|
},
|
||||||
|
file: {
|
||||||
|
component: 'file',
|
||||||
|
},
|
||||||
|
|
||||||
|
subObject: {
|
||||||
|
subField: {
|
||||||
|
label: 'custom labvel',
|
||||||
|
description: '123',
|
||||||
|
},
|
||||||
|
subSubObject: {
|
||||||
|
subSubField: {
|
||||||
|
label: 'sub suuuub',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}) as Config<z.infer<typeof schema>>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AutoForm
|
<AutoForm
|
||||||
class="w-2/3 space-y-6"
|
class="w-2/3 space-y-6"
|
||||||
:schema="schema"
|
:schema="schema"
|
||||||
:field-config="{
|
:field-config="config"
|
||||||
username: {
|
|
||||||
description: 'Test description',
|
|
||||||
inputProps: {
|
|
||||||
id: '123',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
password: {
|
|
||||||
inputProps: {
|
|
||||||
id: '345',
|
|
||||||
type: 'password',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
acceptTerms: {
|
|
||||||
description: 'this is custom',
|
|
||||||
},
|
|
||||||
sendMeMails: {
|
|
||||||
component: 'switch',
|
|
||||||
},
|
|
||||||
color: {
|
|
||||||
enumProps: {
|
|
||||||
options: ['red', 'green', 'blue'],
|
|
||||||
placeholder: 'Choose a color',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
marshmallows: {
|
|
||||||
component: 'radio',
|
|
||||||
},
|
|
||||||
bio: {
|
|
||||||
component: 'textarea',
|
|
||||||
},
|
|
||||||
file: {
|
|
||||||
component: 'file',
|
|
||||||
},
|
|
||||||
|
|
||||||
subObject: {
|
|
||||||
subField: {
|
|
||||||
label: 'custom labvel',
|
|
||||||
description: '123',
|
|
||||||
},
|
|
||||||
subSubObject: {
|
|
||||||
subSubField: {
|
|
||||||
label: 'sub suuuub',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}"
|
|
||||||
@submit="onSubmit"
|
@submit="onSubmit"
|
||||||
>
|
>
|
||||||
<template #username="componentField">
|
<template #username="componentField">
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,10 @@ const props = defineProps<{
|
||||||
fieldConfig?: Config<z.infer<T>>
|
fieldConfig?: Config<z.infer<T>>
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const emits = defineEmits<{
|
||||||
|
submit: [event: Event]
|
||||||
|
}>()
|
||||||
|
|
||||||
const shapes = computed(() => {
|
const shapes = computed(() => {
|
||||||
// @ts-expect-error ignore {} not assignable to object
|
// @ts-expect-error ignore {} not assignable to object
|
||||||
const val: { [key in keyof T]: Shape } = {}
|
const val: { [key in keyof T]: Shape } = {}
|
||||||
|
|
@ -33,11 +37,11 @@ const shapes = computed(() => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<form>
|
<form @submit="emits('submit', $event)">
|
||||||
<template v-for="(shape, key) of shapes" :key="key">
|
<template v-for="(shape, key) of shapes" :key="key">
|
||||||
<slot
|
<slot
|
||||||
:shape="shape"
|
:shape="shape"
|
||||||
:name="key.toString()"
|
:name="key.toString() as keyof z.infer<T>"
|
||||||
:config="fieldConfig?.[key as keyof typeof fieldConfig] as ConfigItem"
|
:config="fieldConfig?.[key as keyof typeof fieldConfig] as ConfigItem"
|
||||||
>
|
>
|
||||||
<AutoFormField
|
<AutoFormField
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,14 @@ defineProps<{
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<FormField v-slot="{ componentField }" :name="name">
|
<FormField v-slot="slotProps" :name="name">
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<div class="space-y-0 mb-3 flex items-center gap-3">
|
<div class="space-y-0 mb-3 flex items-center gap-3">
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Switch v-if="config?.component === 'switch'" v-bind="{ ...componentField }" />
|
<slot v-bind="slotProps">
|
||||||
<Checkbox v-else v-bind="{ ...componentField }" />
|
<Switch v-if="config?.component === 'switch'" v-bind="{ ...slotProps.componentField }" />
|
||||||
|
<Checkbox v-else v-bind="{ ...slotProps.componentField }" />
|
||||||
|
</slot>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<AutoFormLabel v-if="!config?.hideLabel" :required="required">
|
<AutoFormLabel v-if="!config?.hideLabel" :required="required">
|
||||||
{{ config?.label || beautifyObjectName(name) }}
|
{{ config?.label || beautifyObjectName(name) }}
|
||||||
|
|
|
||||||
|
|
@ -23,31 +23,33 @@ const df = new DateFormatter('en-US', {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<FormField v-slot="{ componentField }" :name="name">
|
<FormField v-slot="slotProps" :name="name">
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<AutoFormLabel v-if="!config?.hideLabel" :required="required">
|
<AutoFormLabel v-if="!config?.hideLabel" :required="required">
|
||||||
{{ config?.label || beautifyObjectName(name) }}
|
{{ config?.label || beautifyObjectName(name) }}
|
||||||
</AutoFormLabel>
|
</AutoFormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<div>
|
<slot v-bind="slotProps">
|
||||||
<Popover>
|
<div>
|
||||||
<PopoverTrigger as-child>
|
<Popover>
|
||||||
<Button
|
<PopoverTrigger as-child>
|
||||||
variant="outline"
|
<Button
|
||||||
:class="cn(
|
variant="outline"
|
||||||
'w-full justify-start text-left font-normal',
|
:class="cn(
|
||||||
!componentField.modelValue && 'text-muted-foreground',
|
'w-full justify-start text-left font-normal',
|
||||||
)"
|
!slotProps.componentField.modelValue && 'text-muted-foreground',
|
||||||
>
|
)"
|
||||||
<CalendarIcon class="mr-2 h-4 w-4" />
|
>
|
||||||
{{ componentField.modelValue ? df.format(componentField.modelValue.toDate(getLocalTimeZone())) : "Pick a date" }}
|
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||||
</Button>
|
{{ slotProps.componentField.modelValue ? df.format(slotProps.componentField.modelValue.toDate(getLocalTimeZone())) : "Pick a date" }}
|
||||||
</PopoverTrigger>
|
</Button>
|
||||||
<PopoverContent class="w-auto p-0">
|
</PopoverTrigger>
|
||||||
<Calendar initial-focus v-bind="{ ...componentField }" />
|
<PopoverContent class="w-auto p-0">
|
||||||
</PopoverContent>
|
<Calendar initial-focus v-bind="slotProps.componentField" />
|
||||||
</Popover>
|
</PopoverContent>
|
||||||
</div>
|
</Popover>
|
||||||
|
</div>
|
||||||
|
</slot>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormDescription v-if="config?.description">
|
<FormDescription v-if="config?.description">
|
||||||
|
|
|
||||||
|
|
@ -19,29 +19,31 @@ const computedOptions = computed(() => props.config?.enumProps?.options || props
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<FormField v-slot="{ componentField }" :name="name">
|
<FormField v-slot="slotProps" :name="name">
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<AutoFormLabel v-if="!config?.hideLabel" :required="required">
|
<AutoFormLabel v-if="!config?.hideLabel" :required="required">
|
||||||
{{ config?.label || beautifyObjectName(name) }}
|
{{ config?.label || beautifyObjectName(name) }}
|
||||||
</AutoFormLabel>
|
</AutoFormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<RadioGroup v-if="config?.component === 'radio'" :orientation="'vertical'" v-bind="{ ...componentField }">
|
<slot v-bind="slotProps">
|
||||||
<div v-for="(option, index) in computedOptions" :key="option" class="mb-2 flex items-center gap-3 space-y-0">
|
<RadioGroup v-if="config?.component === 'radio'" :orientation="'vertical'" v-bind="{ ...slotProps.componentField }">
|
||||||
<RadioGroupItem :id="`${option}-${index}`" :value="option" />
|
<div v-for="(option, index) in computedOptions" :key="option" class="mb-2 flex items-center gap-3 space-y-0">
|
||||||
<Label :for="`${option}-${index}`">{{ beautifyObjectName(option) }}</Label>
|
<RadioGroupItem :id="`${option}-${index}`" :value="option" />
|
||||||
</div>
|
<Label :for="`${option}-${index}`">{{ beautifyObjectName(option) }}</Label>
|
||||||
</RadioGroup>
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
|
|
||||||
<Select v-else v-bind="{ ...componentField }">
|
<Select v-else v-bind="{ ...slotProps.componentField }">
|
||||||
<SelectTrigger class="w-full">
|
<SelectTrigger class="w-full">
|
||||||
<SelectValue :placeholder="config?.enumProps?.placeholder" />
|
<SelectValue :placeholder="config?.enumProps?.placeholder" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem v-for="option in computedOptions" :key="option" :value="option">
|
<SelectItem v-for="option in computedOptions" :key="option" :value="option">
|
||||||
{{ beautifyObjectName(option) }}
|
{{ beautifyObjectName(option) }}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
</slot>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormDescription v-if="config?.description">
|
<FormDescription v-if="config?.description">
|
||||||
|
|
|
||||||
|
|
@ -28,21 +28,23 @@ async function parseFileAsString(file: File | undefined): Promise<string> {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<FormField v-slot="{ componentField }" :name="name">
|
<FormField v-slot="slotProps" :name="name">
|
||||||
<FormItem v-bind="$attrs">
|
<FormItem v-bind="$attrs">
|
||||||
<AutoFormLabel v-if="!config?.hideLabel" :required="required">
|
<AutoFormLabel v-if="!config?.hideLabel" :required="required">
|
||||||
{{ config?.label || beautifyObjectName(name) }}
|
{{ config?.label || beautifyObjectName(name) }}
|
||||||
</AutoFormLabel>
|
</AutoFormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<slot v-bind="slotProps">
|
||||||
type="file"
|
<Input
|
||||||
v-bind="{ ...config?.inputProps }"
|
type="file"
|
||||||
@change="async (ev: InputEvent) => {
|
v-bind="{ ...config?.inputProps }"
|
||||||
const file = (ev.target as HTMLInputElement).files?.[0]
|
@change="async (ev: InputEvent) => {
|
||||||
const parsed = await parseFileAsString(file)
|
const file = (ev.target as HTMLInputElement).files?.[0]
|
||||||
componentField.onInput(parsed)
|
const parsed = await parseFileAsString(file)
|
||||||
}"
|
slotProps.componentField.onInput(parsed)
|
||||||
/>
|
}"
|
||||||
|
/>
|
||||||
|
</slot>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription v-if="config?.description">
|
<FormDescription v-if="config?.description">
|
||||||
{{ config.description }}
|
{{ config.description }}
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,16 @@ defineProps<{
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<FormField v-slot="{ componentField }" :name="name">
|
<FormField v-slot="slotProps" :name="name">
|
||||||
<FormItem v-bind="$attrs">
|
<FormItem v-bind="$attrs">
|
||||||
<AutoFormLabel v-if="!config?.hideLabel" :required="required">
|
<AutoFormLabel v-if="!config?.hideLabel" :required="required">
|
||||||
{{ config?.label || beautifyObjectName(name) }}
|
{{ config?.label || beautifyObjectName(name) }}
|
||||||
</AutoFormLabel>
|
</AutoFormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Textarea v-if="config?.component === 'textarea'" type="text" v-bind="{ ...componentField, ...config?.inputProps }" />
|
<slot v-bind="slotProps">
|
||||||
<Input v-else type="text" v-bind="{ ...componentField, ...config?.inputProps }" />
|
<Textarea v-if="config?.component === 'textarea'" type="text" v-bind="{ ...slotProps.componentField, ...config?.inputProps }" />
|
||||||
|
<Input v-else type="text" v-bind="{ ...slotProps.componentField, ...config?.inputProps }" />
|
||||||
|
</slot>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription v-if="config?.description">
|
<FormDescription v-if="config?.description">
|
||||||
{{ config.description }}
|
{{ config.description }}
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,15 @@ defineProps<{
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<FormField v-slot="{ componentField }" :name="name">
|
<FormField v-slot="slotProps" :name="name">
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<AutoFormLabel v-if="!config?.hideLabel" :required="required">
|
<AutoFormLabel v-if="!config?.hideLabel" :required="required">
|
||||||
{{ config?.label || beautifyObjectName(name) }}
|
{{ config?.label || beautifyObjectName(name) }}
|
||||||
</AutoFormLabel>
|
</AutoFormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input type="number" v-bind="{ ...$attrs, ...componentField, ...config?.inputProps }" />
|
<slot v-bind="slotProps">
|
||||||
|
<Input type="number" v-bind="{ ...slotProps.componentField, ...config?.inputProps }" />
|
||||||
|
</slot>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription v-if="config?.description">
|
<FormDescription v-if="config?.description">
|
||||||
{{ config.description }}
|
{{ config.description }}
|
||||||
|
|
|
||||||
|
|
@ -49,20 +49,24 @@ const shapes = computed(() => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Accordion type="multiple" class="w-full" collapsible>
|
<section>
|
||||||
<AccordionItem :value="name" class="border-none">
|
<slot v-bind="props">
|
||||||
<AccordionTrigger class="text-base">
|
<Accordion type="multiple" class="w-full" collapsible>
|
||||||
{{ schema?.description || beautifyObjectName(name) }}
|
<AccordionItem :value="name" class="border-none">
|
||||||
</AccordionTrigger>
|
<AccordionTrigger class="text-base">
|
||||||
<AccordionContent class="p-2 space-y-5">
|
{{ schema?.description || beautifyObjectName(name) }}
|
||||||
<template v-for="(shape, key) in shapes" :key="key">
|
</AccordionTrigger>
|
||||||
<AutoFormField
|
<AccordionContent class="p-2 space-y-5">
|
||||||
:config="config?.[key as keyof typeof config] as ConfigItem"
|
<template v-for="(shape, key) in shapes" :key="key">
|
||||||
:name="key.toString()"
|
<AutoFormField
|
||||||
:shape="shape"
|
:config="config?.[key as keyof typeof config] as ConfigItem"
|
||||||
/>
|
:name="key.toString()"
|
||||||
</template>
|
:shape="shape"
|
||||||
</AccordionContent>
|
/>
|
||||||
</AccordionItem>
|
</template>
|
||||||
</Accordion>
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
</slot>
|
||||||
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
export { default as AutoFormFieldBoolean } from './Boolean.vue'
|
||||||
|
export { default as AutoFormFieldDate } from './Date.vue'
|
||||||
|
export { default as AutoFormFieldEnum } from './Enum.vue'
|
||||||
|
export { default as AutoFormFieldFile } from './File.vue'
|
||||||
|
export { default as AutoFormFieldInput } from './Input.vue'
|
||||||
|
export { default as AutoFormFieldNumber } from './Number.vue'
|
||||||
|
export { default as AutoFormFieldObject } from './Object.vue'
|
||||||
|
|
@ -1,2 +1,6 @@
|
||||||
export { default as AutoForm } from './AutoForm.vue'
|
export { default as AutoForm } from './AutoForm.vue'
|
||||||
|
export { default as AutoFormField } from './AutoFormField.vue'
|
||||||
export { getObjectFormSchema, getBaseSchema, getBaseType, getDefaultValues } from './utils'
|
export { getObjectFormSchema, getBaseSchema, getBaseType, getDefaultValues } from './utils'
|
||||||
|
export type { Config, ConfigItem } from './interface'
|
||||||
|
|
||||||
|
export * from './fields'
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user