From 3f1316183df56b059f7b66d2ec957652d9e46106 Mon Sep 17 00:00:00 2001 From: Joseph Garrone Date: Sun, 5 May 2024 20:47:23 +0200 Subject: [PATCH] Good progress on UserProfileFormFields component --- .../pages/shared/UserProfileFormFields.tsx | 508 +++++++++++------- 1 file changed, 313 insertions(+), 195 deletions(-) diff --git a/src/login/pages/shared/UserProfileFormFields.tsx b/src/login/pages/shared/UserProfileFormFields.tsx index ea5393da..121b49ab 100644 --- a/src/login/pages/shared/UserProfileFormFields.tsx +++ b/src/login/pages/shared/UserProfileFormFields.tsx @@ -1,13 +1,7 @@ import { useEffect, Fragment } from "react"; import type { ClassKey } from "keycloakify/login/TemplateProps"; import { clsx } from "keycloakify/tools/clsx"; -import { - useUserProfileForm, - type KcContextLike, - type FormAction, - type FormFieldError, - type FormFieldState -} from "keycloakify/login/lib/useUserProfileForm"; +import { useUserProfileForm, type KcContextLike, type FormAction, type FormFieldError } from "keycloakify/login/lib/useUserProfileForm"; import type { Attribute, LegacyAttribute } from "keycloakify/login/kcContext/KcContext"; import type { I18n } from "../../i18n"; import { assert } from "tsafe/assert"; @@ -100,30 +94,18 @@ export function UserProfileFormFields(props: UserProfileFormFieldsProps) { )} - - {/*attribute.multivalued && ( - - )*/} - {displayableErrors.length !== 0 && ( - - )} + {attribute.annotations.inputHelperTextAfter !== undefined && (
error.fieldIndex === fieldIndex); + + if (displayableErrors.length === 0) { + return null; + } return ( - >; - 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 && ( - - )} - {hasAdd && ( - - )} - - ); -} - type PropsOfInputFiledByType = { attribute: Attribute; valueOrValues: string | string[]; @@ -410,27 +281,27 @@ function InputFiledByType(props: PropsOfInputFiledByType) { /* <#macro inputFieldByType attribute> - <#switch attribute.annotations.inputType!''> - <#case 'textarea'> - <@textareaTag attribute=attribute/> - <#break> - <#case 'select'> - <#case 'multiselect'> - <@selectTag attribute=attribute/> - <#break> - <#case 'select-radiobuttons'> - <#case 'multiselect-checkboxes'> - <@inputTagSelects attribute=attribute/> - <#break> - <#default> - <#if attribute.multivalued && attribute.values?has_content> - <#list attribute.values as value> - <@inputTag attribute=attribute value=value!''/> - - <#else> - <@inputTag attribute=attribute value=attribute.value!''/> - - + <#switch attribute.annotations.inputType!''> + <#case 'textarea'> + <@textareaTag attribute=attribute/> + <#break> + <#case 'select'> + <#case 'multiselect'> + <@selectTag attribute=attribute/> + <#break> + <#case 'select-radiobuttons'> + <#case 'multiselect-checkboxes'> + <@inputTagSelects attribute=attribute/> + <#break> + <#default> + <#if attribute.multivalued && attribute.values?has_content> + <#list attribute.values as value> + <@inputTag attribute=attribute value=value!''/> + + <#else> + <@inputTag attribute=attribute value=attribute.value!''/> + + */ @@ -459,42 +330,289 @@ function InputFiledByType(props: PropsOfInputFiledByType) { } function InputTag(props: PropsOfInputFiledByType & { fieldIndex: number | undefined }) { - return null; + /* + <#macro inputTag attribute value> + disabled + <#if attribute.autocomplete??>autocomplete="${attribute.autocomplete}" + <#if attribute.annotations.inputTypePlaceholder??>placeholder="${attribute.annotations.inputTypePlaceholder}" + <#if attribute.annotations.inputTypePattern??>pattern="${attribute.annotations.inputTypePattern}" + <#if attribute.annotations.inputTypeSize??>size="${attribute.annotations.inputTypeSize}" + <#if attribute.annotations.inputTypeMaxlength??>maxlength="${attribute.annotations.inputTypeMaxlength}" + <#if attribute.annotations.inputTypeMinlength??>minlength="${attribute.annotations.inputTypeMinlength}" + <#if attribute.annotations.inputTypeMax??>max="${attribute.annotations.inputTypeMax}" + <#if attribute.annotations.inputTypeMin??>min="${attribute.annotations.inputTypeMin}" + <#if attribute.annotations.inputTypeStep??>step="${attribute.annotations.inputTypeStep}" + <#if attribute.annotations.inputTypeStep??>step="${attribute.annotations.inputTypeStep}" + <#list attribute.html5DataAnnotations as key, value> + data-${key}="${value}" + + /> + + + <#macro inputTagType attribute> + <#compress> + <#if attribute.annotations.inputType??> + <#if attribute.annotations.inputType?starts_with("html5-")> + ${attribute.annotations.inputType[6..]} + <#else> + ${attribute.annotations.inputType} + + <#else> + text + + + + + */ + + const { attribute, fieldIndex, getClassName, formValidationDispatch, valueOrValues, i18n, displayableErrors } = props; + + return ( + <> + { + 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; + } + + assert(valueOrValues instanceof Array); + + const values = valueOrValues; + + return ( + <> + + + + ); + })()} + + ); +} + +function AddRemoveButtonsMultiValuedAttribute(props: { + attribute: Attribute; + values: string[]; + fieldIndex: number; + dispatchFormAction: React.Dispatch>; + 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 && ( + + )} + {hasAdd && ( + + )} + + ); } function InputTagSelects(props: PropsOfInputFiledByType) { /* <#macro inputTagSelects attribute> - <#if attribute.annotations.inputType=='select-radiobuttons'> - <#assign inputType='radio'> - <#assign classDiv=properties.kcInputClassRadio!> - <#assign classInput=properties.kcInputClassRadioInput!> - <#assign classLabel=properties.kcInputClassRadioLabel!> - <#else> - <#assign inputType='checkbox'> - <#assign classDiv=properties.kcInputClassCheckbox!> - <#assign classInput=properties.kcInputClassCheckboxInput!> - <#assign classLabel=properties.kcInputClassCheckboxLabel!> - + <#if attribute.annotations.inputType=='select-radiobuttons'> + <#assign inputType='radio'> + <#assign classDiv=properties.kcInputClassRadio!> + <#assign classInput=properties.kcInputClassRadioInput!> + <#assign classLabel=properties.kcInputClassRadioLabel!> + <#else> + <#assign inputType='checkbox'> + <#assign classDiv=properties.kcInputClassCheckbox!> + <#assign classInput=properties.kcInputClassCheckboxInput!> + <#assign classLabel=properties.kcInputClassCheckboxLabel!> + - <#if attribute.annotations.inputOptionsFromValidation?? && attribute.validators[attribute.annotations.inputOptionsFromValidation]?? && attribute.validators[attribute.annotations.inputOptionsFromValidation].options??> - <#assign options=attribute.validators[attribute.annotations.inputOptionsFromValidation].options> - <#elseif attribute.validators.options?? && attribute.validators.options.options??> - <#assign options=attribute.validators.options.options> - <#else> - <#assign options=[]> - + <#if attribute.annotations.inputOptionsFromValidation?? && attribute.validators[attribute.annotations.inputOptionsFromValidation]?? && attribute.validators[attribute.annotations.inputOptionsFromValidation].options??> + <#assign options=attribute.validators[attribute.annotations.inputOptionsFromValidation].options> + <#elseif attribute.validators.options?? && attribute.validators.options.options??> + <#assign options=attribute.validators.options.options> + <#else> + <#assign options=[]> + - <#list options as option> -
- disabled - <#if attribute.values?seq_contains(option)>checked - /> - -
- + <#list options as option> +
+ disabled + <#if attribute.values?seq_contains(option)>checked + /> + +
+ */