Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RNMobile] Add mobile Spacer component #17896

Merged
merged 23 commits into from
Nov 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/block-library/src/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ export const registerCoreBlocks = () => {
mediaText,
// eslint-disable-next-line no-undef
!! __DEV__ ? group : null,
// eslint-disable-next-line no-undef
!! __DEV__ ? spacer : null,
].forEach( registerBlock );

setDefaultBlockName( paragraph.name );
Expand Down
62 changes: 62 additions & 0 deletions packages/block-library/src/spacer/edit.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@

/**
* External dependencies
*/
import { View } from 'react-native';
/**
* WordPress dependencies
*/
import {
PanelBody,
BottomSheet,
} from '@wordpress/components';
import { withPreferredColorScheme } from '@wordpress/compose';
import { useState, useEffect } from '@wordpress/element';
import {
InspectorControls,
} from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import styles from './editor.scss';

const minSpacerHeight = 20;
const maxSpacerHeight = 500;

const SpacerEdit = ( { isSelected, attributes, setAttributes, getStylesFromColorScheme } ) => {
const { height } = attributes;
const [ sliderSpacerMaxHeight, setSpacerMaxHeight ] = useState( height );

// Height defined on the web can be higher than
// `maxSpacerHeight`, so there is a need to `setSpacerMaxHeight`
// after the initial render.
useEffect( () => {
setSpacerMaxHeight( height > maxSpacerHeight ? height * 2 : maxSpacerHeight );
}, [] );

const defaultStyle = getStylesFromColorScheme( styles.staticSpacer, styles.staticDarkSpacer );

return (
<View style={ [ defaultStyle, isSelected && styles.selectedSpacer, { height } ] }>
<InspectorControls>
<PanelBody title={ __( 'Spacer Settings' ) } >
<BottomSheet.RangeCell
icon={ 'admin-settings' }
label={ __( 'Height in pixels' ) }
minimumValue={ minSpacerHeight }
maximumValue={ sliderSpacerMaxHeight }
separatorType={ 'none' }
value={ height }
attribute="height"
setAttributes={ setAttributes }
style={ styles.rangeCellContainer }
/>
</PanelBody>
</InspectorControls>
</View>
);
};

export default withPreferredColorScheme( SpacerEdit );
18 changes: 18 additions & 0 deletions packages/block-library/src/spacer/editor.native.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.staticSpacer {
height: 20px;
background-color: transparent;
border: $border-width dashed $light-gray-500;
border-radius: 1px;
}

.staticDarkSpacer {
border: $border-width dashed rgba($color: $light-gray-500, $alpha: 0.3);
}

.selectedSpacer {
border: $border-width * 2 solid $blue-30;
}

.rangeCellContainer {
padding-bottom: 16px;
}
33 changes: 30 additions & 3 deletions packages/components/src/mobile/bottom-sheet/cell.native.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
import { TouchableOpacity, Text, View, TextInput, I18nManager } from 'react-native';
import { TouchableOpacity, Text, View, TextInput, I18nManager, AccessibilityInfo } from 'react-native';
import { isEmpty } from 'lodash';

