Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Commit

Permalink
Allow Portal to be used as top-level modal (#4338)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
jacogr authored and gavofyork committed Jan 30, 2017
1 parent 4e7b865 commit 15ffd9a
Show file tree
Hide file tree
Showing 7 changed files with 229 additions and 38 deletions.
2 changes: 2 additions & 0 deletions js/src/playground/playground.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ 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';

PlaygroundStore.register(<CurrencySymbol />);
PlaygroundStore.register(<QrCode />);
PlaygroundStore.register(<SectionList />);
PlaygroundStore.register(<Portal />);

@observer
export default class Playground extends Component {
Expand Down
1 change: 1 addition & 0 deletions js/src/ui/Form/AddressSelect/addressSelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ class AddressSelect extends Component {
return (
<Portal
className={ styles.inputContainer }
isChildModal
onClose={ this.handleClose }
onKeyDown={ this.handleKeyDown }
open={ expanded }
Expand Down
80 changes: 52 additions & 28 deletions js/src/ui/Portal/portal.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

Expand All @@ -52,45 +63,58 @@ $top: 20vh;
}

.overlay {
background-color: rgba(0, 0, 0, 1);
box-sizing: border-box;
display: flex;
opacity: 0;
padding: 1.5em;
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-property: opacity, z-index;
transition-timing-function: ease-out;

background-color: rgba(0, 0, 0, 1);
opacity: 0;
z-index: -10;

padding: 1em;
box-sizing: border-box;

* {
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 {
font-size: 4em;
position: absolute;
top: 0.5rem;
right: 1rem;
font-size: 4em;
z-index: 100;

transition-property: opacity;
top: 0.5rem;
transition-duration: 0.25s;
transition-property: opacity;
transition-timing-function: ease-out;
z-index: 100;

&, * {
height: 48px !important;
Expand Down
97 changes: 97 additions & 0 deletions js/src/ui/Portal/portal.example.js
Original file line number Diff line number Diff line change
@@ -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: [] });
}
}
38 changes: 28 additions & 10 deletions js/src/ui/Portal/portal.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
};

Expand All @@ -54,27 +54,37 @@ 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);
backClasses.push(styles.expanded);
}

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>
Expand All @@ -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>
);
Expand All @@ -107,6 +120,7 @@ export default class Portal extends Component {
}

handleKeyDown = (event) => {
const { onKeyDown } = this.props;
const codeName = keycode(event);

switch (codeName) {
Expand All @@ -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') {
Expand Down
47 changes: 47 additions & 0 deletions js/src/ui/Portal/portal.spec.js
Original file line number Diff line number Diff line change
@@ -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;
});
});
Loading

0 comments on commit 15ffd9a

Please sign in to comment.