Skip to content

Commit

Permalink
Illiar/fix/v2 bugfixes (#3016)
Browse files Browse the repository at this point in the history
* fix: fixed bug when context from tasks didnt get to submit

* feat: reworked validation logic

* fix: fixed focus on inputs & fixed abortEarlyOnFirstError

* fix: adjusted validation params
  • Loading branch information
chesterkmr authored Feb 4, 2025
1 parent c72f9a7 commit e17db25
Show file tree
Hide file tree
Showing 34 changed files with 454 additions and 1,044 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import './validator';
import { useDynamicUIContext } from '@/components/organisms/DynamicUI/hooks/useDynamicUIContext';
import { useStateManagerContext } from '@/components/organisms/DynamicUI/StateManager/components/StateProvider/hooks/useStateManagerContext';
import { CollectionFlowContext } from '@/domains/collection-flow/types/flow-context.types';
import { DynamicFormV2, IFormElement, IFormRef } from '@ballerine/ui';
import { DynamicFormV2, IDynamicFormValidationParams, IFormElement, IFormRef } from '@ballerine/ui';
import { FunctionComponent, useCallback, useMemo, useRef } from 'react';
import { usePluginsSubscribe } from './components/utility/PluginsRunner';
import { usePlugins } from './components/utility/PluginsRunner/hooks/external/usePlugins';
Expand All @@ -20,10 +20,12 @@ interface ICollectionFlowUIProps<TValues = CollectionFlowContext> {
isRevision?: boolean;
}

const validationParams = {
validateOnBlur: false,
const validationParams: IDynamicFormValidationParams = {
validateOnChange: true,
validateOnBlur: true,
abortEarly: false,
validationDelay: 500,
abortAfterFirstError: true,
validationDelay: 300,
};

export const CollectionFlowUI: FunctionComponent<ICollectionFlowUIProps> = ({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { CaretSortIcon } from '@radix-ui/react-icons';
import clsx from 'clsx';
import { CheckIcon } from 'lucide-react';
import React, { FocusEvent, FunctionComponent, useCallback, useMemo, useState } from 'react';
import React, {
FocusEvent,
FocusEventHandler,
FunctionComponent,
useCallback,
useMemo,
useState,
} from 'react';

import { ctw } from '@/common';
import {
Expand Down Expand Up @@ -101,6 +108,9 @@ export const DropdownInput: FunctionComponent<DropdownInputProps> = ({
props?.trigger?.className,
)}
disabled={disabled}
tabIndex={0}
onFocus={onFocus as FocusEventHandler<HTMLButtonElement>}
onBlur={onBlur as FocusEventHandler<HTMLButtonElement>}
data-testid={testId ? `${testId}-trigger` : undefined}
>
<span className="flex-1 truncate text-left">
Expand All @@ -114,7 +124,6 @@ export const DropdownInput: FunctionComponent<DropdownInputProps> = ({
align={props?.content?.align || 'center'}
style={{ width: 'var(--radix-popover-trigger-width)' }}
className={clsx('p-2', props?.content?.className)}
onBlur={onBlur}
>
<Command className="w-full">
{searchable ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { UnselectButtonProps } from '@/components/molecules/inputs/MultiSelect/c
import { SelectedElementParams } from '@/components/molecules/inputs/MultiSelect/types';
import { ClickAwayListener } from '@mui/material';
import keyBy from 'lodash/keyBy';
import { FocusEvent, useCallback, useMemo, useRef, useState } from 'react';
import { FocusEvent, FocusEventHandler, useCallback, useMemo, useRef, useState } from 'react';

export type MultiSelectValue = string | number;

Expand Down Expand Up @@ -50,7 +50,9 @@ export const MultiSelect = ({
const [open, setOpen] = useState(false);

const selected = useMemo(() => {
if (!value) return [];
if (!value) {
return [];
}

const optionsMap = keyBy(options, 'value');

Expand Down Expand Up @@ -124,7 +126,9 @@ export const MultiSelect = ({
}, [options, selected, inputValue]);

const handleOutsidePopupClick = useCallback(() => {
if (open) setOpen(false);
if (open) {
setOpen(false);
}
}, [open]);

const buildUnselectButtonProps = useCallback(
Expand Down Expand Up @@ -158,7 +162,12 @@ export const MultiSelect = ({
{ 'pointer-events-none opacity-50': disabled },
)}
>
<div className="flex flex-wrap gap-2 px-2">
<div
className="flex flex-wrap gap-2 px-2"
tabIndex={0}
onFocus={onFocus as FocusEventHandler<HTMLDivElement>}
onBlur={onBlur as FocusEventHandler<HTMLDivElement>}
>
{selected.map(option => {
return renderSelected(
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ export const DynamicFormV2 = forwardRef(
});
const touchedApi = useTouched(elements, valuesApi.values);
const fieldHelpers = useFieldHelpers<TValues>({ valuesApi, touchedApi });
const { submit } = useSubmit({ values: valuesApi.values, onSubmit });
const { submit } = useSubmit<TValues>({ onSubmit });

useImperativeHandle(ref, () => ({
submit,
submit: () => submit(valuesApi.values),
validate: () => null,
setValues: valuesApi.setValues,
setTouched: touchedApi.setTouched,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ describe('DynamicFormV2', () => {
it('should pass correct props to useSubmit', () => {
render(<DynamicFormV2 {...mockProps} />);
expect(useSubmit).toHaveBeenCalledWith({
values: mockProps.values,
onSubmit: mockProps.onSubmit,
});
});
Expand Down Expand Up @@ -278,11 +277,12 @@ describe('DynamicFormV2', () => {
vi.mocked(useFieldHelpers).mockReturnValue(fieldHelpersMock);
});

it('should expose submit method through ref', () => {
it('should call submit method through ref', () => {
const ref = { current: null as IFormRef<any> | null };
render(<DynamicFormV2 {...mockProps} ref={ref} />);

expect(ref.current).toHaveProperty('submit', submitMock.submit);
ref.current?.submit();
expect(submitMock.submit).toHaveBeenCalledWith(valuesMock.values);
});

it('should expose validate method through ref', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,15 @@ const schema: Array<IFormElement<any, any>> = [
placeholder: 'Enter text',
description: 'This is a text field for entering any text value',
},
validate: [],
validate: [
{ type: 'required', value: {} },
{
type: 'minLength',
value: {
minLength: 10,
},
},
],
},
{
id: 'AutocompleteField',
Expand All @@ -30,6 +38,7 @@ const schema: Array<IFormElement<any, any>> = [
{ value: 'option3', label: 'Option 3' },
],
},
validate: [{ type: 'required', value: {} }],
},
{
id: 'CheckboxListField',
Expand All @@ -44,6 +53,7 @@ const schema: Array<IFormElement<any, any>> = [
{ value: 'option3', label: 'Option 3' },
],
},
validate: [{ type: 'required', value: {} }],
},
{
id: 'DateField',
Expand All @@ -53,6 +63,7 @@ const schema: Array<IFormElement<any, any>> = [
label: 'Date Field',
description: 'Select a date from the calendar',
},
validate: [{ type: 'required', value: {} }],
},
{
id: 'MultiselectField',
Expand All @@ -67,6 +78,7 @@ const schema: Array<IFormElement<any, any>> = [
{ value: 'option3', label: 'Option 3' },
],
},
validate: [{ type: 'required', value: {} }],
},
{
id: 'SelectField',
Expand All @@ -81,6 +93,7 @@ const schema: Array<IFormElement<any, any>> = [
{ value: 'option3', label: 'Option 3' },
],
},
validate: [{ type: 'required', value: {} }],
},
{
id: 'CheckboxField',
Expand All @@ -90,6 +103,7 @@ const schema: Array<IFormElement<any, any>> = [
label: 'Checkbox Field',
description: 'Toggle this checkbox for a yes/no selection',
},
validate: [{ type: 'required', value: {} }],
},
{
id: 'PhoneField',
Expand All @@ -100,6 +114,7 @@ const schema: Array<IFormElement<any, any>> = [
description: 'Enter a phone number with country code selection',
defaultCountry: 'il',
},
validate: [{ type: 'required', value: {} }],
},
{
id: 'RadioField',
Expand All @@ -114,6 +129,7 @@ const schema: Array<IFormElement<any, any>> = [
{ value: 'option3', label: 'Option 3' },
],
},
validate: [{ type: 'required', value: {} }],
},
{
id: 'TagsField',
Expand All @@ -123,6 +139,7 @@ const schema: Array<IFormElement<any, any>> = [
label: 'Tags Field',
description: 'Add multiple tags by typing and pressing enter',
},
validate: [{ type: 'required', value: {} }],
},
{
id: 'FileField',
Expand All @@ -133,6 +150,7 @@ const schema: Array<IFormElement<any, any>> = [
placeholder: 'Select File',
description: 'Upload a file from your device',
},
validate: [{ type: 'required', value: {} }],
},
{
id: 'DocumentField-1',
Expand All @@ -154,6 +172,7 @@ const schema: Array<IFormElement<any, any>> = [
resultPath: 'filename',
},
},
validate: [{ type: 'required', value: {} }],
},
{
id: 'DocumentField-2',
Expand All @@ -176,6 +195,7 @@ const schema: Array<IFormElement<any, any>> = [
resultPath: 'filename',
},
},
validate: [{ type: 'required', value: {} }],
},
{
id: 'FieldList',
Expand All @@ -185,6 +205,7 @@ const schema: Array<IFormElement<any, any>> = [
label: 'Field List',
description: 'A list of repeatable form fields that can be added or removed',
},
validate: [{ type: 'required', value: {} }],
children: [
{
id: 'Nested-TextField',
Expand Down Expand Up @@ -212,6 +233,7 @@ const schema: Array<IFormElement<any, any>> = [
params: {
label: 'Submit Button',
},
validate: [{ type: 'required', value: {} }],
},
];

Expand All @@ -228,6 +250,7 @@ export const InputsShowcaseComponent = () => {
console.log('onSubmit');
}}
onChange={setContext}
validationParams={{ abortAfterFirstError: true }}
// onEvent={console.log}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { IDynamicFormValidationParams } from '../../types';
import { schema } from './schema';

const validationParams: IDynamicFormValidationParams = {
validateOnBlur: false,
validateOnBlur: true,
validateOnChange: false,
};

export const ValidationShowcaseComponent = () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { createContext } from 'react';
import { IDynamicFormContext } from './types';

export const DynamicFormContext = createContext({} as IDynamicFormContext<object>);
export const DynamicFormContext = createContext<IDynamicFormContext<any>>(
{} as IDynamicFormContext<any>,
);
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export interface IDynamicFormContext<TValues extends object> {
touched: ITouchedState;
elementsMap: TElementsMap;
fieldHelpers: IFieldHelpers;
submit: () => void;
submit: (values: TValues) => void;
callbacks: IDynamicFormCallbacks;
metadata: Record<string, string>;
validationParams: IDynamicFormValidationParams;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,16 @@ export const SubmitButton: TDynamicFormElement<string, ISubmitButtonParams> = ({
const { fieldHelpers, values, submit } = useDynamicForm();
const { runTasks, isRunning } = useTaskRunner();
const { sendEvent } = useEvents(element);
const { validate, isValid } = useValidator();

const { touchAllFields } = fieldHelpers;

const { isValid, errors } = useValidator();

const { disableWhenFormIsInvalid = false, text = 'Submit' } = element.params || {};

const disabled = useMemo(() => {
if (disableWhenFormIsInvalid && !isValid) return true;
if (disableWhenFormIsInvalid && !isValid) {
return true;
}

return _disabled;
}, [disableWhenFormIsInvalid, isValid, _disabled]);
Expand All @@ -39,9 +40,12 @@ export const SubmitButton: TDynamicFormElement<string, ISubmitButtonParams> = ({

touchAllFields();

const validationResult = await validate();
const isValid = validationResult?.length === 0;

if (!isValid) {
console.log(`Submit button clicked but form is invalid`);
console.log('Validation errors', errors);
console.log('Validation errors', validationResult);

return;
}
Expand All @@ -52,10 +56,9 @@ export const SubmitButton: TDynamicFormElement<string, ISubmitButtonParams> = ({

fieldHelpers.setValues(updatedContext);

submit();

submit(updatedContext);
sendEvent('onSubmit');
}, [submit, isValid, touchAllFields, runTasks, sendEvent, errors, onClick, values, fieldHelpers]);
}, [submit, touchAllFields, runTasks, sendEvent, onClick, values, fieldHelpers, validate]);

return (
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ describe('SubmitButton', () => {
isValid: true,
errors: {},
values: {},
validate: vi.fn(),
validate: vi.fn().mockResolvedValue([]),
} as unknown as IValidatorContext<object>);
vi.mocked(useEvents).mockReturnValue({
sendEvent: mockSendEvent,
Expand Down Expand Up @@ -149,7 +149,7 @@ describe('SubmitButton', () => {
isValid: true,
errors: [],
values: {},
validate: vi.fn(),
validate: vi.fn().mockResolvedValue([]),
});

render(<SubmitButton element={mockElement} />);
Expand Down Expand Up @@ -244,7 +244,7 @@ describe('SubmitButton', () => {
isValid: true,
errors: [],
values: {},
validate: vi.fn(),
validate: vi.fn().mockResolvedValue([]),
} as unknown as IValidatorContext<object>);

render(<SubmitButton element={mockElement} />);
Expand Down
Loading

0 comments on commit e17db25

Please sign in to comment.