import React, { forwardRef } from 'react'; import type { TextInputProps as NativeTextInputProps, TextInput as RNTextInput, StyleProp, ViewStyle, TextStyle, } from 'react-native'; import type { State } from './StyledSearch'; import { StyledAffixContainer, StyledContainer, StyledInput, StyledInputContainer, } from './StyledSearch'; import type { IconName } from '../Icon'; import { renderPrefix, renderSuffix } from './utils'; import Button from '../Button'; import { useTheme } from '../../theme'; type SearchOneLineHandles = Pick< RNTextInput, 'focus' | 'clear' | 'blur' | 'isFocused' | 'setNativeProps' >; interface SearchOneLineProps extends NativeTextInputProps { /** * Name of Icon or ReactElement to render on the left side of the input, before the user's cursor. */ prefix?: IconName | React.ReactElement; /** * Name of Icon or ReactElement to render on the right side of the input. */ suffix?: IconName | React.ReactElement; /** * Additional wrapper style. */ style?: StyleProp; /** * Input text style. */ textStyle?: StyleProp; /** * Testing id of the component. */ testID?: string; /** * Accessibility label for the input (Android). */ accessibilityLabelledBy?: string; /** * Placeholder text to display. * */ placeholder?: string; /** * Whether the input is editable. * */ editable?: boolean; /** * Whether the input is disabled. */ disabled?: boolean; /** * The max length of the input. * If the max length is set, the input will display the current length and the max length. * */ maxLength?: number; /** * Whether to hide the character count. * */ renderInputValue?: (inputProps: NativeTextInputProps) => React.ReactNode; /** * Variant of the SearchTwo * @default 'basic' */ variant?: 'basic' | 'reversed'; /** * Clearable input. */ clearable?: boolean; /** * If true, indicates that the SearchOneLine is accessible to screen readers. */ accessible?: boolean; } export const getState = ({ disabled, editable, isEmptyValue, }: { disabled?: boolean; editable?: boolean; isFocused?: boolean; isEmptyValue?: boolean; }): State => { if (disabled) { return 'disabled'; } if (!editable) { return 'readonly'; } if (!isEmptyValue) { return 'filled'; } return 'default'; }; const renderInput = ({ nativeInputProps, ref, }: { nativeInputProps: NativeTextInputProps; ref?: React.Ref; }) => { return ; }; const SearchOneLine = forwardRef( (props, ref) => { const { prefix = 'search-outlined', suffix, style, allowFontScaling, accessibilityLabelledBy, editable = true, maxLength, value, defaultValue, placeholder, disabled = false, testID, variant = 'basic', clearable = false, accessible, ...nativeProps } = props; const [isFocused, setIsFocused] = React.useState(false); const theme = useTheme(); const displayText = (value !== undefined ? value : defaultValue) ?? ''; const isEmptyValue = displayText.length === 0; const state = getState({ disabled, editable, isFocused, isEmptyValue, }); const innerTextInput = React.useRef(null); React.useImperativeHandle( ref, () => ({ // we don't expose this method, it's for testing https://medium.com/developer-rants/how-to-test-useref-without-mocking-useref-699165f4994e getNativeTextInputRef: () => innerTextInput.current, setNativeProps: (args: NativeTextInputProps) => innerTextInput.current?.setNativeProps(args), focus: () => { innerTextInput.current?.focus(); }, blur: () => innerTextInput.current?.blur(), clear: () => innerTextInput.current?.clear(), isFocused: () => innerTextInput.current?.isFocused() || false, }), [innerTextInput] ); const renderClearButton = () => { return ( { innerTextInput.current?.clear(); nativeProps.onChangeText?.(''); }} intent="text" testID="input-clear-button" icon="circle-cancel" size="xsmall" disabled={state === 'disabled' || state === 'readonly'} /> ); }; const shouldShowClearButton = clearable && (state === 'filled' || isFocused) && !isEmptyValue; return ( {renderPrefix({ prefix })} {renderInput({ nativeInputProps: { testID: 'text-input', accessibilityState: { disabled: state === 'disabled' || state === 'readonly', }, accessibilityLabelledBy, allowFontScaling, ...nativeProps, onFocus: (event) => { setIsFocused(true); nativeProps.onFocus?.(event); }, onBlur: (event) => { setIsFocused(false); nativeProps.onBlur?.(event); }, editable, maxLength, value, defaultValue, placeholder, placeholderTextColor: theme.__hd__.textInput.colors.placeholder[state], }, ref: (rnTextInputRef) => { innerTextInput.current = rnTextInputRef; }, })} {shouldShowClearButton ? renderClearButton() : !!suffix && renderSuffix({ suffix })} ); } ); export default SearchOneLine;