frigate/web/src/components/config-form/ConfigForm.tsx
Josh Hawkins 42a594984d tweaks
2026-02-27 09:37:57 -06:00

154 lines
4.1 KiB
TypeScript

// ConfigForm - Main RJSF form wrapper component
import Form from "@rjsf/core";
import validator from "@rjsf/validator-ajv8";
import type { RJSFSchema, UiSchema } from "@rjsf/utils";
import type { IChangeEvent } from "@rjsf/core";
import { frigateTheme } from "./theme";
import { transformSchema } from "@/lib/config-schema";
import { createErrorTransformer } from "@/lib/config-schema/errorMessages";
import { useMemo, useCallback } from "react";
import { useTranslation } from "react-i18next";
import { cn } from "@/lib/utils";
export interface ConfigFormProps {
/** JSON Schema for the form */
schema: RJSFSchema;
/** Current form data */
formData?: unknown;
/** Called when form data changes */
onChange?: (data: unknown) => void;
/** Called when form is submitted */
onSubmit?: (data: unknown) => void;
/** Called when form has errors on submit */
onError?: (errors: unknown[]) => void;
/** Additional uiSchema overrides */
uiSchema?: UiSchema;
/** Field ordering */
fieldOrder?: string[];
/** Field groups for layout */
fieldGroups?: Record<string, string[]>;
/** Fields to hide */
hiddenFields?: string[];
/** Fields marked as advanced (collapsed by default) */
advancedFields?: string[];
/** Whether form is disabled */
disabled?: boolean;
/** Whether form is read-only */
readonly?: boolean;
/** Whether to show submit button */
showSubmit?: boolean;
/** Custom class name */
className?: string;
/** Live validation mode */
liveValidate?: boolean;
/** Form context passed to all widgets */
formContext?: Record<string, unknown>;
/** i18n namespace for field labels */
i18nNamespace?: string;
}
export function ConfigForm({
schema,
formData,
onChange,
onSubmit,
onError,
uiSchema: customUiSchema,
fieldOrder,
fieldGroups,
hiddenFields,
advancedFields,
disabled = false,
readonly = false,
showSubmit = false,
className,
liveValidate = true,
formContext,
i18nNamespace,
}: ConfigFormProps) {
const { t, i18n } = useTranslation([
i18nNamespace || "common",
"views/settings",
"config/validation",
]);
// Determine which fields to hide based on advanced toggle
const effectiveHiddenFields = useMemo(() => {
return hiddenFields;
}, [hiddenFields]);
// Transform schema and generate uiSchema
const { schema: transformedSchema, uiSchema: generatedUiSchema } = useMemo(
() =>
transformSchema(schema, {
fieldOrder,
hiddenFields: effectiveHiddenFields,
advancedFields: advancedFields,
i18nNamespace,
}),
[schema, fieldOrder, effectiveHiddenFields, advancedFields, i18nNamespace],
);
// Merge generated uiSchema with custom overrides
const finalUiSchema = useMemo(
() => ({
...generatedUiSchema,
"ui:groups": fieldGroups,
...customUiSchema,
"ui:submitButtonOptions": showSubmit
? { norender: false }
: { norender: true },
}),
[generatedUiSchema, customUiSchema, showSubmit, fieldGroups],
);
// Create error transformer for user-friendly error messages
const errorTransformer = useMemo(() => createErrorTransformer(i18n), [i18n]);
const handleChange = useCallback(
(e: IChangeEvent) => {
onChange?.(e.formData);
},
[onChange],
);
const handleSubmit = useCallback(
(e: IChangeEvent) => {
onSubmit?.(e.formData);
},
[onSubmit],
);
// Extended form context with i18n info
const extendedFormContext = useMemo(
() => ({
...formContext,
i18nNamespace,
t,
}),
[formContext, i18nNamespace, t],
);
return (
<div className={cn("config-form", className)}>
<Form
schema={transformedSchema}
uiSchema={finalUiSchema}
formData={formData}
validator={validator}
onChange={handleChange}
onSubmit={handleSubmit}
onError={onError}
disabled={disabled}
readonly={readonly}
liveValidate={liveValidate}
formContext={extendedFormContext}
transformErrors={errorTransformer}
{...frigateTheme}
/>
</div>
);
}
export default ConfigForm;