From 0a4d18ffbad93f2444d311c04e5899feed223dac Mon Sep 17 00:00:00 2001 From: Zain Sajjad Date: Mon, 19 Apr 2021 17:21:12 +0500 Subject: [PATCH] feat: add country selection fix: lint issues feat: autocomplete hook feat: theme components Input, Icon, IconButton test: app snapshot updated to add testID --- __tests__/__snapshots__/App-test.tsx.snap | 1 + android/app/build.gradle | 5 + .../CountriesAutocomplete/ListItem.tsx | 27 ++++++ .../CountriesAutocomplete/index.tsx | 78 +++++++++++++++ app/components/CountriesAutocomplete/style.ts | 61 ++++++++++++ .../tests/index.test.tsx | 66 +++++++++++++ app/platform/LocalStorage/index.ts | 26 +++++ app/router/routeConfigs.ts | 2 +- app/router/types.ts | 4 +- app/screens/HomeScreen/Loadable.ts | 7 -- app/screens/HomeScreen/index.tsx | 10 +- app/screens/HomeScreen/style.ts | 2 +- app/screens/HomeScreen/types.ts | 6 +- app/screens/SearchScreen/Loadable.ts | 7 -- app/screens/SearchScreen/index.tsx | 74 +++++++++++++-- app/screens/SearchScreen/style.ts | 33 +++++-- app/screens/SearchScreen/tests/index.test.tsx | 94 +++++++++++++++++++ app/screens/SearchScreen/types.ts | 16 ++-- app/theme/Autocomplete/index.tsx | 79 ++++++++++++++++ app/theme/Button/IconButton/index.tsx | 89 ++++++++++++++++++ app/theme/Button/index.tsx | 8 +- app/theme/Button/style.ts | 2 +- app/theme/Button/tests/index.test.tsx | 4 +- app/theme/Dimensions/index.ts | 2 +- app/theme/FullScreenLoader/index.tsx | 2 +- app/theme/Icon/index.tsx | 36 +++++++ app/theme/Input/index.tsx | 5 +- app/theme/Input/style.tsx | 2 +- app/theme/Text/index.tsx | 4 +- app/theme/TouchFeedback/index.tsx | 6 +- assetTransformer.js | 2 +- component | 0 e2e/config.json | 3 +- e2e/selectCountry.spec.ts | 50 ++++++++++ ios/Podfile.lock | 6 ++ .../project.pbxproj | 64 +++++++++++++ .../ReactNativeSemaphoreNew.xcscheme | 22 ++--- .../AppIcon.appiconset/Contents.json | 45 ++++++--- ios/ReactNativeSemaphoreNew/Info.plist | 4 + jest.config.js | 6 +- jest.mock.js | 2 + package.json | 3 + yarn.lock | 66 ++++++++++++- 43 files changed, 938 insertions(+), 93 deletions(-) create mode 100644 app/components/CountriesAutocomplete/ListItem.tsx create mode 100644 app/components/CountriesAutocomplete/index.tsx create mode 100644 app/components/CountriesAutocomplete/style.ts create mode 100644 app/components/CountriesAutocomplete/tests/index.test.tsx create mode 100644 app/platform/LocalStorage/index.ts delete mode 100644 app/screens/HomeScreen/Loadable.ts delete mode 100644 app/screens/SearchScreen/Loadable.ts create mode 100644 app/screens/SearchScreen/tests/index.test.tsx create mode 100644 app/theme/Autocomplete/index.tsx create mode 100644 app/theme/Button/IconButton/index.tsx create mode 100644 app/theme/Icon/index.tsx delete mode 100644 component create mode 100644 e2e/selectCountry.spec.ts diff --git a/__tests__/__snapshots__/App-test.tsx.snap b/__tests__/__snapshots__/App-test.tsx.snap index 405aee4..af5f3f2 100644 --- a/__tests__/__snapshots__/App-test.tsx.snap +++ b/__tests__/__snapshots__/App-test.tsx.snap @@ -294,6 +294,7 @@ exports[`jest snapshot tests renders correctly 1`] = ` "backgroundColor": "#ffffff", } } + testID="homeScreen" > void; + testID: string; +}; + +const ListItem: React.FC = props => ( + + {props.data.emoji} + + ({props.data.phone}) {props.data.name} + + +); + +export default ListItem; diff --git a/app/components/CountriesAutocomplete/index.tsx b/app/components/CountriesAutocomplete/index.tsx new file mode 100644 index 0000000..526d886 --- /dev/null +++ b/app/components/CountriesAutocomplete/index.tsx @@ -0,0 +1,78 @@ +/** + * + * CountriesAutocomplete + * + */ + +import sortBy from 'lodash/sortBy'; + +import React from 'react'; +import {FlatList, Text, View} from 'react-native'; +import {countries, Country} from 'countries-list'; + +import Input from 'theme/Input'; +import useAutocomplete from 'theme/Autocomplete'; + +import ListItem from './ListItem'; +import style from './style'; + +interface ICountriesAutocompleteProps { + onSelect: (item: Country) => void; + testID?: string; +} + +const countriesList: Country[] = Object.keys(countries).map( + key => countries[key], +); + +const CountriesAutocomplete: React.FC = props => { + const autocomplete = useAutocomplete({ + data: countriesList, + filterKey: 'name', + }); + + return ( + <> + + + + {autocomplete.focus && autocomplete.data ? ( + name} + renderItem={({item}: {item: Country; index: number}) => ( + { + props.onSelect(item); + autocomplete.clear(); + }} + data={item} + testID={`listItem-${item.name}`} + /> + )} + ListEmptyComponent={ + autocomplete.value ? ( + No result for {autocomplete.value}. + ) : null + } + /> + ) : null} + + ); +}; + +export default CountriesAutocomplete; diff --git a/app/components/CountriesAutocomplete/style.ts b/app/components/CountriesAutocomplete/style.ts new file mode 100644 index 0000000..b975705 --- /dev/null +++ b/app/components/CountriesAutocomplete/style.ts @@ -0,0 +1,61 @@ +import {StyleSheet} from 'react-native'; +import Colors from 'theme/Colors'; +import Dimensions from 'theme/Dimensions'; + +const style = StyleSheet.create({ + selectedItemContainer: { + margin: Dimensions.space2x, + marginTop: Dimensions.space1x, + flexDirection: 'row', + }, + autocompleteContainer: { + flexDirection: 'row', + alignItems: 'center', + position: 'relative', + paddingHorizontal: Dimensions.space2x, + }, + input: { + marginHorizontal: 0, + opacity: 1, + width: '100%', + }, + inputCloseBtnHolder: { + backgroundColor: Colors.translucentBlackMinor, + height: 36, + width: 36, + borderRadius: 20, + position: 'absolute', + right: 16, + }, + inputCloseBtn: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + noResult: { + flex: 1, + textAlign: 'center', + paddingVertical: Dimensions.space1x, + }, + listContainer: { + zIndex: 1000, + width: '100%', + paddingHorizontal: Dimensions.space2x, + }, + listItemContainer: { + flexDirection: 'row', + padding: Dimensions.space1x, + paddingVertical: Dimensions.space2x, + alignItems: 'center', + width: '100%', + backgroundColor: Colors.white, + marginVertical: Dimensions.space1x, + }, + listItemName: { + fontSize: 14, + marginLeft: 8, + fontWeight: '500', + }, +}); + +export default style; diff --git a/app/components/CountriesAutocomplete/tests/index.test.tsx b/app/components/CountriesAutocomplete/tests/index.test.tsx new file mode 100644 index 0000000..71d8392 --- /dev/null +++ b/app/components/CountriesAutocomplete/tests/index.test.tsx @@ -0,0 +1,66 @@ +import 'react-native'; +import React from 'react'; +import {render, fireEvent} from '@testing-library/react-native'; + +import CountriesAutocomplete from '../index'; + +const COUNTRY_NAME = 'Serbia'; + +// Describing a test suite +describe('', () => { + // Describing our test + it('Displays Searched Item', async () => { + // Mocking onPress method so we can check if its called or not + const onSelect = jest.fn(); + + // Rendering Button component using react-native-test-renderer. + const autocomplete = await render( + , + ); + + // Grabbing our input to perform actions on it. + const inputTestID = 'countriesAutocompleteInput'; + const textInput = autocomplete.getByTestId(inputTestID); + + /** + * RNTL gives us API to fire events on node + * Here we are firing on changeText event + */ + fireEvent(textInput, 'focus'); + fireEvent.changeText(textInput, COUNTRY_NAME); + expect(textInput.props.value).toBe(COUNTRY_NAME); + + // Grabbing our input to perform actions on it. + const listItemTestID = `listItem-${COUNTRY_NAME}`; + const firstListItem = autocomplete.getByTestId(listItemTestID); + expect(firstListItem).toBeTruthy(); + }); + + it('onSelect is called when item is pressed', async () => { + // Mocking onPress method so we can check if its called or not + const onSelect = jest.fn(); + + // Rendering Button component using react-native-test-renderer. + const {getByTestId} = await render( + , + ); + + // Grabbing our input to perform actions on it. + const inputTestID = 'countriesAutocompleteInput'; + const textInput = getByTestId(inputTestID); + + /** + * RNTL gives us API to fire events on node + * Here we are firing on focus & changeText event + */ + fireEvent(textInput, 'focus'); + fireEvent.changeText(textInput, COUNTRY_NAME); + + // Grabbing our input to perform actions on it. + const listItemTestID = `listItem-${COUNTRY_NAME}`; + const firstListItem = getByTestId(listItemTestID); + fireEvent.press(firstListItem); + + expect(onSelect).toHaveBeenCalledTimes(1); + }); +}); diff --git a/app/platform/LocalStorage/index.ts b/app/platform/LocalStorage/index.ts new file mode 100644 index 0000000..3c52af5 --- /dev/null +++ b/app/platform/LocalStorage/index.ts @@ -0,0 +1,26 @@ +import AsyncStorage from '@react-native-async-storage/async-storage'; + +class LocalStorage { + setItem(key: string, data: unknown) { + return AsyncStorage.setItem(key, JSON.stringify(data)); + } + + async getItem(key: string) { + const data = await AsyncStorage.getItem(key); + try { + return JSON.parse(data || ''); + } catch (e) { + return data; + } + } + + mergeItem(key, data) { + AsyncStorage.mergeItem(key, JSON.stringify(data)); + } + + removeItem(key) { + AsyncStorage.removeItem(key); + } +} + +export default new LocalStorage(); diff --git a/app/router/routeConfigs.ts b/app/router/routeConfigs.ts index 6b27c43..6f5f89c 100644 --- a/app/router/routeConfigs.ts +++ b/app/router/routeConfigs.ts @@ -1,4 +1,4 @@ -import { HOME, SEARCH } from './routeNames'; +import {HOME, SEARCH} from './routeNames'; const routeConfigs = { [SEARCH]: { diff --git a/app/router/types.ts b/app/router/types.ts index 626b31e..f541fb2 100644 --- a/app/router/types.ts +++ b/app/router/types.ts @@ -1,4 +1,4 @@ export type RootStackParamList = { - Home: {}; - Search: {}; + Home?: Record; + Search?: Record; }; diff --git a/app/screens/HomeScreen/Loadable.ts b/app/screens/HomeScreen/Loadable.ts deleted file mode 100644 index 183fcce..0000000 --- a/app/screens/HomeScreen/Loadable.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Asynchronously loads the component for HomeScreen - */ - -import loadable from 'react-suspense-loadable'; - -export default loadable(() => import('./index')); diff --git a/app/screens/HomeScreen/index.tsx b/app/screens/HomeScreen/index.tsx index d46e38f..7c7c901 100644 --- a/app/screens/HomeScreen/index.tsx +++ b/app/screens/HomeScreen/index.tsx @@ -1,5 +1,10 @@ +/** + * + * Home Screen + * + */ + import React, {useState} from 'react'; -// import Text from 'theme/Text'; import {ScrollView, Text, View, Switch} from 'react-native'; import { @@ -23,7 +28,8 @@ function HomeScreen(props: HomeScreenProps): React.ReactChild { return ( + style={styles.scrollView} + testID="homeScreen">
{global.HermesInternal == null ? null : ( diff --git a/app/screens/HomeScreen/style.ts b/app/screens/HomeScreen/style.ts index bd59b72..79a2230 100644 --- a/app/screens/HomeScreen/style.ts +++ b/app/screens/HomeScreen/style.ts @@ -1,4 +1,4 @@ -import { StyleSheet } from 'react-native'; +import {StyleSheet} from 'react-native'; import Colors from 'theme/Colors'; import Dimensions from 'theme/Dimensions'; diff --git a/app/screens/HomeScreen/types.ts b/app/screens/HomeScreen/types.ts index 6ebf043..e648600 100644 --- a/app/screens/HomeScreen/types.ts +++ b/app/screens/HomeScreen/types.ts @@ -1,6 +1,6 @@ -import { RouteProp } from '@react-navigation/native'; -import { StackNavigationProp } from '@react-navigation/stack'; -import { RootStackParamList } from 'router/types'; +import {RouteProp} from '@react-navigation/native'; +import {StackNavigationProp} from '@react-navigation/stack'; +import {RootStackParamList} from 'router/types'; type HomeScreenRouteProp = RouteProp; diff --git a/app/screens/SearchScreen/Loadable.ts b/app/screens/SearchScreen/Loadable.ts deleted file mode 100644 index 183fcce..0000000 --- a/app/screens/SearchScreen/Loadable.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Asynchronously loads the component for HomeScreen - */ - -import loadable from 'react-suspense-loadable'; - -export default loadable(() => import('./index')); diff --git a/app/screens/SearchScreen/index.tsx b/app/screens/SearchScreen/index.tsx index cb95082..6626f56 100644 --- a/app/screens/SearchScreen/index.tsx +++ b/app/screens/SearchScreen/index.tsx @@ -1,15 +1,75 @@ -import React from 'react'; -import { Text, View } from 'react-native'; +/** + * + * Search Screen + * + */ -import { SearchScreenProps } from './types'; +import React, {useCallback, useEffect, useState} from 'react'; +import {Text, View} from 'react-native'; +import {Country} from 'countries-list'; + +import TouchFeedback from 'theme/TouchFeedback'; + +import {SearchScreenProps} from './types'; import style from './style'; +import LocalStorage from 'platform/LocalStorage'; +import CountriesAutocomplete from 'components/CountriesAutocomplete'; +import IconButton from 'theme/Button/IconButton'; + +export const COUNTRY_LOCAL_STORAGE_KEY = 'country'; + +const SearchScreen: React.FC = () => { + const [selectedItem, setSelectedItem] = useState(); + + const checkSelectedCountry = useCallback(async () => { + const localStorageResponse = await LocalStorage.getItem( + COUNTRY_LOCAL_STORAGE_KEY, + ); + if (localStorageResponse) { + setSelectedItem(localStorageResponse); + } + }, []); + + useEffect(() => { + checkSelectedCountry(); + }, [checkSelectedCountry]); + + const onSelect = useCallback((item: Country | undefined) => { + setSelectedItem(item); + if (item) { + LocalStorage.setItem(COUNTRY_LOCAL_STORAGE_KEY, item); + } else { + LocalStorage.removeItem(COUNTRY_LOCAL_STORAGE_KEY); + } + }, []); -function SearchScreen(props: SearchScreenProps): React.ReactChild { return ( - - Hello World + + Select Country + {selectedItem ? ( + onSelect(undefined)}> + + {selectedItem.emoji} {selectedItem.phone} {selectedItem.name} + + + onSelect(undefined)} + /> + + + ) : ( + + )} ); -} +}; export default SearchScreen; diff --git a/app/screens/SearchScreen/style.ts b/app/screens/SearchScreen/style.ts index e75c66c..b8c0d78 100644 --- a/app/screens/SearchScreen/style.ts +++ b/app/screens/SearchScreen/style.ts @@ -1,15 +1,36 @@ -import { StyleSheet } from 'react-native'; -// import Colors from 'theme/Colors'; +import {StyleSheet} from 'react-native'; +import Colors from 'theme/Colors'; +import Dimensions from 'theme/Dimensions'; const style = StyleSheet.create({ screen: { flex: 1, - alignItems: 'center', - justifyContent: 'center', + alignItems: 'flex-start', + justifyContent: 'flex-start', + }, + heading: { + fontSize: 24, + fontWeight: '600', + margin: Dimensions.space2x, + marginTop: Dimensions.space4x, }, - buttonContainer: { + selectedItemContainer: { flexDirection: 'row', - padding: 12, + padding: Dimensions.space1x, + paddingVertical: Dimensions.space2x, + alignItems: 'center', + width: Dimensions.screenWidth - Dimensions.space4x, + backgroundColor: Colors.white, + margin: Dimensions.space2x, + }, + selectedItemName: { + fontSize: 14, + marginLeft: 8, + fontWeight: '500', + }, + selectedItemCancelButton: { + position: 'absolute', + right: 0, }, }); diff --git a/app/screens/SearchScreen/tests/index.test.tsx b/app/screens/SearchScreen/tests/index.test.tsx new file mode 100644 index 0000000..dd0cc4b --- /dev/null +++ b/app/screens/SearchScreen/tests/index.test.tsx @@ -0,0 +1,94 @@ +import 'react-native'; +import React from 'react'; +import {render, fireEvent} from '@testing-library/react-native'; +import AsyncStorage from '@react-native-async-storage/async-storage'; + +import SearchScreen, {COUNTRY_LOCAL_STORAGE_KEY} from '../index'; + +const COUNTRY_NAME = 'Serbia'; +const COUNTRY_DETAILS = + '{"name":"Serbia","native":"Србија","phone":"381","continent":"EU","capital":"Belgrade","currency":"RSD","languages":["sr"],"emoji":"🇷🇸","emojiU":"U+1F1F7 U+1F1F8"}'; + +// Describing a test suite +describe('', () => { + it('Displays selected country', async () => { + // Rendering Button component using react-native-test-renderer. + const screen = await render(); + + // Grabbing our input to perform actions on it. + const inputTestID = 'countriesAutocompleteInput'; + const textInput = screen.getByTestId(inputTestID); + + /** + * RNTL gives us API to fire events on node + * Here we are firing on focus & changeText event + */ + fireEvent(textInput, 'focus'); + fireEvent.changeText(textInput, COUNTRY_NAME); + + // Grabbing our input to perform actions on it. + const listItemTestID = `listItem-${COUNTRY_NAME}`; + const firstListItem = screen.getByTestId(listItemTestID); + fireEvent.press(firstListItem); + + const selectedCountryName = screen.getByTestId('selectedItemName'); + expect(selectedCountryName).toBeTruthy(); + expect(selectedCountryName.children).toContain(COUNTRY_NAME); + }); + + it('Stores selected country in local storage', async () => { + // Rendering Button component using react-native-test-renderer. + const screen = await render(); + + // Grabbing our input to perform actions on it. + const inputTestID = 'countriesAutocompleteInput'; + const textInput = screen.getByTestId(inputTestID); + + /** + * RNTL gives us API to fire events on node + * Here we are firing on focus & changeText event + */ + fireEvent(textInput, 'focus'); + fireEvent.changeText(textInput, COUNTRY_NAME); + + // Grabbing our input to perform actions on it. + const listItemTestID = `listItem-${COUNTRY_NAME}`; + const firstListItem = screen.getByTestId(listItemTestID); + fireEvent.press(firstListItem); + + // Asserting token storage in local storage. + expect(AsyncStorage.setItem).toHaveBeenCalledWith( + COUNTRY_LOCAL_STORAGE_KEY, + COUNTRY_DETAILS, + ); + }); + + it('Removes selected country from local storage', async () => { + // Rendering Button component using react-native-test-renderer. + const screen = await render(); + + // Grabbing our input to perform actions on it. + const inputTestID = 'countriesAutocompleteInput'; + const textInput = screen.getByTestId(inputTestID); + + /** + * RNTL gives us API to fire events on node + * Here we are firing on focus & changeText event + */ + fireEvent(textInput, 'focus'); + fireEvent.changeText(textInput, COUNTRY_NAME); + + // Grabbing our input to perform actions on it. + const listItemTestID = `listItem-${COUNTRY_NAME}`; + const firstListItem = screen.getByTestId(listItemTestID); + fireEvent.press(firstListItem); + + const selectedItem = screen.getByTestId('selectedItem'); + fireEvent.press(selectedItem); + + // Asserting token storage in local storage. + expect(AsyncStorage.removeItem).toHaveBeenCalledWith( + COUNTRY_LOCAL_STORAGE_KEY, + ); + }); +}); diff --git a/app/screens/SearchScreen/types.ts b/app/screens/SearchScreen/types.ts index d8fc94e..b333f47 100644 --- a/app/screens/SearchScreen/types.ts +++ b/app/screens/SearchScreen/types.ts @@ -1,15 +1,15 @@ -import { RouteProp } from '@react-navigation/native'; -import { StackNavigationProp } from '@react-navigation/stack'; -import { RootStackParamList } from 'router/types'; +import {RouteProp} from '@react-navigation/native'; +import {StackNavigationProp} from '@react-navigation/stack'; +import {RootStackParamList} from 'router/types'; -type SearhScreenRouteProp = RouteProp; +type SearchScreenRouteProp = RouteProp; -type SearhScreenNavigationProp = StackNavigationProp< +type SearchScreenNavigationProp = StackNavigationProp< RootStackParamList, 'Search' >; -export type SearhScreenProps = { - route: SearhScreenRouteProp; - navigation: SearhScreenNavigationProp; +export type SearchScreenProps = { + route: SearchScreenRouteProp; + navigation: SearchScreenNavigationProp; }; diff --git a/app/theme/Autocomplete/index.tsx b/app/theme/Autocomplete/index.tsx new file mode 100644 index 0000000..df04ee8 --- /dev/null +++ b/app/theme/Autocomplete/index.tsx @@ -0,0 +1,79 @@ +/** + * + * Autocomplete + * + */ +import {useState} from 'react'; +import {InputProps} from 'theme/Input'; + +interface AutocompleteState { + value?: string; + focus?: boolean; +} + +interface IAutocompleteProps { + initialState?: AutocompleteState; + data?: ItemType[]; + filterKey?: string; + onChange?: (...args: unknown[]) => void; + onFocus?: (...args: unknown[]) => void; + onBlur?: (...args: unknown[]) => void; + filterFunction?: (payload: {data: ItemType[]; value: string}) => ItemType[]; +} + +interface AutocompleteOutput extends AutocompleteState { + inputProps: InputProps; + data; + clear: () => void; + blur: () => void; +} + +function filterByKey({data, key, value}) { + return (data || []).reduce((list, item) => { + if (item[key] && item[key].toLowerCase().includes(value.toLowerCase())) { + list.push(item); + } + return list; + }, []); +} + +const useAutocomplete = (props: IAutocompleteProps): AutocompleteOutput => { + const [state, set] = useState({ + value: props.initialState?.value || '', + focus: props.initialState?.focus || false, + }); + const setState = s => set({...state, ...s}); + + let data = props.data || []; + + if (props.filterKey) { + data = filterByKey({ + data: props.data, + key: props.filterKey, + value: state.value, + }); + } + + if (props.filterFunction) { + data = props.filterFunction({ + data: props.data, + value: state.value, + }); + } + + return { + inputProps: { + onChangeText: val => setState({value: val}), + value: state.value, + onFocus: () => setState({focus: true}), + onBlur: () => setState({focus: false}), + autoCorrect: false, + }, + data, + clear: () => setState({focus: false, value: ''}), + blur: () => setState({focus: false}), + ...state, + }; +}; + +export default useAutocomplete; diff --git a/app/theme/Button/IconButton/index.tsx b/app/theme/Button/IconButton/index.tsx new file mode 100644 index 0000000..1afebd7 --- /dev/null +++ b/app/theme/Button/IconButton/index.tsx @@ -0,0 +1,89 @@ +/** + * + * IconButton + * + */ +import React from 'react'; +import {StyleSheet} from 'react-native'; +import TouchFeedback from 'theme/TouchFeedback'; + +import Colors from 'theme/Colors'; +import Icon, {IconProps} from 'theme/Icon'; + +const style = StyleSheet.create({ + iconButton: { + height: 36, + width: 36, + borderRadius: 18, + backgroundColor: Colors.white, + alignItems: 'center', + justifyContent: 'center', + position: 'relative', + }, + disabled: { + opacity: 0.7, + }, + icon: { + fontSize: 18, + color: Colors.textBlack, + }, + primaryButton: { + backgroundColor: Colors.white, + borderColor: Colors.separator, + }, + accentButton: { + backgroundColor: Colors.accent, + borderColor: Colors.accent, + }, + tertiaryButton: { + backgroundColor: Colors.tertiary, + borderColor: Colors.tertiary, + }, + primaryForeground: { + color: Colors.textBlack, + }, + accentForeground: { + color: Colors.accentReverse, + }, + tertiaryForeground: { + color: Colors.tertiaryReverse, + }, +}); + +const typeBackground = { + primary: style.primaryButton, + accent: style.accentButton, + tertiary: style.tertiaryButton, +}; + +const typeForeground = { + primary: style.primaryForeground, + accent: style.accentForeground, + tertiary: style.tertiaryForeground, +}; + +type IconButtonProps = { + onPress: (...args: unknown[]) => void; + disabled?: boolean; + type?: 'primary' | 'accent'; + icon?: IconProps; + testID: string; +}; + +const IconButton: React.FC = props => { + const {onPress, disabled, type = 'primary'} = props; + return ( + undefined} + style={[ + style.iconButton, + typeBackground[type], + disabled ? style.disabled : null, + ]} + testID={props.testID}> + + + ); +}; + +export default IconButton; diff --git a/app/theme/Button/index.tsx b/app/theme/Button/index.tsx index 1bf53b8..734f69a 100644 --- a/app/theme/Button/index.tsx +++ b/app/theme/Button/index.tsx @@ -3,11 +3,11 @@ * Button * */ -import React, { useEffect, useRef } from 'react'; -import { Animated } from 'react-native'; +import React, {useEffect, useRef} from 'react'; +import {Animated} from 'react-native'; import Text from 'theme/Text'; -import TouchFeedback, { TouchFeedbackProps } from 'theme/TouchFeedback'; +import TouchFeedback, {TouchFeedbackProps} from 'theme/TouchFeedback'; import style from './style'; @@ -23,7 +23,7 @@ const typeForeground = { }; interface ButtonProps extends TouchFeedbackProps { - onPress: (...args: any[]) => any; + onPress: (...args: unknown[]) => void; label: string | React.ReactNode; mini?: boolean; flex?: boolean; diff --git a/app/theme/Button/style.ts b/app/theme/Button/style.ts index 84b2d68..2b5742d 100644 --- a/app/theme/Button/style.ts +++ b/app/theme/Button/style.ts @@ -1,4 +1,4 @@ -import { StyleSheet } from 'react-native'; +import {StyleSheet} from 'react-native'; import Colors from 'theme/Colors'; import Dimensions from 'theme/Dimensions'; diff --git a/app/theme/Button/tests/index.test.tsx b/app/theme/Button/tests/index.test.tsx index 1296109..1200bb2 100644 --- a/app/theme/Button/tests/index.test.tsx +++ b/app/theme/Button/tests/index.test.tsx @@ -1,6 +1,6 @@ import 'react-native'; import React from 'react'; -import { render, fireEvent } from '@testing-library/react-native'; +import {render, fireEvent} from '@testing-library/react-native'; // import { render } from 'utils/testWrapper'; import Button from '../index'; @@ -16,7 +16,7 @@ describe('