Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Picker dialog validation #3242

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/components/picker/PickerItemsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const PickerItemsList = (props: PickerItemsListProps) => {
useDialog,
mode,
testID,
migrateDialog,
showLoader,
customLoaderElement
} = props;
Expand Down Expand Up @@ -140,7 +141,8 @@ const PickerItemsList = (props: PickerItemsListProps) => {
</Text>
</View>
);
} else if (!useDialog || mode === PickerModes.MULTI) {
//} else if (!migrateDialog && (!useDialog || mode === PickerModes.MULTI)) {
} else if (!migrateDialog && (!useDialog || mode === PickerModes.MULTI)) {
return <Modal.TopBar testID={`${props.testID}.topBar`} {...topBarProps}/>;
}
};
Expand Down
67 changes: 67 additions & 0 deletions src/components/picker/helpers/usePickerDialogProps.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react';
import {StyleSheet} from 'react-native';
import {PickerProps, PickerModes} from '../types';
import Button from '../../button';
import {Colors} from '../../../style';

const DIALOG_PROPS = {
bottom: true,
width: '100%',
height: 250
};

const NEW_DIALOG_PROPS = {
bottom: true,
useSafeArea: true,
height: '60%',
width: '95%'
};

type HookProps = PickerProps & {
shouldDisableDoneButton?: boolean;
};

const usePickerDialogProps = (props: HookProps, onDone: any) => {
const {customPickerProps, mode, testID, selectionOptions, shouldDisableDoneButton} = props;
const migrateDialog = customPickerProps?.migrateDialog;
const defaultProps = migrateDialog ? NEW_DIALOG_PROPS : DIALOG_PROPS;
const {validationMessage, validationMessageStyle} = selectionOptions || {};

console.log(`shouldDisableDoneButton?`, shouldDisableDoneButton);

const modifiedHeaderProps = migrateDialog && {
headerProps: {
trailingAccessory: (
<Button
label="Save"
link
style={{height: 30}}
onPress={mode === PickerModes.MULTI ? onDone : undefined}
testID={`${testID}.dialog.header.save`}
disabled={!shouldDisableDoneButton}
/>
),
subtitle: !shouldDisableDoneButton && validationMessage,
subtitleStyle: [styles.validationMessage, validationMessageStyle],
...customPickerProps?.dialogProps?.headerProps
}
};

const dialogProps: PickerProps['customPickerProps'] = {
dialogProps: {
...defaultProps,
...customPickerProps?.dialogProps,
...modifiedHeaderProps
}
};

return dialogProps;
};

export default usePickerDialogProps;

const styles = StyleSheet.create({
validationMessage: {
color: Colors.red30
}
});
55 changes: 50 additions & 5 deletions src/components/picker/helpers/usePickerSelection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,40 @@ import _ from 'lodash';
import {PickerProps, PickerValue, PickerSingleValue, PickerMultiValue, PickerModes} from '../types';

