Done with select tag
This commit is contained in:
parent
41c2685dc4
commit
b17724fdda
@ -6,7 +6,7 @@ import {
|
|||||||
type KcContextLike,
|
type KcContextLike,
|
||||||
type FormAction,
|
type FormAction,
|
||||||
type FormFieldError,
|
type FormFieldError,
|
||||||
FormFieldState
|
type FormFieldState
|
||||||
} from "keycloakify/login/lib/useUserProfileForm";
|
} 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";
|
||||||
@ -23,11 +23,10 @@ export type UserProfileFormFieldsProps = {
|
|||||||
|
|
||||||
type BeforeAfterFieldProps = {
|
type BeforeAfterFieldProps = {
|
||||||
attribute: Attribute;
|
attribute: Attribute;
|
||||||
index: number;
|
|
||||||
value: string;
|
|
||||||
dispatchFormAction: React.Dispatch<FormAction>;
|
dispatchFormAction: React.Dispatch<FormAction>;
|
||||||
formFieldErrors: FormFieldError[];
|
displayableErrors: FormFieldError[];
|
||||||
i18n: I18n;
|
i18n: I18n;
|
||||||
|
valueOrValues: string | string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
// NOTE: Enabled by default but it's a UX best practice to set it to false.
|
// NOTE: Enabled by default but it's a UX best practice to set it to false.
|
||||||
@ -55,14 +54,14 @@ export function UserProfileFormFields(props: UserProfileFormFieldsProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{formFieldStates.map(({ index, value, attribute, displayableErrors }) => {
|
{formFieldStates.map(({ attribute, displayableErrors, valueOrValues }) => {
|
||||||
const formGroupClassName = clsx(
|
const formGroupClassName = clsx(
|
||||||
getClassName("kcFormGroupClass"),
|
getClassName("kcFormGroupClass"),
|
||||||
displayableErrors.length !== 0 && getClassName("kcFormGroupErrorClass")
|
displayableErrors.length !== 0 && getClassName("kcFormGroupErrorClass")
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment key={`${attribute.name}-${index}`}>
|
<Fragment key={attribute.name}>
|
||||||
<GroupLabel
|
<GroupLabel
|
||||||
attribute={attribute}
|
attribute={attribute}
|
||||||
getClassName={getClassName}
|
getClassName={getClassName}
|
||||||
@ -73,11 +72,10 @@ export function UserProfileFormFields(props: UserProfileFormFieldsProps) {
|
|||||||
{BeforeField !== undefined && (
|
{BeforeField !== undefined && (
|
||||||
<BeforeField
|
<BeforeField
|
||||||
attribute={attribute}
|
attribute={attribute}
|
||||||
index={index}
|
|
||||||
value={value}
|
|
||||||
dispatchFormAction={dispatchFormAction}
|
dispatchFormAction={dispatchFormAction}
|
||||||
formFieldErrors={displayableErrors}
|
displayableErrors={displayableErrors}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
valueOrValues={valueOrValues}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
@ -91,7 +89,7 @@ export function UserProfileFormFields(props: UserProfileFormFieldsProps) {
|
|||||||
{attribute.required && <>*</>}
|
{attribute.required && <>*</>}
|
||||||
</div>
|
</div>
|
||||||
<div className={getClassName("kcInputWrapperClass")}>
|
<div className={getClassName("kcInputWrapperClass")}>
|
||||||
{attribute.annotations.inputHelperTextBefore !== undefined && index === 0 && (
|
{attribute.annotations.inputHelperTextBefore !== undefined && (
|
||||||
<div
|
<div
|
||||||
className={getClassName("kcInputHelperTextBeforeClass")}
|
className={getClassName("kcInputHelperTextBeforeClass")}
|
||||||
id={`form-help-text-before-${attribute.name}`}
|
id={`form-help-text-before-${attribute.name}`}
|
||||||
@ -108,7 +106,8 @@ export function UserProfileFormFields(props: UserProfileFormFieldsProps) {
|
|||||||
getClassName={getClassName}
|
getClassName={getClassName}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
/>
|
/>
|
||||||
{attribute.multivalued && (
|
|
||||||
|
{/*attribute.multivalued && (
|
||||||
<AddRemoveButtonsMultiValuedAttribute
|
<AddRemoveButtonsMultiValuedAttribute
|
||||||
formFieldStates={formFieldStates}
|
formFieldStates={formFieldStates}
|
||||||
attribute={attribute}
|
attribute={attribute}
|
||||||
@ -116,19 +115,19 @@ export function UserProfileFormFields(props: UserProfileFormFieldsProps) {
|
|||||||
dispatchFormAction={dispatchFormAction}
|
dispatchFormAction={dispatchFormAction}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
/>
|
/>
|
||||||
)}
|
)*/}
|
||||||
{displayableErrors.length !== 0 && (
|
{displayableErrors.length !== 0 && (
|
||||||
<FieldErrors
|
<FieldErrors
|
||||||
attribute={attribute}
|
attribute={attribute}
|
||||||
index={index}
|
|
||||||
getClassName={getClassName}
|
getClassName={getClassName}
|
||||||
displayableErrors={displayableErrors}
|
displayableErrors={displayableErrors}
|
||||||
|
fieldIndex={undefined}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{attribute.annotations.inputHelperTextAfter !== undefined && index === 0 && (
|
{attribute.annotations.inputHelperTextAfter !== undefined && (
|
||||||
<div
|
<div
|
||||||
className={getClassName("kcInputHelperTextAfterClass")}
|
className={getClassName("kcInputHelperTextAfterClass")}
|
||||||
id={`form-help-text-before-${attribute.name}`}
|
id={`form-help-text-after-${attribute.name}`}
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
>
|
>
|
||||||
{advancedMsg(attribute.annotations.inputHelperTextAfter)}
|
{advancedMsg(attribute.annotations.inputHelperTextAfter)}
|
||||||
@ -138,11 +137,10 @@ export function UserProfileFormFields(props: UserProfileFormFieldsProps) {
|
|||||||
{AfterField !== undefined && (
|
{AfterField !== undefined && (
|
||||||
<AfterField
|
<AfterField
|
||||||
attribute={attribute}
|
attribute={attribute}
|
||||||
index={index}
|
|
||||||
value={value}
|
|
||||||
dispatchFormAction={dispatchFormAction}
|
dispatchFormAction={dispatchFormAction}
|
||||||
formFieldErrors={displayableErrors}
|
displayableErrors={displayableErrors}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
valueOrValues={valueOrValues}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{/*
|
{/*
|
||||||
@ -254,27 +252,29 @@ function GroupLabel(props: {
|
|||||||
|
|
||||||
function FieldErrors(props: {
|
function FieldErrors(props: {
|
||||||
attribute: Attribute;
|
attribute: Attribute;
|
||||||
index: number;
|
|
||||||
getClassName: UserProfileFormFieldsProps["getClassName"];
|
getClassName: UserProfileFormFieldsProps["getClassName"];
|
||||||
displayableErrors: FormFieldError[];
|
displayableErrors: FormFieldError[];
|
||||||
|
fieldIndex: number | undefined;
|
||||||
}) {
|
}) {
|
||||||
const { attribute, index, getClassName, displayableErrors } = props;
|
const { attribute, getClassName, displayableErrors, fieldIndex } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
id={`input-error-${attribute.name}${index === 0 ? "" : `-${index + 1}`}`}
|
id={`input-error-${attribute.name}${fieldIndex === undefined ? "" : `-${fieldIndex}`}`}
|
||||||
className={getClassName("kcInputErrorMessageClass")}
|
className={getClassName("kcInputErrorMessageClass")}
|
||||||
style={{
|
style={{
|
||||||
"position": displayableErrors.length === 1 ? "absolute" : undefined
|
"position": displayableErrors.length === 1 ? "absolute" : undefined
|
||||||
}}
|
}}
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
>
|
>
|
||||||
{displayableErrors.map(({ errorMessage }, i, arr) => (
|
{displayableErrors
|
||||||
<>
|
.filter(error => error.fieldIndex === fieldIndex)
|
||||||
<span key={i}>{errorMessage}</span>
|
.map(({ errorMessage }, i, arr) => (
|
||||||
{arr.length - 1 !== i && <br />}
|
<>
|
||||||
</>
|
<span key={i}>{errorMessage}</span>
|
||||||
))}
|
{arr.length - 1 !== i && <br />}
|
||||||
|
</>
|
||||||
|
))}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -398,8 +398,7 @@ function AddRemoveButtonsMultiValuedAttribute(props: {
|
|||||||
|
|
||||||
type PropsOfInputFiledByType = {
|
type PropsOfInputFiledByType = {
|
||||||
attribute: Attribute;
|
attribute: Attribute;
|
||||||
index: number;
|
valueOrValues: string | string[];
|
||||||
value: string;
|
|
||||||
displayableErrors: FormFieldError[];
|
displayableErrors: FormFieldError[];
|
||||||
formValidationDispatch: React.Dispatch<FormAction>;
|
formValidationDispatch: React.Dispatch<FormAction>;
|
||||||
getClassName: UserProfileFormFieldsProps["getClassName"];
|
getClassName: UserProfileFormFieldsProps["getClassName"];
|
||||||
@ -407,7 +406,7 @@ type PropsOfInputFiledByType = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function InputFiledByType(props: PropsOfInputFiledByType) {
|
function InputFiledByType(props: PropsOfInputFiledByType) {
|
||||||
const { attribute } = props;
|
const { attribute, valueOrValues } = props;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
<#macro inputFieldByType attribute>
|
<#macro inputFieldByType attribute>
|
||||||
@ -445,10 +444,24 @@ function InputFiledByType(props: PropsOfInputFiledByType) {
|
|||||||
case "multiselect-checkboxes":
|
case "multiselect-checkboxes":
|
||||||
return <InputTagSelects {...props} />;
|
return <InputTagSelects {...props} />;
|
||||||
default:
|
default:
|
||||||
return <InputTag {...props} />;
|
if (valueOrValues instanceof Array) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{valueOrValues.map((...[, i]) => (
|
||||||
|
<InputTag key={i} {...props} fieldIndex={i} />
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <InputTag {...props} fieldIndex={undefined} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function InputTag(props: PropsOfInputFiledByType & { fieldIndex: number | undefined }) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function InputTagSelects(props: PropsOfInputFiledByType) {
|
function InputTagSelects(props: PropsOfInputFiledByType) {
|
||||||
/*
|
/*
|
||||||
<#macro inputTagSelects attribute>
|
<#macro inputTagSelects attribute>
|
||||||
@ -485,31 +498,37 @@ function InputTagSelects(props: PropsOfInputFiledByType) {
|
|||||||
</#macro>
|
</#macro>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { attribute, formValidationDispatch, getClassName, index } = props;
|
const { attribute, formValidationDispatch, getClassName, valueOrValues } = props;
|
||||||
|
|
||||||
const { advancedMsg } = props.i18n;
|
const { advancedMsg } = props.i18n;
|
||||||
|
|
||||||
const { classDiv, classInput, classLabel, inputType } =
|
const { classDiv, classInput, classLabel, inputType } = (() => {
|
||||||
attribute.annotations.inputType === "select-radiobuttons"
|
const { inputType } = attribute.annotations;
|
||||||
? {
|
|
||||||
"inputType": "radio",
|
assert(inputType === "select-radiobuttons" || inputType === "multiselect-checkboxes");
|
||||||
"classDiv": getClassName("kcInputClassRadio"),
|
|
||||||
"classInput": getClassName("kcInputClassRadioInput"),
|
switch (inputType) {
|
||||||
"classLabel": getClassName("kcInputClassRadioLabel")
|
case "select-radiobuttons":
|
||||||
}
|
return {
|
||||||
: {
|
"inputType": "radio",
|
||||||
"inputType": "checkbox",
|
"classDiv": getClassName("kcInputClassRadio"),
|
||||||
"classDiv": getClassName("kcInputClassCheckbox"),
|
"classInput": getClassName("kcInputClassRadioInput"),
|
||||||
"classInput": getClassName("kcInputClassCheckboxInput"),
|
"classLabel": getClassName("kcInputClassRadioLabel")
|
||||||
"classLabel": getClassName("kcInputClassCheckboxLabel")
|
};
|
||||||
};
|
case "multiselect-checkboxes":
|
||||||
|
return {
|
||||||
|
"inputType": "checkbox",
|
||||||
|
"classDiv": getClassName("kcInputClassCheckbox"),
|
||||||
|
"classInput": getClassName("kcInputClassCheckboxInput"),
|
||||||
|
"classLabel": getClassName("kcInputClassCheckboxLabel")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
const options = (() => {
|
const options = (() => {
|
||||||
walk: {
|
walk: {
|
||||||
const { inputOptionsFromValidation } = attribute.annotations;
|
const { inputOptionsFromValidation } = attribute.annotations;
|
||||||
|
|
||||||
assert(typeof inputOptionsFromValidation === "string");
|
|
||||||
|
|
||||||
if (inputOptionsFromValidation === undefined) {
|
if (inputOptionsFromValidation === undefined) {
|
||||||
break walk;
|
break walk;
|
||||||
}
|
}
|
||||||
@ -536,26 +555,41 @@ function InputTagSelects(props: PropsOfInputFiledByType) {
|
|||||||
<div key={option} className={classDiv}>
|
<div key={option} className={classDiv}>
|
||||||
<input
|
<input
|
||||||
type={inputType}
|
type={inputType}
|
||||||
id={`${attribute.name}-${option}-${index === 0 ? "" : index + 1}`}
|
id={`${attribute.name}-${option}`}
|
||||||
name={attribute.name}
|
name={attribute.name}
|
||||||
value={option}
|
value={option}
|
||||||
className={classInput}
|
className={classInput}
|
||||||
aria-invalid={props.displayableErrors.length !== 0}
|
aria-invalid={props.displayableErrors.length !== 0}
|
||||||
disabled={attribute.readOnly}
|
disabled={attribute.readOnly}
|
||||||
checked={props.value === option}
|
checked={valueOrValues.includes(option)}
|
||||||
onChange={() =>
|
onChange={event =>
|
||||||
formValidationDispatch({
|
formValidationDispatch({
|
||||||
"action": "update value",
|
"action": "update",
|
||||||
"name": attribute.name,
|
"name": attribute.name,
|
||||||
"index": props.index,
|
"valueOrValues": (() => {
|
||||||
"newValue": option
|
const isChecked = event.target.checked;
|
||||||
|
|
||||||
|
if (valueOrValues instanceof Array) {
|
||||||
|
const newValues = [...valueOrValues];
|
||||||
|
|
||||||
|
if (isChecked) {
|
||||||
|
newValues.push(option);
|
||||||
|
} else {
|
||||||
|
newValues.splice(newValues.indexOf(option), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
return event.target.checked ? option : "";
|
||||||
|
})()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
onBlur={() =>
|
onBlur={() =>
|
||||||
formValidationDispatch({
|
formValidationDispatch({
|
||||||
"action": "focus lost",
|
"action": "focus lost",
|
||||||
"name": attribute.name,
|
"name": attribute.name,
|
||||||
"index": props.index
|
"fieldIndex": undefined
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -572,11 +606,15 @@ function InputTagSelects(props: PropsOfInputFiledByType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function TextareaTag(props: PropsOfInputFiledByType) {
|
function TextareaTag(props: PropsOfInputFiledByType) {
|
||||||
const { attribute, index, value, formValidationDispatch, getClassName, displayableErrors } = props;
|
const { attribute, formValidationDispatch, getClassName, displayableErrors, valueOrValues } = props;
|
||||||
|
|
||||||
|
assert(typeof valueOrValues === "string");
|
||||||
|
|
||||||
|
const value = valueOrValues;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<textarea
|
<textarea
|
||||||
id={`${attribute.name}-${index === 0 ? "" : index + 1}`}
|
id={attribute.name}
|
||||||
name={attribute.name}
|
name={attribute.name}
|
||||||
className={getClassName("kcInputClass")}
|
className={getClassName("kcInputClass")}
|
||||||
aria-invalid={displayableErrors.length !== 0}
|
aria-invalid={displayableErrors.length !== 0}
|
||||||
@ -587,17 +625,16 @@ function TextareaTag(props: PropsOfInputFiledByType) {
|
|||||||
value={value}
|
value={value}
|
||||||
onChange={event =>
|
onChange={event =>
|
||||||
formValidationDispatch({
|
formValidationDispatch({
|
||||||
"action": "update value",
|
"action": "update",
|
||||||
"name": attribute.name,
|
"name": attribute.name,
|
||||||
index,
|
"valueOrValues": event.target.value
|
||||||
"newValue": event.target.value
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
onBlur={() =>
|
onBlur={() =>
|
||||||
formValidationDispatch({
|
formValidationDispatch({
|
||||||
"action": "focus lost",
|
"action": "focus lost",
|
||||||
"name": attribute.name,
|
"name": attribute.name,
|
||||||
index
|
"fieldIndex": undefined
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -605,7 +642,7 @@ function TextareaTag(props: PropsOfInputFiledByType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function SelectTag(props: PropsOfInputFiledByType) {
|
function SelectTag(props: PropsOfInputFiledByType) {
|
||||||
const { attribute, index, value, formValidationDispatch, getClassName, displayableErrors, i18n } = props;
|
const { attribute, formValidationDispatch, getClassName, displayableErrors, i18n, valueOrValues } = props;
|
||||||
|
|
||||||
const { advancedMsg } = i18n;
|
const { advancedMsg } = i18n;
|
||||||
|
|
||||||
@ -613,31 +650,36 @@ function SelectTag(props: PropsOfInputFiledByType) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<select
|
<select
|
||||||
id={`${attribute.name}-${index === 0 ? "" : index + 1}`}
|
id={attribute.name}
|
||||||
name={attribute.name}
|
name={attribute.name}
|
||||||
className={getClassName("kcInputClass")}
|
className={getClassName("kcInputClass")}
|
||||||
aria-invalid={displayableErrors.length !== 0}
|
aria-invalid={displayableErrors.length !== 0}
|
||||||
disabled={attribute.readOnly}
|
disabled={attribute.readOnly}
|
||||||
multiple={isMultiple}
|
multiple={isMultiple}
|
||||||
size={attribute.annotations.inputTypeSize === undefined ? undefined : parseInt(attribute.annotations.inputTypeSize)}
|
size={attribute.annotations.inputTypeSize === undefined ? undefined : parseInt(attribute.annotations.inputTypeSize)}
|
||||||
value={value}
|
value={valueOrValues}
|
||||||
onChange={event =>
|
onChange={event =>
|
||||||
formValidationDispatch({
|
formValidationDispatch({
|
||||||
"action": "update value",
|
"action": "update",
|
||||||
"name": attribute.name,
|
"name": attribute.name,
|
||||||
index,
|
"valueOrValues": (() => {
|
||||||
"newValue": event.target.value
|
if (isMultiple) {
|
||||||
|
return Array.from(event.target.selectedOptions).map(option => option.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return event.target.value;
|
||||||
|
})()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
onBlur={() =>
|
onBlur={() =>
|
||||||
formValidationDispatch({
|
formValidationDispatch({
|
||||||
"action": "focus lost",
|
"action": "focus lost",
|
||||||
"name": attribute.name,
|
"name": attribute.name,
|
||||||
index
|
"fieldIndex": undefined
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{attribute.annotations.inputType === "select" && <option value=""></option>}
|
{!isMultiple && <option value=""></option>}
|
||||||
{(() => {
|
{(() => {
|
||||||
const options = (() => {
|
const options = (() => {
|
||||||
walk: {
|
walk: {
|
||||||
@ -666,7 +708,7 @@ function SelectTag(props: PropsOfInputFiledByType) {
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
return options.map(option => (
|
return options.map(option => (
|
||||||
<option key={option} value={option} selected={value === option}>
|
<option key={option} value={option}>
|
||||||
{(() => {
|
{(() => {
|
||||||
if (attribute.annotations.inputOptionLabels !== undefined) {
|
if (attribute.annotations.inputOptionLabels !== undefined) {
|
||||||
const { inputOptionLabels } = attribute.annotations;
|
const { inputOptionLabels } = attribute.annotations;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user