Extend Portal component with title, buttons & steps (as per Modal) (#4392)
* Allow Portal to take title & buttons props * Fix tests * Portal consistent in screen center * Allow hiding of Close (e.g. FirstRun usage) * Set overflow style on body based on open * Don't lock scroll for child popups (overlaps) * Override buttons to be white * Expose ~/ui/Modal/Title as re-usable component * Use ~/ui/Title to render the Title * Update tests * Added a portal example with buttons and steps * Address PR comments * Fix AddressSelect with new container withing container * Move legend to "buttons" * AddressSelect extra padding
This commit is contained in:
		
							parent
							
								
									a68ca7444e
								
							
						
					
					
						commit
						c7f5ee481d
					
				| @ -20,7 +20,6 @@ | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .container { | .container { | ||||||
|   margin-top: 1.5em; |  | ||||||
|   overflow-y: auto; |   overflow-y: auto; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -18,7 +18,7 @@ import { observer } from 'mobx-react'; | |||||||
| import React, { Component, PropTypes } from 'react'; | import React, { Component, PropTypes } from 'react'; | ||||||
| import { FormattedMessage } from 'react-intl'; | import { FormattedMessage } from 'react-intl'; | ||||||
| 
 | 
 | ||||||
| import { ContainerTitle, DappCard, Portal, SectionList } from '~/ui'; | import { DappCard, Portal, SectionList } from '~/ui'; | ||||||
| import { CheckIcon } from '~/ui/Icons'; | import { CheckIcon } from '~/ui/Icons'; | ||||||
| 
 | 
 | ||||||
| import styles from './addDapps.css'; | import styles from './addDapps.css'; | ||||||
| @ -41,15 +41,13 @@ export default class AddDapps extends Component { | |||||||
|         className={ styles.modal } |         className={ styles.modal } | ||||||
|         onClose={ store.closeModal } |         onClose={ store.closeModal } | ||||||
|         open |         open | ||||||
|  |         title={ | ||||||
|  |           <FormattedMessage | ||||||
|  |             id='dapps.add.label' | ||||||
|  |             defaultMessage='visible applications' | ||||||
|  |           /> | ||||||
|  |         } | ||||||
|       > |       > | ||||||
|         <ContainerTitle |  | ||||||
|           title={ |  | ||||||
|             <FormattedMessage |  | ||||||
|               id='dapps.add.label' |  | ||||||
|               defaultMessage='visible applications' |  | ||||||
|             /> |  | ||||||
|           } |  | ||||||
|         /> |  | ||||||
|         <div className={ styles.container }> |         <div className={ styles.container }> | ||||||
|           <div className={ styles.warning } /> |           <div className={ styles.warning } /> | ||||||
|           { |           { | ||||||
|  | |||||||
| @ -15,12 +15,7 @@ | |||||||
| /* along with Parity.  If not, see <http://www.gnu.org/licenses/>. | /* along with Parity.  If not, see <http://www.gnu.org/licenses/>. | ||||||
| */ | */ | ||||||
| 
 | 
 | ||||||
| .modal { |  | ||||||
|   flex-direction: column; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .container { | .container { | ||||||
|   margin-top: 1.5em; |  | ||||||
|   overflow-y: auto; |   overflow-y: auto; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -65,7 +60,6 @@ | |||||||
| 
 | 
 | ||||||
| .legend { | .legend { | ||||||
|   opacity: 0.75; |   opacity: 0.75; | ||||||
|   margin-top: 1em; |  | ||||||
| 
 | 
 | ||||||
|   span { |   span { | ||||||
|     line-height: 24px; |     line-height: 24px; | ||||||
|  | |||||||
| @ -18,7 +18,7 @@ import { observer } from 'mobx-react'; | |||||||
| import React, { Component, PropTypes } from 'react'; | import React, { Component, PropTypes } from 'react'; | ||||||
| import { FormattedMessage } from 'react-intl'; | import { FormattedMessage } from 'react-intl'; | ||||||
| 
 | 
 | ||||||
| import { AccountCard, ContainerTitle, Portal, SectionList } from '~/ui'; | import { AccountCard, Portal, SectionList } from '~/ui'; | ||||||
| import { CheckIcon, StarIcon, StarOutlineIcon } from '~/ui/Icons'; | import { CheckIcon, StarIcon, StarOutlineIcon } from '~/ui/Icons'; | ||||||
| 
 | 
 | ||||||
| import styles from './dappPermissions.css'; | import styles from './dappPermissions.css'; | ||||||
| @ -38,18 +38,27 @@ export default class DappPermissions extends Component { | |||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <Portal |       <Portal | ||||||
|         className={ styles.modal } |         buttons={ | ||||||
|  |           <div className={ styles.legend }> | ||||||
|  |             <FormattedMessage | ||||||
|  |               id='dapps.permissions.description' | ||||||
|  |               defaultMessage='{activeIcon} account is available to application, {defaultIcon} account is the default account' | ||||||
|  |               values={ { | ||||||
|  |                 activeIcon: <CheckIcon />, | ||||||
|  |                 defaultIcon: <StarIcon /> | ||||||
|  |               } } | ||||||
|  |             /> | ||||||
|  |           </div> | ||||||
|  |         } | ||||||
|         onClose={ store.closeModal } |         onClose={ store.closeModal } | ||||||
|         open |         open | ||||||
|  |         title={ | ||||||
|  |           <FormattedMessage | ||||||
|  |             id='dapps.permissions.label' | ||||||
|  |             defaultMessage='visible dapp accounts' | ||||||
|  |           /> | ||||||
|  |         } | ||||||
|       > |       > | ||||||
|         <ContainerTitle |  | ||||||
|           title={ |  | ||||||
|             <FormattedMessage |  | ||||||
|               id='dapps.permissions.label' |  | ||||||
|               defaultMessage='visible dapp accounts' |  | ||||||
|             /> |  | ||||||
|           } |  | ||||||
|         /> |  | ||||||
|         <div className={ styles.container }> |         <div className={ styles.container }> | ||||||
|           <SectionList |           <SectionList | ||||||
|             items={ store.accounts } |             items={ store.accounts } | ||||||
| @ -57,16 +66,6 @@ export default class DappPermissions extends Component { | |||||||
|             renderItem={ this.renderAccount } |             renderItem={ this.renderAccount } | ||||||
|           /> |           /> | ||||||
|         </div> |         </div> | ||||||
|         <div className={ styles.legend }> |  | ||||||
|           <FormattedMessage |  | ||||||
|             id='dapps.permissions.description' |  | ||||||
|             defaultMessage='{activeIcon} account is available to application, {defaultIcon} account is the default account' |  | ||||||
|             values={ { |  | ||||||
|               activeIcon: <CheckIcon />, |  | ||||||
|               defaultIcon: <StarIcon /> |  | ||||||
|             } } |  | ||||||
|           /> |  | ||||||
|         </div> |  | ||||||
|       </Portal> |       </Portal> | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -14,30 +14,36 @@ | |||||||
| /* You should have received a copy of the GNU General Public License | /* You should have received a copy of the GNU General Public License | ||||||
| /* along with Parity.  If not, see <http://www.gnu.org/licenses/>. | /* along with Parity.  If not, see <http://www.gnu.org/licenses/>. | ||||||
| */ | */ | ||||||
| .byline, .description { | 
 | ||||||
|  | $bylineColor: #aaa; | ||||||
|  | $bylineLineHeight: 1.2rem; | ||||||
|  | $bylineMaxHeight: 2.4rem; | ||||||
|  | $titleLineHeight: 2rem; | ||||||
|  | $smallFontSize: 0.75rem; | ||||||
|  | 
 | ||||||
|  | .byline, | ||||||
|  | .description { | ||||||
|  |   color: $bylineColor; | ||||||
|  |   display: -webkit-box; | ||||||
|  |   line-height: $bylineLineHeight; | ||||||
|  |   max-height: $bylineMaxHeight; | ||||||
|   overflow: hidden; |   overflow: hidden; | ||||||
|   position: relative; |   position: relative; | ||||||
|   line-height: 1.2em; |  | ||||||
|   max-height: 2.4em; |  | ||||||
| 
 |  | ||||||
|   display: -webkit-box; |  | ||||||
|   -webkit-line-clamp: 2; |   -webkit-line-clamp: 2; | ||||||
|   -webkit-box-orient: vertical; |   -webkit-box-orient: vertical; | ||||||
| 
 | 
 | ||||||
|   color: #aaa; |  | ||||||
| 
 |  | ||||||
|   * { |   * { | ||||||
|     color: #aaa !important; |     color: $bylineColor !important; | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .description { | .description { | ||||||
|   font-size: 0.75em; |   font-size: $smallFontSize; | ||||||
|   margin: 0.5em 0 0; |   margin: 0.5em 0 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .title { | .title { | ||||||
|   text-transform: uppercase; |   line-height: $titleLineHeight; | ||||||
|   margin: 0; |   margin: 0; | ||||||
|   line-height: 34px; |   text-transform: uppercase; | ||||||
| } | } | ||||||
|  | |||||||
| @ -29,29 +29,41 @@ export default class Title extends Component { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   render () { |   render () { | ||||||
|     const { byline, className, title } = this.props; |     const { className, title } = this.props; | ||||||
| 
 |  | ||||||
|     const byLine = typeof byline === 'string' |  | ||||||
|       ? ( |  | ||||||
|         <span title={ byline }> |  | ||||||
|           { byline } |  | ||||||
|         </span> |  | ||||||
|       ) |  | ||||||
|       : byline; |  | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <div className={ className }> |       <div className={ className }> | ||||||
|         <h3 className={ styles.title }> |         <h3 className={ styles.title }> | ||||||
|           { title } |           { title } | ||||||
|         </h3> |         </h3> | ||||||
|         <div className={ styles.byline }> |         { this.renderByline() } | ||||||
|           { byLine } |  | ||||||
|         </div> |  | ||||||
|         { this.renderDescription() } |         { this.renderDescription() } | ||||||
|       </div> |       </div> | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   renderByline () { | ||||||
|  |     const { byline } = this.props; | ||||||
|  | 
 | ||||||
|  |     if (!byline) { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ( | ||||||
|  |       <div className={ styles.byline }> | ||||||
|  |         { | ||||||
|  |           typeof byline === 'string' | ||||||
|  |             ? ( | ||||||
|  |               <span title={ byline }> | ||||||
|  |                 { byline } | ||||||
|  |               </span> | ||||||
|  |             ) | ||||||
|  |             : byline | ||||||
|  |         } | ||||||
|  |       </div> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   renderDescription () { |   renderDescription () { | ||||||
|     const { description } = this.props; |     const { description } = this.props; | ||||||
| 
 | 
 | ||||||
| @ -59,17 +71,17 @@ export default class Title extends Component { | |||||||
|       return null; |       return null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const desc = typeof description === 'string' |  | ||||||
|       ? ( |  | ||||||
|         <span title={ description }> |  | ||||||
|           { description } |  | ||||||
|         </span> |  | ||||||
|       ) |  | ||||||
|       : description; |  | ||||||
| 
 |  | ||||||
|     return ( |     return ( | ||||||
|       <div className={ styles.description }> |       <div className={ styles.description }> | ||||||
|         { desc } |         { | ||||||
|  |           typeof description === 'string' | ||||||
|  |             ? ( | ||||||
|  |               <span title={ description }> | ||||||
|  |                 { description } | ||||||
|  |               </span> | ||||||
|  |             ) | ||||||
|  |             : description | ||||||
|  |         } | ||||||
|       </div> |       </div> | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -73,6 +73,12 @@ | |||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .title { | ||||||
|  |   display: flex; | ||||||
|  |   flex-direction: column; | ||||||
|  |   position: relative; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .label { | .label { | ||||||
|   margin: 1rem 0.5rem 0.25em; |   margin: 1rem 0.5rem 0.25em; | ||||||
|   color: rgba(255, 255, 255, 0.498039); |   color: rgba(255, 255, 255, 0.498039); | ||||||
| @ -102,14 +108,11 @@ | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .categories { | .categories { | ||||||
|   flex: 1; |  | ||||||
| 
 |  | ||||||
|   display: flex; |   display: flex; | ||||||
|  |   flex: 1; | ||||||
|   flex-direction: row; |   flex-direction: row; | ||||||
|   justify-content: flex-start; |   justify-content: flex-start; | ||||||
| 
 | 
 | ||||||
|   margin: 2rem 0 0; |  | ||||||
| 
 |  | ||||||
|   > * { |   > * { | ||||||
|     flex: 1; |     flex: 1; | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -180,34 +180,38 @@ class AddressSelect extends Component { | |||||||
|         onClose={ this.handleClose } |         onClose={ this.handleClose } | ||||||
|         onKeyDown={ this.handleKeyDown } |         onKeyDown={ this.handleKeyDown } | ||||||
|         open={ expanded } |         open={ expanded } | ||||||
|  |         title={ | ||||||
|  |           <div className={ styles.title }> | ||||||
|  |             <label className={ styles.label } htmlFor={ id }> | ||||||
|  |               { label } | ||||||
|  |             </label> | ||||||
|  |             <div className={ styles.outerInput }> | ||||||
|  |               <input | ||||||
|  |                 id={ id } | ||||||
|  |                 className={ styles.input } | ||||||
|  |                 placeholder={ ilHint } | ||||||
|  |                 onBlur={ this.handleInputBlur } | ||||||
|  |                 onFocus={ this.handleInputFocus } | ||||||
|  |                 onChange={ this.handleChange } | ||||||
|  |                 ref={ this.setInputRef } | ||||||
|  |               /> | ||||||
|  |               { this.renderLoader() } | ||||||
|  |             </div> | ||||||
|  | 
 | ||||||
|  |             <div className={ styles.underline }> | ||||||
|  |               <TextFieldUnderline | ||||||
|  |                 focus={ inputFocused } | ||||||
|  |                 focusStyle={ BOTTOM_BORDER_STYLE } | ||||||
|  |                 muiTheme={ muiTheme } | ||||||
|  |                 style={ BOTTOM_BORDER_STYLE } | ||||||
|  |               /> | ||||||
|  |             </div> | ||||||
|  | 
 | ||||||
|  |             { this.renderCurrentInput() } | ||||||
|  |             { this.renderRegistryValues() } | ||||||
|  |           </div> | ||||||
|  |         } | ||||||
|       > |       > | ||||||
|         <label className={ styles.label } htmlFor={ id }> |  | ||||||
|           { label } |  | ||||||
|         </label> |  | ||||||
|         <div className={ styles.outerInput }> |  | ||||||
|           <input |  | ||||||
|             id={ id } |  | ||||||
|             className={ styles.input } |  | ||||||
|             placeholder={ ilHint } |  | ||||||
|             onBlur={ this.handleInputBlur } |  | ||||||
|             onFocus={ this.handleInputFocus } |  | ||||||
|             onChange={ this.handleChange } |  | ||||||
|             ref={ this.setInputRef } |  | ||||||
|           /> |  | ||||||
|           { this.renderLoader() } |  | ||||||
|         </div> |  | ||||||
| 
 |  | ||||||
|         <div className={ styles.underline }> |  | ||||||
|           <TextFieldUnderline |  | ||||||
|             focus={ inputFocused } |  | ||||||
|             focusStyle={ BOTTOM_BORDER_STYLE } |  | ||||||
|             muiTheme={ muiTheme } |  | ||||||
|             style={ BOTTOM_BORDER_STYLE } |  | ||||||
|           /> |  | ||||||
|         </div> |  | ||||||
| 
 |  | ||||||
|         { this.renderCurrentInput() } |  | ||||||
|         { this.renderRegistryValues() } |  | ||||||
|         { this.renderAccounts() } |         { this.renderAccounts() } | ||||||
|       </Portal> |       </Portal> | ||||||
|     ); |     ); | ||||||
|  | |||||||
| @ -47,20 +47,6 @@ | |||||||
| .title { | .title { | ||||||
|   background: rgba(0, 0, 0, 0.25) !important; |   background: rgba(0, 0, 0, 0.25) !important; | ||||||
|   padding: 1em; |   padding: 1em; | ||||||
|   margin-bottom: 0; |  | ||||||
| 
 |  | ||||||
|   h3 { |  | ||||||
|     margin: 0; |  | ||||||
|     text-transform: uppercase; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   .steps { |  | ||||||
|     margin-bottom: -1em; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .waiting { |  | ||||||
|   margin: 1em -1em -1em -1em; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .overlay { | .overlay { | ||||||
|  | |||||||
| @ -22,7 +22,7 @@ import { connect } from 'react-redux'; | |||||||
| import { nodeOrStringProptype } from '~/util/proptypes'; | import { nodeOrStringProptype } from '~/util/proptypes'; | ||||||
| 
 | 
 | ||||||
| import Container from '../Container'; | import Container from '../Container'; | ||||||
| import Title from './Title'; | import Title from '../Title'; | ||||||
| 
 | 
 | ||||||
| const ACTIONS_STYLE = { borderStyle: 'none' }; | const ACTIONS_STYLE = { borderStyle: 'none' }; | ||||||
| const TITLE_STYLE = { borderStyle: 'none' }; | const TITLE_STYLE = { borderStyle: 'none' }; | ||||||
| @ -63,11 +63,12 @@ class Modal extends Component { | |||||||
|     const contentStyle = muiTheme.parity.getBackgroundStyle(null, settings.backgroundSeed); |     const contentStyle = muiTheme.parity.getBackgroundStyle(null, settings.backgroundSeed); | ||||||
|     const header = ( |     const header = ( | ||||||
|       <Title |       <Title | ||||||
|  |         activeStep={ current } | ||||||
|         busy={ busy } |         busy={ busy } | ||||||
|         current={ current } |         busySteps={ waiting } | ||||||
|  |         className={ styles.title } | ||||||
|         steps={ steps } |         steps={ steps } | ||||||
|         title={ title } |         title={ title } | ||||||
|         waiting={ waiting } |  | ||||||
|       /> |       /> | ||||||
|     ); |     ); | ||||||
|     const classes = `${styles.dialog} ${className}`; |     const classes = `${styles.dialog} ${className}`; | ||||||
|  | |||||||
| @ -16,13 +16,14 @@ | |||||||
| */ | */ | ||||||
| 
 | 
 | ||||||
| $modalMargin: 1.5em; | $modalMargin: 1.5em; | ||||||
|  | $modalPadding: 1.5em; | ||||||
| $modalBackZ: 2500; | $modalBackZ: 2500; | ||||||
| 
 | 
 | ||||||
| /* This should be the default case, the Portal used as a stand-alone modal */ | /* This should be the default case, the Portal used as a stand-alone modal */ | ||||||
| $modalBottom: 15vh; | $modalBottom: $modalMargin; | ||||||
| $modalLeft: $modalMargin; | $modalLeft: $modalMargin; | ||||||
| $modalRight: $modalMargin; | $modalRight: $modalMargin; | ||||||
| $modalTop: 0; | $modalTop: $modalMargin; | ||||||
| $modalZ: 3500; | $modalZ: 3500; | ||||||
| 
 | 
 | ||||||
| /* This is the case where popped-up over another modal, Portal or otherwise */ | /* This is the case where popped-up over another modal, Portal or otherwise */ | ||||||
| @ -55,7 +56,9 @@ $popoverZ: 3600; | |||||||
|   background-color: rgba(0, 0, 0, 1); |   background-color: rgba(0, 0, 0, 1); | ||||||
|   box-sizing: border-box; |   box-sizing: border-box; | ||||||
|   display: flex; |   display: flex; | ||||||
|   padding: 1.5em; |   flex-direction: column; | ||||||
|  |   justify-content: space-between; | ||||||
|  |   padding: $modalPadding; | ||||||
|   position: fixed; |   position: fixed; | ||||||
| 
 | 
 | ||||||
|   * { |   * { | ||||||
| @ -77,22 +80,48 @@ $popoverZ: 3600; | |||||||
|     width: calc(100vw - $popoverLeft - $popoverRight); |     width: calc(100vw - $popoverLeft - $popoverRight); | ||||||
|     z-index: $popoverZ; |     z-index: $popoverZ; | ||||||
|   } |   } | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| .closeIcon { |   .buttonRow { | ||||||
|   font-size: 4em; |     display: flex; | ||||||
|   position: absolute; |     flex-direction: row; | ||||||
|   right: 1rem; |     flex-wrap: nowrap; | ||||||
|   top: 0.5rem; |     justify-content: flex-end; | ||||||
|   z-index: 100; |     padding: $modalPadding 0 0 0; | ||||||
| 
 | 
 | ||||||
|   &, * { |     button:not([disabled]) { | ||||||
|     height: 48px !important; |       color: white !important; | ||||||
|     width: 48px !important; | 
 | ||||||
|  |       svg { | ||||||
|  |         fill: white !important; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   &:hover { |   .childContainer { | ||||||
|     cursor: pointer; |     flex: 1; | ||||||
|     opacity: 0.5; |     overflow-x: hidden; | ||||||
|  |     overflow-y: auto; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .closeIcon { | ||||||
|  |     font-size: 4em; | ||||||
|  |     position: absolute; | ||||||
|  |     right: 1rem; | ||||||
|  |     top: 0.5rem; | ||||||
|  |     z-index: 100; | ||||||
|  | 
 | ||||||
|  |     &, * { | ||||||
|  |       height: 48px !important; | ||||||
|  |       width: 48px !important; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     &:hover { | ||||||
|  |       cursor: pointer; | ||||||
|  |       opacity: 0.5; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .titleRow { | ||||||
|  |     margin-bottom: $modalPadding; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -16,6 +16,7 @@ | |||||||
| 
 | 
 | ||||||
| import React, { Component } from 'react'; | import React, { Component } from 'react'; | ||||||
| 
 | 
 | ||||||
|  | import { Button } from '~/ui'; | ||||||
| import PlaygroundExample from '~/playground/playgroundExample'; | import PlaygroundExample from '~/playground/playgroundExample'; | ||||||
| 
 | 
 | ||||||
| import Modal from '../Modal'; | import Modal from '../Modal'; | ||||||
| @ -77,6 +78,29 @@ export default class PortalExample extends Component { | |||||||
|             </Portal> |             </Portal> | ||||||
|           </div> |           </div> | ||||||
|         </PlaygroundExample> |         </PlaygroundExample> | ||||||
|  | 
 | ||||||
|  |         <PlaygroundExample name='Portal with Buttons'> | ||||||
|  |           <div> | ||||||
|  |             <button onClick={ this.handleOpen(4) }>Open</button> | ||||||
|  |             <Portal | ||||||
|  |               activeStep={ 0 } | ||||||
|  |               buttons={ [ | ||||||
|  |                 <Button | ||||||
|  |                   key='close' | ||||||
|  |                   label='close' | ||||||
|  |                   onClick={ this.handleClose } | ||||||
|  |                 /> | ||||||
|  |               ] } | ||||||
|  |               isChildModal | ||||||
|  |               open={ open[4] || false } | ||||||
|  |               onClose={ this.handleClose } | ||||||
|  |               steps={ [ 'step 1', 'step 2' ] } | ||||||
|  |               title='Portal with button' | ||||||
|  |             > | ||||||
|  |               <p>This is the fourth portal</p> | ||||||
|  |             </Portal> | ||||||
|  |           </div> | ||||||
|  |         </PlaygroundExample> | ||||||
|       </div> |       </div> | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -20,8 +20,10 @@ import ReactDOM from 'react-dom'; | |||||||
| import ReactPortal from 'react-portal'; | import ReactPortal from 'react-portal'; | ||||||
| import keycode from 'keycode'; | import keycode from 'keycode'; | ||||||
| 
 | 
 | ||||||
|  | import { nodeOrStringProptype } from '~/util/proptypes'; | ||||||
| import { CloseIcon } from '~/ui/Icons'; | import { CloseIcon } from '~/ui/Icons'; | ||||||
| import ParityBackground from '~/ui/ParityBackground'; | import ParityBackground from '~/ui/ParityBackground'; | ||||||
|  | import Title from '~/ui/Title'; | ||||||
| 
 | 
 | ||||||
| import styles from './portal.css'; | import styles from './portal.css'; | ||||||
| 
 | 
 | ||||||
| @ -29,14 +31,35 @@ export default class Portal extends Component { | |||||||
|   static propTypes = { |   static propTypes = { | ||||||
|     onClose: PropTypes.func.isRequired, |     onClose: PropTypes.func.isRequired, | ||||||
|     open: PropTypes.bool.isRequired, |     open: PropTypes.bool.isRequired, | ||||||
|  |     activeStep: PropTypes.number, | ||||||
|  |     busy: PropTypes.bool, | ||||||
|  |     busySteps: PropTypes.array, | ||||||
|  |     buttons: PropTypes.array, | ||||||
|     children: PropTypes.node, |     children: PropTypes.node, | ||||||
|     className: PropTypes.string, |     className: PropTypes.string, | ||||||
|  |     hideClose: PropTypes.bool, | ||||||
|     isChildModal: PropTypes.bool, |     isChildModal: PropTypes.bool, | ||||||
|     onKeyDown: PropTypes.func |     onKeyDown: PropTypes.func, | ||||||
|  |     steps: PropTypes.array, | ||||||
|  |     title: nodeOrStringProptype() | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|  |   componentDidMount () { | ||||||
|  |     this.setBodyOverflow(this.props.open); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   componentWillReceiveProps (nextProps) { | ||||||
|  |     if (nextProps.open !== this.props.open) { | ||||||
|  |       this.setBodyOverflow(nextProps.open); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   componentWillUnmount () { | ||||||
|  |     this.setBodyOverflow(false); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   render () { |   render () { | ||||||
|     const { children, className, isChildModal, open } = this.props; |     const { activeStep, busy, busySteps, children, className, isChildModal, open, steps, title } = this.props; | ||||||
| 
 | 
 | ||||||
|     if (!open) { |     if (!open) { | ||||||
|       return null; |       return null; | ||||||
| @ -69,32 +92,72 @@ export default class Portal extends Component { | |||||||
|               onKeyUp={ this.handleKeyUp } |               onKeyUp={ this.handleKeyUp } | ||||||
|             /> |             /> | ||||||
|             <ParityBackground className={ styles.parityBackground } /> |             <ParityBackground className={ styles.parityBackground } /> | ||||||
|             <div |             { this.renderClose() } | ||||||
|               className={ styles.closeIcon } |             <Title | ||||||
|               onClick={ this.handleClose } |               activeStep={ activeStep } | ||||||
|             > |               busy={ busy } | ||||||
|               <CloseIcon /> |               busySteps={ busySteps } | ||||||
|  |               className={ styles.titleRow } | ||||||
|  |               steps={ steps } | ||||||
|  |               title={ title } | ||||||
|  |             /> | ||||||
|  |             <div className={ styles.childContainer }> | ||||||
|  |               { children } | ||||||
|             </div> |             </div> | ||||||
|             { children } |             { this.renderButtons() } | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|       </ReactPortal> |       </ReactPortal> | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   renderButtons () { | ||||||
|  |     const { buttons } = this.props; | ||||||
|  | 
 | ||||||
|  |     if (!buttons) { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ( | ||||||
|  |       <div className={ styles.buttonRow }> | ||||||
|  |         { buttons } | ||||||
|  |       </div> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   renderClose () { | ||||||
|  |     const { hideClose } = this.props; | ||||||
|  | 
 | ||||||
|  |     if (hideClose) { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ( | ||||||
|  |       <CloseIcon | ||||||
|  |         className={ styles.closeIcon } | ||||||
|  |         onClick={ this.handleClose } | ||||||
|  |       /> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   stopEvent = (event) => { |   stopEvent = (event) => { | ||||||
|     event.preventDefault(); |     event.preventDefault(); | ||||||
|     event.stopPropagation(); |     event.stopPropagation(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   handleClose = () => { |   handleClose = () => { | ||||||
|     this.props.onClose(); |     const { hideClose, onClose } = this.props; | ||||||
|  | 
 | ||||||
|  |     if (!hideClose) { | ||||||
|  |       onClose(); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   handleKeyDown = (event) => { |   handleKeyDown = (event) => { | ||||||
|     const { onKeyDown } = this.props; |     const { onKeyDown } = this.props; | ||||||
| 
 | 
 | ||||||
|     event.persist(); |     event.persist(); | ||||||
|  | 
 | ||||||
|     return onKeyDown |     return onKeyDown | ||||||
|       ? onKeyDown(event) |       ? onKeyDown(event) | ||||||
|       : false; |       : false; | ||||||
| @ -111,10 +174,11 @@ export default class Portal extends Component { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   handleDOMAction = (ref, method) => { |   handleDOMAction = (ref, method) => { | ||||||
|     const refItem = typeof ref === 'string' |     const element = ReactDOM.findDOMNode( | ||||||
|       ? this.refs[ref] |       typeof ref === 'string' | ||||||
|       : ref; |         ? this.refs[ref] | ||||||
|     const element = ReactDOM.findDOMNode(refItem); |         : ref | ||||||
|  |     ); | ||||||
| 
 | 
 | ||||||
|     if (!element || typeof element[method] !== 'function') { |     if (!element || typeof element[method] !== 'function') { | ||||||
|       console.warn('could not find', ref, 'or method', method); |       console.warn('could not find', ref, 'or method', method); | ||||||
| @ -123,4 +187,12 @@ export default class Portal extends Component { | |||||||
| 
 | 
 | ||||||
|     return element[method](); |     return element[method](); | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   setBodyOverflow (open) { | ||||||
|  |     if (!this.props.isChildModal) { | ||||||
|  |       document.body.style.overflow = open | ||||||
|  |         ? 'hidden' | ||||||
|  |         : null; | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -44,4 +44,21 @@ describe('ui/Portal', () => { | |||||||
|   it('renders defaults', () => { |   it('renders defaults', () => { | ||||||
|     expect(component).to.be.ok; |     expect(component).to.be.ok; | ||||||
|   }); |   }); | ||||||
|  | 
 | ||||||
|  |   describe('title rendering', () => { | ||||||
|  |     const TITLE = 'some test title'; | ||||||
|  |     let title; | ||||||
|  | 
 | ||||||
|  |     beforeEach(() => { | ||||||
|  |       title = render({ title: TITLE }).find('Title'); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('renders the specified title', () => { | ||||||
|  |       expect(title).to.have.length(1); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('renders the passed title', () => { | ||||||
|  |       expect(title.props().title).to.equal(TITLE); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
| }); | }); | ||||||
|  | |||||||
							
								
								
									
										26
									
								
								js/src/ui/Title/title.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								js/src/ui/Title/title.css
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | |||||||
|  | /* 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/>. | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | .title { | ||||||
|  |   .steps { | ||||||
|  |     margin: -0.5em 0 -1em 0; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .waiting { | ||||||
|  |     margin: 1em -1em -1em -1em; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -14,35 +14,49 @@ | |||||||
| // You should have received a copy of the GNU General Public License
 | // You should have received a copy of the GNU General Public License
 | ||||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||||
| 
 | 
 | ||||||
| import React, { Component, PropTypes } from 'react'; |  | ||||||
| import { LinearProgress } from 'material-ui'; | import { LinearProgress } from 'material-ui'; | ||||||
| import { Step, Stepper, StepLabel } from 'material-ui/Stepper'; | import { Step, Stepper, StepLabel } from 'material-ui/Stepper'; | ||||||
|  | import React, { Component, PropTypes } from 'react'; | ||||||
| 
 | 
 | ||||||
|  | // TODO: It would make sense (going forward) to replace all uses of
 | ||||||
|  | // ContainerTitle with this component. In that case the styles for the
 | ||||||
|  | // h3 (title) can be pulled from there. (As it stands the duplication
 | ||||||
|  | // between the 2 has been removed, but as a short-term DRY only)
 | ||||||
|  | import { Title as ContainerTitle } from '~/ui/Container'; | ||||||
| import { nodeOrStringProptype } from '~/util/proptypes'; | import { nodeOrStringProptype } from '~/util/proptypes'; | ||||||
| 
 | 
 | ||||||
| import styles from '../modal.css'; | import styles from './title.css'; | ||||||
| 
 | 
 | ||||||
| export default class Title extends Component { | export default class Title extends Component { | ||||||
|   static propTypes = { |   static propTypes = { | ||||||
|  |     activeStep: PropTypes.number, | ||||||
|     busy: PropTypes.bool, |     busy: PropTypes.bool, | ||||||
|     current: PropTypes.number, |     busySteps: PropTypes.array, | ||||||
|  |     className: PropTypes.string, | ||||||
|     steps: PropTypes.array, |     steps: PropTypes.array, | ||||||
|     waiting: PropTypes.array, |  | ||||||
|     title: nodeOrStringProptype() |     title: nodeOrStringProptype() | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   render () { |   render () { | ||||||
|     const { current, steps, title } = this.props; |     const { activeStep, className, steps, title } = this.props; | ||||||
|  | 
 | ||||||
|  |     if (!title && !steps) { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <div className={ styles.title }> |       <div | ||||||
|         <h3> |         className={ | ||||||
|           { |           [styles.title, className].join(' ') | ||||||
|  |         } | ||||||
|  |       > | ||||||
|  |         <ContainerTitle | ||||||
|  |           title={ | ||||||
|             steps |             steps | ||||||
|               ? steps[current] |               ? steps[activeStep || 0] | ||||||
|               : title |               : title | ||||||
|           } |           } | ||||||
|         </h3> |         /> | ||||||
|         { this.renderSteps() } |         { this.renderSteps() } | ||||||
|         { this.renderWaiting() } |         { this.renderWaiting() } | ||||||
|       </div> |       </div> | ||||||
| @ -50,7 +64,7 @@ export default class Title extends Component { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   renderSteps () { |   renderSteps () { | ||||||
|     const { current, steps } = this.props; |     const { activeStep, steps } = this.props; | ||||||
| 
 | 
 | ||||||
|     if (!steps) { |     if (!steps) { | ||||||
|       return; |       return; | ||||||
| @ -58,7 +72,7 @@ export default class Title extends Component { | |||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <div className={ styles.steps }> |       <div className={ styles.steps }> | ||||||
|         <Stepper activeStep={ current }> |         <Stepper activeStep={ activeStep }> | ||||||
|           { this.renderTimeline() } |           { this.renderTimeline() } | ||||||
|         </Stepper> |         </Stepper> | ||||||
|       </div> |       </div> | ||||||
| @ -80,8 +94,8 @@ export default class Title extends Component { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   renderWaiting () { |   renderWaiting () { | ||||||
|     const { current, busy, waiting } = this.props; |     const { activeStep, busy, busySteps } = this.props; | ||||||
|     const isWaiting = busy || (waiting || []).includes(current); |     const isWaiting = busy || (busySteps || []).includes(activeStep); | ||||||
| 
 | 
 | ||||||
|     if (!isWaiting) { |     if (!isWaiting) { | ||||||
|       return null; |       return null; | ||||||
| @ -55,6 +55,7 @@ import SectionList from './SectionList'; | |||||||
| import ShortenedHash from './ShortenedHash'; | import ShortenedHash from './ShortenedHash'; | ||||||
| import SignerIcon from './SignerIcon'; | import SignerIcon from './SignerIcon'; | ||||||
| import Tags from './Tags'; | import Tags from './Tags'; | ||||||
|  | import Title from './Title'; | ||||||
| import Tooltips, { Tooltip } from './Tooltips'; | import Tooltips, { Tooltip } from './Tooltips'; | ||||||
| import TxHash from './TxHash'; | import TxHash from './TxHash'; | ||||||
| import TxList from './TxList'; | import TxList from './TxList'; | ||||||
| @ -119,6 +120,7 @@ export { | |||||||
|   SectionList, |   SectionList, | ||||||
|   SignerIcon, |   SignerIcon, | ||||||
|   Tags, |   Tags, | ||||||
|  |   Title, | ||||||
|   Tooltip, |   Tooltip, | ||||||
|   Tooltips, |   Tooltips, | ||||||
|   TxHash, |   TxHash, | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user