Modal.jsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  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. border-top-radius: ${StyleProps.borderRadius};
  26. text-align: center;
  27. line-height: 48px;
  28. `
  29. class NewModal extends React.Component {
  30. static defaultProps = {
  31. topBottomMargin: 8,
  32. }
  33. static propTypes = {
  34. children: PropTypes.node.isRequired,
  35. isOpen: PropTypes.bool.isRequired,
  36. contentLabel: PropTypes.string,
  37. onRequestClose: PropTypes.func,
  38. contentStyle: PropTypes.object,
  39. topBottomMargin: PropTypes.number,
  40. title: PropTypes.string,
  41. }
  42. constructor() {
  43. super()
  44. this.positionModal = this.positionModal.bind(this)
  45. }
  46. componentDidMount() {
  47. window.addEventListener('resize', this.positionModal, true)
  48. setTimeout(this.positionModal, 100)
  49. }
  50. componentWillReceiveProps(newProps) {
  51. if (this.props.isOpen === true && newProps.isOpen === false) {
  52. this.handleModalClose()
  53. }
  54. }
  55. componentWillUpdate() {
  56. setTimeout(this.positionModal, 100)
  57. }
  58. componentWillUnmount() {
  59. window.removeEventListener('resize', this.positionModal, true)
  60. }
  61. handleChildUpdate() {
  62. setTimeout(this.positionModal, 100)
  63. }
  64. handleModalOpen() {
  65. this.windowScrollY = window.scrollY
  66. document.body.style.top = `${-this.windowScrollY}px`
  67. document.body.style.position = 'fixed'
  68. document.body.style.width = '100%'
  69. document.body.style.height = '100%'
  70. }
  71. handleModalClose() {
  72. document.body.style.top = ''
  73. document.body.style.position = ''
  74. document.body.style.width = ''
  75. document.body.style.height = ''
  76. window.scroll(0, this.windowScrollY)
  77. }
  78. positionModal() {
  79. let pageNode = this.modalDiv && this.modalDiv.node.firstChild
  80. let contentNode = pageNode && pageNode.firstChild
  81. if (!contentNode) {
  82. return
  83. }
  84. let scrollTop = contentNode.scrollTop
  85. contentNode.style.height = 'auto'
  86. let left = (pageNode.offsetWidth / 2) - (contentNode.offsetWidth / 2)
  87. let top = (pageNode.offsetHeight / 2) - (contentNode.offsetHeight / 2)
  88. let height = 'auto'
  89. if (top < this.props.topBottomMargin) {
  90. top = this.props.topBottomMargin
  91. height = `${pageNode.offsetHeight - (this.props.topBottomMargin * 2)}px`
  92. }
  93. contentNode.style.left = `${left}px`
  94. contentNode.style.top = `${top}px`
  95. contentNode.style.height = height
  96. contentNode.style.opacity = 1
  97. contentNode.scrollTo(0, scrollTop)
  98. }
  99. renderTitle() {
  100. if (!this.props.title) {
  101. return null
  102. }
  103. return <Title>{this.props.title}</Title>
  104. }
  105. render() {
  106. let modalStyle = {
  107. overlay: {
  108. position: 'fixed',
  109. zIndex: 1000,
  110. top: 0,
  111. left: 0,
  112. right: 0,
  113. bottom: 0,
  114. backgroundColor: 'rgba(164, 170, 181, 0.69)',
  115. },
  116. content: {
  117. padding: 0,
  118. width: '576px',
  119. overflowX: 'hidden',
  120. borderRadius: '4px',
  121. border: 'none',
  122. bottom: 'auto',
  123. top: 'auto',
  124. left: 'auto',
  125. right: 'auto',
  126. transition: 'all 0.2s',
  127. opacity: 0,
  128. },
  129. }
  130. modalStyle.content = {
  131. ...modalStyle.content,
  132. ...this.props.contentStyle,
  133. }
  134. let children = React.Children.map(this.props.children,
  135. child => React.cloneElement(child, {
  136. onResizeUpdate: () => { this.handleChildUpdate() },
  137. })
  138. )
  139. return (
  140. <Modal
  141. ref={m => { this.modalDiv = m }}
  142. isOpen={this.props.isOpen}
  143. contentLabel={this.props.contentLabel || this.props.title}
  144. style={modalStyle}
  145. onRequestClose={this.props.onRequestClose}
  146. onAfterOpen={() => { this.handleModalOpen() }}
  147. >
  148. {this.renderTitle()}
  149. {children}
  150. </Modal>
  151. )
  152. }
  153. }
  154. export default NewModal