-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[RNMobile] Add mobile Spacer component (#17896)
* Revert package-lock.json * Setting attributes for spacer height * Correct the condition for setting maximum value * Small code refactor * Improve Accessibility in range-cell * More accessibility improvements * Small code refactor * Styling spacer refactor * Move logic to RangeCell * Keep Slider along with TextInput within RangeCell * Small cleanup * Fix missing binds * Fix focusing slider on iphoneX when VO is on * Adjust a11y voice over * Refactor pointerEvents when screen reader is on * Announce current value when finished * Improve a11y * Fix a11y on iPhoneX * Update info for translators
- Loading branch information
1 parent
afdd6df
commit f73ebe6
Showing
7 changed files
with
283 additions
and
165 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
208 changes: 171 additions & 37 deletions
208
packages/components/src/mobile/bottom-sheet/range-cell.native.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
7 changes: 0 additions & 7 deletions
7
.../components/src/mobile/slider/styles.scss → ...obile/bottom-sheet/range-cell.native.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.