Răsfoiți Sursa

refactor(dashboard): Enhance FormFieldWrapper with renderFormControl prop

David Höck 6 luni în urmă
părinte
comite
c38f96b4be

+ 2 - 9
packages/dashboard/src/lib/components/data-input/money-input.tsx

@@ -1,18 +1,11 @@
+import { DataInputComponentProps } from '@/vdb/framework/component-registry/component-registry.js';
 import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
 import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
 import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
 import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
 import { useEffect, useMemo, useState } from 'react';
 import { useEffect, useMemo, useState } from 'react';
 import { AffixedInput } from './affixed-input.js';
 import { AffixedInput } from './affixed-input.js';
 
 
 // Original component
 // Original component
-function MoneyInputInternal({
-    value,
-    currency,
-    onChange,
-}: {
-    value: number;
-    currency: string;
-    onChange: (value: number) => void;
-}) {
+function MoneyInputInternal({ value, currency, onChange }: DataInputComponentProps) {
     const {
     const {
         settings: { displayLanguage, displayLocale },
         settings: { displayLanguage, displayLocale },
     } = useUserSettings();
     } = useUserSettings();

+ 22 - 13
packages/dashboard/src/lib/components/shared/form-field-wrapper.tsx

@@ -1,25 +1,34 @@
-import {
-    FormControl,
-    FormDescription,
-    FormItem,
-    FormLabel,
-    FormMessage,
-    FormField,
-} from '../ui/form.js';
-import { FieldValues, FieldPath } from 'react-hook-form';
+import { FieldPath, FieldValues } from 'react-hook-form';
+import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '../ui/form.js';
 
 
 export type FormFieldWrapperProps<
 export type FormFieldWrapperProps<
     TFieldValues extends FieldValues = FieldValues,
     TFieldValues extends FieldValues = FieldValues,
-    TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
+    TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
 > = React.ComponentProps<typeof FormField<TFieldValues, TName>> & {
 > = React.ComponentProps<typeof FormField<TFieldValues, TName>> & {
     label?: React.ReactNode;
     label?: React.ReactNode;
     description?: React.ReactNode;
     description?: React.ReactNode;
+    /**
+     * @description
+     * Whether to render the form control.
+     * If false, the form control will not be rendered.
+     * This is useful when you want to render the form control in a custom way, e.g. for <Select/> components,
+     * where the FormControl needs to nested in the root component.
+     * @default true
+     */
+    renderFormControl?: boolean;
 };
 };
 
 
 export function FormFieldWrapper<
 export function FormFieldWrapper<
     TFieldValues extends FieldValues = FieldValues,
     TFieldValues extends FieldValues = FieldValues,
-    TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
->({ control, name, render, label, description }: FormFieldWrapperProps<TFieldValues, TName>) {
+    TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
+>({
+    control,
+    name,
+    render,
+    label,
+    description,
+    renderFormControl = true,
+}: FormFieldWrapperProps<TFieldValues, TName>) {
     return (
     return (
         <FormField
         <FormField
             control={control}
             control={control}
@@ -27,7 +36,7 @@ export function FormFieldWrapper<
             render={renderArgs => (
             render={renderArgs => (
                 <FormItem>
                 <FormItem>
                     {label && <FormLabel>{label}</FormLabel>}
                     {label && <FormLabel>{label}</FormLabel>}
-                    <FormControl>{render(renderArgs)}</FormControl>
+                    {renderFormControl ? <FormControl>{render(renderArgs)}</FormControl> : render(renderArgs)}
                     {description && <FormDescription>{description}</FormDescription>}
                     {description && <FormDescription>{description}</FormDescription>}
                     <FormMessage />
                     <FormMessage />
                 </FormItem>
                 </FormItem>

+ 5 - 3
packages/dashboard/src/lib/framework/component-registry/component-registry.tsx

@@ -1,4 +1,5 @@
 import * as React from 'react';
 import * as React from 'react';
+import { ControllerRenderProps, FieldPath, FieldValues } from 'react-hook-form';
 import { addDisplayComponent, getDisplayComponent } from '../extension-api/display-component-extensions.js';
 import { addDisplayComponent, getDisplayComponent } from '../extension-api/display-component-extensions.js';
 import { addInputComponent, getInputComponent } from '../extension-api/input-component-extensions.js';
 import { addInputComponent, getInputComponent } from '../extension-api/input-component-extensions.js';
 
 
@@ -13,9 +14,10 @@ export interface DataDisplayComponentProps {
     [key: string]: any;
     [key: string]: any;
 }
 }
 
 
-export interface DataInputComponentProps {
-    value: any;
-    onChange: (value: any) => void;
+export interface DataInputComponentProps<
+    TFieldValues extends FieldValues = FieldValues,
+    TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
+> extends ControllerRenderProps<TFieldValues, TName> {
     [key: string]: any;
     [key: string]: any;
 }
 }
 
 

+ 28 - 17
packages/dashboard/src/lib/framework/page/detail-page.tsx

@@ -17,6 +17,7 @@ import {
 } from '../document-introspection/get-document-structure.js';
 } from '../document-introspection/get-document-structure.js';
 
 
 import { TranslatableFormFieldWrapper } from '@/vdb/components/shared/translatable-form-field.js';
 import { TranslatableFormFieldWrapper } from '@/vdb/components/shared/translatable-form-field.js';
+import { FormControl } from '@/vdb/components/ui/form.js';
 import { ControllerRenderProps, FieldPath, FieldValues } from 'react-hook-form';
 import { ControllerRenderProps, FieldPath, FieldValues } from 'react-hook-form';
 import { useComponentRegistry } from '../component-registry/component-registry.js';
 import { useComponentRegistry } from '../component-registry/component-registry.js';
 import { generateInputComponentKey } from '../extension-api/input-component-extensions.js';
 import { generateInputComponentKey } from '../extension-api/input-component-extensions.js';
@@ -120,23 +121,31 @@ function FieldInputRenderer<
         return <InputComponent {...field} />;
         return <InputComponent {...field} />;
     }
     }
 
 
-    switch (fieldInfo.type) {
-        case 'Int':
-        case 'Float':
-            return (
-                <Input
-                    type="number"
-                    value={field.value}
-                    onChange={e => field.onChange(e.target.valueAsNumber)}
-                />
-            );
-        case 'DateTime':
-            return <DateTimeInput {...field} />;
-        case 'Boolean':
-            return <Checkbox value={field.value} onCheckedChange={field.onChange} />;
-        default:
-            return <Input {...field} />;
-    }
+    const DefaultComponent = () => {
+        switch (fieldInfo.type) {
+            case 'Int':
+            case 'Float':
+                return (
+                    <Input
+                        type="number"
+                        value={field.value}
+                        onChange={e => field.onChange(e.target.valueAsNumber)}
+                    />
+                );
+            case 'DateTime':
+                return <DateTimeInput {...field} />;
+            case 'Boolean':
+                return <Checkbox value={field.value} onCheckedChange={field.onChange} />;
+            default:
+                return <Input {...field} />;
+        }
+    };
+
+    return (
+        <FormControl>
+            <DefaultComponent />
+        </FormControl>
+    );
 }
 }
 
 
 /**
 /**
@@ -227,6 +236,7 @@ export function DetailPage<
                                         control={form.control}
                                         control={form.control}
                                         name={fieldInfo.name as never}
                                         name={fieldInfo.name as never}
                                         label={fieldInfo.name}
                                         label={fieldInfo.name}
+                                        renderFormControl={false}
                                         render={({ field }) => (
                                         render={({ field }) => (
                                             <FieldInputRenderer
                                             <FieldInputRenderer
                                                 fieldInfo={fieldInfo}
                                                 fieldInfo={fieldInfo}
@@ -249,6 +259,7 @@ export function DetailPage<
                                         control={form.control}
                                         control={form.control}
                                         name={fieldInfo.name as never}
                                         name={fieldInfo.name as never}
                                         label={fieldInfo.name}
                                         label={fieldInfo.name}
+                                        renderFormControl={false}
                                         render={({ field }) => (
                                         render={({ field }) => (
                                             <FieldInputRenderer
                                             <FieldInputRenderer
                                                 fieldInfo={fieldInfo}
                                                 fieldInfo={fieldInfo}