/**
Expand All @@ -23,7 +23,10 @@ class BottomSheetCell extends Component {
super( ...arguments );
this.state = {
isEditingValue: props.autoFocus || false,
isScreenReaderEnabled: false,
};

this.handleScreenReaderToggled = this.handleScreenReaderToggled.bind( this );
}

componentDidUpdate() {
Expand All @@ -32,8 +35,31 @@ class BottomSheetCell extends Component {
}
}

componentDidMount() {
AccessibilityInfo.addEventListener(
'screenReaderChanged',
this.handleScreenReaderToggled,
);

AccessibilityInfo.isScreenReaderEnabled().then( ( isScreenReaderEnabled ) => {
this.setState( { isScreenReaderEnabled } );
} );
}

componentWillUnmount() {
AccessibilityInfo.removeEventListener(
'screenReaderChanged',
this.handleScreenReaderToggled,
);
}

handleScreenReaderToggled( isScreenReaderEnabled ) {
this.setState( { isScreenReaderEnabled } );
}

render() {
const {
accessible,
accessibilityLabel,
accessibilityHint,
accessibilityRole,
Expand Down Expand Up @@ -157,10 +183,11 @@ class BottomSheetCell extends Component {
};

const iconStyle = getStylesFromColorScheme( styles.icon, styles.iconDark );
const containerPointerEvents = this.state.isScreenReaderEnabled && accessible ? 'none' : 'auto';

return (
<TouchableOpacity
accessible={ ! this.state.isEditingValue }
accessible={ accessible !== undefined ? accessible : ! this.state.isEditingValue }
accessibilityLabel={ getAccessibilityLabel() }
accessibilityRole={ accessibilityRole || 'button' }
accessibilityHint={ isValueEditable ?
Expand All @@ -174,7 +201,7 @@ class BottomSheetCell extends Component {
{ drawTopSeparator && (
<View style={ separatorStyle() } />
) }
<View style={ styles.cellContainer }>
<View style={ styles.cellContainer } pointerEvents={ containerPointerEvents }>
<View style={ styles.cellRowContainer }>
{ icon && (
<View style={ styles.cellRowContainer }>
Expand Down
208 changes: 171 additions & 37 deletions packages/components/src/mobile/bottom-sheet/range-cell.native.js
Original file line number Diff line number Diff line change
@@ -1,46 +1,180 @@
/**
* External dependencies
*/
import { Platform } from 'react-native';
import { Platform, AccessibilityInfo, findNodeHandle, TextInput, Slider } from 'react-native';

/**
* WordPress dependencies
*/
import { _x, __, sprintf } from '@wordpress/i18n';
import { Component } from '@wordpress/element';

/**
* Internal dependencies
*/
import Cell from './cell';
import Slider from '../slider';

