| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256 |
- /*
- Copyright (C) 2017 Cloudbase Solutions SRL
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
- This program 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 Affero General Public License for more details.
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- import * as React from "react";
- import { observer } from "mobx-react";
- import styled from "styled-components";
- import Modal from "react-modal";
- import autobind from "autobind-decorator";
- import ReactTooltip from "react-tooltip";
- import KeyboardManager from "@src/utils/KeyboardManager";
- import { ThemeProps } from "@src/components/Theme";
- import backgroundImage from "@src/components/ui/Images/star-bg.jpg";
- const headerHeight = 48;
- const Title = styled.div`
- height: ${headerHeight}px;
- font-size: 24px;
- font-weight: ${ThemeProps.fontWeights.light};
- text-align: center;
- line-height: 48px;
- color: white;
- background: url("${backgroundImage}") center no-repeat;
- background-size: cover;
- `;
- type Props = {
- children: React.ReactNode;
- isOpen: boolean;
- contentLabel?: string;
- onRequestClose?: () => void;
- contentWidth?: React.CSSProperties["width"];
- topBottomMargin?: number;
- title?: string;
- componentRef?: (ref: any) => void;
- onScrollableRef?: () => HTMLElement | null | undefined;
- fixedHeight?: number;
- };
- @observer
- class NewModal extends React.Component<Props> {
- scrollableRef: HTMLDivElement | undefined | null;
- windowScrollY: number | undefined;
- contentRef: HTMLDivElement | undefined | null;
- overlayRef: HTMLDivElement | undefined | null;
- UNSAFE_componentWillMount() {
- if (this.props.componentRef) {
- this.props.componentRef(this);
- }
- }
- componentDidMount() {
- window.addEventListener("resize", this.handleResize, true);
- setTimeout(() => {
- this.positionModal(0);
- }, 100);
- }
- UNSAFE_componentWillReceiveProps(newProps: Props) {
- if (!this.props.isOpen && newProps.isOpen) {
- KeyboardManager.onKeyDown("modal", null, 1);
- } else if (this.props.isOpen && !newProps.isOpen) {
- KeyboardManager.removeKeyDown("modal");
- this.handleModalClose();
- }
- }
- UNSAFE_componentWillUpdate() {
- setTimeout(() => {
- this.positionModal(0);
- }, 100);
- }
- componentWillUnmount() {
- this.handleModalClose();
- window.removeEventListener("resize", this.handleResize, true);
- }
- @autobind
- handleResize() {
- this.positionModal(0);
- }
- handleChildUpdate(scrollableRef: HTMLDivElement, scrollOffset: number) {
- if (scrollableRef) {
- this.scrollableRef = scrollableRef;
- }
- setTimeout(() => {
- this.positionModal(scrollOffset);
- }, 100);
- }
- handleModalOpen() {
- if (!document.body) {
- return;
- }
- this.windowScrollY = window.scrollY;
- document.body.style.top = `${-this.windowScrollY}px`;
- document.body.style.position = "fixed";
- document.body.style.width = "100%";
- document.body.style.height = "100%";
- }
- handleModalClose() {
- if (!document.body) {
- return;
- }
- document.body.style.top = "";
- document.body.style.position = "";
- document.body.style.width = "";
- document.body.style.height = "";
- window.scroll(0, this.windowScrollY || 0);
- }
- handleRequestClose() {
- /** Remove existing tooltips. Tooltips are not automatically removed when a modal is closed
- ** (for example when pressing ESC key while hovering over a tooltip) **/
- ReactTooltip.hide();
- if (this.props.onRequestClose) {
- this.props.onRequestClose();
- }
- }
- @autobind
- positionModal(scrollOffset: number) {
- const overlay = this.overlayRef as HTMLDivElement;
- const content = this.contentRef as HTMLDivElement;
- if (!overlay || !content) {
- return;
- }
- const scrollableRef =
- this.props.onScrollableRef && this.props.onScrollableRef();
- const scrollableNode = (scrollableRef ||
- this.scrollableRef ||
- content) as HTMLDivElement;
- const scrollTop = scrollableNode.scrollTop;
- content.style.height = "auto";
- const contentDesiredHeight = this.props.fixedHeight
- ? this.props.fixedHeight + headerHeight
- : content.offsetHeight;
- const left = overlay.offsetWidth / 2 - content.offsetWidth / 2;
- let top = overlay.offsetHeight / 2 - contentDesiredHeight / 2;
- let height = this.props.fixedHeight
- ? `${this.props.fixedHeight + headerHeight}px`
- : "auto";
- const topBottomMargin = this.props.topBottomMargin || 8;
- if (top < topBottomMargin) {
- top = topBottomMargin;
- height = `${overlay.offsetHeight - topBottomMargin * 2}px`;
- }
- content.style.left = `${left}px`;
- content.style.top = `${top}px`;
- content.style.height = height;
- content.style.opacity = "1";
- scrollableNode.scrollTop = scrollTop + scrollOffset;
- }
- renderTitle() {
- if (!this.props.title) {
- return null;
- }
- return <Title>{this.props.title}</Title>;
- }
- render() {
- const modalStyle: Modal.Styles = {
- overlay: {
- position: "fixed",
- zIndex: 1000,
- top: 0,
- left: 0,
- right: 0,
- bottom: 0,
- backgroundColor: "rgba(73, 76, 81, 0.69)",
- },
- content: {
- padding: 0,
- width: "576px",
- overflowX: "hidden",
- borderRadius: "4px",
- border: "none",
- bottom: "auto",
- top: "auto",
- left: "auto",
- right: "auto",
- transition: "all 0.2s",
- opacity: 0,
- display: "flex",
- flexDirection: "column",
- },
- };
- if (this.props.contentWidth) {
- modalStyle.content!.width = this.props.contentWidth;
- }
- const children = React.Children.map(this.props.children, child =>
- React.cloneElement(child as React.ReactElement<any>, {
- onResizeUpdate: (
- scrollableRef: HTMLDivElement,
- scrollOffset: number
- ) => {
- this.handleChildUpdate(scrollableRef, scrollOffset);
- },
- })
- );
- return (
- <Modal
- contentRef={(m: HTMLDivElement) => {
- this.contentRef = m;
- }}
- overlayRef={(m: HTMLDivElement) => {
- this.overlayRef = m;
- }}
- isOpen={this.props.isOpen}
- contentLabel={this.props.contentLabel || this.props.title}
- style={modalStyle}
- onRequestClose={() => this.handleRequestClose()}
- onAfterOpen={() => {
- this.handleModalOpen();
- }}
- ariaHideApp={false}
- >
- {this.renderTitle()}
- {children}
- </Modal>
- );
- }
- }
- export default NewModal;
|