Skip to content

Commit

Permalink
Merge pull request #51173 from MrMuzyk/feat/push-row-with-modal
Browse files Browse the repository at this point in the history
feat: PushRowWithModal component
  • Loading branch information
madmax330 authored Oct 23, 2024
2 parents 8f82215 + a5db236 commit 22ddb6d
Show file tree
Hide file tree
Showing 3 changed files with 226 additions and 1 deletion.
120 changes: 120 additions & 0 deletions src/components/PushRowWithModal/PushRowModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import React, {useEffect, useState} from 'react';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import Modal from '@components/Modal';
import ScreenWrapper from '@components/ScreenWrapper';
import SelectionList from '@components/SelectionList';
import RadioListItem from '@components/SelectionList/RadioListItem';
import useLocalize from '@hooks/useLocalize';
import CONST from '@src/CONST';

type PushRowModalProps = {
/** Whether the modal is visible */
isVisible: boolean;

/** The currently selected option */
selectedOption: string;

/** Function to call when the user selects an option */
onOptionChange: (option: string) => void;

/** Function to call when the user closes the modal */
onClose: () => void;

/** The list of items to render */
optionsList: Record<string, string>;

/** The title of the modal */
headerTitle: string;

/** The title of the search input */
searchInputTitle?: string;
};

type ListItemType = {
value: string;
text: string;
keyForList: string;
isSelected: boolean;
};

function PushRowModal({isVisible, selectedOption, onOptionChange, onClose, optionsList, headerTitle, searchInputTitle}: PushRowModalProps) {
const {translate} = useLocalize();

const allOptions = Object.entries(optionsList).map(([key, value]) => ({
value: key,
text: value,
keyForList: key,
isSelected: key === selectedOption,
}));
const [searchbarInputText, setSearchbarInputText] = useState('');
const [optionListItems, setOptionListItems] = useState(allOptions);

useEffect(() => {
setOptionListItems((prevOptionListItems) =>
prevOptionListItems.map((option) => ({
...option,
isSelected: option.value === selectedOption,
})),
);
}, [selectedOption]);

const filterShownOptions = (searchText: string) => {
setSearchbarInputText(searchText);
const searchWords = searchText.toLowerCase().match(/[a-z0-9]+/g) ?? [];
setOptionListItems(
allOptions.filter((option) =>
searchWords.every((word) =>
option.text
.toLowerCase()
.replace(/[^a-z0-9]/g, ' ')
.includes(word),
),
),
);
};

const handleSelectRow = (option: ListItemType) => {
onOptionChange(option.value);
onClose();
};

return (
<Modal
onClose={onClose}
isVisible={isVisible}
type={CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED}
onModalHide={onClose}
shouldUseCustomBackdrop
useNativeDriver
>
<ScreenWrapper
includePaddingTop={false}
includeSafeAreaPaddingBottom={false}
testID={PushRowModal.displayName}
>
<HeaderWithBackButton
title={headerTitle}
onBackButtonPress={onClose}
/>
<SelectionList
headerMessage={searchbarInputText.trim().length && !optionListItems.length ? translate('common.noResultsFound') : ''}
textInputLabel={searchInputTitle}
textInputValue={searchbarInputText}
onChangeText={filterShownOptions}
onSelectRow={handleSelectRow}
sections={[{data: optionListItems}]}
initiallyFocusedOptionKey={optionListItems.find((option) => option.value === selectedOption)?.keyForList}
showScrollIndicator
shouldShowTooltips={false}
ListItem={RadioListItem}
/>
</ScreenWrapper>
</Modal>
);
}

PushRowModal.displayName = 'PushRowModal';

export type {ListItemType};

export default PushRowModal;
88 changes: 88 additions & 0 deletions src/components/PushRowWithModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React, {useState} from 'react';
import type {StyleProp, ViewStyle} from 'react-native';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import CONST from '@src/CONST';
import PushRowModal from './PushRowModal';

type PushRowWithModalProps = {
/** The list of options that we want to display where key is option code and value is option name */
optionsList: Record<string, string>;

/** The currently selected option */
selectedOption: string;

/** Function to call when the user selects an option */
onOptionChange: (value: string) => void;

/** Additional styles to apply to container */
wrapperStyles?: StyleProp<ViewStyle>;

/** The description for the picker */
description: string;

/** The title of the modal */
modalHeaderTitle: string;

/** The title of the search input */
searchInputTitle: string;

/** Whether the selected option is editable */
shouldAllowChange?: boolean;

/** Text to display on error message */
errorText?: string;
};

function PushRowWithModal({
selectedOption,
onOptionChange,
optionsList,
wrapperStyles,
description,
modalHeaderTitle,
searchInputTitle,
shouldAllowChange = true,
errorText,
}: PushRowWithModalProps) {
const [isModalVisible, setIsModalVisible] = useState(false);

const handleModalClose = () => {
setIsModalVisible(false);
};

const handleModalOpen = () => {
setIsModalVisible(true);
};

const handleOptionChange = (value: string) => {
onOptionChange(value);
};

return (
<>
<MenuItemWithTopDescription
description={description}
title={optionsList[selectedOption]}
shouldShowRightIcon={shouldAllowChange}
onPress={handleModalOpen}
wrapperStyle={wrapperStyles}
interactive={shouldAllowChange}
brickRoadIndicator={errorText ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
errorText={errorText}
/>
<PushRowModal
isVisible={isModalVisible}
selectedOption={selectedOption}
onOptionChange={handleOptionChange}
onClose={handleModalClose}
optionsList={optionsList}
headerTitle={modalHeaderTitle}
searchInputTitle={searchInputTitle}
/>
</>
);
}

PushRowWithModal.displayName = 'PushRowWithModal';

export default PushRowWithModal;
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import React from 'react';
import React, {useState} from 'react';
import FormProvider from '@components/Form/FormProvider';
import PushRowWithModal from '@components/PushRowWithModal';
import SafeAreaConsumer from '@components/SafeAreaConsumer';
import ScrollView from '@components/ScrollView';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import type {SubStepProps} from '@hooks/useSubStep/types';
import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';

function Confirmation({onNext}: SubStepProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();

const [selectedCountry, setSelectedCountry] = useState<string>('');

const handleSelectingCountry = (country: string) => {
setSelectedCountry(country);
};

return (
<SafeAreaConsumer>
{({safeAreaPaddingBottomStyle}) => (
Expand All @@ -27,6 +35,15 @@ function Confirmation({onNext}: SubStepProps) {
submitButtonStyles={[styles.mh5, styles.pb0, styles.mbn1]}
>
<Text style={[styles.textHeadlineLineHeightXXL, styles.ph5, styles.mb3]}>{translate('countryStep.confirmBusinessBank')}</Text>
{/* This is only to showcase usage of PushRowWithModal component. The actual implementation will come in next issue - https://github.com/Expensify/App/issues/50897 */}
<PushRowWithModal
optionsList={CONST.ALL_COUNTRIES}
selectedOption={selectedCountry}
onOptionChange={handleSelectingCountry}
description={translate('common.country')}
modalHeaderTitle="Select country"
searchInputTitle="Find country"
/>
</FormProvider>
</ScrollView>
)}
Expand Down

0 comments on commit 22ddb6d

Please sign in to comment.