// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. /* eslint-disable react/prop-types */ // We disable the prop types check here as forwardRef & typescript has a bug import {SearchBar} from '@rneui/base'; import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; import {useIntl} from 'react-intl'; import {type ActivityIndicatorProps, Keyboard, Platform, type StyleProp, TextInput, type TextInputProps, type TextStyle, type TouchableOpacityProps, type ViewStyle} from 'react-native'; import CompassIcon from '@components/compass_icon'; import {SEARCH_INPUT_HEIGHT} from '@constants/view'; import {useTheme} from '@context/theme'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {typography} from '@utils/typography'; export type SearchProps = TextInputProps & { cancelIcon?: React.ReactElement; cancelButtonProps?: Partial & { buttonStyle?: StyleProp; buttonTextStyle?: StyleProp; color?: string; buttonDisabledStyle?: StyleProp; buttonDisabledTextStyle?: StyleProp; }; cancelButtonTitle?: string; clearIcon?: React.ReactElement; clearIconColor?: string; containerStyle?: StyleProp; inputContainerStyle?: StyleProp; inputStyle?: StyleProp; leftIconContainerStyle?: StyleProp; loadingProps?: ActivityIndicatorProps; onCancel?(): void; onClear?(): void; rightIconContainerStyle?: StyleProp; searchIcon?: React.ReactElement; searchIconColor?: string; selectionColor?: string; showCancel?: boolean; showLoading?: boolean; }; type Selection = {start: number; end: number}; export type SearchRef = { blur: () => void; cancel: () => void; clear: () => void; focus: () => void; setCaretPosition(selection: Selection): void; } const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ containerStyle: { backgroundColor: undefined, height: undefined, paddingTop: 0, paddingBottom: 0, }, inputContainerStyle: { backgroundColor: changeOpacity(theme.centerChannelColor, 0.12), borderRadius: 8, height: SEARCH_INPUT_HEIGHT, marginLeft: 0, }, inputStyle: { color: theme.centerChannelColor, marginLeft: Platform.select({ios: 6, android: 14}), top: Platform.select({android: 1}), ...typography('Body', 200, 'Regular'), lineHeight: undefined, }, })); const Search = forwardRef((props: SearchProps, ref) => { const intl = useIntl(); const theme = useTheme(); const styles = getStyleSheet(theme); const searchRef = useRef(null); const [value, setValue] = useState(props.defaultValue || props.value || ''); const searchClearButtonTestID = `${props.testID}.search.clear.button`; const searchCancelButtonTestID = `${props.testID}.search.cancel.button`; const searchInputTestID = `${props.testID}.search.input`; const onCancel = useCallback(() => { Keyboard.dismiss(); setValue(''); props.onCancel?.(); }, [props.onCancel]); const onClear = useCallback(() => { setValue(''); props.onClear?.(); }, [props.onClear]); const onChangeText = useCallback((text: string) => { setValue(text); props.onChangeText?.(text); }, [props.onChangeText]); const cancelButtonProps = useMemo(() => ({ buttonTextStyle: { color: changeOpacity(theme.centerChannelColor, 0.72), ...typography('Body', 100, 'Regular'), }, }), [theme]); useEffect(() => { setValue(props.defaultValue || props.value || ''); }, [props.defaultValue, props.value]); const clearIcon = ( ); const searchIcon = ( ); const cancelIcon = ( ); useImperativeHandle(ref, () => ({ blur: () => { searchRef.current?.blur(); }, cancel: () => { // @ts-expect-error cancel is not part of TextInput does exist in SearchBar searchRef.current?.cancel(); }, clear: () => { searchRef.current?.clear(); }, focus: () => { searchRef.current?.focus(); }, setCaretPosition: (nativeProps: Selection) => { searchRef.current?.setSelection(nativeProps.start, nativeProps.end); }, }), [searchRef]); return ( ); }); Search.displayName = 'SeachBar'; export default Search;