0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

fix(ui): input fields (#1125)

input fields auto input fix
This commit is contained in:
simeng-li 2022-06-17 09:37:27 +08:00 committed by GitHub
parent 254fac52a7
commit 20f7ad9863
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 94 additions and 94 deletions

View file

@ -13,7 +13,6 @@ type Value = { countryCallingCode?: CountryCallingCode; nationalNumber?: string
export type Props = {
name: string;
autoComplete?: AutoCompleteType;
// eslint-disable-next-line react/boolean-prop-naming
autoFocus?: boolean;
className?: string;
@ -27,7 +26,6 @@ export type Props = {
const PhoneInput = ({
name,
autoComplete,
autoFocus,
className,
placeholder,
@ -50,6 +48,7 @@ const PhoneInput = ({
<span>{`+${countryCallingCode}`}</span>
<DownArrowIcon />
<select
autoComplete="tel-country-code"
onChange={({ target: { value } }) => {
onChange({ countryCallingCode: value });
@ -77,13 +76,13 @@ const PhoneInput = ({
{countrySelector}
<input
ref={inputReference}
type="tel"
inputMode="tel"
autoComplete="tel-national"
autoFocus={autoFocus}
name={name}
placeholder={placeholder}
value={nationalNumber}
type="tel"
inputMode="numeric"
autoComplete={autoComplete}
onFocus={() => {
setOnFocus(true);
}}

View file

@ -83,7 +83,7 @@ describe('Passcode Component', () => {
if (inputElements[2]) {
fireEvent.input(inputElements[2], { target: { value: '37' } });
expect(onChange).toBeCalledWith(['1', '2', '7', '4', '5', '6']);
expect(onChange).toBeCalledWith(['1', '2', '3', '7', '5', '6']);
}
});

View file

@ -23,7 +23,7 @@ export type Props = {
onChange: (value: string[]) => void;
};
const isNumeric = (char: string) => /^\d*$/.test(char);
const isNumeric = (char: string) => /^\d+$/.test(char);
const normalize = (value: string[], length: number): string[] => {
if (value.length > length) {
@ -38,15 +38,6 @@ const normalize = (value: string[], length: number): string[] => {
return value;
};
const trim = (oldValue: string | undefined, newValue: string) => {
// Pop oldValue from the latest input to get the updated Digit
if (newValue.length > 1 && oldValue) {
return newValue.replace(oldValue, '');
}
return newValue;
};
const Passcode = ({ name, className, value, length = defaultLength, error, onChange }: Props) => {
/* eslint-disable @typescript-eslint/ban-types */
const inputReferences = useRef<Array<HTMLInputElement | null>>(
@ -56,6 +47,32 @@ const Passcode = ({ name, className, value, length = defaultLength, error, onCha
const codes = useMemo(() => normalize(value, length), [length, value]);
const updateValue = useCallback(
(data: string, targetId: number) => {
// Filter non-numeric input
if (!isNumeric(data)) {
return;
}
const chars = data.split('');
const trimmedChars = chars.slice(0, Math.min(chars.length, codes.length - targetId));
const value = [
...codes.slice(0, targetId),
...trimmedChars,
...codes.slice(targetId + trimmedChars.length, codes.length),
];
onChange(value);
// Move to the next target
const nextTarget =
inputReferences.current[Math.min(targetId + trimmedChars.length, codes.length - 1)];
nextTarget?.focus();
},
[codes, onChange]
);
const onInputHandler: FormEventHandler<HTMLInputElement> = useCallback(
(event) => {
const { target } = event;
@ -67,72 +84,16 @@ const Passcode = ({ name, className, value, length = defaultLength, error, onCha
const { value, dataset } = target;
// Unrecognized target input field
if (dataset.id === undefined) {
if (!dataset.id) {
return;
}
event.preventDefault();
// Filter non-numeric input
if (!isNumeric(value)) {
return;
}
const targetId = Number(dataset.id);
// Update the root input value
onChange(Object.assign([], codes, { [targetId]: trim(codes[targetId], value) }));
// Move to the next target
if (value) {
const nextTarget = inputReferences.current[targetId + 1];
nextTarget?.focus();
}
updateValue(value, Number(dataset.id));
},
[codes, onChange]
[updateValue]
);
const onKeyDownHandler: KeyboardEventHandler<HTMLInputElement> = useCallback((event) => {
const { key, target } = event;
if (!(target instanceof HTMLInputElement)) {
return;
}
const { value, dataset } = target;
if (!dataset.id) {
return;
}
const targetId = Number(dataset.id);
const nextTarget = inputReferences.current[targetId + 1];
const previousTarget = inputReferences.current[targetId - 1];
switch (key) {
case 'Backspace':
if (!value) {
previousTarget?.focus();
}
break;
case 'ArrowLeft':
event.preventDefault();
previousTarget?.focus();
break;
case 'ArrowRight':
event.preventDefault();
nextTarget?.focus();
break;
case 'ArrowUp':
case 'ArrowDown':
event.preventDefault();
break;
default:
break;
}
}, []);
const onPasteHandler: ClipboardEventHandler<HTMLInputElement> = useCallback(
(event) => {
if (!(event.target instanceof HTMLInputElement)) {
@ -144,29 +105,64 @@ const Passcode = ({ name, className, value, length = defaultLength, error, onCha
clipboardData,
} = event;
const data = clipboardData.getData('text');
// Unrecognized target input field
if (!dataset.id) {
return;
}
event.preventDefault();
updateValue(data, Number(dataset.id));
},
[updateValue]
);
const data = clipboardData.getData('text');
const onKeyDownHandler: KeyboardEventHandler<HTMLInputElement> = useCallback(
(event) => {
const { key, target } = event;
if (!data || !isNumeric(data)) {
if (!(target instanceof HTMLInputElement)) {
return;
}
const { value, dataset } = target;
if (!dataset.id) {
return;
}
const chars = data.split('');
const targetId = Number(dataset.id);
const trimmedChars = chars.slice(0, Math.min(chars.length, codes.length - targetId));
const value = [
...codes.slice(0, targetId),
...trimmedChars,
...codes.slice(targetId + trimmedChars.length, codes.length),
];
const nextTarget = inputReferences.current[targetId + 1];
const previousTarget = inputReferences.current[targetId - 1];
onChange(value);
switch (key) {
case 'Backspace':
event.preventDefault();
if (!value) {
previousTarget?.focus();
break;
}
onChange(Object.assign([], codes, { [targetId]: '' }));
break;
case 'ArrowLeft':
event.preventDefault();
previousTarget?.focus();
break;
case 'ArrowRight':
event.preventDefault();
nextTarget?.focus();
break;
case 'ArrowUp':
case 'ArrowDown':
event.preventDefault();
break;
default:
break;
}
},
[codes, onChange]
);
@ -191,9 +187,9 @@ const Passcode = ({ name, className, value, length = defaultLength, error, onCha
name={`${name}_${index}`}
data-id={index}
value={codes[index]}
type="text" // Number type allows 'e' as input but returns empty value
type="number"
inputMode="numeric"
maxLength={2} // Allow overwrite input
pattern="[0-9]*"
autoComplete="off"
onPaste={onPasteHandler}
onInput={onInputHandler}

View file

@ -94,6 +94,7 @@ const CreateAccount = ({ className, autoFocus }: Props) => {
className={styles.inputField}
name="password"
type="password"
autoComplete="new-password"
placeholder={t('input.password')}
{...fieldRegister('password', passwordValidation)}
onClear={() => {
@ -104,6 +105,7 @@ const CreateAccount = ({ className, autoFocus }: Props) => {
className={styles.inputField}
name="confirm_password"
type="password"
autoComplete="new-password"
placeholder={t('input.confirm_password')}
{...fieldRegister('confirmPassword', (confirmPassword) =>
confirmPasswordValidation(fieldValue.password, confirmPassword)

View file

@ -99,11 +99,13 @@ const EmailPasswordless = ({ type, autoFocus, className }: Props) => {
<>
<form className={classNames(styles.form, className)}>
<Input
autoFocus={autoFocus}
className={styles.inputField}
type="email"
name="email"
autoComplete="email"
inputMode="email"
placeholder={t('input.email')}
autoFocus={autoFocus}
className={styles.inputField}
{...register('email', emailValidation)}
onClear={() => {
setFieldValue((state) => ({ ...state, email: '' }));

View file

@ -115,9 +115,8 @@ const PhonePasswordless = ({ type, autoFocus, className }: Props) => {
<form className={classNames(styles.form, className)}>
<PhoneInput
name="phone"
className={styles.inputField}
autoComplete="mobile"
placeholder={t('input.phone_number')}
className={styles.inputField}
countryCallingCode={phoneNumber.countryCallingCode}
nationalNumber={phoneNumber.nationalNumber}
autoFocus={autoFocus}

View file

@ -7,7 +7,7 @@ import { useEffect, useState } from 'react';
import * as styles from '@/App.module.scss';
import { Context } from '@/hooks/use-page-context';
import initI18n from '@/i18n/init';
import { SignInExperienceSettingsResponse, Platform } from '@/types';
import { SignInExperienceSettingsResponse, SignInExperienceSettings, Platform } from '@/types';
import { parseQueryParameters } from '@/utils';
import { getPrimarySignInMethod, getSecondarySignInMethods } from '@/utils/sign-in-experience';
import { filterPreviewSocialConnectors } from '@/utils/social-connectors';
@ -66,7 +66,7 @@ const usePreview = (context: Context): [boolean, PreviewConfig?] => {
platform,
} = previewConfig;
const experienceSettings = {
const experienceSettings: SignInExperienceSettings = {
...rest,
branding: {
...branding,

View file

@ -69,7 +69,9 @@ type AutoCompleteType =
| 'sex'
| 'url'
| 'photo'
| 'mobile';
| 'tel'
| 'tel-country-code'
| 'tel-national';
// TO-DO: remove me
interface Body {