import * as React from 'react'; import { Animated, GestureResponderEvent, I18nManager, Platform, StyleProp, StyleSheet, TextInput, TextInputProps, TextStyle, View, ViewStyle, } from 'react-native'; import color from 'color'; import { useInternalTheme } from '../core/theming'; import type { ThemeProp } from '../types'; import { forwardRef } from '../utils/forwardRef'; import ActivityIndicator from './ActivityIndicator'; import Divider from './Divider'; import type { IconSource } from './Icon'; import IconButton from './IconButton/IconButton'; import MaterialCommunityIcon from './MaterialCommunityIcon'; import Surface from './Surface'; interface Style { marginRight: number; } export type Props = React.ComponentPropsWithRef & { /** * Hint text shown when the input is empty. */ placeholder?: string; /** * The value of the text input. */ value: string; /** * Callback that is called when the text input's text changes. */ onChangeText?: (query: string) => void; /** * @supported Available in v5.x with theme version 3 * Search layout mode, the default value is "bar". */ mode?: 'bar' | 'view'; /** * Icon name for the left icon button (see `onIconPress`). */ icon?: IconSource; /** * Custom color for icon, default will be derived from theme */ iconColor?: string; /** * Callback to execute if we want the left icon to act as button. */ onIconPress?: (e: GestureResponderEvent) => void; /** * Callback to execute if we want to add custom behaviour to close icon button. */ onClearIconPress?: (e: GestureResponderEvent) => void; /** * Accessibility label for the button. This is read by the screen reader when the user taps the button. */ searchAccessibilityLabel?: string; /** * Custom icon for clear button, default will be icon close. It's visible when `loading` is set to `false`. * In v5.x with theme version 3, `clearIcon` is visible only `right` prop is not defined. */ clearIcon?: IconSource; /** * Accessibility label for the button. This is read by the screen reader when the user taps the button. */ clearAccessibilityLabel?: string; /** * @supported Available in v5.x with theme version 3 * Icon name for the right trailering icon button. * Works only when `mode` is set to "bar". It won't be displayed if `loading` is set to `true`. */ traileringIcon?: IconSource; /** * @supported Available in v5.x with theme version 3 * Custom color for the right trailering icon, default will be derived from theme */ traileringIconColor?: string; /** * @supported Available in v5.x with theme version 3 * Callback to execute on the right trailering icon button press. */ onTraileringIconPress?: (e: GestureResponderEvent) => void; /** * Accessibility label for the right trailering icon button. This is read by the screen reader when the user taps the button. */ traileringIconAccessibilityLabel?: string; /** * @supported Available in v5.x with theme version 3 * Callback which returns a React element to display on the right side. * Works only when `mode` is set to "bar". */ right?: (props: { color: string; style: Style; testID: string; }) => React.ReactNode; /** * @supported Available in v5.x with theme version 3 * Whether to show `Divider` at the bottom of the search. * Works only when `mode` is set to "view". True by default. */ showDivider?: boolean; /** * @supported Available in v5.x with theme version 3 * Changes Searchbar shadow and background on iOS and Android. */ elevation?: 0 | 1 | 2 | 3 | 4 | 5 | Animated.Value; /** * Set style of the TextInput component inside the searchbar */ inputStyle?: StyleProp; style?: Animated.WithAnimatedValue>; /** * Custom flag for replacing clear button with activity indicator. */ loading?: Boolean; /** * TestID used for testing purposes */ testID?: string; /** * @optional */ theme?: ThemeProp; }; type TextInputHandles = Pick< TextInput, 'setNativeProps' | 'isFocused' | 'clear' | 'blur' | 'focus' >; /** * Searchbar is a simple input box where users can type search queries. * *
* *
* * ## Usage * ```js * import * as React from 'react'; * import { Searchbar } from 'react-native-paper'; * * const MyComponent = () => { * const [searchQuery, setSearchQuery] = React.useState(''); * * const onChangeSearch = query => setSearchQuery(query); * * return ( * * ); * }; * * export default MyComponent; * ``` */ const Searchbar = forwardRef( ( { icon, iconColor: customIconColor, onIconPress, searchAccessibilityLabel = 'search', clearIcon, clearAccessibilityLabel = 'clear', onClearIconPress, traileringIcon, traileringIconColor, traileringIconAccessibilityLabel, onTraileringIconPress, right, mode = 'bar', showDivider = true, inputStyle, placeholder, elevation = 0, style, theme: themeOverrides, value, loading = false, testID = 'search-bar', ...rest }: Props, ref ) => { const theme = useInternalTheme(themeOverrides); const root = React.useRef(null); React.useImperativeHandle(ref, () => { const input = root.current; if (input) { return { focus: () => input.focus(), clear: () => input.clear(), setNativeProps: (args: TextInputProps) => input.setNativeProps(args), isFocused: () => input.isFocused(), blur: () => input.blur(), }; } const noop = () => { throw new Error('TextInput is not available'); }; return { focus: noop, clear: noop, setNativeProps: noop, isFocused: noop, blur: noop, }; }); const handleClearPress = (e: any) => { root.current?.clear(); rest.onChangeText?.(''); onClearIconPress?.(e); }; const { roundness, dark, isV3, fonts } = theme; const placeholderTextColor = isV3 ? theme.colors.onSurface : theme.colors?.placeholder; const textColor = isV3 ? theme.colors.onSurfaceVariant : theme.colors.text; const md2IconColor = dark ? textColor : color(textColor).alpha(0.54).rgb().string(); const iconColor = customIconColor || (isV3 ? theme.colors.onSurfaceVariant : md2IconColor); const rippleColor = color(textColor).alpha(0.32).rgb().string(); const font = isV3 ? { ...fonts.bodyLarge, lineHeight: Platform.select({ ios: 0, default: fonts.bodyLarge.lineHeight, }), } : theme.fonts.regular; const isBarMode = isV3 && mode === 'bar'; const shouldRenderTraileringIcon = isBarMode && traileringIcon && !loading && (!value || right !== undefined); return ( ( )) } theme={theme} accessibilityLabel={searchAccessibilityLabel} /> {loading ? ( ) : ( // Clear icon should be always rendered within Searchbar – it's transparent, // without touch events, when there is no value. It's done to avoid issues // with the abruptly stopping ripple effect and changing bar width on web, // when clearing the value. ( )) } accessibilityRole="button" theme={theme} /> )} {shouldRenderTraileringIcon ? ( ) : null} {isBarMode && right?.({ color: textColor, style: styles.rightStyle, testID })} {isV3 && !isBarMode && showDivider && ( )} ); } ); const styles = StyleSheet.create({ container: { flexDirection: 'row', alignItems: 'center', }, input: { flex: 1, fontSize: 18, paddingLeft: 8, alignSelf: 'stretch', textAlign: I18nManager.getConstants().isRTL ? 'right' : 'left', minWidth: 0, }, barModeInput: { paddingLeft: 0, minHeight: 56, }, viewModeInput: { paddingLeft: 0, minHeight: 72, }, elevation: { elevation: 4, }, loader: { margin: 10, }, v3Loader: { marginHorizontal: 16, }, rightStyle: { marginRight: 16, }, v3ClearIcon: { position: 'absolute', right: 0, marginLeft: 16, }, v3ClearIconHidden: { display: 'none', }, divider: { position: 'absolute', bottom: 0, width: '100%', }, }); export default Searchbar;