interface UsePickerSelectionProps
extends Pick<PickerProps, 'migrate' | 'value' | 'onChange' | 'getItemValue' | 'topBarProps' | 'mode'> {
extends Pick<
PickerProps,
| 'migrate'
| 'value'
| 'onChange'
| 'getItemValue'
| 'topBarProps'
| 'mode'
| 'selectionValidation'
| 'selectionOptions'
| 'useDialog'
> {
pickerExpandableRef: RefObject<any>;
setSearchValue: (searchValue: string) => void;
}

const usePickerSelection = (props: UsePickerSelectionProps) => {
const {migrate, value, onChange, topBarProps, pickerExpandableRef, getItemValue, setSearchValue, mode} = props;
const {
migrate,
value,
onChange,
topBarProps,
pickerExpandableRef,
getItemValue,
setSearchValue,
mode,
selectionValidation,
selectionOptions,
useDialog
} = props;
const {onValidationFailed, validateOnStart} = selectionOptions || {};
const [multiDraftValue, setMultiDraftValue] = useState(value as PickerMultiValue);
const [multiFinalValue, setMultiFinalValue] = useState(value as PickerMultiValue);
const [isValid, setIsValid] = useState<boolean | undefined>(true);

useEffect(() => {
if (mode === PickerModes.MULTI && multiFinalValue !== value) {
Expand All @@ -20,13 +45,31 @@ const usePickerSelection = (props: UsePickerSelectionProps) => {
}
}, [value]);

useEffect(() => {
if (validateOnStart) {
_selectionValidation?.(value);
}
}, []);

const _selectionValidation = (item: PickerValue) => {
if (useDialog) {
const isValid = selectionValidation?.(item);
console.log(`isValid:`, isValid);
if (!isValid) {
onValidationFailed?.(item);
}
setIsValid(isValid);
}
};

const onDoneSelecting = useCallback((item: PickerValue) => {
setSearchValue('');
setMultiFinalValue(item as PickerMultiValue);
pickerExpandableRef.current?.closeExpandable?.();
_selectionValidation(item);
onChange?.(item);
},
[onChange]);
[onChange, useDialog, selectionValidation]);

const toggleItemSelection = useCallback((item: PickerSingleValue) => {
let newValue;
Expand All @@ -37,9 +80,10 @@ const usePickerSelection = (props: UsePickerSelectionProps) => {
newValue = _.xor(multiDraftValue, itemAsArray);
}

_selectionValidation(newValue);
setMultiDraftValue(newValue);
},
[multiDraftValue, getItemValue]);
[multiDraftValue, getItemValue, useDialog, selectionValidation]);

const cancelSelect = useCallback(() => {
setSearchValue('');
Expand All @@ -52,7 +96,8 @@ const usePickerSelection = (props: UsePickerSelectionProps) => {
multiDraftValue,
onDoneSelecting,
toggleItemSelection,
cancelSelect
cancelSelect,
shouldDisableDoneButton: isValid
};
};

Expand Down
46 changes: 26 additions & 20 deletions src/components/picker/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import usePickerSearch from './helpers/usePickerSearch';
import useImperativePickerHandle from './helpers/useImperativePickerHandle';
import useFieldType from './helpers/useFieldType';
import useNewPickerProps from './helpers/useNewPickerProps';
import usePickerDialogProps from './helpers/usePickerDialogProps';
// import usePickerMigrationWarnings from './helpers/usePickerMigrationWarnings';
import {extractPickerItems} from './PickerPresenter';
import {
Expand All @@ -31,12 +32,6 @@ import {
PickerMethods
} from './types';

const DIALOG_PROPS = {
bottom: true,
width: '100%',
height: 250
};

type PickerStatics = {
Item: typeof PickerItem;
modes: typeof PickerModes;
Expand Down Expand Up @@ -76,6 +71,8 @@ const Picker = React.forwardRef((props: PickerProps, ref) => {
accessibilityLabel,
accessibilityHint,
items: propItems,
selectionValidation,
selectionOptions,
showLoader,
customLoaderElement,
...others
Expand All @@ -86,10 +83,10 @@ const Picker = React.forwardRef((props: PickerProps, ref) => {
const [items, setItems] = useState<PickerItemProps[]>(propItems || extractPickerItems(themeProps));
const pickerExpandable = useRef<ExpandableOverlayMethods>(null);
const pickerRef = useImperativePickerHandle(ref, pickerExpandable);

// TODO: Remove this when migration is completed, starting of v8
// usePickerMigrationWarnings({children, migrate, getItemLabel, getItemValue});

useEffect(() => {
if (propItems) {
setItems(propItems);
Expand All @@ -101,17 +98,24 @@ const Picker = React.forwardRef((props: PickerProps, ref) => {
setSearchValue,
onSearchChange: _onSearchChange
} = usePickerSearch({showSearch, onSearchChange, getItemLabel, children});

const {multiDraftValue, onDoneSelecting, toggleItemSelection, cancelSelect} = usePickerSelection({
migrate,
value,
onChange,
pickerExpandableRef: pickerExpandable,
getItemValue,
topBarProps,
setSearchValue,
mode
});

const {multiDraftValue, onDoneSelecting, toggleItemSelection, cancelSelect, shouldDisableDoneButton} =
usePickerSelection({
migrate,
value,
onChange,
pickerExpandableRef: pickerExpandable,
getItemValue,
topBarProps,
setSearchValue,
mode,
selectionValidation,
selectionOptions,
useDialog
});

const {dialogProps = {}} = usePickerDialogProps({...themeProps, shouldDisableDoneButton}, () =>
onDoneSelecting(multiDraftValue));

const {label, accessibilityInfo} = usePickerLabel({
value,
Expand Down Expand Up @@ -244,6 +248,7 @@ const Picker = React.forwardRef((props: PickerProps, ref) => {
renderHeader={renderHeader}
listProps={listProps}
useSafeArea={useSafeArea}
migrateDialog={customPickerProps?.migrateDialog}
showLoader={showLoader}
customLoaderElement={customLoaderElement}
>
Expand Down Expand Up @@ -276,15 +281,16 @@ const Picker = React.forwardRef((props: PickerProps, ref) => {
return (
<PickerContext.Provider value={contextValue}>
{
//@ts-ignore remove after dialog migration finished
<ExpandableOverlay
ref={pickerExpandable}
useDialog={useDialog || useWheelPicker}
dialogProps={DIALOG_PROPS}
expandableContent={expandableModalContent}
renderCustomOverlay={renderOverlay ? _renderOverlay : undefined}
onPress={onPress}
testID={testID}
{...customPickerProps}
dialogProps={dialogProps}
disabled={themeProps.editable === false}
>
{renderTextField()}
Expand Down
26 changes: 25 additions & 1 deletion src/components/picker/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,32 @@ type PickerExpandableOverlayProps = {
enableModalBlur?: boolean;
};

type PickerSelectionOptions = Pick<
TextFieldProps,
'validateOnStart' | 'onChangeValidity' | 'validationMessage' | 'validationMessageStyle'
> & {
/**
* Callback for when field validated and failed
*/
onValidationFailed?: (value: PickerValue) => void;
};

type PickerSelectionValidation = {
/**
* Callback for when selection was made
*/
selectionValidation?: (value: PickerValue) => boolean;
/**
* Selection validation options
*/
selectionOptions?: PickerSelectionOptions;
};

export type PickerBaseProps = Omit<TextFieldProps, 'value' | 'onChange'> &
PickerPropsDeprecation &
PickerExpandableOverlayProps &
PickerListProps & {
PickerListProps &
PickerSelectionValidation & {
/* ...TextField.propTypes, */
/**
* Use dialog instead of modal picker
Expand Down Expand Up @@ -327,6 +349,8 @@ export type PickerItemsListProps = Pick<
> & {
//TODO: after finish Picker props migration, items should be taken from PickerProps
items?: {value: any; label: any}[];
//Remove the migrateDialog prop when the dialog migration finished in v8
migrateDialog?: boolean;
};

export type PickerMethods = TextFieldMethods & ExpandableOverlayMethods;