refactor the keyValue input to have a override component (#40130)

fixes: #40129

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
This commit is contained in:
Erik Jan de Wit 2025-06-05 13:53:35 +02:00 committed by GitHub
parent 25b88392cc
commit 066fd26dd7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 124 additions and 115 deletions

View File

@ -9,7 +9,7 @@ import { useState } from "react";
import { useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { useAdminClient } from "../../admin-client";
import { KeySelect } from "../key-value-form/KeySelect";
import { KeySelect } from "../../realm-settings/user-profile/attribute/KeySelect";
import type { ComponentProps } from "./components";
export const UserProfileAttributeListComponent = ({
@ -40,7 +40,7 @@ export const UserProfileAttributeListComponent = ({
return config.attributes.map((option) => ({
key: option.name!,
label: option.name!,
value: option.name!,
}));
};

View File

@ -12,7 +12,7 @@ import {
TextInput,
} from "@patternfly/react-core";
import { MinusCircleIcon, PlusCircleIcon } from "@patternfly/react-icons";
import { Fragment } from "react";
import { Fragment, FunctionComponent, PropsWithChildren } from "react";
import {
FieldValues,
useFieldArray,
@ -21,27 +21,34 @@ import {
} from "react-hook-form";
import { useTranslation } from "react-i18next";
import { KeySelect } from "./KeySelect";
import { ValueSelect } from "./ValueSelect";
export type DefaultValue = {
key: string;
values?: string[];
label: string;
};
type KeyValueInputProps = {
type Field = {
name: string;
};
type ValueField = Field & {
keyValue: string;
};
type KeyValueInputProps = PropsWithChildren & {
name: string;
label?: string;
defaultKeyValue?: DefaultValue[];
isDisabled?: boolean;
KeyComponent?: FunctionComponent<Field>;
ValueComponent?: FunctionComponent<ValueField>;
};
export const KeyValueInput = ({
name,
label = "attributes",
defaultKeyValue,
isDisabled = false,
KeyComponent,
ValueComponent,
}: KeyValueInputProps) => {
const { t } = useTranslation();
const {
@ -80,12 +87,8 @@ export const KeyValueInput = ({
return (
<Fragment key={attribute.id}>
<GridItem span={5}>
{defaultKeyValue ? (
<KeySelect
name={`${name}.${index}.key`}
selectItems={defaultKeyValue}
rules={{ required: true }}
/>
{KeyComponent ? (
<KeyComponent name={`${name}.${index}.key`} />
) : (
<TextInput
placeholder={t("keyPlaceholder")}
@ -106,12 +109,10 @@ export const KeyValueInput = ({
)}
</GridItem>
<GridItem span={5}>
{defaultKeyValue ? (
<ValueSelect
{ValueComponent ? (
<ValueComponent
name={`${name}.${index}.value`}
keyValue={values[index]?.key}
selectItems={defaultKeyValue}
rules={{ required: true }}
/>
) : (
<TextInput

View File

@ -104,9 +104,9 @@ export { HelpHeader } from "./components/help-enabler/HelpHeader";
export { FileUploadForm } from "./components/json-file-upload/FileUploadForm";
export { JsonFileUpload } from "./components/json-file-upload/JsonFileUpload";
export { AttributesForm } from "./components/key-value-form/AttributeForm";
export { KeySelect } from "./components/key-value-form/KeySelect";
export { KeySelect } from "./realm-settings/user-profile/attribute/KeySelect";
export { KeyValueInput } from "./components/key-value-form/KeyValueInput";
export { ValueSelect } from "./components/key-value-form/ValueSelect";
export { ValueSelect } from "./realm-settings/user-profile/attribute/ValueSelect";
export { ClickableCard } from "./components/keycloak-card/ClickableCard";
export { KeycloakCard } from "./components/keycloak-card/KeycloakCard";
export { MultiLineInput } from "./components/multi-line-input/MultiLineInput";

View File

@ -1,13 +1,16 @@
import { FormGroup, Grid, GridItem, TextInput } from "@patternfly/react-core";
import { useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { FormGroup, Grid, GridItem } from "@patternfly/react-core";
import { FormAccess } from "../../../components/form/FormAccess";
import { KeyValueInput } from "../../../components/key-value-form/KeyValueInput";
import { KeySelect } from "./KeySelect";
import { ValueSelect } from "./ValueSelect";
import "../../realm-settings-section.css";
export const AttributeAnnotations = () => {
const { t } = useTranslation();
const { register } = useFormContext();
return (
<FormAccess role="manage-realm" isHorizontal>
@ -22,70 +25,90 @@ export const AttributeAnnotations = () => {
<KeyValueInput
name="annotations"
label={t("annotations")}
defaultKeyValue={[
{
key: "inputType",
label: t("inputType"),
values: [
"text",
"textarea",
"select",
"select-radiobuttons",
"multiselect",
"multiselect-checkboxes",
"html5-email",
"html5-tel",
"html5-url",
"html5-number",
"html5-range",
"html5-datetime-local",
"html5-date",
"html5-month",
"html5-week",
"html5-time",
],
},
{
key: "inputHelperTextBefore",
label: t("inputHelperTextBefore"),
},
{
key: "inputHelperTextAfter",
label: t("inputHelperTextAfter"),
},
{
key: "inputOptionLabelsI18nPrefix",
label: t("inputOptionLabelsI18nPrefix"),
},
{
key: "inputTypePlaceholder",
label: t("inputTypePlaceholder"),
},
{
key: "inputTypeSize",
label: t("inputTypeSize"),
},
{
key: "inputTypeCols",
label: t("inputTypeCols"),
},
{
key: "inputTypeRows",
label: t("inputTypeRows"),
},
{
key: "inputTypeStep",
label: t("inputTypeStep"),
},
{
key: "kcNumberFormat",
label: t("kcNumberFormat"),
},
{
key: "kcNumberUnFormat",
label: t("kcNumberUnFormat"),
},
]}
KeyComponent={(props) => (
<KeySelect
{...props}
selectItems={[
{
key: "inputType",
value: t("inputType"),
},
{
key: "inputHelperTextBefore",
value: t("inputHelperTextBefore"),
},
{
key: "inputHelperTextAfter",
value: t("inputHelperTextAfter"),
},
{
key: "inputOptionLabelsI18nPrefix",
value: t("inputOptionLabelsI18nPrefix"),
},
{
key: "inputTypePlaceholder",
value: t("inputTypePlaceholder"),
},
{
key: "inputTypeSize",
value: t("inputTypeSize"),
},
{
key: "inputTypeCols",
value: t("inputTypeCols"),
},
{
key: "inputTypeRows",
value: t("inputTypeRows"),
},
{
key: "inputTypeStep",
value: t("inputTypeStep"),
},
{
key: "kcNumberFormat",
value: t("kcNumberFormat"),
},
{
key: "kcNumberUnFormat",
value: t("kcNumberUnFormat"),
},
]}
/>
)}
ValueComponent={(props) =>
props.keyValue === "inputType" ? (
<ValueSelect
selectItems={[
"text",
"textarea",
"select",
"select-radiobuttons",
"multiselect",
"multiselect-checkboxes",
"html5-email",
"html5-tel",
"html5-url",
"html5-number",
"html5-range",
"html5-datetime-local",
"html5-date",
"html5-month",
"html5-week",
"html5-time",
]}
rules={{ required: true }}
{...props}
/>
) : (
<TextInput
aria-label={t("customValue")}
data-testid={props.name}
{...props}
{...register(props.name)}
/>
)
}
/>
</GridItem>
</Grid>

View File

@ -1,4 +1,7 @@
import { KeycloakSelect } from "@keycloak/keycloak-ui-shared";
import {
KeycloakSelect,
SelectControlOption,
} from "@keycloak/keycloak-ui-shared";
import {
Grid,
GridItem,
@ -8,11 +11,10 @@ import {
import { useState } from "react";
import { UseControllerProps, useController } from "react-hook-form";
import { useTranslation } from "react-i18next";
import useToggle from "../../utils/useToggle";
import { DefaultValue } from "./KeyValueInput";
import useToggle from "../../../utils/useToggle";
type KeySelectProp = UseControllerProps & {
selectItems: DefaultValue[];
selectItems: SelectControlOption[];
};
export const KeySelect = ({ selectItems, ...rest }: KeySelectProp) => {
@ -44,7 +46,7 @@ export const KeySelect = ({ selectItems, ...rest }: KeySelectProp) => {
</SelectOption>,
...selectItems.map((item) => (
<SelectOption key={item.key} value={item.key}>
{item.label}
{item.value}
</SelectOption>
)),
]}

View File

@ -1,30 +1,19 @@
import { KeycloakSelect } from "@keycloak/keycloak-ui-shared";
import { SelectOption, TextInput } from "@patternfly/react-core";
import { useMemo, useState } from "react";
import { SelectOption } from "@patternfly/react-core";
import { useState } from "react";
import { UseControllerProps, useController } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { DefaultValue } from "./KeyValueInput";
type ValueSelectProps = UseControllerProps & {
selectItems: DefaultValue[];
keyValue: string;
selectItems: string[];
};
export const ValueSelect = ({
selectItems,
keyValue,
...rest
}: ValueSelectProps) => {
export const ValueSelect = ({ selectItems, ...rest }: ValueSelectProps) => {
const { t } = useTranslation();
const { field } = useController(rest);
const [open, setOpen] = useState(false);
const defaultItem = useMemo(
() => selectItems.find((v) => v.key === keyValue),
[selectItems, keyValue],
);
return defaultItem?.values ? (
return (
<KeycloakSelect
onToggle={(isOpen) => setOpen(isOpen)}
isOpen={open}
@ -35,17 +24,11 @@ export const ValueSelect = ({
selections={field.value ? [field.value] : t("choose")}
placeholderText={t("valuePlaceholder")}
>
{defaultItem.values.map((item) => (
{selectItems.map((item) => (
<SelectOption key={item} value={item}>
{item}
</SelectOption>
))}
</KeycloakSelect>
) : (
<TextInput
aria-label={t("customValue")}
data-testid={rest.name}
{...field}
/>
);
};