Add UI config messages framework (#22692)

* add config messages to sections and fields

* add alert variants

* add messages to types

* add detect fps, review, and audio messages

* add a basic set of messages

* remove emptySelectionHintKey from switches widget

use the new messages framework and revert the changes made in #22664
This commit is contained in:
Josh Hawkins
2026-03-29 15:25:40 -06:00
committed by GitHub
parent 257dae11c1
commit 953d244c52
18 changed files with 475 additions and 18 deletions
@@ -28,6 +28,7 @@ import {
} from "../utils";
import { normalizeOverridePath } from "../utils/overrides";
import get from "lodash/get";
import { ConfigFieldMessage } from "../../ConfigFieldMessage";
import isEqual from "lodash/isEqual";
import { SPLIT_ROW_CLASS_NAME } from "@/components/card/SettingsGroupCard";
@@ -382,6 +383,46 @@ export function FieldTemplate(props: FieldTemplateProps) {
const beforeContent = renderCustom(beforeSpec);
const afterContent = renderCustom(afterSpec);
// Render conditional field messages from ui:messages
const fieldMessageSpecs = uiSchema?.["ui:messages"] as
| Array<{
key: string;
messageKey: string;
severity: string;
position?: string;
}>
| undefined;
const beforeMessages = fieldMessageSpecs?.filter(
(m) => (m.position ?? "before") === "before",
);
const afterMessages = fieldMessageSpecs?.filter(
(m) => m.position === "after",
);
const beforeMessagesContent =
beforeMessages && beforeMessages.length > 0 ? (
<div className="space-y-2">
{beforeMessages.map((m) => (
<ConfigFieldMessage
key={m.key}
messageKey={m.messageKey}
severity={m.severity}
/>
))}
</div>
) : null;
const afterMessagesContent =
afterMessages && afterMessages.length > 0 ? (
<div className="space-y-2">
{afterMessages.map((m) => (
<ConfigFieldMessage
key={m.key}
messageKey={m.messageKey}
severity={m.severity}
/>
))}
</div>
) : null;
const WrapIfAdditionalTemplate = getTemplate(
"WrapIfAdditionalTemplate",
registry,
@@ -600,6 +641,7 @@ export function FieldTemplate(props: FieldTemplateProps) {
>
<div className="flex flex-col space-y-6">
{beforeContent}
{beforeMessagesContent}
<div className={cn("space-y-1")} data-field-id={translationPath}>
{renderStandardLabel()}
{renderFieldLayout()}
@@ -607,6 +649,7 @@ export function FieldTemplate(props: FieldTemplateProps) {
{errors}
{help}
</div>
{afterMessagesContent}
{afterContent}
</div>
</WrapIfAdditionalTemplate>
@@ -45,8 +45,6 @@ export type SwitchesWidgetOptions = {
enableSearch?: boolean;
/** Allow users to add custom entries not in the predefined list */
allowCustomEntries?: boolean;
/** i18n key for a hint shown when no entities are selected */
emptySelectionHintKey?: string;
};
function normalizeValue(value: unknown): string[] {
@@ -131,11 +129,6 @@ export function SwitchesWidget(props: WidgetProps) {
[props.options],
);
const emptySelectionHintKey = useMemo(
() => props.options?.emptySelectionHintKey as string | undefined,
[props.options],
);
const selectedEntities = useMemo(() => normalizeValue(value), [value]);
const [isOpen, setIsOpen] = useState(selectedEntities.length > 0);
const [searchTerm, setSearchTerm] = useState("");
@@ -215,12 +208,6 @@ export function SwitchesWidget(props: WidgetProps) {
</Button>
</CollapsibleTrigger>
{emptySelectionHintKey && selectedEntities.length === 0 && t && (
<div className="mt-0 pb-2 text-sm text-success">
{t(emptySelectionHintKey, { ns: namespace })}
</div>
)}
<CollapsibleContent className="rounded-lg border border-input bg-secondary pb-1 pr-0 pt-2 md:max-w-md">
{allEntities.length === 0 && !allowCustomEntries ? (
<div className="text-sm text-muted-foreground">{emptyMessage}</div>