@wordpress/components
Version:
UI components for WordPress.
275 lines (238 loc) • 6.46 kB
JavaScript
/**
* External dependencies
*/
import {
TextInput,
Text,
View,
TouchableOpacity,
Platform,
useColorScheme,
Keyboard,
} from 'react-native';
/**
* WordPress dependencies
*/
import {
useState,
useRef,
useMemo,
useEffect,
useCallback,
} from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import {
Icon,
cancelCircleFilled as cancelCircleFilledIcon,
arrowLeft as arrowLeftIcon,
close as closeIcon,
} from '@wordpress/icons';
/**
* Internal dependencies
*/
import allStyles from './style.scss';
import platformStyles from './platform-style.scss';
import Button from '../button';
import Gridicons from '../mobile/gridicons';
// Merge platform specific styles with the default styles.
const baseStyles = { ...allStyles };
for ( const selector in platformStyles ) {
baseStyles[ selector ] = {
...baseStyles[ selector ],
...platformStyles[ selector ],
};
}
function selectModifiedStyles( styles, modifier ) {
const modifierMatcher = new RegExp( `--${ modifier }$` );
const modifierSelectors = Object.keys( styles ).filter( ( selector ) =>
selector.match( modifierMatcher )
);
return modifierSelectors.reduce( ( modifiedStyles, modifierSelector ) => {
const blockElementSelector = modifierSelector.split( '--' )[ 0 ];
modifiedStyles[ blockElementSelector ] = styles[ modifierSelector ];
return modifiedStyles;
}, {} );
}
function mergeStyles( styles, updateStyles, selectors ) {
selectors.forEach( ( selector ) => {
styles[ selector ] = {
...styles[ selector ],
...updateStyles[ selector ],
};
} );
return styles;
}
function SearchControl( {
value,
onChange,
placeholder = __( 'Search blocks' ),
} ) {
const [ isActive, setIsActive ] = useState( false );
const [ currentStyles, setCurrentStyles ] = useState( baseStyles );
const isDark = useColorScheme() === 'dark';
const inputRef = useRef();
const onCancelTimer = useRef();
const isIOS = Platform.OS === 'ios';
const darkStyles = useMemo( () => {
return selectModifiedStyles( baseStyles, 'dark' );
}, [] );
const activeStyles = useMemo( () => {
return selectModifiedStyles( baseStyles, 'active' );
}, [] );
const activeDarkStyles = useMemo( () => {
return selectModifiedStyles( baseStyles, 'active-dark' );
}, [] );
useEffect( () => {
let futureStyles = { ...baseStyles };
function mergeFutureStyles( modifiedStyles, shouldUseConditions ) {
const shouldUseModified = shouldUseConditions.every(
( should ) => should
);
const updatedStyles = shouldUseModified
? modifiedStyles
: futureStyles;
const selectors = Object.keys( modifiedStyles );
futureStyles = mergeStyles(
futureStyles,
updatedStyles,
selectors
);
}
mergeFutureStyles( activeStyles, [ isActive ] );
mergeFutureStyles( darkStyles, [ isDark ] );
mergeFutureStyles( activeDarkStyles, [ isActive, isDark ] );
setCurrentStyles( futureStyles );
// See https://github.com/WordPress/gutenberg/pull/41166
}, [ isActive, isDark ] );
const clearInput = useCallback( () => {
onChange( '' );
}, [ onChange ] );
const onPress = useCallback( () => {
setIsActive( true );
inputRef.current?.focus();
}, [] );
const onFocus = useCallback( () => {
setIsActive( true );
}, [] );
const onCancel = useCallback( () => {
clearTimeout( onCancelTimer.current );
onCancelTimer.current = setTimeout( () => {
inputRef.current?.blur();
clearInput();
setIsActive( false );
}, 0 );
}, [ clearInput ] );
const onKeyboardDidHide = useCallback( () => {
if ( ! isIOS ) {
onCancel();
}
}, [ isIOS, onCancel ] );
useEffect( () => {
const keyboardHideSubscription = Keyboard.addListener(
'keyboardDidHide',
onKeyboardDidHide
);
return () => {
clearTimeout( onCancelTimer.current );
keyboardHideSubscription.remove();
};
}, [ onKeyboardDidHide ] );
const {
'search-control__container': containerStyle,
'search-control__inner-container': innerContainerStyle,
'search-control__input-container': inputContainerStyle,
'search-control__form-input': formInputStyle,
'search-control__form-input-placeholder': placeholderStyle,
'search-control__input-button': inputButtonStyle,
'search-control__input-button-left': inputButtonLeftStyle,
'search-control__input-button-right': inputButtonRightStyle,
'search-control__cancel-button': cancelButtonStyle,
'search-control__cancel-button-text': cancelButtonTextStyle,
'search-control__icon': iconStyle,
'search-control__right-icon': rightIconStyle,
} = currentStyles;
function renderLeftButton() {
const button =
! isIOS && isActive ? (
<Button
label={ __( 'Cancel search' ) }
icon={ arrowLeftIcon }
onClick={ onCancel }
style={ iconStyle }
/>
) : (
<Icon icon={ Gridicons.search } fill={ iconStyle?.color } />
);
return (
<View style={ [ inputButtonStyle, inputButtonLeftStyle ] }>
{ button }
</View>
);
}
function renderRightButton() {
let button;
// Add a View element to properly center the input placeholder via flexbox.
if ( isIOS && ! isActive ) {
button = <View />;
}
if ( !! value ) {
button = (
<Button
label={ __( 'Clear search' ) }
icon={ isIOS ? cancelCircleFilledIcon : closeIcon }
onClick={ clearInput }
style={ [ iconStyle, rightIconStyle ] }
/>
);
}
return (
<View style={ [ inputButtonStyle, inputButtonRightStyle ] }>
{ button }
</View>
);
}
function renderCancel() {
if ( ! isIOS ) {
return null;
}
return (
<View style={ cancelButtonStyle }>
<Text
onPress={ onCancel }
style={ cancelButtonTextStyle }
accessible
accessibilityRole="button"
accessibilityLabel={ __( 'Cancel search' ) }
accessibilityHint={ __( 'Cancel search' ) }
>
{ __( 'Cancel' ) }
</Text>
</View>
);
}
return (
<TouchableOpacity
style={ containerStyle }
onPress={ onPress }
activeOpacity={ 1 }
>
<View style={ innerContainerStyle }>
<View style={ inputContainerStyle }>
{ renderLeftButton() }
<TextInput
ref={ inputRef }
style={ formInputStyle }
placeholderTextColor={ placeholderStyle?.color }
onChangeText={ onChange }
onFocus={ onFocus }
value={ value }
placeholder={ placeholder }
/>
{ renderRightButton() }
</View>
{ isActive && renderCancel() }
</View>
</TouchableOpacity>
);
}
export default SearchControl;