frigate/web/src/components/config-form/theme/widgets/OptionalFieldWidget.tsx
Josh Hawkins ca75f06456
Miscellaneous fixes (#23186)
* improve scroll handling for non-modal DropdownMenu in classification and face selection dialogs

* clean up

* fix incorrect key capitalization

* fix profile array overrides not replacing base arrays

don't use lodash merge(), it does positional merging and an empty source array doesn't override the destination, and shorter arrays leak destination elements through.

backend is unaffected, so the saved config and actual backend functionality was right

* only show audio debug tab when audio is enabled in config

* move apple_compatibility out of advanced

* remove retry_interval from UI

99% of users should never be changing this

* hide switch in optionalfieldwidget if editing a profile

* add override badges for cameras and profiles

collect shared functions into the config util and separate hooks

* Use new models endpoint info to determine modalities

* clarify language

* fix linter

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
2026-05-13 11:04:11 -05:00

75 lines
2.3 KiB
TypeScript

// Optional Field Widget - wraps any inner widget with an enable/disable switch
// Used for nullable fields where None means "disabled" (not the same as 0)
import type { WidgetProps } from "@rjsf/utils";
import { getWidget } from "@rjsf/utils";
import { Switch } from "@/components/ui/switch";
import { cn } from "@/lib/utils";
import { getNonNullSchema } from "../fields/nullableUtils";
import type { ConfigFormContext } from "@/types/configForm";
export function OptionalFieldWidget(props: WidgetProps) {
const { id, value, disabled, readonly, onChange, schema, options, registry } =
props;
const innerWidgetName = (options.innerWidget as string) || undefined;
const isEnabled = value !== undefined && value !== null;
const formContext = registry?.formContext as ConfigFormContext | undefined;
const isProfile = !!formContext?.isProfile;
// Extract the non-null branch from anyOf [Type, null]
const innerSchema = getNonNullSchema(schema) ?? schema;
const InnerWidget = getWidget(innerSchema, innerWidgetName, registry.widgets);
const getDefaultValue = () => {
if (innerSchema.default !== undefined && innerSchema.default !== null) {
return innerSchema.default;
}
if (innerSchema.minimum !== undefined) {
return innerSchema.minimum;
}
if (innerSchema.type === "integer" || innerSchema.type === "number") {
return 0;
}
if (innerSchema.type === "string") {
return "";
}
return 0;
};
const handleToggle = (checked: boolean) => {
onChange(checked ? getDefaultValue() : undefined);
};
const innerProps: WidgetProps = {
...props,
schema: innerSchema,
disabled: disabled || readonly || (!isProfile && !isEnabled),
value: isEnabled ? value : getDefaultValue(),
};
// don't show the switch if we're editing in a profile
// to disable in a profile, users should edit the config manually, eg:
// skip_motion_threshold: None
if (isProfile) {
return <InnerWidget {...innerProps} />;
}
return (
<div className="flex items-center gap-3">
<Switch
id={`${id}-toggle`}
checked={isEnabled}
disabled={disabled || readonly}
onCheckedChange={handleToggle}
/>
<div
className={cn("flex-1", !isEnabled && "pointer-events-none opacity-40")}
>
<InnerWidget {...innerProps} />
</div>
</div>
);
}