feat: dependencies rendering
This commit is contained in:
parent
c986fed5ef
commit
0cd3371e4a
|
|
@ -3,25 +3,35 @@ import * as z from 'zod'
|
||||||
import { h, reactive, ref } from 'vue'
|
import { h, reactive, ref } 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 { DependencyType } from '../ui/auto-form/interface'
|
||||||
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 type { Config } from '@/lib/registry/new-york/ui/auto-form'
|
import type { Config } from '@/lib/registry/new-york/ui/auto-form'
|
||||||
import { AutoForm as AutoFormComponent, AutoFormField } from '@/lib/registry/new-york/ui/auto-form'
|
import { AutoForm, AutoFormField } from '@/lib/registry/new-york/ui/auto-form'
|
||||||
|
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
guestListName: z.string(),
|
age: z.number().default(20),
|
||||||
|
parentsAllowed: z.boolean().optional(),
|
||||||
|
vegetarian: z.boolean().default(true),
|
||||||
|
mealOptions: z.enum(['Pasta', 'Salad', 'Beef Wellington']).optional(),
|
||||||
invitedGuests: z
|
invitedGuests: z
|
||||||
.array(
|
.array(
|
||||||
z.object({
|
z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
age: z.coerce.number(),
|
age: z.coerce.number(),
|
||||||
}),
|
}),
|
||||||
).default([
|
).default([{ name: '123', age: 0 }]),
|
||||||
{ name: '123', age: 30 },
|
|
||||||
{ name: '456', age: 30 },
|
|
||||||
]).describe('How many guests'),
|
|
||||||
|
|
||||||
list: z.array(z.string()).describe('test the config').min(1, 'Please add some item').default([]),
|
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'),
|
||||||
|
})
|
||||||
|
.describe('Sub Sub Object Description'),
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
function onSubmit(values: Record<string, any>) {
|
function onSubmit(values: Record<string, any>) {
|
||||||
|
|
@ -30,42 +40,64 @@ function onSubmit(values: Record<string, any>) {
|
||||||
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 form = useForm({
|
|
||||||
keepValuesOnUnmount: true, // make sure the array/object field doesn't destroy the linked field
|
|
||||||
validationSchema: toTypedSchema(schema),
|
|
||||||
})
|
|
||||||
|
|
||||||
form.setValues({
|
|
||||||
guestListName: 'testing 123',
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AutoFormComponent
|
<AutoForm
|
||||||
class="w-2/3 space-y-6"
|
class="w-2/3 space-y-6"
|
||||||
:form="form"
|
|
||||||
:schema="schema"
|
:schema="schema"
|
||||||
:field-config="{
|
:field-config="{
|
||||||
guestListName: {
|
age: {
|
||||||
label: 'Lisst',
|
description:
|
||||||
inputProps: {
|
'Setting this below 18 will require parents consent.',
|
||||||
placeholder: 'testign123',
|
|
||||||
},
|
},
|
||||||
|
parentsAllowed: {
|
||||||
|
label: 'Did your parents allow you to register?',
|
||||||
},
|
},
|
||||||
invitedGuests: {
|
vegetarian: {
|
||||||
name: {
|
label: 'Are you a vegetarian?',
|
||||||
label: 'walaaaaao',
|
description:
|
||||||
|
'Setting this to true will remove non-vegetarian food options.',
|
||||||
},
|
},
|
||||||
},
|
mealOptions: {
|
||||||
list: {
|
component: 'radio',
|
||||||
label: 'woohooo',
|
|
||||||
},
|
},
|
||||||
}"
|
}"
|
||||||
|
: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"
|
@submit="onSubmit"
|
||||||
>
|
>
|
||||||
<Button type="submit">
|
<Button type="submit">
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
</AutoFormComponent>
|
</AutoForm>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,36 @@
|
||||||
<script setup lang="ts" generic="U extends ZodRawShape, T extends ZodObject<U>">
|
<script setup lang="ts" generic="U extends ZodRawShape, T extends ZodObject<U>">
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref, toRef, toRefs } from 'vue'
|
||||||
import type { ZodAny, ZodObject, ZodRawShape, z } from 'zod'
|
import type { ZodAny, ZodObject, ZodRawShape, z } from 'zod'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import type { FormContext, GenericObject } from 'vee-validate'
|
import type { FormContext, GenericObject } from 'vee-validate'
|
||||||
import { getBaseType, getDefaultValueInZodStack } from './utils'
|
import { getBaseSchema, getBaseType, getDefaultValueInZodStack } from './utils'
|
||||||
import type { Config, ConfigItem, Shape } from './interface'
|
import type { Config, ConfigItem, Dependency, Shape } from './interface'
|
||||||
import AutoFormField from './AutoFormField.vue'
|
import AutoFormField from './AutoFormField.vue'
|
||||||
|
import { provideDependencies } from './dependencies'
|
||||||
import { Form } from '@/lib/registry/new-york/ui/form'
|
import { Form } from '@/lib/registry/new-york/ui/form'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
schema: T
|
schema: T
|
||||||
form?: FormContext<GenericObject>
|
form?: FormContext<GenericObject>
|
||||||
fieldConfig?: Config<z.infer<T>>
|
fieldConfig?: Config<z.infer<T>>
|
||||||
|
dependencies?: Dependency<z.infer<T>>[]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emits = defineEmits<{
|
const emits = defineEmits<{
|
||||||
submit: [event: GenericObject]
|
submit: [event: GenericObject]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const { dependencies } = toRefs(props)
|
||||||
|
provideDependencies(dependencies)
|
||||||
|
|
||||||
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 } = {}
|
||||||
const shape = props.schema.shape
|
const shape = props.schema.shape
|
||||||
Object.keys(shape).forEach((name) => {
|
Object.keys(shape).forEach((name) => {
|
||||||
const item = shape[name] as ZodAny
|
const item = shape[name] as ZodAny
|
||||||
let options = 'values' in item._def ? item._def.values as string[] : undefined
|
const baseItem = getBaseSchema(item) as ZodAny
|
||||||
|
let options = (baseItem && 'values' in baseItem._def) ? baseItem._def.values as string[] : undefined
|
||||||
if (!Array.isArray(options) && typeof options === 'object')
|
if (!Array.isArray(options) && typeof options === 'object')
|
||||||
options = Object.values(options)
|
options = Object.values(options)
|
||||||
|
|
||||||
|
|
@ -33,7 +39,7 @@ const shapes = computed(() => {
|
||||||
default: getDefaultValueInZodStack(item),
|
default: getDefaultValueInZodStack(item),
|
||||||
options,
|
options,
|
||||||
required: !['ZodOptional', 'ZodNullable'].includes(item._def.typeName),
|
required: !['ZodOptional', 'ZodNullable'].includes(item._def.typeName),
|
||||||
schema: item,
|
schema: baseItem,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return val
|
return val
|
||||||
|
|
@ -41,13 +47,13 @@ const shapes = computed(() => {
|
||||||
|
|
||||||
const formComponent = computed(() => props.form ? 'form' : Form)
|
const formComponent = computed(() => props.form ? 'form' : Form)
|
||||||
const formComponentProps = computed(() => {
|
const formComponentProps = computed(() => {
|
||||||
const formSchema = toTypedSchema(props.schema)
|
|
||||||
if (props.form) {
|
if (props.form) {
|
||||||
return {
|
return {
|
||||||
onSubmit: props.form.handleSubmit(val => emits('submit', val)),
|
onSubmit: props.form.handleSubmit(val => emits('submit', val)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
const formSchema = toTypedSchema(props.schema)
|
||||||
return {
|
return {
|
||||||
keepValues: true,
|
keepValues: true,
|
||||||
validationSchema: formSchema,
|
validationSchema: formSchema,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import type { ZodAny } from 'zod'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import type { Config, ConfigItem, Shape } from './interface'
|
import type { Config, ConfigItem, Shape } from './interface'
|
||||||
import { DEFAULT_ZOD_HANDLERS, INPUT_COMPONENTS } from './constant'
|
import { DEFAULT_ZOD_HANDLERS, INPUT_COMPONENTS } from './constant'
|
||||||
|
import useDependencies from './dependencies'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
name: string
|
name: string
|
||||||
|
|
@ -20,15 +21,19 @@ const delegatedProps = computed(() => {
|
||||||
return { schema: props.shape?.schema }
|
return { schema: props.shape?.schema }
|
||||||
return undefined
|
return undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { isDisabled, isHidden, isRequired, overrideOptions } = useDependencies(props.name)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<component
|
<component
|
||||||
:is="isValidConfig(config) ? INPUT_COMPONENTS[config.component!] : INPUT_COMPONENTS[DEFAULT_ZOD_HANDLERS[shape.type]] "
|
:is="isValidConfig(config) ? INPUT_COMPONENTS[config.component!] : INPUT_COMPONENTS[DEFAULT_ZOD_HANDLERS[shape.type]] "
|
||||||
|
v-if="!isHidden"
|
||||||
:name="name"
|
:name="name"
|
||||||
:label="label"
|
:label="label"
|
||||||
:required="shape.required"
|
:required="isRequired || shape.required"
|
||||||
:options="shape.options"
|
:options="overrideOptions || shape.options"
|
||||||
|
:disabled="isDisabled"
|
||||||
:config="config"
|
:config="config"
|
||||||
v-bind="delegatedProps"
|
v-bind="delegatedProps"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
110
apps/www/src/lib/registry/new-york/ui/auto-form/dependencies.ts
Normal file
110
apps/www/src/lib/registry/new-york/ui/auto-form/dependencies.ts
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
import type * as z from 'zod'
|
||||||
|
import type { Ref } from 'vue'
|
||||||
|
import { computed, inject, ref, watch } from 'vue'
|
||||||
|
import { FieldContextKey, FormContextKey } from 'vee-validate'
|
||||||
|
import { createContext } from 'radix-vue'
|
||||||
|
import { type Dependency, DependencyType, type EnumValues } from './interface'
|
||||||
|
import { getIndexIfArray } from './utils'
|
||||||
|
|
||||||
|
export const [injectDependencies, provideDependencies] = createContext<Ref<Dependency<z.infer<z.ZodObject<any>>>[] | undefined>>('AutoFormDependencies')
|
||||||
|
|
||||||
|
function getValueByPath<T extends Record<string, any>>(obj: T, path: string): any {
|
||||||
|
const keys = path.split('.')
|
||||||
|
let value = obj
|
||||||
|
|
||||||
|
for (const key of keys) {
|
||||||
|
if (value && typeof value === 'object' && key in value)
|
||||||
|
value = value[key]
|
||||||
|
else
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useDependencies(
|
||||||
|
fieldName: string,
|
||||||
|
) {
|
||||||
|
const form = inject(FormContextKey)
|
||||||
|
const field = inject(FieldContextKey)
|
||||||
|
|
||||||
|
const currentFieldName = fieldName.replace(/\[\d+\]/g, '')
|
||||||
|
|
||||||
|
if (!form)
|
||||||
|
throw new Error('useDependencies should be used within <AutoForm>')
|
||||||
|
|
||||||
|
const { controlledValues } = form
|
||||||
|
const dependencies = injectDependencies()
|
||||||
|
const isDisabled = ref(false)
|
||||||
|
const isHidden = ref(false)
|
||||||
|
const isRequired = ref(false)
|
||||||
|
const overrideOptions = ref<EnumValues | undefined>()
|
||||||
|
|
||||||
|
const currentFieldValue = computed(() => field?.value.value)
|
||||||
|
const currentFieldDependencies = computed(() => dependencies.value?.filter(
|
||||||
|
dependency => dependency.targetField === currentFieldName,
|
||||||
|
))
|
||||||
|
|
||||||
|
function getSourceValue(dep: Dependency<any>) {
|
||||||
|
const source = dep.sourceField as string
|
||||||
|
const lastKey = source.split('.').at(-1)
|
||||||
|
if (source.includes('.') && lastKey) {
|
||||||
|
if (Array.isArray(field?.value.value)) {
|
||||||
|
const index = getIndexIfArray(fieldName) ?? -1
|
||||||
|
return field?.value.value[index][lastKey]
|
||||||
|
}
|
||||||
|
|
||||||
|
return getValueByPath(form!.values, source)
|
||||||
|
}
|
||||||
|
|
||||||
|
return controlledValues.value[source as string]
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceFieldValues = computed(() => currentFieldDependencies.value?.map(dep => getSourceValue(dep)))
|
||||||
|
|
||||||
|
const resetConditionState = () => {
|
||||||
|
isDisabled.value = false
|
||||||
|
isHidden.value = false
|
||||||
|
isRequired.value = false
|
||||||
|
overrideOptions.value = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
watch([sourceFieldValues, dependencies], () => {
|
||||||
|
resetConditionState()
|
||||||
|
currentFieldDependencies.value?.forEach((dep) => {
|
||||||
|
const sourceValue = getSourceValue(dep)
|
||||||
|
|
||||||
|
const conditionMet = dep.when(sourceValue, currentFieldValue.value)
|
||||||
|
|
||||||
|
switch (dep.type) {
|
||||||
|
case DependencyType.DISABLES:
|
||||||
|
if (conditionMet)
|
||||||
|
isDisabled.value = true
|
||||||
|
|
||||||
|
break
|
||||||
|
case DependencyType.REQUIRES:
|
||||||
|
if (conditionMet)
|
||||||
|
isRequired.value = true
|
||||||
|
|
||||||
|
break
|
||||||
|
case DependencyType.HIDES:
|
||||||
|
if (conditionMet)
|
||||||
|
isHidden.value = true
|
||||||
|
|
||||||
|
break
|
||||||
|
case DependencyType.SETS_OPTIONS:
|
||||||
|
if (conditionMet)
|
||||||
|
overrideOptions.value = dep.options
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, { immediate: true, deep: true })
|
||||||
|
|
||||||
|
return {
|
||||||
|
isDisabled,
|
||||||
|
isHidden,
|
||||||
|
isRequired,
|
||||||
|
overrideOptions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,6 +17,7 @@ const props = defineProps<{
|
||||||
required?: boolean
|
required?: boolean
|
||||||
config?: Config<T>
|
config?: Config<T>
|
||||||
schema?: z.ZodArray<T>
|
schema?: z.ZodArray<T>
|
||||||
|
disabled?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const fieldContext = useField(props.name)
|
const fieldContext = useField(props.name)
|
||||||
|
|
@ -57,7 +58,7 @@ provide(FieldContextKey, fieldContext)
|
||||||
<template>
|
<template>
|
||||||
<FieldArray v-slot="{ fields, remove, push }" as="section" :name="name">
|
<FieldArray v-slot="{ fields, remove, push }" as="section" :name="name">
|
||||||
<slot v-bind="props">
|
<slot v-bind="props">
|
||||||
<Accordion type="multiple" class="w-full" collapsible>
|
<Accordion type="multiple" class="w-full" collapsible :disabled="disabled">
|
||||||
<AccordionItem :value="name" class="border-none">
|
<AccordionItem :value="name" class="border-none">
|
||||||
<AccordionTrigger>
|
<AccordionTrigger>
|
||||||
<AutoFormLabel class="text-base" :required="required">
|
<AutoFormLabel class="text-base" :required="required">
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import { beautifyObjectName } from '../utils'
|
import { beautifyObjectName } from '../utils'
|
||||||
import type { ConfigItem, FieldProps } from '../interface'
|
import type { ConfigItem, FieldProps } from '../interface'
|
||||||
import AutoFormLabel from '../AutoFormLabel.vue'
|
import AutoFormLabel from '../AutoFormLabel.vue'
|
||||||
|
|
@ -6,7 +7,9 @@ import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessa
|
||||||
import { Switch } from '@/lib/registry/new-york/ui/switch'
|
import { Switch } from '@/lib/registry/new-york/ui/switch'
|
||||||
import { Checkbox } from '@/lib/registry/new-york/ui/checkbox'
|
import { Checkbox } from '@/lib/registry/new-york/ui/checkbox'
|
||||||
|
|
||||||
defineProps<FieldProps>()
|
const props = defineProps<FieldProps>()
|
||||||
|
|
||||||
|
const booleanComponent = computed(() => props.config?.component === 'switch' ? Switch : Checkbox)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -15,8 +18,13 @@ defineProps<FieldProps>()
|
||||||
<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>
|
||||||
<slot v-bind="slotProps">
|
<slot v-bind="slotProps">
|
||||||
<Switch v-if="config?.component === 'switch'" v-bind="{ ...slotProps.componentField }" />
|
<component
|
||||||
<Checkbox v-else v-bind="{ ...slotProps.componentField }" />
|
:is="booleanComponent"
|
||||||
|
v-bind="{ ...slotProps.componentField }"
|
||||||
|
:disabled="disabled"
|
||||||
|
:checked="slotProps.componentField.modelValue"
|
||||||
|
@update:checked="slotProps.componentField['onUpdate:modelValue']"
|
||||||
|
/>
|
||||||
</slot>
|
</slot>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<AutoFormLabel v-if="!config?.hideLabel" :required="required">
|
<AutoFormLabel v-if="!config?.hideLabel" :required="required">
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ const df = new DateFormatter('en-US', {
|
||||||
<slot v-bind="slotProps">
|
<slot v-bind="slotProps">
|
||||||
<div>
|
<div>
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger as-child>
|
<PopoverTrigger as-child :disabled="disabled">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
:class="cn(
|
:class="cn(
|
||||||
|
|
|
||||||
|
|
@ -23,14 +23,14 @@ const computedOptions = computed(() => props.config?.enumProps?.options || props
|
||||||
</AutoFormLabel>
|
</AutoFormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<slot v-bind="slotProps">
|
<slot v-bind="slotProps">
|
||||||
<RadioGroup v-if="config?.component === 'radio'" :orientation="'vertical'" v-bind="{ ...slotProps.componentField }">
|
<RadioGroup v-if="config?.component === 'radio'" :disabled="disabled" :orientation="'vertical'" v-bind="{ ...slotProps.componentField }">
|
||||||
<div v-for="(option, index) in computedOptions" :key="option" class="mb-2 flex items-center gap-3 space-y-0">
|
<div v-for="(option, index) in computedOptions" :key="option" class="mb-2 flex items-center gap-3 space-y-0">
|
||||||
<RadioGroupItem :id="`${option}-${index}`" :value="option" />
|
<RadioGroupItem :id="`${option}-${index}`" :value="option" />
|
||||||
<Label :for="`${option}-${index}`">{{ beautifyObjectName(option) }}</Label>
|
<Label :for="`${option}-${index}`">{{ beautifyObjectName(option) }}</Label>
|
||||||
</div>
|
</div>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
|
||||||
<Select v-else v-bind="{ ...slotProps.componentField }">
|
<Select v-else :disabled="disabled" v-bind="{ ...slotProps.componentField }">
|
||||||
<SelectTrigger class="w-full">
|
<SelectTrigger class="w-full">
|
||||||
<SelectValue :placeholder="config?.enumProps?.placeholder" />
|
<SelectValue :placeholder="config?.enumProps?.placeholder" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ async function parseFileAsString(file: File | undefined): Promise<string> {
|
||||||
<Input
|
<Input
|
||||||
type="file"
|
type="file"
|
||||||
v-bind="{ ...config?.inputProps }"
|
v-bind="{ ...config?.inputProps }"
|
||||||
|
:disabled="disabled"
|
||||||
@change="async (ev: InputEvent) => {
|
@change="async (ev: InputEvent) => {
|
||||||
const file = (ev.target as HTMLInputElement).files?.[0]
|
const file = (ev.target as HTMLInputElement).files?.[0]
|
||||||
const parsed = await parseFileAsString(file)
|
const parsed = await parseFileAsString(file)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import { beautifyObjectName } from '../utils'
|
import { beautifyObjectName } from '../utils'
|
||||||
import type { Config, ConfigItem, FieldProps } from '../interface'
|
import type { Config, ConfigItem, FieldProps } from '../interface'
|
||||||
import AutoFormLabel from '../AutoFormLabel.vue'
|
import AutoFormLabel from '../AutoFormLabel.vue'
|
||||||
|
|
@ -6,7 +7,8 @@ import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessa
|
||||||
import { Input } from '@/lib/registry/new-york/ui/input'
|
import { Input } from '@/lib/registry/new-york/ui/input'
|
||||||
import { Textarea } from '@/lib/registry/new-york/ui/textarea'
|
import { Textarea } from '@/lib/registry/new-york/ui/textarea'
|
||||||
|
|
||||||
defineProps<FieldProps>()
|
const props = defineProps<FieldProps>()
|
||||||
|
const inputComponent = computed(() => props.config?.component === 'textarea' ? Textarea : Input)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -17,8 +19,12 @@ defineProps<FieldProps>()
|
||||||
</AutoFormLabel>
|
</AutoFormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<slot v-bind="slotProps">
|
<slot v-bind="slotProps">
|
||||||
<Textarea v-if="config?.component === 'textarea'" type="text" v-bind="{ ...slotProps.componentField, ...config?.inputProps }" />
|
<component
|
||||||
<Input v-else type="text" v-bind="{ ...slotProps.componentField, ...config?.inputProps }" />
|
:is="inputComponent"
|
||||||
|
type="text"
|
||||||
|
v-bind="{ ...slotProps.componentField, ...config?.inputProps }"
|
||||||
|
:disabled="disabled"
|
||||||
|
/>
|
||||||
</slot>
|
</slot>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription v-if="config?.description">
|
<FormDescription v-if="config?.description">
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ defineProps<FieldProps>()
|
||||||
</AutoFormLabel>
|
</AutoFormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<slot v-bind="slotProps">
|
<slot v-bind="slotProps">
|
||||||
<Input type="number" v-bind="{ ...slotProps.componentField, ...config?.inputProps }" />
|
<Input type="number" v-bind="{ ...slotProps.componentField, ...config?.inputProps }" :disabled="disabled" />
|
||||||
</slot>
|
</slot>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription v-if="config?.description">
|
<FormDescription v-if="config?.description">
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ const props = defineProps<{
|
||||||
required?: boolean
|
required?: boolean
|
||||||
config?: Config<T>
|
config?: Config<T>
|
||||||
schema?: ZodObject<T>
|
schema?: ZodObject<T>
|
||||||
|
disabled?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const shapes = computed(() => {
|
const shapes = computed(() => {
|
||||||
|
|
@ -43,7 +44,7 @@ const shapes = computed(() => {
|
||||||
<template>
|
<template>
|
||||||
<section>
|
<section>
|
||||||
<slot v-bind="props">
|
<slot v-bind="props">
|
||||||
<Accordion type="multiple" class="w-full" collapsible>
|
<Accordion type="multiple" class="w-full" collapsible :disabled="disabled">
|
||||||
<AccordionItem :value="name" class="border-none">
|
<AccordionItem :value="name" class="border-none">
|
||||||
<AccordionTrigger class="text-base">
|
<AccordionTrigger class="text-base">
|
||||||
{{ schema?.description || beautifyObjectName(name) }}
|
{{ schema?.description || beautifyObjectName(name) }}
|
||||||
|
|
@ -52,7 +53,7 @@ const shapes = computed(() => {
|
||||||
<template v-for="(shape, key) in shapes" :key="key">
|
<template v-for="(shape, key) in shapes" :key="key">
|
||||||
<AutoFormField
|
<AutoFormField
|
||||||
:config="config?.[key as keyof typeof config] as ConfigItem"
|
:config="config?.[key as keyof typeof config] as ConfigItem"
|
||||||
:name="`${name}${key.toString()}`"
|
:name="`${name}.${key.toString()}`"
|
||||||
:label="key.toString()"
|
:label="key.toString()"
|
||||||
:shape="shape"
|
:shape="shape"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ export interface FieldProps {
|
||||||
label?: string
|
label?: string
|
||||||
required?: boolean
|
required?: boolean
|
||||||
config?: ConfigItem
|
config?: ConfigItem
|
||||||
|
disabled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Shape {
|
export interface Shape {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,20 @@ export function beautifyObjectName(string: string) {
|
||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse string and extract the index
|
||||||
|
* @param string
|
||||||
|
* @returns index or undefined
|
||||||
|
*/
|
||||||
|
export function getIndexIfArray(string: string) {
|
||||||
|
const indexRegex = /\[(\d+)\]/
|
||||||
|
// Match the index
|
||||||
|
const match = string.match(indexRegex)
|
||||||
|
// Extract the index (number)
|
||||||
|
const index = match ? Number.parseInt(match[1]) : undefined
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the lowest level Zod type.
|
* Get the lowest level Zod type.
|
||||||
* This will unpack optionals, refinements, etc.
|
* This will unpack optionals, refinements, etc.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user