Modal.jsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. /*
  2. Copyright (C) 2017 Cloudbase Solutions SRL
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. */
  14. import React from 'react'
  15. import styled from 'styled-components'
  16. import PropTypes from 'prop-types'
  17. import Modal from 'react-modal'
  18. import Palette from '../../styleUtils/Palette'
  19. import StyleProps from '../../styleUtils/StyleProps'
  20. const Title = styled.div`
  21. height: 48px;
  22. font-size: 24px;
  23. font-weight: ${StyleProps.fontWeights.light};
  24. background: ${Palette.grayscale[1]};
  25. text-align: center;
  26. line-height: 48px;
  27. `
  28. class NewModal extends React.Component {
  29. static defaultProps = {
  30. topBottomMargin: 8,
  31. }
  32. static propTypes = {
  33. children: PropTypes.node.isRequired,
  34. isOpen: PropTypes.bool.isRequired,
  35. contentLabel: PropTypes.string,
  36. onRequestClose: PropTypes.func,
  37. contentStyle: PropTypes.object,
  38. topBottomMargin: PropTypes.number,
  39. title: PropTypes.string,
  40. }
  41. constructor() {
  42. super()
  43. this.positionModal = this.positionModal.bind(this)
  44. }
  45. componentDidMount() {
  46. window.addEventListener('resize', this.positionModal, true)
  47. setTimeout(this.positionModal, 100)
  48. }
  49. componentWillReceiveProps(newProps) {
  50. if (this.props.isOpen === true && newProps.isOpen === false) {
  51. this.handleModalClose()
  52. }
  53. }
  54. componentWillUpdate() {
  55. setTimeout(this.positionModal, 100)
  56. }
  57. componentWillUnmount() {
  58. window.removeEventListener('resize', this.positionModal, true)
  59. }
  60. handleChildUpdate(scrollableRef) {
  61. if (scrollableRef) {
  62. this.scrollableRef = scrollableRef
  63. }
  64. setTimeout(() => {
  65. this.positionModal()
  66. }, 100)
  67. }
  68. handleModalOpen() {
  69. this.windowScrollY = window.scrollY
  70. document.body.style.top = `${-this.windowScrollY}px`
  71. document.body.style.position = 'fixed'
  72. document.body.style.width = '100%'
  73. document.body.style.height = '100%'
  74. }
  75. handleModalClose() {
  76. document.body.style.top = ''
  77. document.body.style.position = ''
  78. document.body.style.width = ''
  79. document.body.style.height = ''
  80. window.scroll(0, this.windowScrollY)
  81. }
  82. positionModal() {
  83. let pageNode = this.modalDiv && this.modalDiv.node.firstChild
  84. let contentNode = pageNode && pageNode.firstChild
  85. if (!contentNode) {
  86. return
  87. }
  88. let scrollableNode = this.scrollableRef || contentNode
  89. let scrollTop = scrollableNode.scrollTop
  90. contentNode.style.height = 'auto'
  91. let left = (pageNode.offsetWidth / 2) - (contentNode.offsetWidth / 2)
  92. let top = (pageNode.offsetHeight / 2) - (contentNode.offsetHeight / 2)
  93. let height = 'auto'
  94. if (top < this.props.topBottomMargin) {
  95. top = this.props.topBottomMargin
  96. height = `${pageNode.offsetHeight - (this.props.topBottomMargin * 2)}px`
  97. }
  98. contentNode.style.left = `${left}px`
  99. contentNode.style.top = `${top}px`
  100. contentNode.style.height = height
  101. contentNode.style.opacity = 1
  102. scrollableNode.scrollTo(0, scrollTop)
  103. }
  104. renderTitle() {
  105. if (!this.props.title) {
  106. return null
  107. }
  108. return <Title>{this.props.title}</Title>
  109. }
  110. render() {
  111. let modalStyle = {
  112. overlay: {
  113. position: 'fixed',
  114. zIndex: 1000,
  115. top: 0,
  116. left: 0,
  117. right: 0,
  118. bottom: 0,
  119. backgroundColor: 'rgba(164, 170, 181, 0.69)',
  120. },
  121. content: {
  122. padding: 0,
  123. width: '576px',
  124. overflowX: 'hidden',
  125. borderRadius: '4px',
  126. border: 'none',
  127. bottom: 'auto',
  128. top: 'auto',
  129. left: 'auto',
  130. right: 'auto',
  131. transition: 'all 0.2s',
  132. opacity: 0,
  133. display: 'flex',
  134. flexDirection: 'column',
  135. },
  136. }
  137. modalStyle.content = {
  138. ...modalStyle.content,
  139. ...this.props.contentStyle,
  140. }
  141. let children = React.Children.map(this.props.children,
  142. child => React.cloneElement(child, {
  143. onResizeUpdate: scrollableRef => { this.handleChildUpdate(scrollableRef) },
  144. })
  145. )
  146. return (
  147. <Modal
  148. ref={m => { this.modalDiv = m }}
  149. isOpen={this.props.isOpen}
  150. contentLabel={this.props.contentLabel || this.props.title}
  151. style={modalStyle}
  152. onRequestClose={this.props.onRequestClose}
  153. onAfterOpen={() => { this.handleModalOpen() }}
  154. >
  155. {this.renderTitle()}
  156. {children}
  157. </Modal>
  158. )
  159. }
  160. }
  161. export default NewModal