[IMPROVE] Redesign search box component (#4195)

Co-authored-by: Danish Ahmed Mirza <danishmirza30602@gmail.com>
Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
Alex Junior 2022-06-06 10:53:02 -03:00 committed by GitHub
parent 02c1bc50b9
commit e212a3c946
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 131 additions and 28 deletions

View File

@ -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', () => <SearchBox />);

View File

@ -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) => <SearchBox testID={testID} onChangeText={onChangeText} />;
describe('SearchBox', () => {
it('should render the searchbox component', () => {
const { findByTestId } = render(<Render onChangeText={testSearchInputs.onChangeText} testID={testSearchInputs.testID} />);
expect(findByTestId('searchbox')).toBeTruthy();
});
it('should not render clear-input icon', async () => {
const { queryByTestId } = render(<Render onChangeText={testSearchInputs.onChangeText} testID={testSearchInputs.testID} />);
const clearInput = await queryByTestId('clear-text-input');
expect(clearInput).toBeNull();
});
it('should input new value with onChangeText function', async () => {
const { findByTestId } = render(<Render onChangeText={onChangeTextMock} testID={testSearchInputs.testID} />);
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(<Render testID={'input-with-value'} onChangeText={onChangeTextMock} />);
const component = await findByTestId('clear-text-input');
fireEvent.press(component, 'input-with-value');
expect(onChangeTextMock).toHaveBeenCalledWith('input-with-value');
});
});

View File

