UNPKG

@wordpress/components

Version:
380 lines (352 loc) 9.41 kB
/** * External dependencies */ import { TouchableOpacity, Text, View, TextInput, I18nManager, AccessibilityInfo, } from 'react-native'; import { isEmpty, get } from 'lodash'; /** * WordPress dependencies */ import { Icon } from '@wordpress/components'; import { check } from '@wordpress/icons'; import { Component } from '@wordpress/element'; import { __, _x, sprintf } from '@wordpress/i18n'; import { withPreferredColorScheme } from '@wordpress/compose'; /** * Internal dependencies */ import styles from './styles.scss'; import platformStyles from './cellStyles.scss'; import TouchableRipple from './ripple'; class BottomSheetCell extends Component { constructor( props ) { super( ...arguments ); this.state = { isEditingValue: props.autoFocus || false, isScreenReaderEnabled: false, }; this.handleScreenReaderToggled = this.handleScreenReaderToggled.bind( this ); this.isCurrent = false; } componentDidUpdate( prevProps, prevState ) { if ( ! prevState.isEditingValue && this.state.isEditingValue ) { this._valueTextInput.focus(); } } componentDidMount() { this.isCurrent = true; AccessibilityInfo.addEventListener( 'screenReaderChanged', this.handleScreenReaderToggled ); AccessibilityInfo.isScreenReaderEnabled().then( ( isScreenReaderEnabled ) => { if ( this.isCurrent ) { this.setState( { isScreenReaderEnabled } ); } } ); } componentWillUnmount() { this.isCurrent = false; AccessibilityInfo.removeEventListener( 'screenReaderChanged', this.handleScreenReaderToggled ); } handleScreenReaderToggled( isScreenReaderEnabled ) { this.setState( { isScreenReaderEnabled } ); } typeToKeyboardType( type, step ) { let keyboardType = `default`; if ( type === `number` ) { if ( step && Math.abs( step ) < 1 ) { keyboardType = `decimal-pad`; } else { keyboardType = `number-pad`; } } return keyboardType; } render() { const { accessible, accessibilityLabel, accessibilityHint, accessibilityRole, disabled = false, activeOpacity, onPress, onLongPress, label, value, valuePlaceholder = '', icon, leftAlign, labelStyle = {}, valueStyle = {}, cellContainerStyle = {}, cellRowContainerStyle = {}, onChangeValue, onSubmit, children, editable = true, isSelected = false, separatorType, style = {}, getStylesFromColorScheme, customActionButton, type, step, borderless, ...valueProps } = this.props; const showValue = value !== undefined; const isValueEditable = editable && onChangeValue !== undefined; const cellLabelStyle = getStylesFromColorScheme( styles.cellLabel, styles.cellTextDark ); const cellLabelCenteredStyle = getStylesFromColorScheme( styles.cellLabelCentered, styles.cellTextDark ); const cellLabelLeftAlignNoIconStyle = getStylesFromColorScheme( styles.cellLabelLeftAlignNoIcon, styles.cellTextDark ); const defaultMissingIconAndValue = leftAlign ? cellLabelLeftAlignNoIconStyle : cellLabelCenteredStyle; const defaultLabelStyle = showValue || customActionButton || icon ? cellLabelStyle : defaultMissingIconAndValue; const drawSeparator = ( separatorType && separatorType !== 'none' ) || separatorStyle === undefined; const drawTopSeparator = drawSeparator && separatorType === 'topFullWidth'; const cellContainerStyles = [ styles.cellContainer, cellContainerStyle, ]; const rowContainerStyles = [ styles.cellRowContainer, cellRowContainerStyle, ]; const isInteractive = isValueEditable || onPress !== undefined || onLongPress !== undefined; const onCellPress = () => { if ( isValueEditable ) { startEditing(); } else if ( onPress !== undefined ) { onPress(); } }; const finishEditing = () => { this.setState( { isEditingValue: false } ); }; const startEditing = () => { if ( this.state.isEditingValue === false ) { this.setState( { isEditingValue: true } ); } }; const separatorStyle = () => { //eslint-disable-next-line @wordpress/no-unused-vars-before-return const defaultSeparatorStyle = this.props.getStylesFromColorScheme( styles.separator, styles.separatorDark ); const cellSeparatorStyle = this.props.getStylesFromColorScheme( styles.cellSeparator, styles.cellSeparatorDark ); const leftMarginStyle = { ...cellSeparatorStyle, ...platformStyles.separatorMarginLeft, }; switch ( separatorType ) { case 'leftMargin': return leftMarginStyle; case 'fullWidth': case 'topFullWidth': return defaultSeparatorStyle; case 'none': return undefined; case undefined: if ( showValue && icon ) { return leftMarginStyle; } return defaultSeparatorStyle; } }; const getValueComponent = () => { const styleRTL = I18nManager.isRTL && styles.cellValueRTL; const cellValueStyle = this.props.getStylesFromColorScheme( styles.cellValue, styles.cellTextDark ); const finalStyle = { ...cellValueStyle, ...valueStyle, ...styleRTL, }; // To be able to show the `middle` ellipsizeMode on editable cells // we show the TextInput just when the user wants to edit the value, // and the Text component to display it. // We also show the TextInput to display placeholder. const shouldShowPlaceholder = isValueEditable && value === ''; return this.state.isEditingValue || shouldShowPlaceholder ? ( <TextInput ref={ ( c ) => ( this._valueTextInput = c ) } numberOfLines={ 1 } style={ finalStyle } value={ value } placeholder={ valuePlaceholder } placeholderTextColor={ '#87a6bc' } onChangeText={ onChangeValue } editable={ isValueEditable } pointerEvents={ this.state.isEditingValue ? 'auto' : 'none' } onFocus={ startEditing } onBlur={ finishEditing } onSubmitEditing={ onSubmit } keyboardType={ this.typeToKeyboardType( type, step ) } { ...valueProps } /> ) : ( <Text style={ { ...cellValueStyle, ...valueStyle } } numberOfLines={ 1 } ellipsizeMode={ 'middle' } > { value } </Text> ); }; const getAccessibilityLabel = () => { if ( accessible === false ) { return; } if ( accessibilityLabel || ! showValue ) { return accessibilityLabel || label; } return isEmpty( value ) ? sprintf( /* translators: accessibility text. Empty state of a inline textinput cell. %s: The cell's title */ _x( '%s. Empty', 'inline textinput cell' ), label ) : // Separating by ',' is necessary to make a pause on urls (non-capitalized text) sprintf( /* translators: accessibility text. Inline textinput title and value.%1: Cell title, %2: cell value. */ _x( '%1$s, %2$s', 'inline textinput cell' ), label, value ); }; const iconStyle = getStylesFromColorScheme( styles.icon, styles.iconDark ); const resetButtonStyle = getStylesFromColorScheme( styles.resetButton, styles.resetButtonDark ); const containerPointerEvents = this.state.isScreenReaderEnabled && accessible ? 'none' : 'auto'; const { title, handler } = customActionButton || {}; const opacity = activeOpacity !== undefined ? activeOpacity : get( platformStyles, 'activeOpacity.opacity' ); return ( <TouchableRipple accessible={ accessible !== undefined ? accessible : ! this.state.isEditingValue } accessibilityLabel={ getAccessibilityLabel() } accessibilityRole={ accessibilityRole || 'button' } accessibilityHint={ isValueEditable ? /* translators: accessibility text */ __( 'Double tap to edit this value' ) : accessibilityHint } disabled={ disabled || ! isInteractive } activeOpacity={ opacity } onPress={ onCellPress } onLongPress={ onLongPress } style={ [ styles.clipToBounds, style ] } borderless={ borderless } > { drawTopSeparator && <View style={ separatorStyle() } /> } <View style={ cellContainerStyles } pointerEvents={ containerPointerEvents } > <View style={ rowContainerStyles }> <View style={ styles.cellRowContainer }> { icon && ( <View style={ styles.cellRowContainer }> <Icon icon={ icon } size={ 24 } fill={ iconStyle.color } isPressed={ false } /> <View style={ platformStyles.labelIconSeparator } /> </View> ) } { label && ( <Text style={ [ defaultLabelStyle, labelStyle ] } > { label } </Text> ) } </View> { customActionButton && ( <TouchableOpacity onPress={ handler } accessibilityRole={ 'button' } > <Text style={ resetButtonStyle }> { title } </Text> </TouchableOpacity> ) } </View> { isSelected && ( <Icon icon={ check } fill={ platformStyles.isSelected.color } /> ) } { showValue && getValueComponent() } { children } </View> { ! drawTopSeparator && <View style={ separatorStyle() } /> } </TouchableRipple> ); } } export default withPreferredColorScheme( BottomSheetCell );