Skip to content

Commit 0486d14

Browse files
author
Kubit
committed
Enhance Input Component with internalErrorExecution Prop for Flexible Validation
This update introduces the internalErrorExecution prop to the Input component, offering enhanced control over the timing of internal error validation, particularly for email inputs. By default set to onChangeOnBlur, this new optional property allows developers to specify if the internal email validation should trigger on blur events, providing greater flexibility in error handling and improving user experience by reducing the frequency of validation messages during input.
1 parent 5bc23a2 commit 0486d14

28 files changed

+211
-72
lines changed

src/components/input/hooks/useInternalValidations.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const useInternalValidations = (
2727
}
2828
// Min length validation
2929
const minLengthValidation = () => {
30-
if (value && type === InputTypeType.TEXT && minLength && value.length < minLength) {
30+
if (type === InputTypeType.TEXT && minLength && value.length < minLength) {
3131
errors.push(InternalErrorType.INVALID_MIN_LENGTH);
3232
}
3333
};

src/components/input/inputStandAlone.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import {
3131
LABEL_TYPE,
3232
MultipleRef,
3333
} from './types';
34-
import { buildAriaLabelledBy, hasError, isDisabled } from './utils';
34+
import { buildAriaDescribedBy, buildAriaLabelledBy, hasError, isDisabled } from './utils';
3535

3636
const InputStandAloneComponent = (
3737
{
@@ -77,15 +77,19 @@ const InputStandAloneComponent = (
7777
aria-haspopup={props['aria-haspopup']}
7878
aria-invalid={hasError(state)}
7979
aria-labelledby={buildAriaLabelledBy({
80-
extraAriaLabelledBy,
8180
labelId,
81+
extraAriaLabelledBy,
8282
helpMessage: helpMessage?.content as string,
8383
helpMessageId,
84-
errorMessage: errorMessage?.content,
84+
state: state,
85+
})}
86+
{...ariaProps}
87+
aria-describedby={buildAriaDescribedBy({
88+
ariaDescribedBy: props['aria-describedby'],
89+
errorMessage: errorMessage?.content as string,
8590
errorMessageId,
8691
state,
8792
})}
88-
{...ariaProps}
8993
autoCapitalize={autoCapitalize}
9094
autoComplete={autocomplete}
9195
cursorPointer={styles?.[state]?.inputContainer?.cursor}
@@ -149,7 +153,9 @@ const InputStandAloneComponent = (
149153
disabled={isDisabled(state)}
150154
icon={{ dataTestId: `${props.dataTestId}Icon`, ...props.icon }}
151155
iconPosition={props.iconPosition}
156+
leftIcon={props.leftIcon}
152157
loading={props.loading}
158+
rightIcon={props.rightIcon}
153159
state={state}
154160
styles={styles?.[state]}
155161
/>

src/components/input/inputUnControlled.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@ import * as React from 'react';
33
import { useInput } from '@/hooks/useInput/useInput';
44

55
import { InputControlled } from './inputControlled';
6-
import { IInputUnControlled, InputTypeType } from './types';
6+
import { IInputUnControlled, INTERNAL_ERROR_EXECUTION, InputTypeType } from './types';
77

88
const InputUnControlledComponent = <V extends string | unknown>(
99
{
1010
type = InputTypeType.TEXT,
1111
truncate = false,
12+
internalErrorExecution = INTERNAL_ERROR_EXECUTION.ON_CHANGE_ON_BLUR,
13+
disabledArrowUpDownInputNumber = false,
14+
disabledWheelMouse = true,
1215
errorExecution,
1316
keyValidation,
14-
disabledArrowUpDownInputNumber = false,
1517
min,
1618
max,
1719
maxLength,
@@ -26,7 +28,6 @@ const InputUnControlledComponent = <V extends string | unknown>(
2628
ignoreKeys,
2729
formatNumber,
2830
locale,
29-
disabledWheelMouse = true,
3031
onBlur,
3132
onChange,
3233
onFocus,
@@ -50,6 +51,7 @@ const InputUnControlledComponent = <V extends string | unknown>(
5051
} = useInput({
5152
ref,
5253
errorExecution,
54+
internalErrorExecution,
5355
keyValidation,
5456
disabledArrowUpDownInputNumber,
5557
max,

src/components/input/stories/argtypes.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ import { IThemeObjectVariants } from '@/designSystem/themesObject';
33
import { ArgTypesReturn, ROLES } from '@/types';
44
import { AriaLiveOptionType } from '@/types/ariaLiveOption';
55

6-
import { AUTOCAPITALIZE_TYPE, ERROR_EXECUTION, MASK_TYPE } from '../types/input';
6+
import {
7+
AUTOCAPITALIZE_TYPE,
8+
ERROR_EXECUTION,
9+
INTERNAL_ERROR_EXECUTION,
10+
MASK_TYPE,
11+
} from '../types/input';
712
import { InputIconPosition } from '../types/inputTheme';
813
import { InputTypeType } from '../types/inputType';
914

@@ -324,6 +329,20 @@ export const argtypes = (variants: IThemeObjectVariants, themeSelected: string):
324329
category: CATEGORY_CONTROL.MODIFIERS,
325330
},
326331
},
332+
internalErrorExecution: {
333+
control: { type: 'select' },
334+
description: 'When internal error handling occurs',
335+
options: ['onChange', 'onBlur', 'onChangeOnBlur'],
336+
type: { name: 'string' },
337+
table: {
338+
type: {
339+
summary: 'INTERNAL_ERROR_EXECUTION',
340+
detail: Object.keys(INTERNAL_ERROR_EXECUTION).join(', '),
341+
},
342+
defaultValue: { summary: INTERNAL_ERROR_EXECUTION.ON_CHANGE_ON_BLUR },
343+
category: CATEGORY_CONTROL.MODIFIERS,
344+
},
345+
},
327346
errorMessage: {
328347
description:
329348
'Object with error message properties. In the error state, the field is accompanied by a description of the problem to help resolve it.',

src/components/input/types/input.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ export enum ERROR_EXECUTION {
7070
ON_BLUR = 'onBlur',
7171
}
7272

73+
export enum INTERNAL_ERROR_EXECUTION {
74+
ON_CHANGE = 'onChange',
75+
ON_BLUR = 'onBlur',
76+
ON_CHANGE_ON_BLUR = 'onChangeOnBlur',
77+
}
78+
7379
export enum AUTOCAPITALIZE_TYPE {
7480
OFF = 'off',
7581
NONE = 'none',
@@ -421,6 +427,7 @@ export type IInputUnControlled<V = undefined extends string ? unknown : string>
421427
disabled?: boolean;
422428
value?: string | number;
423429
errorExecution?: ERROR_EXECUTION;
430+
internalErrorExecution?: INTERNAL_ERROR_EXECUTION;
424431
keyValidation?: string;
425432
regex?: string | RegExp;
426433
// functions

src/components/input/utils/aria.utils.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,41 @@ export const buildAriaLabelledBy = ({
1515
helpMessage?: React.ReactNode;
1616
helpMessageId?: string;
1717
errorMessage?: string;
18-
errorMessageId: string;
18+
errorMessageId?: string;
1919
state?: InputState;
2020
}): string => {
2121
let res = labelId;
2222
if (extraAriaLabelledBy) {
2323
res += ` ${extraAriaLabelledBy}`;
2424
}
25-
if (errorMessage && hasError(state)) {
25+
if (errorMessageId && errorMessage && hasError(state)) {
2626
res += ` ${errorMessageId}`;
2727
}
28-
if (helpMessage) {
28+
if (helpMessageId && helpMessage) {
2929
res += ` ${helpMessageId}`;
3030
}
3131
return res;
3232
};
33+
34+
export const buildAriaDescribedBy = ({
35+
ariaDescribedBy,
36+
errorMessage,
37+
errorMessageId,
38+
state,
39+
}: {
40+
ariaDescribedBy?: string;
41+
errorMessage?: string;
42+
errorMessageId?: string;
43+
state?: InputState;
44+
}): string | undefined => {
45+
if (!ariaDescribedBy && !errorMessage) return;
46+
47+
let res: string = '';
48+
if (ariaDescribedBy) {
49+
res += ` ${ariaDescribedBy}`;
50+
}
51+
if (errorMessageId && errorMessage && hasError(state)) {
52+
res += ` ${errorMessageId}`;
53+
}
54+
return res;
55+
};

src/components/inputCounter/inputCounter.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { STYLES_NAME } from '@/constants';
44
import { useId, useInput, useStyles } from '@/hooks';
55
import { ErrorBoundary, FallbackComponent } from '@/provider/errorBoundary';
66

7-
import { InputTypeType } from '../input/types';
7+
import { INTERNAL_ERROR_EXECUTION, InputTypeType } from '../input/types';
88
import { InputCounterStandAlone } from './inputCounterStandAlone';
99
import { IInputCounter, IInputCounterStandAlone, InputCounterStylesProps } from './types';
1010

@@ -13,6 +13,7 @@ const InputCounterComponent = React.forwardRef(
1313
{
1414
type = InputTypeType.TEXT,
1515
truncate = false,
16+
internalErrorExecution = INTERNAL_ERROR_EXECUTION.ON_CHANGE_ON_BLUR,
1617
errorExecution,
1718
keyValidation,
1819
min,
@@ -57,6 +58,7 @@ const InputCounterComponent = React.forwardRef(
5758
} = useInput({
5859
ref,
5960
errorExecution,
61+
internalErrorExecution,
6062
keyValidation,
6163
max,
6264
min,

src/components/inputCounter/types/inputCounter.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ERROR_EXECUTION, IInputStandAlone } from '@/components/input';
1+
import { ERROR_EXECUTION, IInputStandAlone, INTERNAL_ERROR_EXECUTION } from '@/components/input';
22
import { ITextCountControlled } from '@/components/textCount';
33
import { CustomTokenTypes } from '@/types';
44

@@ -36,6 +36,7 @@ export interface IInputCounter<V = undefined extends string ? unknown : string>
3636
error?: boolean;
3737
disabled?: boolean;
3838
errorExecution?: ERROR_EXECUTION;
39+
internalErrorExecution?: INTERNAL_ERROR_EXECUTION;
3940
keyValidation?: string;
4041
regex?: string | RegExp;
4142
onInternalErrors?: (errors: string[]) => void;

src/components/inputCurrency/__tests__/inputCurrency.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as React from 'react';
33

44
import { axe } from 'jest-axe';
55

6-
import { FormatNumber, InputContentPosition, InputState } from '@/components/input/types';
6+
import { InputContentPosition, InputState } from '@/components/input/types';
77
import { renderProvider } from '@/tests/renderProvider/renderProvider.utility';
88
import { POSITIONS } from '@/types/positions';
99

@@ -28,7 +28,7 @@ const format = {
2828
style: 'decimal',
2929
maximumFractionDigits: 3,
3030
minimumFractionDigits: 1,
31-
} as FormatNumber;
31+
};
3232

3333
describe('New Input Currency Component', () => {
3434
test('Should render InputCurrency component', async () => {

src/components/inputCurrency/inputCurrency.tsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useStyles } from '@/hooks/useStyles/useStyles';
55
import { ErrorBoundary, FallbackComponent } from '@/provider/errorBoundary';
66

77
// helpers
8-
import { InputTypeType } from '../input';
8+
import { INTERNAL_ERROR_EXECUTION, InputTypeType } from '../input';
99
import { InputCurrencyStandAlone } from './inputCurrencyStandAlone';
1010
import { IInputCurrency, IInputCurrencyStandAlone, InputCurrencyStylesProps } from './types';
1111

@@ -17,6 +17,11 @@ const InputCurrencyComponent = React.forwardRef(
1717
maxDecimals = 2,
1818
truncate = true,
1919
min = 0,
20+
internalErrorExecution = INTERNAL_ERROR_EXECUTION.ON_CHANGE_ON_BLUR,
21+
disabledArrowUpDownInputNumber = false,
22+
ignoreKeys = ['+', '-', 'e'],
23+
disabledWheelMouse = true,
24+
type = InputTypeType.NUMBER,
2025
max,
2126
errorExecution,
2227
keyValidation,
@@ -25,32 +30,25 @@ const InputCurrencyComponent = React.forwardRef(
2530
mask,
2631
maskType,
2732
disabled,
28-
disabledArrowUpDownInputNumber = false,
2933
error,
3034
value: currentValue,
3135
informationAssociatedValue,
32-
ignoreKeys = ['+', '-', 'e'],
3336
regex,
3437
formatNumber,
3538
locale,
36-
disabledWheelMouse = true,
39+
variant,
3740
onBlur,
3841
onChange,
3942
onFocus,
4043
onKeyDown,
4144
onError,
4245
onInternalErrors,
4346
ctv,
44-
type = InputTypeType.NUMBER,
4547
...props
4648
}: IInputCurrency<V>,
4749
ref: React.ForwardedRef<HTMLInputElement | undefined>
4850
): JSX.Element => {
49-
const styles = useStyles<InputCurrencyStylesProps, V>(
50-
INPUT_CURRENCY_STYLES,
51-
props.variant,
52-
ctv
53-
);
51+
const styles = useStyles<InputCurrencyStylesProps, V>(INPUT_CURRENCY_STYLES, variant, ctv);
5452
const inputCurrencyType = formatNumber ? InputTypeType.TEXT : type;
5553
// if formatNumber is true (input element of type text), min and maxDecimals should be undefined because it will be handled by the formatNumber function
5654
const inputCurrencyMin = formatNumber ? undefined : min;
@@ -67,6 +65,7 @@ const InputCurrencyComponent = React.forwardRef(
6765
} = useInput({
6866
ref,
6967
errorExecution,
68+
internalErrorExecution,
7069
keyValidation,
7170
max,
7271
min: inputCurrencyMin,

0 commit comments

Comments
 (0)