[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:
parent
02c1bc50b9
commit
e212a3c946
|
@ -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 />);
|
|
@ -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');
|
||||
});
|
||||
});
|
|
@ -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\\":[\\"\\"]}]}]}]}"`;
|
|
@ -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;
|
|
@ -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<IRCTextInputProps
|
|||
name={iconLeft}
|
||||
testID={testID ? `${testID}-icon-left` : undefined}
|
||||
size={20}
|
||||
color={themes[theme].bodyText}
|
||||
color={themes[theme].auxiliaryText}
|
||||
style={[styles.iconContainer, styles.iconLeft]}
|
||||
/>
|
||||
) : null;
|
||||
}
|
||||
|
||||
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 ? (
|
||||
<Touchable onPress={onIconRightPress} style={[styles.iconContainer, styles.iconRight]}>
|
||||
<CustomIcon name={iconRight} size={20} color={themes[theme].bodyText} />
|
||||
</Touchable>
|
||||
<CustomIcon
|
||||
name={iconRight}
|
||||
size={20}
|
||||
color={themes[theme].auxiliaryText}
|
||||
style={[styles.iconContainer, styles.iconRight]}
|
||||
/>
|
||||
) : null;
|
||||
}
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ const styles = StyleSheet.create({
|
|||
...sharedStyles.textRegular
|
||||
},
|
||||
buttonContainer: {
|
||||
paddingVertical: 25
|
||||
paddingBottom: 16
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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 (
|
||||
<>
|
||||
<View style={styles.search}>
|
||||
<SearchBox onChangeText={onChangeText} />
|
||||
</View>
|
||||
<SearchBox onChangeText={onChangeText} />
|
||||
<List.Separator />
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -111,11 +111,7 @@ class SelectListView extends React.Component<ISelectListViewProps, ISelectListVi
|
|||
const { theme } = this.props;
|
||||
return (
|
||||
<View style={{ backgroundColor: themes[theme].auxiliaryBackground }}>
|
||||
<SearchBox
|
||||
onChangeText={(text: string) => this.search(text)}
|
||||
testID='select-list-view-search'
|
||||
onCancelPress={() => this.setState({ isSearching: false })}
|
||||
/>
|
||||
<SearchBox onChangeText={(text: string) => this.search(text)} testID='select-list-view-search' />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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: {
|
|
@ -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';
|
||||
|
|
Loading…
Reference in New Issue