@ -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\\":[\\"\\"]}]}]}]}"`;

View File

@ -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 (
<View testID='searchbox'>
<FormTextInput
autoCapitalize='none'
autoCorrect={false}
blurOnSubmit
placeholder={I18n.t('Search')}
returnKeyType='search'
underlineColorAndroid='transparent'
containerStyle={styles.inputContainer}
onChangeText={internalOnChangeText}
onSubmitEditing={onSubmitEditing}
value={text}
theme={theme}
testID={testID}
onClearInput={() => internalOnChangeText('')}
iconRight={'search'}
/>
</View>
);
};
export default SearchBox;

View File

@ -62,8 +62,8 @@ export interface IRCTextInputProps extends TextInputProps {
iconLeft?: TIconsName; iconLeft?: TIconsName;
iconRight?: TIconsName; iconRight?: TIconsName;
left?: JSX.Element; left?: JSX.Element;
onIconRightPress?(): void;
theme: TSupportedThemes; theme: TSupportedThemes;
onClearInput?: () => void;
} }
interface IRCTextInputState { interface IRCTextInputState {
@ -87,18 +87,29 @@ export default class FormTextInput extends React.PureComponent<IRCTextInputProps
name={iconLeft} name={iconLeft}
testID={testID ? `${testID}-icon-left` : undefined} testID={testID ? `${testID}-icon-left` : undefined}
size={20} size={20}
color={themes[theme].bodyText} color={themes[theme].auxiliaryText}
style={[styles.iconContainer, styles.iconLeft]} style={[styles.iconContainer, styles.iconLeft]}
/> />
) : null; ) : null;
} }
get iconRight() { get iconRight() {
const { iconRight, onIconRightPress, theme } = this.props; const { iconRight, theme, onClearInput, value } = this.props;
if (onClearInput && value && value.length > 0) {
return (
<Touchable onPress={onClearInput} style={[styles.iconContainer, styles.iconRight]} testID='clear-text-input'>
<CustomIcon name='input-clear' size={20} color={themes[theme].auxiliaryTintColor} />
</Touchable>
);
}
return iconRight ? ( return iconRight ? (
<Touchable onPress={onIconRightPress} style={[styles.iconContainer, styles.iconRight]}> <CustomIcon
<CustomIcon name={iconRight} size={20} color={themes[theme].bodyText} /> name={iconRight}
</Touchable> size={20}
color={themes[theme].auxiliaryText}
style={[styles.iconContainer, styles.iconRight]}
/>
) : null; ) : null;
} }

View File

@ -44,7 +44,7 @@ const styles = StyleSheet.create({
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },
buttonContainer: { buttonContainer: {
paddingVertical: 25 paddingBottom: 16
} }
}); });

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { FlatList, StyleSheet, Text, View } from 'react-native'; import { FlatList, StyleSheet, Text } from 'react-native';
import { IBaseScreen } from '../definitions'; import { IBaseScreen } from '../definitions';
import I18n from '../i18n'; import I18n from '../i18n';
@ -14,10 +14,6 @@ import { ChatsStackParamList } from '../stacks/types';
import { IOptionsField } from './NotificationPreferencesView/options'; import { IOptionsField } from './NotificationPreferencesView/options';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
search: {
width: '100%',
height: 56
},
noResult: { noResult: {
fontSize: 16, fontSize: 16,
paddingVertical: 56, paddingVertical: 56,
@ -65,9 +61,7 @@ const RenderSearch = ({ hasSearch, onChangeText }: IRenderSearch) => {
} }
return ( return (
<> <>
<View style={styles.search}> <SearchBox onChangeText={onChangeText} />
<SearchBox onChangeText={onChangeText} />
</View>
<List.Separator /> <List.Separator />
</> </>
); );

View File

@ -111,11 +111,7 @@ class SelectListView extends React.Component<ISelectListViewProps, ISelectListVi
const { theme } = this.props; const { theme } = this.props;
return ( return (
<View style={{ backgroundColor: themes[theme].auxiliaryBackground }}> <View style={{ backgroundColor: themes[theme].auxiliaryBackground }}>
<SearchBox <SearchBox onChangeText={(text: string) => this.search(text)} testID='select-list-view-search' />
onChangeText={(text: string) => this.search(text)}
testID='select-list-view-search'
onCancelPress={() => this.setState({ isSearching: false })}
/>
</View> </View>
); );
}; };

View File

@ -2,7 +2,7 @@ import React, { useState } from 'react';
import { Keyboard, StyleSheet, View } from 'react-native'; import { Keyboard, StyleSheet, View } from 'react-native';
import ShareExtension from 'rn-extensions-share'; import ShareExtension from 'rn-extensions-share';
import SearchBox from '../../../containers/SearchBox'; import SearchBox from './SearchBox';
import * as HeaderButton from '../../../containers/HeaderButton'; import * as HeaderButton from '../../../containers/HeaderButton';
import { themes } from '../../../lib/constants'; import { themes } from '../../../lib/constants';
import sharedStyles from '../../Styles'; import sharedStyles from '../../Styles';

View File

@ -2,13 +2,13 @@ import React from 'react';
import { StyleSheet, Text, TextInput as RNTextInput, TextInputProps, View } from 'react-native'; import { StyleSheet, Text, TextInput as RNTextInput, TextInputProps, View } from 'react-native';
import Touchable from 'react-native-platform-touchable'; import Touchable from 'react-native-platform-touchable';
import { themes } from '../lib/constants'; import { themes } from '../../../lib/constants';
import I18n from '../i18n'; import I18n from '../../../i18n';
import { CustomIcon } from './CustomIcon'; import { CustomIcon } from '../../../containers/CustomIcon';
import TextInput from './TextInput'; import TextInput from '../../../containers/TextInput';
import { useTheme } from '../theme'; import { useTheme } from '../../../theme';
import { isIOS } from '../utils/deviceInfo'; import { isIOS } from '../../../utils/deviceInfo';
import sharedStyles from '../views/Styles'; import sharedStyles from '../../Styles';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {

View File

@ -21,6 +21,7 @@ import '../../app/views/CannedResponsesListView/CannedResponseItem.stories';
import '../../app/containers/TextInput/TextInput.stories'; import '../../app/containers/TextInput/TextInput.stories';
import '../../app/containers/message/Components/CollapsibleQuote/CollapsibleQuote.stories'; import '../../app/containers/message/Components/CollapsibleQuote/CollapsibleQuote.stories';
import '../../app/containers/Button/Button.stories'; import '../../app/containers/Button/Button.stories';
import '../../app/containers/SearchBox/SearchBox.stories';
// Change here to see themed storybook // Change here to see themed storybook
export const theme = 'light'; export const theme = 'light';