keycloak-scim/js/libs/ui-shared/src/user-profile/MultiInputComponent.tsx
Jon Koops dc0455b73c
Unify utils for interpolated labels under ui-shared (#25203)
Signed-off-by: Jon Koops <jonkoops@gmail.com>
2023-12-01 14:41:15 +01:00

131 lines
3.4 KiB
TypeScript

import {
Button,
ButtonVariant,
InputGroup,
TextInput,
TextInputProps,
} from "@patternfly/react-core";
import { MinusCircleIcon, PlusCircleIcon } from "@patternfly/react-icons";
import { type TFunction } from "i18next";
import { Fragment, useEffect, useMemo } from "react";
import { FieldPath, UseFormReturn, useWatch } from "react-hook-form";
import { UserProfileFieldProps } from "./UserProfileFields";
import { UserProfileGroup } from "./UserProfileGroup";
import { UserFormFields, fieldName, labelAttribute } from "./utils";
export const MultiInputComponent = ({
t,
form,
attribute,
renderer,
}: UserProfileFieldProps) => (
<UserProfileGroup t={t} form={form} attribute={attribute} renderer={renderer}>
<MultiLineInput
t={t}
form={form}
aria-label={labelAttribute(t, attribute)}
name={fieldName(attribute.name)!}
addButtonLabel={t("addMultivaluedLabel", {
fieldLabel: labelAttribute(t, attribute),
})}
/>
</UserProfileGroup>
);
export type MultiLineInputProps = Omit<TextInputProps, "form"> & {
t: TFunction;
name: FieldPath<UserFormFields>;
form: UseFormReturn<UserFormFields>;
addButtonLabel?: string;
isDisabled?: boolean;
defaultValue?: string[];
};
const MultiLineInput = ({
t,
name,
form,
addButtonLabel,
isDisabled = false,
defaultValue,
id,
...rest
}: MultiLineInputProps) => {
const { register, setValue, control } = form;
const value = useWatch({
name,
control,
defaultValue: defaultValue || "",
});
const fields = useMemo<string[]>(() => {
return Array.isArray(value) && value.length !== 0
? value
: defaultValue || [""];
}, [value]);
const remove = (index: number) => {
update([...fields.slice(0, index), ...fields.slice(index + 1)]);
};
const append = () => {
update([...fields, ""]);
};
const updateValue = (index: number, value: string) => {
update([...fields.slice(0, index), value, ...fields.slice(index + 1)]);
};
const update = (values: string[]) => {
const fieldValue = values.flatMap((field) => field);
setValue(name, fieldValue, {
shouldDirty: true,
});
};
useEffect(() => {
register(name);
}, [register]);
return (
<div id={id}>
{fields.map((value, index) => (
<Fragment key={index}>
<InputGroup>
<TextInput
data-testid={name + index}
onChange={(value) => updateValue(index, value)}
name={`${name}.${index}.value`}
value={value}
isDisabled={isDisabled}
{...rest}
/>
<Button
data-testid={"remove" + index}
variant={ButtonVariant.link}
onClick={() => remove(index)}
tabIndex={-1}
aria-label={t("remove")}
isDisabled={fields.length === 1 || isDisabled}
>
<MinusCircleIcon />
</Button>
</InputGroup>
{index === fields.length - 1 && (
<Button
variant={ButtonVariant.link}
onClick={append}
tabIndex={-1}
aria-label={t("add")}
data-testid="addValue"
isDisabled={!value || isDisabled}
>
<PlusCircleIcon /> {t(addButtonLabel || "add")}
</Button>
)}
</Fragment>
))}
</div>
);
};