From e212a3c94669447edbcdbbf7588dc4a4e0a05b00 Mon Sep 17 00:00:00 2001 From: Alex Junior Date: Mon, 6 Jun 2022 10:53:02 -0300 Subject: [PATCH] [IMPROVE] Redesign search box component (#4195) Co-authored-by: Danish Ahmed Mirza Co-authored-by: Diego Mello --- app/containers/SearchBox/SearchBox.stories.js | 7 +++ app/containers/SearchBox/SearchBox.test.tsx | 45 ++++++++++++++++++ .../__snapshots__/SearchBox.stories.storyshot | 3 ++ app/containers/SearchBox/index.tsx | 46 +++++++++++++++++++ app/containers/TextInput/FormTextInput.tsx | 23 +++++++--- app/views/NewMessageView.tsx | 2 +- app/views/PickerView.tsx | 10 +--- app/views/SelectListView.tsx | 6 +-- app/views/ShareListView/Header/Header.ios.tsx | 2 +- .../ShareListView/Header}/SearchBox.tsx | 14 +++--- storybook/stories/index.js | 1 + 11 files changed, 131 insertions(+), 28 deletions(-) create mode 100644 app/containers/SearchBox/SearchBox.stories.js create mode 100644 app/containers/SearchBox/SearchBox.test.tsx create mode 100644 app/containers/SearchBox/__snapshots__/SearchBox.stories.storyshot create mode 100644 app/containers/SearchBox/index.tsx rename app/{containers => views/ShareListView/Header}/SearchBox.tsx (86%) diff --git a/app/containers/SearchBox/SearchBox.stories.js b/app/containers/SearchBox/SearchBox.stories.js new file mode 100644 index 000000000..ad4e07573 --- /dev/null +++ b/app/containers/SearchBox/SearchBox.stories.js @@ -0,0 +1,7 @@ +import { storiesOf } from '@storybook/react-native'; +import React from 'react'; +import SearchBox from './index'; + +const stories = storiesOf('SearchBox', module); + +stories.add('Item', () => ); diff --git a/app/containers/SearchBox/SearchBox.test.tsx b/app/containers/SearchBox/SearchBox.test.tsx new file mode 100644 index 000000000..6ca161d36 --- /dev/null +++ b/app/containers/SearchBox/SearchBox.test.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { fireEvent, render } from '@testing-library/react-native'; +import { TextInputProps } from 'react-native'; + +import SearchBox from '.'; + +const onChangeTextMock = jest.fn(); + +const testSearchInputs = { + onChangeText: onChangeTextMock, + testID: 'search-box-text-input' +}; + +const Render = ({ onChangeText, testID }: TextInputProps) => ; + +describe('SearchBox', () => { + it('should render the searchbox component', () => { + const { findByTestId } = render(); + + expect(findByTestId('searchbox')).toBeTruthy(); + }); + it('should not render clear-input icon', async () => { + const { queryByTestId } = render(); + const clearInput = await queryByTestId('clear-text-input'); + expect(clearInput).toBeNull(); + }); + + it('should input new value with onChangeText function', async () => { + const { findByTestId } = render(); + + const component = await findByTestId(testSearchInputs.testID); + fireEvent.changeText(component, 'new-input-value'); + expect(onChangeTextMock).toHaveBeenCalledWith('new-input-value'); + }); + + // we need skip this test for now, until discovery how handle with functions effect + // https://github.com/callstack/react-native-testing-library/issues/978 + it.skip('should clear input when call onCancelSearch function', async () => { + const { findByTestId } = render(); + + const component = await findByTestId('clear-text-input'); + fireEvent.press(component, 'input-with-value'); + expect(onChangeTextMock).toHaveBeenCalledWith('input-with-value'); + }); +}); diff --git a/app/containers/SearchBox/__snapshots__/SearchBox.stories.storyshot b/app/containers/SearchBox/__snapshots__/SearchBox.stories.storyshot new file mode 100644 index 000000000..837f8ad53 --- /dev/null +++ b/app/containers/SearchBox/__snapshots__/SearchBox.stories.storyshot @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots SearchBox Item 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"testID\\":\\"searchbox\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"marginBottom\\":10},{\\"margin\\":16,\\"marginBottom\\":16}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"position\\":\\"relative\\"}},\\"children\\":[{\\"type\\":\\"TextInput\\",\\"props\\":{\\"allowFontScaling\\":true,\\"rejectResponderTermination\\":true,\\"underlineColorAndroid\\":\\"transparent\\",\\"style\\":[{\\"color\\":\\"#0d0e12\\"},[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\",\\"height\\":48,\\"fontSize\\":16,\\"padding\\":14,\\"borderWidth\\":0.5,\\"borderRadius\\":2},null,{\\"paddingRight\\":45},{\\"backgroundColor\\":\\"#ffffff\\",\\"borderColor\\":\\"#cbcbcc\\",\\"color\\":\\"#0d0e12\\"},null,null],{\\"textAlign\\":\\"auto\\"}],\\"placeholderTextColor\\":\\"#9ca2a8\\",\\"keyboardAppearance\\":\\"light\\",\\"autoCorrect\\":false,\\"autoCapitalize\\":\\"none\\",\\"accessibilityLabel\\":\\"Search\\",\\"placeholder\\":\\"Search\\",\\"blurOnSubmit\\":true,\\"returnKeyType\\":\\"search\\",\\"value\\":\\"\\"},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":20,\\"color\\":\\"#9ca2a8\\"},[{\\"position\\":\\"absolute\\",\\"top\\":14},{\\"right\\":15}],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}"`; diff --git a/app/containers/SearchBox/index.tsx b/app/containers/SearchBox/index.tsx new file mode 100644 index 000000000..e18ea342c --- /dev/null +++ b/app/containers/SearchBox/index.tsx @@ -0,0 +1,46 @@ +import React, { useCallback, useState } from 'react'; +import { StyleSheet, TextInputProps, View } from 'react-native'; + +import { useTheme } from '../../theme'; +import I18n from '../../i18n'; +import FormTextInput from '../TextInput/FormTextInput'; + +const styles = StyleSheet.create({ + inputContainer: { + margin: 16, + marginBottom: 16 + } +}); + +const SearchBox = ({ onChangeText, onSubmitEditing, testID }: TextInputProps): JSX.Element => { + const { theme } = useTheme(); + const [text, setText] = useState(''); + + const internalOnChangeText = useCallback(value => { + setText(value); + onChangeText?.(value); + }, []); + + return ( + + internalOnChangeText('')} + iconRight={'search'} + /> + + ); +}; + +export default SearchBox; diff --git a/app/containers/TextInput/FormTextInput.tsx b/app/containers/TextInput/FormTextInput.tsx index f013b9f09..0604dbb14 100644 --- a/app/containers/TextInput/FormTextInput.tsx +++ b/app/containers/TextInput/FormTextInput.tsx @@ -62,8 +62,8 @@ export interface IRCTextInputProps extends TextInputProps { iconLeft?: TIconsName; iconRight?: TIconsName; left?: JSX.Element; - onIconRightPress?(): void; theme: TSupportedThemes; + onClearInput?: () => void; } interface IRCTextInputState { @@ -87,18 +87,29 @@ export default class FormTextInput extends React.PureComponent ) : null; } get iconRight() { - const { iconRight, onIconRightPress, theme } = this.props; + const { iconRight, theme, onClearInput, value } = this.props; + if (onClearInput && value && value.length > 0) { + return ( + + + + ); + } + return iconRight ? ( - - - + ) : null; } diff --git a/app/views/NewMessageView.tsx b/app/views/NewMessageView.tsx index 00394c62c..0216044a0 100644 --- a/app/views/NewMessageView.tsx +++ b/app/views/NewMessageView.tsx @@ -44,7 +44,7 @@ const styles = StyleSheet.create({ ...sharedStyles.textRegular }, buttonContainer: { - paddingVertical: 25 + paddingBottom: 16 } }); diff --git a/app/views/PickerView.tsx b/app/views/PickerView.tsx index 556e6414f..edbe271ce 100644 --- a/app/views/PickerView.tsx +++ b/app/views/PickerView.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { FlatList, StyleSheet, Text, View } from 'react-native'; +import { FlatList, StyleSheet, Text } from 'react-native'; import { IBaseScreen } from '../definitions'; import I18n from '../i18n'; @@ -14,10 +14,6 @@ import { ChatsStackParamList } from '../stacks/types'; import { IOptionsField } from './NotificationPreferencesView/options'; const styles = StyleSheet.create({ - search: { - width: '100%', - height: 56 - }, noResult: { fontSize: 16, paddingVertical: 56, @@ -65,9 +61,7 @@ const RenderSearch = ({ hasSearch, onChangeText }: IRenderSearch) => { } return ( <> - - - + ); diff --git a/app/views/SelectListView.tsx b/app/views/SelectListView.tsx index e6105a4cb..98a03a620 100644 --- a/app/views/SelectListView.tsx +++ b/app/views/SelectListView.tsx @@ -111,11 +111,7 @@ class SelectListView extends React.Component - this.search(text)} - testID='select-list-view-search' - onCancelPress={() => this.setState({ isSearching: false })} - /> + this.search(text)} testID='select-list-view-search' /> ); }; diff --git a/app/views/ShareListView/Header/Header.ios.tsx b/app/views/ShareListView/Header/Header.ios.tsx index 7b01bd9b2..136d49b00 100644 --- a/app/views/ShareListView/Header/Header.ios.tsx +++ b/app/views/ShareListView/Header/Header.ios.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { Keyboard, StyleSheet, View } from 'react-native'; import ShareExtension from 'rn-extensions-share'; -import SearchBox from '../../../containers/SearchBox'; +import SearchBox from './SearchBox'; import * as HeaderButton from '../../../containers/HeaderButton'; import { themes } from '../../../lib/constants'; import sharedStyles from '../../Styles'; diff --git a/app/containers/SearchBox.tsx b/app/views/ShareListView/Header/SearchBox.tsx similarity index 86% rename from app/containers/SearchBox.tsx rename to app/views/ShareListView/Header/SearchBox.tsx index 34432f5d7..a88516d87 100644 --- a/app/containers/SearchBox.tsx +++ b/app/views/ShareListView/Header/SearchBox.tsx @@ -2,13 +2,13 @@ import React from 'react'; import { StyleSheet, Text, TextInput as RNTextInput, TextInputProps, View } from 'react-native'; import Touchable from 'react-native-platform-touchable'; -import { themes } from '../lib/constants'; -import I18n from '../i18n'; -import { CustomIcon } from './CustomIcon'; -import TextInput from './TextInput'; -import { useTheme } from '../theme'; -import { isIOS } from '../utils/deviceInfo'; -import sharedStyles from '../views/Styles'; +import { themes } from '../../../lib/constants'; +import I18n from '../../../i18n'; +import { CustomIcon } from '../../../containers/CustomIcon'; +import TextInput from '../../../containers/TextInput'; +import { useTheme } from '../../../theme'; +import { isIOS } from '../../../utils/deviceInfo'; +import sharedStyles from '../../Styles'; const styles = StyleSheet.create({ container: { diff --git a/storybook/stories/index.js b/storybook/stories/index.js index 0c3a1a0e9..d6009d7b9 100644 --- a/storybook/stories/index.js +++ b/storybook/stories/index.js @@ -21,6 +21,7 @@ import '../../app/views/CannedResponsesListView/CannedResponseItem.stories'; import '../../app/containers/TextInput/TextInput.stories'; import '../../app/containers/message/Components/CollapsibleQuote/CollapsibleQuote.stories'; import '../../app/containers/Button/Button.stories'; +import '../../app/containers/SearchBox/SearchBox.stories'; // Change here to see themed storybook export const theme = 'light';