diff --git a/App.tsx b/App.tsx index 4ea5dfa..8ae3d53 100644 --- a/App.tsx +++ b/App.tsx @@ -27,54 +27,55 @@ const App = () => { return ( {}} multiOptionsText={'others'} placeholder={'seleccione un pais'} - isDisabled={true} + isDisabled={false} isMulti={false} isSearchable={false} // eslint-disable-next-line no-console onSelectOption={(option) => console.log(option)} /> {}} - multiOptionsText={'others'} + // multiOptionsText={'others'} placeholder={'seleccione un pais'} isDisabled={false} isMulti={false} - isSearchable={false} + isSearchable={true} + // variantOptions={'Modal'} // eslint-disable-next-line no-console onSelectOption={(option) => console.log(option)} /> diff --git a/src/components/BaseButton/index.tsx b/src/components/BaseButton/index.tsx index 8ba4b1d..08dc144 100644 --- a/src/components/BaseButton/index.tsx +++ b/src/components/BaseButton/index.tsx @@ -1,5 +1,5 @@ import React, {FC} from 'react'; -import {Pressable, PressableProps, ViewStyle, StyleSheet} from 'react-native'; +import {Pressable, PressableProps, ViewStyle, StyleSheet, TextStyle} from 'react-native'; import {palette} from '../../theme/palette'; import Text from '../Text'; import Icon from '../Icon'; @@ -17,7 +17,7 @@ interface BaseButtonProps extends PressableProps { pressedColor?: string; style?: ViewStyle; iconStyle?: ViewStyle; - textStyle?: ViewStyle; + textStyle?: TextStyle; children?: React.ReactNode; } diff --git a/src/components/Select/Components/Dropdown/index.test.tsx b/src/components/Select/Components/Dropdown/index.test.tsx new file mode 100644 index 0000000..df9bac9 --- /dev/null +++ b/src/components/Select/Components/Dropdown/index.test.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import {create} from 'react-test-renderer'; +import Dropdown from '.'; +import Text from '../../../Text'; +import {Pressable} from 'react-native'; + +const validData = { + show: true, + setShow: () => {}, + children: Option 1, + measures: { + width: 0, + pageY: 0, + pageX: 0, + }, +}; + +describe('Dropdown component', () => { + it('render correctly and press to change show value', () => { + const {root} = create(); + const PresableComp = root.findByType(Pressable); + PresableComp.props.onPress(); + + expect(root).toBeTruthy(); + }); +}); diff --git a/src/components/Select/Components/Dropdown/index.tsx b/src/components/Select/Components/Dropdown/index.tsx new file mode 100644 index 0000000..29d5f03 --- /dev/null +++ b/src/components/Select/Components/Dropdown/index.tsx @@ -0,0 +1,36 @@ +import React, {FC} from 'react'; +import {Modal, Pressable, StyleSheet, View} from 'react-native'; +import {DropdownMeasures} from '../..'; + +export interface DropdownProps { + show: boolean; + setShow: (isShowed: boolean) => void; + children: React.Component | React.ReactNode; + measures: DropdownMeasures; +} + +const Dropdown: FC = ({show, setShow, children, measures}) => { + const styles = StyleSheet.create({ + background: { + width: '100%', + height: '100%', + }, + dropdown: { + position: 'absolute', + height: '100%', + width: measures.width, + top: measures.pageY, + left: measures.pageX, + }, + }); + + return ( + + setShow(false)}> + {children} + + + ); +}; + +export default Dropdown; diff --git a/src/components/Select/Components/Modal/index.test.tsx b/src/components/Select/Components/Modal/index.test.tsx new file mode 100644 index 0000000..ff03f65 --- /dev/null +++ b/src/components/Select/Components/Modal/index.test.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import {create} from 'react-test-renderer'; +import Text from '../../../Text'; +import BaseButton from '../../../BaseButton'; +import Modal from '.'; + +const validData = { + show: true, + setShow: () => {}, + children: Option 1, + measures: { + width: 0, + pageY: 0, + pageX: 0, + }, + isMulti: true, + modalAcceptText: 'accept', +}; + +describe('Modal component', () => { + it('render correctly and press to change show value', () => { + const {root} = create(); + const PresableComp = root.findByType(BaseButton); + PresableComp.props.onPress(); + + expect(root).toBeTruthy(); + }); +}); diff --git a/src/components/Select/Components/Modal/index.tsx b/src/components/Select/Components/Modal/index.tsx new file mode 100644 index 0000000..5feb3ef --- /dev/null +++ b/src/components/Select/Components/Modal/index.tsx @@ -0,0 +1,77 @@ +import {FC} from 'react'; +import {Modal as ModalComponent, StyleSheet, View} from 'react-native'; +import {base, primary, white} from '../../../../theme/palette'; +import BaseButton from '../../../BaseButton'; +import {DropdownProps} from '../Dropdown'; +import React from 'react'; + +interface ModalProps extends DropdownProps { + isMulti: boolean; + modalAcceptText: string; +} + +const Modal: FC = ({show, setShow, isMulti, modalAcceptText, children}) => { + const styles = StyleSheet.create({ + background: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + width: '100%', + height: '100%', + backgroundColor: '#0009', + }, + containerModal: { + justifyContent: 'space-between', + }, + contentWrapper: { + bottom: 20, + minWidth: 270, + paddingTop: 24, + paddingBottom: 12, + paddingLeft: 20, + paddingRight: 20, + backgroundColor: base.white, + elevation: 5, + }, + buttonWrapper: { + flexDirection: 'row', + justifyContent: 'flex-end', + flexWrap: 'wrap', + left: 8, + top: 4, + }, + button: { + backgroundColor: base.white, + }, + buttonText: { + color: primary.main, + fontSize: 13, + fontWeight: '700', + textTransform: 'uppercase', + }, + }); + + return ( + + + + {children} + {isMulti && ( + + setShow(false)} + /> + + )} + + + + ); +}; + +export default Modal; diff --git a/src/components/Select/Components/Options/index.test.tsx b/src/components/Select/Components/Options/index.test.tsx index 3f88c7a..9c07b98 100644 --- a/src/components/Select/Components/Options/index.test.tsx +++ b/src/components/Select/Components/Options/index.test.tsx @@ -1,8 +1,8 @@ import React from 'react'; import {create} from 'react-test-renderer'; -import Dropdown from '.'; -import {Text, TouchableOpacity, View} from 'react-native'; -import {primary, white} from '../../../../theme/palette'; +import Options from '.'; +import {VariantOptions} from '../..'; +import {ScrollView, TouchableOpacity} from 'react-native'; const validOptions = [ {label: '1', value: '1'}, @@ -15,115 +15,126 @@ const validStyles = { backgroundColor: '#02BFFB', }; +const dropdownMeasures = { + width: 0, + pageY: 0, + pageX: 0, +}; + const validProp = { - isShowedDropdown: true, + variantOptions: VariantOptions.Dropdown, + dropdownMeasures: dropdownMeasures, + isShowedOptions: true, + setIsShowedOptions: jest.fn(), filteredOptions: validOptions, selectedOptions: selectValidOptions, - noOptionsMessage: 'no options test', + noOptionsMessage: 'options', optionStyles: validStyles, callbackOption: jest.fn(), customOptionComponent: jest.fn(), + isMulti: true, + modalAcceptText: 'Accept', }; -describe('Dropdown component', () => { +describe('Options component', () => { describe('render correctly', () => { - it('when props are valid', () => { + it('when props are valid and is switched to dropdown', () => { const {toJSON} = create( - ); expect(toJSON()).toBeTruthy(); }); - it('when selectedOption is included in filteredOptions', () => { - const {root} = create( - - ); - const OptionsComp = root.findAllByType(TouchableOpacity); - const OptionTextComp = root.findAllByType(Text); - const {backgroundColor} = OptionsComp[1].props.style; - const {color} = OptionTextComp[1].props.style; - - expect(backgroundColor).toBe(white.light); - expect(color).toBe(primary.main); - }); - }); - - describe('render null', () => { - it('when isShowedDropdown is invalid', () => { + it('when props are valid and is switched to modal', () => { const {toJSON} = create( - ); - expect(toJSON()).toBeNull(); + expect(toJSON()).toBeTruthy(); }); - }); - describe('render noOptions', () => { - it('when isShowedDropdown is invalid', () => { + it('when show is no options because filteredOptions is an empty array', () => { const {root} = create( - ); - const [, noOptionsComp] = root.findAllByType(View); - const opTionText = noOptionsComp.props.children.props.children[1]; - - expect(opTionText).toBe(validProp.noOptionsMessage); + const [noOptionsComp] = root.findAllByType(ScrollView); + const ViewComp = noOptionsComp.props.children; + const TextComp = ViewComp.props.children; + const optionText = TextComp.props.children[1]; + expect(optionText).toBe(validProp.noOptionsMessage); }); - }); - describe('when handleSelectedOption is pressed', () => { - it("should call callback with selected option's value", () => { + it('when is updated filteredOptions', () => { const {root} = create( - ); - const [, OptionsComp] = root.findAllByType(TouchableOpacity); - const {onPress} = OptionsComp.props; - onPress(); + const [TouchableOpacityComp] = root.findAllByType(TouchableOpacity); + TouchableOpacityComp.props.onPress(); - expect(validProp.callbackOption).toBeCalledWith(selectValidOptions[0]); + expect(root).toBeTruthy(); }); - }); - describe('when has custom option component', () => { - it('it should render correctly', () => { + it('when render CustomOptionComponent', () => { const {toJSON} = create( - ); diff --git a/src/components/Select/Components/Options/index.tsx b/src/components/Select/Components/Options/index.tsx index 66ed57b..186b8af 100644 --- a/src/components/Select/Components/Options/index.tsx +++ b/src/components/Select/Components/Options/index.tsx @@ -15,6 +15,8 @@ interface OptionsProps { optionStyles?: {}; callbackOption: (option: Option) => void; customOptionComponent?: CustomOptionComponent | null; + isMulti: boolean; + modalAcceptText: string; } const Options: FC = (props) => { @@ -29,24 +31,28 @@ const Options: FC = (props) => { noOptionsMessage, optionStyles, customOptionComponent = null, + isMulti, + modalAcceptText, } = props; const handleSelectedOption = (option: Option) => callbackOption(option); + const isModal = variantOptions === 'Modal'; const styles = StyleSheet.create({ container: { + position: 'relative', width: '100%', - padding: 8, + padding: !isModal ? 8 : 0, }, optionWrapper: { - position: 'absolute', + position: !isModal ? 'absolute' : 'relative', maxHeight: 168, + minHeight: 'auto', borderColor: grey[200], backgroundColor: base.white, width: '100%', - top: 60, + top: !isModal ? 60 : 0, marginBottom: 20, - elevation: 5, - zIndex: 10, + elevation: !isModal ? 5 : 0, flex: 1, }, option: { @@ -110,7 +116,9 @@ const Options: FC = (props) => { measures={dropdownMeasures} variant={variantOptions} show={isShowedOptions} - setShow={setIsShowedOptions}> + setShow={setIsShowedOptions} + isMulti={isMulti} + modalAcceptText={modalAcceptText}> {filteredOptions?.length ? renderOptions : noRenderOptions} diff --git a/src/components/Select/Components/SwitcherComponent/index.test.tsx b/src/components/Select/Components/SwitcherComponent/index.test.tsx index e69de29..0bb5dff 100644 --- a/src/components/Select/Components/SwitcherComponent/index.test.tsx +++ b/src/components/Select/Components/SwitcherComponent/index.test.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import {create} from 'react-test-renderer'; +import Text from '../../../Text'; +import {VariantOptions} from '../..'; +import SwitcherComponent from '.'; + +const validData = { + variant: VariantOptions.Dropdown, + show: true, + isMulti: true, + measures: { + width: 0, + pageY: 0, + pageX: 0, + }, + children: Text, + modalAcceptText: 'Accept Button', + setShow: () => {}, +}; + +describe('SwitcherComponent', () => { + it('when is Dropdown component', () => { + const {toJSON} = create( + + ); + + expect(toJSON()).toBeTruthy(); + }); + + it('when is Modal component', () => { + const {toJSON} = create( + + ); + + expect(toJSON()).toBeTruthy(); + }); +}); diff --git a/src/components/Select/Components/SwitcherComponent/index.tsx b/src/components/Select/Components/SwitcherComponent/index.tsx index 7b92929..0bf3b2d 100644 --- a/src/components/Select/Components/SwitcherComponent/index.tsx +++ b/src/components/Select/Components/SwitcherComponent/index.tsx @@ -1,82 +1,23 @@ -import React, {FC, ReactNode} from 'react'; -import {Modal, Pressable, StyleSheet, View} from 'react-native'; +import React, {FC} from 'react'; import {DropdownMeasures, VariantOptions} from '../..'; +import Dropdown from '../Dropdown'; +import Modal from '../Modal'; interface SwitcherProps { - variant: VariantOptions; show: boolean; - setShow: (isShowed: boolean) => void; - children: ReactNode; + isMulti: boolean; + variant: VariantOptions; measures: DropdownMeasures; -} -interface DropdownProps { - show: boolean; + children: React.Component | React.ReactNode; + modalAcceptText: string; setShow: (isShowed: boolean) => void; - children: ReactNode; - measures: DropdownMeasures; } -interface ModalProps extends DropdownProps {} - -const DropdownComponent: FC = ({show, setShow, children, measures}) => { - const styles = StyleSheet.create({ - containerModal: { - position: 'absolute', - width: measures.width, - top: measures.pageY, - left: measures.pageX, - height: '100%', - }, - backgroundModal: { - width: '100%', - height: '100%', - }, - }); - - return ( - - setShow(false)}> - {children} - - - ); -}; - -const ModalComponent: FC = ({show, setShow, children, measures}) => { - const styles = StyleSheet.create({ - containerModal: { - position: 'absolute', - width: measures.width, - top: measures.pageY, - left: measures.pageX, - height: '100%', - }, - backgroundModal: { - width: '100%', - height: '100%', - }, - container: { - width: '100%', - padding: 8, - }, - }); - - console.log('Modal'); - - return ( - - setShow(false)}> - {children} - - - ); -}; const components = { - Dropdown: DropdownComponent, - Modal: ModalComponent, + Dropdown, + Modal, }; -const SwitcherComponent: FC = ({variant, show, setShow, children, measures}) => - components[variant]({show, setShow, children, measures}); +const SwitcherComponent: FC = (props) => components[props?.variant](props); export default SwitcherComponent; diff --git a/src/components/Select/index.test.tsx b/src/components/Select/index.test.tsx index bbbea9c..93d6d33 100644 --- a/src/components/Select/index.test.tsx +++ b/src/components/Select/index.test.tsx @@ -1,10 +1,9 @@ import React from 'react'; import {create} from 'react-test-renderer'; -import ChevronIcon from './Components/Icons/Chevron'; -import DeleteIcon from './Components/Icons/Delete'; -import Dropdown from './Components/Dropdown'; -import Select from './'; +import Options from './Components/Options'; import {TextInput} from 'react-native'; +import Icon from '../Icon'; +import Select from './'; jest.spyOn(React, 'useEffect').mockImplementation((f) => f()); @@ -13,6 +12,7 @@ const setInputValueSpy = jest.fn(); const setSelectedOptions = jest.fn(); const setFilteredOptionsSpy = jest.fn(); const setIsShowedDropdownSpy = jest.fn(); +const setDropdownMeasures = jest.fn(); const validOptions = [ {label: 'Argentina', value: '1'}, @@ -28,6 +28,8 @@ const validProps = { onFocusSpy: jest.fn(), }; +const validMeasures = {width: 0, pageY: 0, pageX: 0}; + describe('Select component', () => { describe('render correctly', () => { it('when has minimum props needed', () => { @@ -40,11 +42,12 @@ describe('Select component', () => { .mockReturnValueOnce(['', setInputValueSpy]) .mockReturnValueOnce([[], setSelectedOptions]) .mockReturnValueOnce([validOptions, setFilteredOptionsSpy]) - .mockReturnValueOnce([false, setIsShowedDropdownSpy]); + .mockReturnValueOnce([false, setIsShowedDropdownSpy]) + .mockReturnValueOnce([validMeasures, setDropdownMeasures]); const {root} = create( ); - const DeleteComponent = root.findByType(DeleteIcon); + const {root} = create( + ); const InputComponent = root.findByType(TextInput); - const DropdownComponent = root.findByType(Dropdown); + const OptionsComponent = root.findByType(Options); InputComponent.props.onFocus(); - DropdownComponent.props.callbackOption(validOptions[0]); + OptionsComponent.props.callbackOption(validOptions[0]); expect(setIsShowedDropdownSpy).toBeCalledWith(true); expect(setIsShowedDropdownSpy).toBeCalledWith(false); @@ -94,7 +101,8 @@ describe('Select component', () => { .mockReturnValueOnce(['', setInputValueSpy]) .mockReturnValueOnce([[], setSelectedOptions]) .mockReturnValueOnce([validOptions, setFilteredOptionsSpy]) - .mockReturnValueOnce([true, setIsShowedDropdownSpy]); + .mockReturnValueOnce([true, setIsShowedDropdownSpy]) + .mockReturnValueOnce([validMeasures, setDropdownMeasures]); const {root} = create( @@ -130,7 +139,8 @@ describe('Select component', () => { .mockReturnValueOnce(['', setInputValueSpy]) .mockReturnValueOnce([[], setSelectedOptions]) .mockReturnValueOnce([validOptions, setFilteredOptionsSpy]) - .mockReturnValueOnce([true, setIsShowedDropdownSpy]); + .mockReturnValueOnce([true, setIsShowedDropdownSpy]) + .mockReturnValueOnce([validMeasures, setDropdownMeasures]); const {root} = create( ); - const DropdownComponent = root.findByType(Dropdown); - const ChevronComponent = root.findByType(ChevronIcon); - DropdownComponent.props.callbackOption(validOptions[0]); + const OptionsComponent = root.findByType(Options); + const ChevronComponent = root.findByType(Icon); + OptionsComponent.props.callbackOption(validOptions[0]); ChevronComponent.props.onPress(); expect(setSelectedOptions).toBeCalledWith([validOptions[0]]); @@ -175,14 +186,15 @@ describe('Select component', () => { .mockReturnValueOnce(['', setInputValueSpy]) .mockReturnValueOnce([[validOptions[0]], setSelectedOptions]) .mockReturnValueOnce([validOptions, setFilteredOptionsSpy]) - .mockReturnValueOnce([true, setIsShowedDropdownSpy]); + .mockReturnValueOnce([true, setIsShowedDropdownSpy]) + .mockReturnValueOnce([validMeasures, setDropdownMeasures]); const {root} = create( { isMulti /> ); - const DropdownComponent = root.findByType(Dropdown); - const ChevronComponent = root.findByType(ChevronIcon); - DropdownComponent.props.callbackOption(validOptions[0]); + const OptionsComponent = root.findByType(Options); + const ChevronComponent = root.findByType(Icon); + OptionsComponent.props.callbackOption(validOptions[0]); ChevronComponent.props.onPress(); expect(setSelectedOptions).toBeCalledWith([]); diff --git a/src/components/Select/index.tsx b/src/components/Select/index.tsx index ce1f5a8..47653f8 100644 --- a/src/components/Select/index.tsx +++ b/src/components/Select/index.tsx @@ -57,6 +57,7 @@ interface SelectProps { onFocus?: () => void; onSelectOption?: (selectedOptions: Option[]) => void; customOptionComponent?: CustomOptionComponent | null; + modalAcceptText?: string; } const Select: FC = ({ @@ -76,6 +77,7 @@ const Select: FC = ({ onFocus = () => {}, onSelectOption = () => {}, customOptionComponent = null, + modalAcceptText = 'accept', ...props }) => { const [inputValue, setInputValue] = useState(''); @@ -112,7 +114,7 @@ const Select: FC = ({ }; const handleOnFocus = () => { - if (!isSearchable || isMulti) { + if (!isSearchable || !isShowedOptions || isMulti || variantOptions === VariantOptions.Modal) { Keyboard.dismiss(); } onFocus(); @@ -251,7 +253,6 @@ const Select: FC = ({ style={styles.arrowIcon} onPress={handleCloseDropdown} /> - {label} = ({ optionStyles={optionStyles} callbackOption={handleSelectedOption} customOptionComponent={customOptionComponent} + isMulti={isMulti} + modalAcceptText={modalAcceptText} /> );