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

Allow Portal to be used as top-level modal #4338

Merged
merged 7 commits into from
Jan 30, 2017
Merged
Show file tree
Hide file tree
Changes from all 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
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