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

Mobile bottom sheet component #13612

Merged
merged 18 commits into from
Feb 1, 2019
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
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
35 changes: 26 additions & 9 deletions packages/block-library/src/image/edit.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* External dependencies
*/
import React from 'react';
import { View, Image, TextInput } from 'react-native';
import { View, Image, TextInput, Text, TouchableOpacity } from 'react-native';
import {
subscribeMediaUpload,
requestMediaPickFromMediaLibrary,
Expand All @@ -14,11 +14,12 @@ import {
/**
* Internal dependencies
*/
import { MediaPlaceholder, RichText, BlockControls, InspectorControls } from '@wordpress/editor';
import { MediaPlaceholder, RichText, BlockControls, InspectorControls, BottomSheet } from '@wordpress/editor';
import { Toolbar, ToolbarButton, Spinner } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import ImageSize from './image-size';
import { isURL } from '@wordpress/url';
import inspectorStyles from './inspector-styles.scss';

const MEDIA_ULOAD_STATE_UPLOADING = 1;
const MEDIA_ULOAD_STATE_SUCCEEDED = 2;
Expand All @@ -31,6 +32,7 @@ export default class ImageEdit extends React.Component {
this.state = {
progress: 0,
isUploadInProgress: false,
showSettings: false,
};

this.mediaUpload = this.mediaUpload.bind( this );
Expand Down Expand Up @@ -144,7 +146,11 @@ export default class ImageEdit extends React.Component {
}

const onImageSettingsButtonPressed = () => {
this.setState( { showSettings: true } );
};

const onImageSettingsClose = () => {
this.setState( { showSettings: false } );
};

const toolbarEditButton = (
Expand All @@ -157,12 +163,18 @@ export default class ImageEdit extends React.Component {
</Toolbar>
);

const inlineToolbarButtons = (
<ToolbarButton
label={ __( 'Image Settings' ) }
icon="admin-generic"
onClick={ onImageSettingsButtonPressed }
/>
const getInspectorControls = () => (
<BottomSheet
isVisible={ this.state.showSettings }
title={ __( 'Image Settings' ) }
onClose={ onImageSettingsClose }
rightButtonConfig={ { text: __( 'Done' ), color: '#0087be', onPress: onImageSettingsClose } }
etoledom marked this conversation as resolved.
Show resolved Hide resolved
>
<TouchableOpacity style={ inspectorStyles.bottomSheetCell } onPress={ () => { } }>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could probably be part of BottomSheet which would have its default style and could accept a prop containerStyle or style to customize it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean, similar to the Button above?
To have a Cell kind-of component exposed from BottomSheet?

Copy link
Contributor

@Tug Tug Jan 31, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant more like update the BottomSheet render method to something like:

render() {
	const { isVisible, leftButtonConfig, rightButtonConfig, containerStyle, onContainerPress } = this.props;
 	return (
		<Modal
			....
			<View style={ styles.separator } />
			<TouchableOpacity style={ { ... defaultContainerStyle, ...containerStyle } } onPress={ onContainerPress || noop }>
				{ this.props.children }
			</TouchableOpacity>
			<View style={ { flexGrow: 1 } }></View>
			....
		</Modal>
	);
}

Copy link
Contributor Author

@etoledom etoledom Jan 31, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, the problem there is that the buttons or cells are defined by the caller side. And each of those cells will have a TouchableOpacity component. Later on we will add more cells to it:

<BottomSheet
	isVisible={ this.state.showSettings }
	title={ __( 'Image Settings' ) }
	onClose={ onImageSettingsClose }
	rightButtonConfig={ { text: __( 'Done' ), color: '#0087be', onPress: onImageSettingsClose } }
>
	<TouchableOpacity style={ inspectorStyles.bottomSheetCell } onPress={ () => { } }>
		<Text style={ inspectorStyles.bottomSheetCellLabel }>{ __( 'Alt Text' ) }</Text>
		<Text style={ inspectorStyles.bottomSheetCellValue }>{ __( 'None' ) }</Text>
	</TouchableOpacity>
	<TouchableOpacity style={ inspectorStyles.bottomSheetCell } onPress={ () => { } }>
		<Text style={ inspectorStyles.bottomSheetCellLabel }>{ __( 'Size' ) }</Text>
		<Text style={ inspectorStyles.bottomSheetCellValue }>{ __( 'Full Size' ) }</Text>
	</TouchableOpacity>
</BottomSheet>

That's why I thought that maybe extracting the whole cell as a small component would be good:

<TouchableOpacity style={ inspectorStyles.bottomSheetCell } onPress={ () => { } }>
	<Text style={ inspectorStyles.bottomSheetCellLabel }>{ __( 'Size' ) }</Text>
	<Text style={ inspectorStyles.bottomSheetCellValue }>{ __( 'Full Size' ) }</Text>
</TouchableOpacity>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see, so the TouchableOpacity is for a "cell" I thought it was for the whole content. Yeah makes sense. I'm not sure the concept of a cell needs to be part or BottomSheet but it looks like a nice addition at this point 👍

<Text style={ inspectorStyles.bottomSheetCellLabel }>{ __( 'Alt Text' ) }</Text>
<Text style={ inspectorStyles.bottomSheetCellValue }>{ __( 'None' ) }</Text>
</TouchableOpacity>
</BottomSheet>
);

const showSpinner = this.state.isUploadInProgress;
Expand All @@ -176,7 +188,11 @@ export default class ImageEdit extends React.Component {
{ toolbarEditButton }
</BlockControls>
<InspectorControls>
{ inlineToolbarButtons }
<ToolbarButton
label={ __( 'Image Settings' ) }
icon="admin-generic"
onClick={ onImageSettingsButtonPressed }
/>
</InspectorControls>
<ImageSize src={ url } >
{ ( sizes ) => {
Expand All @@ -197,6 +213,7 @@ export default class ImageEdit extends React.Component {

return (
<View style={ { flex: 1 } } >
{ getInspectorControls() }
<Image
style={ { width: finalWidth, height: finalHeight, opacity } }
resizeMethod="scale"
Expand Down
20 changes: 20 additions & 0 deletions packages/block-library/src/image/inspector-styles.native.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//Bottom Sheet

.bottomSheetCell {
flex-direction: row;
min-height: 60;
justify-content: space-between;
padding-left: 12;
padding-right: 12;
align-items: center;
}

.bottomSheetCellLabel {
font-size: 18px;
color: #000;
}

.bottomSheetCellValue {
font-size: 18px;
color: $dark-gray-400;
}
1 change: 1 addition & 0 deletions packages/editor/src/components/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export { default as PostTitle } from './post-title';
export { default as EditorHistoryRedo } from './editor-history/redo';
export { default as EditorHistoryUndo } from './editor-history/undo';
export { default as InspectorControls } from './inspector-controls';
export { default as BottomSheet } from './mobile/bottom-sheet';
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* External dependencies
*/
import { TouchableOpacity, View } from 'react-native';

export default function Button( props ) {
const {
children,
onPress,
disabled,
} = props;

return (
<TouchableOpacity
accessible={ true }
onPress={ onPress }
disabled={ disabled }
>
<View style={ { flexDirection: 'row', justifyContent: 'center' } }>
{ children }
</View>
</TouchableOpacity>
);
}
96 changes: 96 additions & 0 deletions packages/editor/src/components/mobile/bottom-sheet/index.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* External dependencies
*/
import { Text, View } from 'react-native';
import Modal from 'react-native-modal';
import SafeArea from 'react-native-safe-area';

/**
* WordPress dependencies
*/
import { Component } from '@wordpress/element';

/**
* Internal dependencies
*/
import styles from './styles.scss';
import Button from './button';

class BottomSheet extends Component {
constructor() {
super( ...arguments );
this.onSafeAreaInsetsUpdate = this.onSafeAreaInsetsUpdate.bind( this );
this.state = {
safeAreaBottomInset: 0,
};

SafeArea.getSafeAreaInsetsForRootView().then( this.onSafeAreaInsetsUpdate );
}

componentDidMount() {
SafeArea.addEventListener( 'safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsUpdate );
}

componentWillUnmount() {
SafeArea.removeEventListener( 'safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsUpdate );
}

headerButton( config ) {
return (
<Button onPress={ config.onPress }>
<Text style={ { ...styles.buttonText, color: config.color } }>
{ config.text }
</Text>
</Button>
);
}

onSafeAreaInsetsUpdate( result ) {
const { safeAreaInsets } = result;
if ( this.state.safeAreaBottomInset !== safeAreaInsets.bottom ) {
this.setState( { safeAreaBottomInset: safeAreaInsets.bottom } );
}
}

render() {
const { isVisible, leftButtonConfig, rightButtonConfig } = this.props;

return (
<Modal
isVisible={ isVisible }
style={ styles.bottomModal }
animationInTiming={ 500 }
animationOutTiming={ 500 }
backdropTransitionInTiming={ 500 }
backdropTransitionOutTiming={ 500 }
onBackdropPress={ this.props.onClose }
onSwipe={ this.props.onClose }
swipeDirection="down"
>
<View style={ { ...styles.content, borderColor: 'rgba(0, 0, 0, 0.1)' } }>
<View style={ styles.dragIndicator } />
<View style={ styles.head }>
<View style={ { flex: 1 } }>
{ leftButtonConfig && this.headerButton( leftButtonConfig ) }
</View>
<View style={ styles.titleContainer }>
<Text style={ styles.title }>
{ this.props.title }
</Text>
</View>
<View style={ { flex: 1 } }>
{ rightButtonConfig && this.headerButton( rightButtonConfig ) }
</View>
</View>

<View style={ styles.separator } />
{ this.props.children }
<View style={ { flexGrow: 1 } }></View>
<View style={ { height: this.state.safeAreaBottomInset } } />
</View>
</Modal>
);
}
}

export default BottomSheet;
59 changes: 59 additions & 0 deletions packages/editor/src/components/mobile/bottom-sheet/styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//Bottom Sheet

.bottomModal {
justify-content: flex-end;
margin: 0;
}

.dragIndicator {
background-color: $light-gray-400;
height: 4px;
width: 10%;
top: -12px;
margin: auto;
border-radius: 2px;
}

.separator {
background-color: $light-gray-400;
height: 1px;
width: 100%;
margin: auto;
}

.content {
background-color: $white;
padding: 18px 10px 5px 10px;
justify-content: center;
border-top-right-radius: 8px;
border-top-left-radius: 8px;
}

.head {
flex-direction: row;
width: 100%;
margin-bottom: 5px;
justify-content: space-between;
align-items: center;
align-content: center;
}

.title {
color: $dark-gray-600;
font-size: 18px;
font-weight: 600;
text-align: center;
}

.titleContainer {
justify-content: center;
flex: 2;
align-content: center;
}

// Button

.buttonText {
font-size: 18px;
padding: 5px;
}