[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;
|
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;
|
||||||
return iconRight ? (
|
if (onClearInput && value && value.length > 0) {
|
||||||
<Touchable onPress={onIconRightPress} style={[styles.iconContainer, styles.iconRight]}>
|
return (
|
||||||
<CustomIcon name={iconRight} size={20} color={themes[theme].bodyText} />
|
<Touchable onPress={onClearInput} style={[styles.iconContainer, styles.iconRight]} testID='clear-text-input'>
|
||||||
|
<CustomIcon name='input-clear' size={20} color={themes[theme].auxiliaryTintColor} />
|
||||||
</Touchable>
|
</Touchable>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return iconRight ? (
|
||||||
|
<CustomIcon
|
||||||
|
name={iconRight}
|
||||||
|
size={20}
|
||||||
|
color={themes[theme].auxiliaryText}
|
||||||
|
style={[styles.iconContainer, styles.iconRight]}
|
||||||
|
/>
|
||||||
) : null;
|
) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ const styles = StyleSheet.create({
|
||||||
...sharedStyles.textRegular
|
...sharedStyles.textRegular
|
||||||
},
|
},
|
||||||
buttonContainer: {
|
buttonContainer: {
|
||||||
paddingVertical: 25
|
paddingBottom: 16
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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 />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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: {
|
|
@ -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';
|
||||||
|
|
Loading…
Reference in New Issue