export default function BottomSheetRangeCell( props ) {
const {
value,
defaultValue,
onChangeValue,
minimumValue = 0,
maximumValue = 10,
disabled,
step = 1,
minimumTrackTintColor = '#00669b',
maximumTrackTintColor = Platform.OS === 'ios' ? '#e9eff3' : '#909090',
thumbTintColor = Platform.OS === 'ios' ? '#fff' : '#00669b',
...cellProps
} = props;

return (
<Cell
editable={ false }
{ ...cellProps }
>
<Slider
value={ value }
defaultValue={ defaultValue }
disabled={ disabled }
step={ step }
minimumValue={ minimumValue }
maximumValue={ maximumValue }
minimumTrackTintColor={ minimumTrackTintColor }
maximumTrackTintColor={ maximumTrackTintColor }
thumbTintColor={ thumbTintColor }
onChangeValue={ onChangeValue }
/>
</Cell>
);
import styles from './range-cell.scss';

class BottomSheetRangeCell extends Component {
constructor( props ) {
super( props );
this.handleToggleFocus = this.handleToggleFocus.bind( this );
this.handleChange = this.handleChange.bind( this );
this.handleValueSave = this.handleValueSave.bind( this );
this.handleReset = this.handleReset.bind( this );
this.onChangeValue = this.onChangeValue.bind( this );
this.onCellPress = this.onCellPress.bind( this );

const initialValue = this.validateInput( props.value || props.defaultValue || props.minimumValue );

this.state = { accessible: true, sliderValue: initialValue, initialValue, hasFocus: false };
}

componentDidUpdate( ) {
const reset = this.props.value === null;
if ( reset ) {
this.handleReset();
}
}

componentWillUnmount() {
this.handleToggleFocus();
}

handleChange( text ) {
if ( ! isNaN( Number( text ) ) ) {
this.setState( { sliderValue: text } );
this.announceCurrentValue( text );
}
}

handleReset() {
this.handleValueSave( this.props.defaultValue || this.state.initialValue );
}

handleToggleFocus( validateInput = true ) {
const newState = { hasFocus: ! this.state.hasFocus };

if ( validateInput ) {
const sliderValue = this.validateInput( this.state.sliderValue );
this.handleValueSave( sliderValue );
}

this.setState( newState );
}

validateInput( text ) {
const { minimumValue, maximumValue } = this.props;
if ( ! text ) {
return minimumValue;
}
if ( typeof text === 'number' ) {
return Math.min( Math.max( text, minimumValue ), maximumValue );
}
return Math.min( Math.max( text.replace( /[^0-9]/g, '' ).replace( /^0+(?=\d)/, '' ), minimumValue ), maximumValue );
}

handleValueSave( text ) {
if ( ! isNaN( Number( text ) ) ) {
this.onChangeValue( text );
this.setState( { sliderValue: text } );
this.announceCurrentValue( text );
}
}

onChangeValue( initialValue ) {
const { minimumValue, maximumValue, setAttributes, attribute } = this.props;

let sliderValue = initialValue;
if ( sliderValue < minimumValue ) {
sliderValue = minimumValue;
} else if ( sliderValue > maximumValue ) {
sliderValue = maximumValue;
}
setAttributes( {
[ attribute ]: sliderValue,
} );
}

onCellPress() {
this.setState( { accessible: false } );
if ( this.sliderRef ) {
const reactTag = findNodeHandle( this.sliderRef );
AccessibilityInfo.setAccessibilityFocus( reactTag );
}
}

announceCurrentValue( value ) {
const announcement = sprintf( __( 'Current value is %s' ), value );
AccessibilityInfo.announceForAccessibility( announcement );
}

render() {
const {
value,
defaultValue,
minimumValue = 0,
maximumValue = 10,
disabled,
step = 1,
minimumTrackTintColor = '#00669b',
maximumTrackTintColor = Platform.OS === 'ios' ? '#e9eff3' : '#909090',
thumbTintColor = Platform.OS === 'android' && '#00669b',
...cellProps
} = this.props;

const { hasFocus, sliderValue, accessible } = this.state;

const accessibilityLabel =
sprintf(
/* translators: accessibility text. Inform about current value. %1$s: Control label %2$s: Current value. */
_x( '%1$s. Current value is %2$s', 'Slider for picking a number inside a range' ),
cellProps.label, value
);

return (
<Cell
{ ...cellProps }
accessibilityRole={ 'none' }
editable={ false }
accessible={ accessible }
onPress={ this.onCellPress }
accessibilityLabel={ accessibilityLabel }
accessibilityHint={
/* translators: accessibility text (hint for focusing a slider) */
__( 'Double tap to change the value using slider' )
}
>
<Slider
value={ this.validateInput( sliderValue ) }
defaultValue={ defaultValue }
disabled={ disabled }
step={ step }
minimumValue={ minimumValue }
maximumValue={ maximumValue }
minimumTrackTintColor={ minimumTrackTintColor }
maximumTrackTintColor={ maximumTrackTintColor }
thumbTintColor={ thumbTintColor }
onValueChange={ this.handleChange }
onSlidingComplete={ this.handleValueSave }
ref={ ( slider ) => {
this.sliderRef = slider;
} }
style={ styles.slider }
accessibilityRole={ 'adjustable' }
/>
<TextInput
style={ [ styles.sliderTextInput, hasFocus ? styles.isSelected : {} ] }
onChangeText={ this.handleChange }
onFocus={ this.handleToggleFocus }
onBlur={ this.handleToggleFocus }
keyboardType="number-pad"
returnKeyType="done"
value={ `${ sliderValue }` }
/>
</Cell>
);
}
}

export default BottomSheetRangeCell;
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
.sliderContainer {
flex: 1;
flex-direction: row;
align-content: center;
justify-content: space-evenly;
}

.slider {
flex-grow: 1;
}
Expand Down
Loading