Good progress on UserProfileFormFields component
This commit is contained in:
parent
b17724fdda
commit
3f1316183d
@ -1,13 +1,7 @@
|
|||||||
import { useEffect, Fragment } from "react";
|
import { useEffect, Fragment } from "react";
|
||||||
import type { ClassKey } from "keycloakify/login/TemplateProps";
|
import type { ClassKey } from "keycloakify/login/TemplateProps";
|
||||||
import { clsx } from "keycloakify/tools/clsx";
|
import { clsx } from "keycloakify/tools/clsx";
|
||||||
import {
|
import { useUserProfileForm, type KcContextLike, type FormAction, type FormFieldError } from "keycloakify/login/lib/useUserProfileForm";
|
||||||
useUserProfileForm,
|
|
||||||
type KcContextLike,
|
|
||||||
type FormAction,
|
|
||||||
type FormFieldError,
|
|
||||||
type FormFieldState
|
|
||||||
} from "keycloakify/login/lib/useUserProfileForm";
|
|
||||||
import type { Attribute, LegacyAttribute } from "keycloakify/login/kcContext/KcContext";
|
import type { Attribute, LegacyAttribute } from "keycloakify/login/kcContext/KcContext";
|
||||||
import type { I18n } from "../../i18n";
|
import type { I18n } from "../../i18n";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
@ -100,30 +94,18 @@ export function UserProfileFormFields(props: UserProfileFormFieldsProps) {
|
|||||||
)}
|
)}
|
||||||
<InputFiledByType
|
<InputFiledByType
|
||||||
attribute={attribute}
|
attribute={attribute}
|
||||||
index={index}
|
valueOrValues={valueOrValues}
|
||||||
value={value}
|
displayableErrors={displayableErrors}
|
||||||
formValidationDispatch={dispatchFormAction}
|
formValidationDispatch={dispatchFormAction}
|
||||||
getClassName={getClassName}
|
getClassName={getClassName}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/*attribute.multivalued && (
|
|
||||||
<AddRemoveButtonsMultiValuedAttribute
|
|
||||||
formFieldStates={formFieldStates}
|
|
||||||
attribute={attribute}
|
|
||||||
index={index}
|
|
||||||
dispatchFormAction={dispatchFormAction}
|
|
||||||
i18n={i18n}
|
|
||||||
/>
|
|
||||||
)*/}
|
|
||||||
{displayableErrors.length !== 0 && (
|
|
||||||
<FieldErrors
|
<FieldErrors
|
||||||
attribute={attribute}
|
attribute={attribute}
|
||||||
getClassName={getClassName}
|
getClassName={getClassName}
|
||||||
displayableErrors={displayableErrors}
|
displayableErrors={displayableErrors}
|
||||||
fieldIndex={undefined}
|
fieldIndex={undefined}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
{attribute.annotations.inputHelperTextAfter !== undefined && (
|
{attribute.annotations.inputHelperTextAfter !== undefined && (
|
||||||
<div
|
<div
|
||||||
className={getClassName("kcInputHelperTextAfterClass")}
|
className={getClassName("kcInputHelperTextAfterClass")}
|
||||||
@ -256,7 +238,13 @@ function FieldErrors(props: {
|
|||||||
displayableErrors: FormFieldError[];
|
displayableErrors: FormFieldError[];
|
||||||
fieldIndex: number | undefined;
|
fieldIndex: number | undefined;
|
||||||
}) {
|
}) {
|
||||||
const { attribute, getClassName, displayableErrors, fieldIndex } = props;
|
const { attribute, getClassName, fieldIndex } = props;
|
||||||
|
|
||||||
|
const displayableErrors = props.displayableErrors.filter(error => error.fieldIndex === fieldIndex);
|
||||||
|
|
||||||
|
if (displayableErrors.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
@ -279,123 +267,6 @@ function FieldErrors(props: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AddRemoveButtonsMultiValuedAttribute(props: {
|
|
||||||
formFieldStates: FormFieldState[];
|
|
||||||
attribute: Attribute;
|
|
||||||
index: number;
|
|
||||||
dispatchFormAction: React.Dispatch<
|
|
||||||
Extract<FormAction, { action: "add value to multi-valued attribute" | "remove value from multi-valued attribute" }>
|
|
||||||
>;
|
|
||||||
i18n: I18n;
|
|
||||||
}) {
|
|
||||||
const { formFieldStates, attribute, index, dispatchFormAction, i18n } = props;
|
|
||||||
|
|
||||||
const { msg } = i18n;
|
|
||||||
|
|
||||||
const currentCount = formFieldStates.filter(({ attribute: attribute_i }) => attribute_i.name === attribute.name).length;
|
|
||||||
|
|
||||||
const hasRemove = (() => {
|
|
||||||
if (currentCount === 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const minCount = (() => {
|
|
||||||
const { multivalued } = attribute.validators;
|
|
||||||
|
|
||||||
if (multivalued === undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const minStr = multivalued.min;
|
|
||||||
|
|
||||||
if (minStr === undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return parseInt(minStr);
|
|
||||||
})();
|
|
||||||
|
|
||||||
if (minCount === undefined) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentCount === minCount) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
})();
|
|
||||||
|
|
||||||
const hasAdd = (() => {
|
|
||||||
if (index + 1 !== currentCount) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxCount = (() => {
|
|
||||||
const { multivalued } = attribute.validators;
|
|
||||||
|
|
||||||
if (multivalued === undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxStr = multivalued.max;
|
|
||||||
|
|
||||||
if (maxStr === undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return parseInt(maxStr);
|
|
||||||
})();
|
|
||||||
|
|
||||||
if (maxCount === undefined) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentCount === maxCount) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
})();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{hasRemove && (
|
|
||||||
<button
|
|
||||||
id={`kc-remove-${attribute.name}-${index + 1}`}
|
|
||||||
type="button"
|
|
||||||
className="pf-c-button pf-m-inline pf-m-link"
|
|
||||||
onClick={() =>
|
|
||||||
dispatchFormAction({
|
|
||||||
"action": "remove value from multi-valued attribute",
|
|
||||||
"name": attribute.name,
|
|
||||||
index
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{msg("remove")}
|
|
||||||
{hasRemove ? <> | </> : null}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{hasAdd && (
|
|
||||||
<button
|
|
||||||
id="kc-add-titles-1"
|
|
||||||
type="button"
|
|
||||||
className="pf-c-button pf-m-inline pf-m-link"
|
|
||||||
onClick={() =>
|
|
||||||
dispatchFormAction({
|
|
||||||
"action": "add value to multi-valued attribute",
|
|
||||||
"name": attribute.name
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{msg("addValue")}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
type PropsOfInputFiledByType = {
|
type PropsOfInputFiledByType = {
|
||||||
attribute: Attribute;
|
attribute: Attribute;
|
||||||
valueOrValues: string | string[];
|
valueOrValues: string | string[];
|
||||||
@ -459,9 +330,256 @@ function InputFiledByType(props: PropsOfInputFiledByType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function InputTag(props: PropsOfInputFiledByType & { fieldIndex: number | undefined }) {
|
function InputTag(props: PropsOfInputFiledByType & { fieldIndex: number | undefined }) {
|
||||||
|
/*
|
||||||
|
<#macro inputTag attribute value>
|
||||||
|
<input type="<@inputTagType attribute=attribute/>" id="${attribute.name}" name="${attribute.name}" value="${(value!'')}" class="${properties.kcInputClass!}"
|
||||||
|
aria-invalid="<#if messagesPerField.existsError('${attribute.name}')>true</#if>"
|
||||||
|
<#if attribute.readOnly>disabled</#if>
|
||||||
|
<#if attribute.autocomplete??>autocomplete="${attribute.autocomplete}"</#if>
|
||||||
|
<#if attribute.annotations.inputTypePlaceholder??>placeholder="${attribute.annotations.inputTypePlaceholder}"</#if>
|
||||||
|
<#if attribute.annotations.inputTypePattern??>pattern="${attribute.annotations.inputTypePattern}"</#if>
|
||||||
|
<#if attribute.annotations.inputTypeSize??>size="${attribute.annotations.inputTypeSize}"</#if>
|
||||||
|
<#if attribute.annotations.inputTypeMaxlength??>maxlength="${attribute.annotations.inputTypeMaxlength}"</#if>
|
||||||
|
<#if attribute.annotations.inputTypeMinlength??>minlength="${attribute.annotations.inputTypeMinlength}"</#if>
|
||||||
|
<#if attribute.annotations.inputTypeMax??>max="${attribute.annotations.inputTypeMax}"</#if>
|
||||||
|
<#if attribute.annotations.inputTypeMin??>min="${attribute.annotations.inputTypeMin}"</#if>
|
||||||
|
<#if attribute.annotations.inputTypeStep??>step="${attribute.annotations.inputTypeStep}"</#if>
|
||||||
|
<#if attribute.annotations.inputTypeStep??>step="${attribute.annotations.inputTypeStep}"</#if>
|
||||||
|
<#list attribute.html5DataAnnotations as key, value>
|
||||||
|
data-${key}="${value}"
|
||||||
|
</#list>
|
||||||
|
/>
|
||||||
|
</#macro>
|
||||||
|
|
||||||
|
<#macro inputTagType attribute>
|
||||||
|
<#compress>
|
||||||
|
<#if attribute.annotations.inputType??>
|
||||||
|
<#if attribute.annotations.inputType?starts_with("html5-")>
|
||||||
|
${attribute.annotations.inputType[6..]}
|
||||||
|
<#else>
|
||||||
|
${attribute.annotations.inputType}
|
||||||
|
</#if>
|
||||||
|
<#else>
|
||||||
|
text
|
||||||
|
</#if>
|
||||||
|
</#compress>
|
||||||
|
</#macro>
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { attribute, fieldIndex, getClassName, formValidationDispatch, valueOrValues, i18n, displayableErrors } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<input
|
||||||
|
type={(() => {
|
||||||
|
const { inputType } = attribute.annotations;
|
||||||
|
|
||||||
|
if (inputType?.startsWith("html5-")) {
|
||||||
|
return inputType.slice(6);
|
||||||
|
}
|
||||||
|
|
||||||
|
return inputType ?? "text";
|
||||||
|
})()}
|
||||||
|
id={attribute.name}
|
||||||
|
name={attribute.name}
|
||||||
|
value={(() => {
|
||||||
|
if (fieldIndex !== undefined) {
|
||||||
|
assert(valueOrValues instanceof Array);
|
||||||
|
return valueOrValues[fieldIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(typeof valueOrValues === "string");
|
||||||
|
|
||||||
|
return valueOrValues;
|
||||||
|
})()}
|
||||||
|
className={getClassName("kcInputClass")}
|
||||||
|
aria-invalid={displayableErrors.find(error => error.fieldIndex === fieldIndex) !== undefined}
|
||||||
|
disabled={attribute.readOnly}
|
||||||
|
autoComplete={attribute.autocomplete}
|
||||||
|
placeholder={attribute.annotations.inputTypePlaceholder}
|
||||||
|
pattern={attribute.annotations.inputTypePattern}
|
||||||
|
size={attribute.annotations.inputTypeSize === undefined ? undefined : parseInt(attribute.annotations.inputTypeSize)}
|
||||||
|
maxLength={attribute.annotations.inputTypeMaxlength === undefined ? undefined : parseInt(attribute.annotations.inputTypeMaxlength)}
|
||||||
|
minLength={attribute.annotations.inputTypeMinlength === undefined ? undefined : parseInt(attribute.annotations.inputTypeMinlength)}
|
||||||
|
max={attribute.annotations.inputTypeMax}
|
||||||
|
min={attribute.annotations.inputTypeMin}
|
||||||
|
step={attribute.annotations.inputTypeStep}
|
||||||
|
//{...Object.fromEntries(Object.entries(props.attribute.html5DataAnnotations).map(([key, value]) => [`data-${key}`, value])}
|
||||||
|
onChange={event =>
|
||||||
|
formValidationDispatch({
|
||||||
|
"action": "update",
|
||||||
|
"name": attribute.name,
|
||||||
|
"valueOrValues": (() => {
|
||||||
|
if (fieldIndex !== undefined) {
|
||||||
|
assert(valueOrValues instanceof Array);
|
||||||
|
|
||||||
|
return valueOrValues.map((value, i) => {
|
||||||
|
if (i === fieldIndex) {
|
||||||
|
return event.target.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return event.target.value;
|
||||||
|
})()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onBlur={() =>
|
||||||
|
props.formValidationDispatch({
|
||||||
|
"action": "focus lost",
|
||||||
|
"name": attribute.name,
|
||||||
|
"fieldIndex": fieldIndex
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{(() => {
|
||||||
|
if (fieldIndex === undefined) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(valueOrValues instanceof Array);
|
||||||
|
|
||||||
|
const values = valueOrValues;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FieldErrors
|
||||||
|
attribute={attribute}
|
||||||
|
getClassName={getClassName}
|
||||||
|
displayableErrors={displayableErrors}
|
||||||
|
fieldIndex={fieldIndex}
|
||||||
|
/>
|
||||||
|
<AddRemoveButtonsMultiValuedAttribute
|
||||||
|
attribute={attribute}
|
||||||
|
values={values}
|
||||||
|
fieldIndex={fieldIndex}
|
||||||
|
dispatchFormAction={formValidationDispatch}
|
||||||
|
i18n={i18n}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AddRemoveButtonsMultiValuedAttribute(props: {
|
||||||
|
attribute: Attribute;
|
||||||
|
values: string[];
|
||||||
|
fieldIndex: number;
|
||||||
|
dispatchFormAction: React.Dispatch<Extract<FormAction, { action: "update" }>>;
|
||||||
|
i18n: I18n;
|
||||||
|
}) {
|
||||||
|
const { attribute, values, fieldIndex, dispatchFormAction, i18n } = props;
|
||||||
|
|
||||||
|
const { msg } = i18n;
|
||||||
|
|
||||||
|
const hasRemove = (() => {
|
||||||
|
if (values.length === 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const minCount = (() => {
|
||||||
|
const { multivalued } = attribute.validators;
|
||||||
|
|
||||||
|
if (multivalued === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const minStr = multivalued.min;
|
||||||
|
|
||||||
|
if (minStr === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseInt(minStr);
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (minCount === undefined) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (values.length === minCount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const hasAdd = (() => {
|
||||||
|
if (fieldIndex + 1 !== values.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxCount = (() => {
|
||||||
|
const { multivalued } = attribute.validators;
|
||||||
|
|
||||||
|
if (multivalued === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxStr = multivalued.max;
|
||||||
|
|
||||||
|
if (maxStr === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseInt(maxStr);
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (maxCount === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (values.length === maxCount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{hasRemove && (
|
||||||
|
<button
|
||||||
|
id={`kc-remove-${attribute.name}-${fieldIndex + 1}`}
|
||||||
|
type="button"
|
||||||
|
className="pf-c-button pf-m-inline pf-m-link"
|
||||||
|
onClick={() =>
|
||||||
|
dispatchFormAction({
|
||||||
|
"action": "update",
|
||||||
|
"name": attribute.name,
|
||||||
|
"valueOrValues": values.filter((_, i) => i !== fieldIndex)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{msg("remove")}
|
||||||
|
{hasRemove ? <> | </> : null}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{hasAdd && (
|
||||||
|
<button
|
||||||
|
id="kc-add-titles-1"
|
||||||
|
type="button"
|
||||||
|
className="pf-c-button pf-m-inline pf-m-link"
|
||||||
|
onClick={() =>
|
||||||
|
dispatchFormAction({
|
||||||
|
"action": "update",
|
||||||
|
"name": attribute.name,
|
||||||
|
"valueOrValues": [...values, ""]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{msg("addValue")}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function InputTagSelects(props: PropsOfInputFiledByType) {
|
function InputTagSelects(props: PropsOfInputFiledByType) {
|
||||||
/*
|
/*
|
||||||
<#macro inputTagSelects attribute>
|
<#macro inputTagSelects attribute>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user