keycloak_theme/src/lib/components/shared/UserProfileCommons.tsx

174 lines
8.7 KiB
TypeScript
Raw Normal View History

2022-09-09 02:07:29 +02:00
import React, { memo, useEffect, Fragment } from "react";
import type { KcProps } from "../KcProps";
import type { Attribute } from "../../getKcContext/KcContextBase";
2022-10-16 00:49:49 +02:00
import { clsx } from "../../tools/clsx";
2022-09-09 02:07:29 +02:00
import type { ReactComponent } from "../../tools/ReactComponent";
import { useCallbackFactory } from "powerhooks/useCallbackFactory";
import { useFormValidationSlice } from "../../useFormValidationSlice";
import type { I18n } from "../../i18n";
import type { Param0 } from "tsafe/Param0";
export type UserProfileFormFieldsProps = {
kcContext: Param0<typeof useFormValidationSlice>["kcContext"];
i18n: I18n;
} & KcProps &
Partial<Record<"BeforeField" | "AfterField", ReactComponent<{ attribute: Attribute }>>> & {
onIsFormSubmittableValueChange: (isFormSubmittable: boolean) => void;
};
export const UserProfileFormFields = memo(
({ kcContext, onIsFormSubmittableValueChange, i18n, BeforeField, AfterField, ...props }: UserProfileFormFieldsProps) => {
2022-09-09 02:07:29 +02:00
const { advancedMsg } = i18n;
const {
formValidationState: { fieldStateByAttributeName, isFormSubmittable },
formValidationReducer,
attributesWithPassword
} = useFormValidationSlice({
kcContext,
i18n
});
useEffect(() => {
onIsFormSubmittableValueChange(isFormSubmittable);
}, [isFormSubmittable]);
const onChangeFactory = useCallbackFactory(
(
[name]: [string],
[
{
target: { value }
}
]: [React.ChangeEvent<HTMLInputElement | HTMLSelectElement>]
) =>
formValidationReducer({
"action": "update value",
name,
"newValue": value
})
);
const onBlurFactory = useCallbackFactory(([name]: [string]) =>
formValidationReducer({
"action": "focus lost",
name
})
);
let currentGroup = "";
return (
<>
{attributesWithPassword.map((attribute, i) => {
2022-09-09 02:07:29 +02:00
const { group = "", groupDisplayHeader = "", groupDisplayDescription = "" } = attribute;
const { value, displayableErrors } = fieldStateByAttributeName[attribute.name];
2022-10-16 00:49:49 +02:00
const formGroupClassName = clsx(props.kcFormGroupClass, displayableErrors.length !== 0 && props.kcFormGroupErrorClass);
2022-09-09 02:07:29 +02:00
return (
<Fragment key={i}>
{group !== currentGroup && (currentGroup = group) !== "" && (
<div className={formGroupClassName}>
2022-10-16 00:49:49 +02:00
<div className={clsx(props.kcContentWrapperClass)}>
<label id={`header-${group}`} className={clsx(props.kcFormGroupHeader)}>
2022-09-09 02:07:29 +02:00
{advancedMsg(groupDisplayHeader) || currentGroup}
</label>
</div>
{groupDisplayDescription !== "" && (
2022-10-16 00:49:49 +02:00
<div className={clsx(props.kcLabelWrapperClass)}>
<label id={`description-${group}`} className={`${clsx(props.kcLabelClass)}`}>
2022-09-09 02:07:29 +02:00
{advancedMsg(groupDisplayDescription)}
</label>
</div>
)}
</div>
)}
{BeforeField && <BeforeField attribute={attribute} />}
<div className={formGroupClassName}>
2022-10-16 00:49:49 +02:00
<div className={clsx(props.kcLabelWrapperClass)}>
<label htmlFor={attribute.name} className={clsx(props.kcLabelClass)}>
2022-09-09 02:07:29 +02:00
{advancedMsg(attribute.displayName ?? "")}
</label>
{attribute.required && <>*</>}
</div>
2022-10-16 00:49:49 +02:00
<div className={clsx(props.kcInputWrapperClass)}>
2022-09-09 02:07:29 +02:00
{(() => {
const { options } = attribute.validators;
if (options !== undefined) {
return (
<select
id={attribute.name}
name={attribute.name}
onChange={onChangeFactory(attribute.name)}
onBlur={onBlurFactory(attribute.name)}
value={value}
>
{options.options.map(option => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
);
}
return (
<input
type={(() => {
switch (attribute.name) {
case "password-confirm":
case "password":
return "password";
default:
return "text";
}
})()}
id={attribute.name}
name={attribute.name}
value={value}
onChange={onChangeFactory(attribute.name)}
2022-10-16 00:49:49 +02:00
className={clsx(props.kcInputClass)}
2022-09-09 02:07:29 +02:00
aria-invalid={displayableErrors.length !== 0}
disabled={attribute.readOnly}
autoComplete={attribute.autocomplete}
onBlur={onBlurFactory(attribute.name)}
/>
);
})()}
2022-10-16 00:49:49 +02:00
{displayableErrors.length !== 0 &&
(() => {
const divId = `input-error-${attribute.name}`;
return (
<>
<style>{`#${divId} > span: { display: block; }`}</style>
<span
id={divId}
className={clsx(props.kcInputErrorMessageClass)}
style={{
"position": displayableErrors.length === 1 ? "absolute" : undefined
}}
aria-live="polite"
>
{displayableErrors.map(({ errorMessage }) => errorMessage)}
</span>
</>
);
})()}
2022-09-09 02:07:29 +02:00
</div>
</div>
{AfterField && <AfterField attribute={attribute} />}
</Fragment>
);
})}
</>
);
}
);