Allow Portal to be used as top-level modal (#4338)
* Portal * Allow Portal to be used in as both top-level and popover * modal/popover variable naming * export Portal in ~/ui * Properly handle optional onKeyDown * Add simple Playground Example
This commit is contained in:
parent
4e7b8652c8
commit
15ffd9a09c
@ -20,6 +20,7 @@ import React, { Component } from 'react';
|
||||
import CurrencySymbol from '~/ui/CurrencySymbol/currencySymbol.example';
|
||||
import QrCode from '~/ui/QrCode/qrCode.example';
|
||||
import SectionList from '~/ui/SectionList/sectionList.example';
|
||||
import Portal from '~/ui/Portal/portal.example';
|
||||
|
||||
import PlaygroundStore from './store';
|
||||
import styles from './playground.css';
|
||||
@ -27,6 +28,7 @@ import styles from './playground.css';
|
||||
PlaygroundStore.register(<CurrencySymbol />);
|
||||
PlaygroundStore.register(<QrCode />);
|
||||
PlaygroundStore.register(<SectionList />);
|
||||
PlaygroundStore.register(<Portal />);
|
||||
|
||||
@observer
|
||||
export default class Playground extends Component {
|
||||
|
@ -176,6 +176,7 @@ class AddressSelect extends Component {
|
||||
return (
|
||||
<Portal
|
||||
className={ styles.inputContainer }
|
||||
isChildModal
|
||||
onClose={ this.handleClose }
|
||||
onKeyDown={ this.handleKeyDown }
|
||||
open={ expanded }
|
||||
|
@ -15,29 +15,40 @@
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
$left: 1.5em;
|
||||
$right: $left;
|
||||
$bottom: $left;
|
||||
$top: 20vh;
|
||||
$modalMargin: 1.5em;
|
||||
$modalBackZ: 2500;
|
||||
|
||||
/* This should be the default case, the Portal used as a stand-alone modal */
|
||||
$modalBottom: 15vh;
|
||||
$modalLeft: $modalMargin;
|
||||
$modalRight: $modalMargin;
|
||||
$modalTop: 0;
|
||||
$modalZ: 3500;
|
||||
|
||||
/* This is the case where popped-up over another modal, Portal or otherwise */
|
||||
$popoverBottom: $modalMargin;
|
||||
$popoverLeft: $modalMargin;
|
||||
$popoverRight: $modalMargin;
|
||||
$popoverTop: 20vh;
|
||||
$popoverZ: 3600;
|
||||
|
||||
.backOverlay {
|
||||
background-color: rgba(255, 255, 255, 0.25);
|
||||
opacity: 0;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: rgba(255, 255, 255, 0.25);
|
||||
z-index: -10;
|
||||
opacity: 0;
|
||||
|
||||
transform-origin: 100% 0;
|
||||
transition-property: opacity, z-index;
|
||||
transition-duration: 0.25s;
|
||||
transition-property: opacity, z-index;
|
||||
transition-timing-function: ease-out;
|
||||
z-index: -10;
|
||||
|
||||
&.expanded {
|
||||
opacity: 1;
|
||||
z-index: 2500;
|
||||
z-index: $modalBackZ;
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,45 +63,58 @@ $top: 20vh;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
display: flex;
|
||||
position: fixed;
|
||||
top: $top;
|
||||
left: $left;
|
||||
width: calc(100vw - $left - $right);
|
||||
height: calc(100vh - $top - $bottom);
|
||||
|
||||
transform-origin: 100% 0;
|
||||
transition-property: opacity, z-index;
|
||||
transition-duration: 0.25s;
|
||||
transition-timing-function: ease-out;
|
||||
|
||||
background-color: rgba(0, 0, 0, 1);
|
||||
opacity: 0;
|
||||
z-index: -10;
|
||||
|
||||
padding: 1em;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
opacity: 0;
|
||||
padding: 1.5em;
|
||||
position: fixed;
|
||||
transform-origin: 100% 0;
|
||||
transition-duration: 0.25s;
|
||||
transition-property: opacity, z-index;
|
||||
transition-timing-function: ease-out;
|
||||
z-index: -10;
|
||||
|
||||
* {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&.modal {
|
||||
bottom: $modalBottom;
|
||||
left: $modalLeft;
|
||||
right: $modalRight;
|
||||
top: $modalTop;
|
||||
}
|
||||
|
||||
&.popover {
|
||||
left: $popoverLeft;
|
||||
top: $popoverTop;
|
||||
height: calc(100vh - $popoverTop - $popoverBottom);
|
||||
width: calc(100vw - $popoverLeft - $popoverRight);
|
||||
}
|
||||
|
||||
&.expanded {
|
||||
opacity: 1;
|
||||
z-index: 3500;
|
||||
|
||||
&.popover {
|
||||
z-index: $popoverZ;
|
||||
}
|
||||
|
||||
&.modal {
|
||||
z-index: $modalZ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.closeIcon {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 1rem;
|
||||
font-size: 4em;
|
||||
z-index: 100;
|
||||
|
||||
transition-property: opacity;
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
top: 0.5rem;
|
||||
transition-duration: 0.25s;
|
||||
transition-property: opacity;
|
||||
transition-timing-function: ease-out;
|
||||
z-index: 100;
|
||||
|
||||
&, * {
|
||||
height: 48px !important;
|
||||
|
97
js/src/ui/Portal/portal.example.js
Normal file
97
js/src/ui/Portal/portal.example.js
Normal file
@ -0,0 +1,97 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import PlaygroundExample from '~/playground/playgroundExample';
|
||||
|
||||
import Modal from '../Modal';
|
||||
import Portal from './portal';
|
||||
|
||||
export default class PortalExample extends Component {
|
||||
state = {
|
||||
open: []
|
||||
};
|
||||
|
||||
render () {
|
||||
const { open } = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PlaygroundExample name='Standard Portal'>
|
||||
<div>
|
||||
<button onClick={ this.handleOpen(0) }>Open</button>
|
||||
<Portal
|
||||
open={ open[0] || false }
|
||||
onClose={ this.handleClose }
|
||||
>
|
||||
<p>This is the first portal</p>
|
||||
</Portal>
|
||||
</div>
|
||||
</PlaygroundExample>
|
||||
|
||||
<PlaygroundExample name='Popover Portal'>
|
||||
<div>
|
||||
<button onClick={ this.handleOpen(1) }>Open</button>
|
||||
<Portal
|
||||
isChildModal
|
||||
open={ open[1] || false }
|
||||
onClose={ this.handleClose }
|
||||
>
|
||||
<p>This is the second portal</p>
|
||||
</Portal>
|
||||
</div>
|
||||
</PlaygroundExample>
|
||||
|
||||
<PlaygroundExample name='Portal in Modal'>
|
||||
<div>
|
||||
<button onClick={ this.handleOpen(2) }>Open</button>
|
||||
|
||||
<Modal
|
||||
title='Modal'
|
||||
visible={ open[2] || false }
|
||||
>
|
||||
<button onClick={ this.handleOpen(3) }>Open</button>
|
||||
<button onClick={ this.handleClose }>Close</button>
|
||||
</Modal>
|
||||
|
||||
<Portal
|
||||
isChildModal
|
||||
open={ open[3] || false }
|
||||
onClose={ this.handleClose }
|
||||
>
|
||||
<p>This is the second portal</p>
|
||||
</Portal>
|
||||
</div>
|
||||
</PlaygroundExample>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleOpen = (index) => {
|
||||
return () => {
|
||||
const { open } = this.state;
|
||||
const nextOpen = open.slice();
|
||||
|
||||
nextOpen[index] = true;
|
||||
this.setState({ open: nextOpen });
|
||||
};
|
||||
}
|
||||
|
||||
handleClose = () => {
|
||||
this.setState({ open: [] });
|
||||
}
|
||||
}
|
@ -28,9 +28,9 @@ export default class Portal extends Component {
|
||||
static propTypes = {
|
||||
onClose: PropTypes.func.isRequired,
|
||||
open: PropTypes.bool.isRequired,
|
||||
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
isChildModal: PropTypes.bool,
|
||||
onKeyDown: PropTypes.func
|
||||
};
|
||||
|
||||
@ -54,11 +54,16 @@ export default class Portal extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { children, className, isChildModal } = this.props;
|
||||
const { expanded } = this.state;
|
||||
const { children, className } = this.props;
|
||||
|
||||
const classes = [ styles.overlay, className ];
|
||||
const backClasses = [ styles.backOverlay ];
|
||||
const classes = [
|
||||
styles.overlay,
|
||||
isChildModal
|
||||
? styles.popover
|
||||
: styles.modal,
|
||||
className
|
||||
];
|
||||
|
||||
if (expanded) {
|
||||
classes.push(styles.expanded);
|
||||
@ -66,15 +71,20 @@ export default class Portal extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<ReactPortal isOpened onClose={ this.handleClose }>
|
||||
<div className={ backClasses.join(' ') } onClick={ this.handleClose }>
|
||||
<ReactPortal
|
||||
isOpened
|
||||
onClose={ this.handleClose }
|
||||
>
|
||||
<div
|
||||
className={ backClasses.join(' ') }
|
||||
onClick={ this.handleClose }
|
||||
>
|
||||
<div
|
||||
className={ classes.join(' ') }
|
||||
onClick={ this.stopEvent }
|
||||
onKeyDown={ this.handleKeyDown }
|
||||
>
|
||||
<ParityBackground className={ styles.parityBackground } />
|
||||
|
||||
{ this.renderCloseIcon() }
|
||||
{ children }
|
||||
</div>
|
||||
@ -91,7 +101,10 @@ export default class Portal extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.closeIcon } onClick={ this.handleClose }>
|
||||
<div
|
||||
className={ styles.closeIcon }
|
||||
onClick={ this.handleClose }
|
||||
>
|
||||
<CloseIcon />
|
||||
</div>
|
||||
);
|
||||
@ -107,6 +120,7 @@ export default class Portal extends Component {
|
||||
}
|
||||
|
||||
handleKeyDown = (event) => {
|
||||
const { onKeyDown } = this.props;
|
||||
const codeName = keycode(event);
|
||||
|
||||
switch (codeName) {
|
||||
@ -116,12 +130,16 @@ export default class Portal extends Component {
|
||||
|
||||
default:
|
||||
event.persist();
|
||||
return this.props.onKeyDown(event);
|
||||
return onKeyDown
|
||||
? onKeyDown(event)
|
||||
: false;
|
||||
}
|
||||
}
|
||||
|
||||
handleDOMAction = (ref, method) => {
|
||||
const refItem = typeof ref === 'string' ? this.refs[ref] : ref;
|
||||
const refItem = typeof ref === 'string'
|
||||
? this.refs[ref]
|
||||
: ref;
|
||||
const element = ReactDOM.findDOMNode(refItem);
|
||||
|
||||
if (!element || typeof element[method] !== 'function') {
|
||||
|
47
js/src/ui/Portal/portal.spec.js
Normal file
47
js/src/ui/Portal/portal.spec.js
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import Portal from './';
|
||||
|
||||
let component;
|
||||
let onClose;
|
||||
|
||||
function render (props = {}) {
|
||||
onClose = sinon.stub();
|
||||
component = shallow(
|
||||
<Portal
|
||||
onClose={ onClose }
|
||||
open
|
||||
{ ...props }
|
||||
/>
|
||||
);
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('ui/Portal', () => {
|
||||
beforeEach(() => {
|
||||
render();
|
||||
});
|
||||
|
||||
it('renders defaults', () => {
|
||||
expect(component).to.be.ok;
|
||||
});
|
||||
});
|
@ -47,6 +47,7 @@ import muiTheme from './Theme';
|
||||
import Page from './Page';
|
||||
import ParityBackground from './ParityBackground';
|
||||
import PasswordStrength from './Form/PasswordStrength';
|
||||
import Portal from './Portal';
|
||||
import QrCode from './QrCode';
|
||||
import SectionList from './SectionList';
|
||||
import ShortenedHash from './ShortenedHash';
|
||||
@ -103,6 +104,7 @@ export {
|
||||
Page,
|
||||
ParityBackground,
|
||||
PasswordStrength,
|
||||
Portal,
|
||||
QrCode,
|
||||
RadioButtons,
|
||||
Select,
|
||||
|
Loading…
Reference in New Issue
Block a user