diff --git a/app/components/Modal.js b/app/components/Modal.js
index 03b8518dc..05caa8ca2 100644
--- a/app/components/Modal.js
+++ b/app/components/Modal.js
@@ -4,15 +4,17 @@ import { CloseIcon, BackIcon } from "outline-icons";
import { transparentize } from "polished";
import * as React from "react";
import { useTranslation } from "react-i18next";
-import ReactModal from "react-modal";
-import styled, { createGlobalStyle } from "styled-components";
+import { Dialog, DialogBackdrop, useDialogState } from "reakit/Dialog";
+import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
import { fadeAndScaleIn } from "shared/styles/animations";
import Flex from "components/Flex";
import NudeButton from "components/NudeButton";
import Scrollable from "components/Scrollable";
+import usePrevious from "hooks/usePrevious";
+import useUnmount from "hooks/useUnmount";
-ReactModal.setAppElement("#root");
+let openModals = 0;
type Props = {|
children?: React.Node,
@@ -21,44 +23,6 @@ type Props = {|
onRequestClose: () => void,
|};
-const GlobalStyles = createGlobalStyle`
- .ReactModal__Overlay {
- background-color: ${(props) =>
- transparentize(0.25, props.theme.background)} !important;
- z-index: ${(props) => props.theme.depths.modalOverlay};
- }
-
- ${breakpoint("tablet")`
- .ReactModalPortal + .ReactModalPortal,
- .ReactModalPortal + [data-react-modal-body-trap] + .ReactModalPortal {
- .ReactModal__Overlay {
- margin-left: 12px;
- box-shadow: 0 -2px 10px ${(props) => props.theme.shadow};
- border-radius: 8px 0 0 8px;
- overflow: hidden;
- }
- }
-
- .ReactModalPortal + .ReactModalPortal + .ReactModalPortal,
- .ReactModalPortal + .ReactModalPortal + [data-react-modal-body-trap] + .ReactModalPortal {
- .ReactModal__Overlay {
- margin-left: 24px;
- }
- }
-
- .ReactModalPortal + .ReactModalPortal + .ReactModalPortal + .ReactModalPortal,
- .ReactModalPortal + .ReactModalPortal + .ReactModalPortal + [data-react-modal-body-trap] + .ReactModalPortal {
- .ReactModal__Overlay {
- margin-left: 36px;
- }
- }
- `};
-
- .ReactModal__Body--open {
- overflow: hidden;
- }
-`;
-
const Modal = ({
children,
isOpen,
@@ -66,36 +30,112 @@ const Modal = ({
onRequestClose,
...rest
}: Props) => {
+ const dialog = useDialogState({ animated: 250 });
+ const [depth, setDepth] = React.useState(0);
+ const wasOpen = usePrevious(isOpen);
const { t } = useTranslation();
+
+ React.useEffect(() => {
+ if (!wasOpen && isOpen) {
+ setDepth(openModals++);
+ dialog.show();
+ }
+ if (wasOpen && !isOpen) {
+ setDepth(openModals--);
+ dialog.hide();
+ }
+ }, [dialog, wasOpen, isOpen]);
+
+ useUnmount(() => {
+ if (isOpen) {
+ openModals--;
+ }
+ });
+
if (!isOpen) return null;
return (
- <>
-
-
-
- ev.stopPropagation()} column>
- {title && {title}
}
- {children}
-
-
-
-
- {t("Back")}
-
-
-
-
-
- >
+
+ {(props) => (
+
+
+
+ )}
+
);
};
+const Backdrop = styled.div`
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: ${(props) =>
+ transparentize(0.25, props.theme.background)} !important;
+ z-index: ${(props) => props.theme.depths.modalOverlay};
+ transition: opacity 50ms ease-in-out;
+ opacity: 0;
+
+ &[data-enter] {
+ opacity: 1;
+ }
+`;
+
+const Scene = styled.div`
+ animation: ${fadeAndScaleIn} 250ms ease;
+
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: ${(props) => props.theme.depths.modal};
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+ background: ${(props) => props.theme.background};
+ transition: ${(props) => props.theme.backgroundTransition};
+ outline: none;
+
+ ${breakpoint("tablet")`
+ ${(props) =>
+ props.$nested &&
+ `
+ box-shadow: 0 -2px 10px ${props.theme.shadow};
+ border-radius: 8px 0 0 8px;
+ overflow: hidden;
+ `}
+`}
+`;
+
const Content = styled(Scrollable)`
width: 100%;
padding: 8vh 2rem 2rem;
@@ -112,23 +152,6 @@ const Centered = styled(Flex)`
margin: 0 auto;
`;
-const StyledModal = styled(ReactModal)`
- animation: ${fadeAndScaleIn} 250ms ease;
-
- position: absolute;
- top: 0;
- left: 0;
- bottom: 0;
- right: 0;
- z-index: ${(props) => props.theme.depths.modal};
- display: flex;
- justify-content: center;
- align-items: flex-start;
- background: ${(props) => props.theme.background};
- transition: ${(props) => props.theme.backgroundTransition};
- outline: none;
-`;
-
const Text = styled.span`
font-size: 16px;
font-weight: 500;
diff --git a/app/hooks/useUnmount.js b/app/hooks/useUnmount.js
new file mode 100644
index 000000000..18d556c03
--- /dev/null
+++ b/app/hooks/useUnmount.js
@@ -0,0 +1,16 @@
+// @flow
+import * as React from "react";
+
+const useUnmount = (callback: Function) => {
+ const ref = React.useRef(callback);
+
+ ref.current = callback;
+
+ React.useEffect(() => {
+ return () => {
+ ref.current();
+ };
+ }, []);
+};
+
+export default useUnmount;
diff --git a/app/menus/DocumentMenu.js b/app/menus/DocumentMenu.js
index 92e53d8ed..e58f568a5 100644
--- a/app/menus/DocumentMenu.js
+++ b/app/menus/DocumentMenu.js
@@ -8,6 +8,7 @@ import { VisuallyHidden } from "reakit/VisuallyHidden";
import styled from "styled-components";
import Document from "models/Document";
import DocumentDelete from "scenes/DocumentDelete";
+import DocumentMove from "scenes/DocumentMove";
import DocumentShare from "scenes/DocumentShare";
import DocumentTemplatize from "scenes/DocumentTemplatize";
import CollectionIcon from "components/CollectionIcon";
@@ -21,7 +22,6 @@ import useStores from "hooks/useStores";
import getDataTransferFiles from "utils/getDataTransferFiles";
import {
documentHistoryUrl,
- documentMoveUrl,
documentUrl,
editDocumentUrl,
newDocumentUrl,
@@ -64,6 +64,7 @@ function DocumentMenu({
const { t } = useTranslation();
const [renderModals, setRenderModals] = React.useState(false);
const [showDeleteModal, setShowDeleteModal] = React.useState(false);
+ const [showMoveModal, setShowMoveModal] = React.useState(false);
const [showTemplateModal, setShowTemplateModal] = React.useState(false);
const [showShareModal, setShowShareModal] = React.useState(false);
const file = React.useRef();
@@ -351,7 +352,7 @@ function DocumentMenu({
},
{
title: `${t("Move")}…`,
- to: documentMoveUrl(document),
+ onClick: () => setShowMoveModal(true),
visible: !!can.move,
},
{
@@ -379,6 +380,18 @@ function DocumentMenu({
{renderModals && (
<>
+ setShowMoveModal(false)}
+ isOpen={showMoveModal}
+ >
+ setShowMoveModal(false)}
+ />
+
{
@observable isPublishing: boolean = false;
@observable isDirty: boolean = false;
@observable isEmpty: boolean = true;
- @observable moveModalOpen: boolean = false;
@observable lastRevision: number = this.props.document.revision;
@observable title: string = this.props.document.title;
getEditorText: () => string = () => this.props.document.text;
@@ -186,9 +186,6 @@ class DocumentScene extends React.Component {
}
}
- handleCloseMoveModal = () => (this.moveModalOpen = false);
- handleOpenMoveModal = () => (this.moveModalOpen = true);
-
onSave = async (
options: {
done?: boolean,
@@ -337,7 +334,16 @@ class DocumentScene extends React.Component {
(
-
+
+
+
)}
/>
{
};
render() {
- const { document, collections, onRequestClose } = this.props;
+ const { document, collections } = this.props;
const data = this.results;
- return (
-
- {document && collections.isLoaded && (
-
-
-
- {this.renderPathToCurrentDocument()}
-
-
+ if (!document || !collections.isLoaded) {
+ return null;
+ }
-
-
- )}
-
+ return (
+
+
+
+ {this.renderPathToCurrentDocument()}
+
+
+
+
+
);
}
}
diff --git a/flow-typed/npm/react-modal_v3.1.x.js b/flow-typed/npm/react-modal_v3.1.x.js
deleted file mode 100644
index 5cf3f3ff1..000000000
--- a/flow-typed/npm/react-modal_v3.1.x.js
+++ /dev/null
@@ -1,51 +0,0 @@
-// flow-typed signature: 096d0e067d7269fca81b46322149e3f5
-// flow-typed version: c6154227d1/react-modal_v3.1.x/flow_>=v0.104.x
-
-declare module 'react-modal' {
- declare type DefaultProps = {
- isOpen?: boolean,
- portalClassName?: string,
- bodyOpenClassName?: string,
- ariaHideApp?: boolean,
- closeTimeoutMS?: number,
- shouldFocusAfterRender?: boolean,
- shouldCloseOnEsc?: boolean,
- shouldCloseOnOverlayClick?: boolean,
- shouldReturnFocusAfterClose?: boolean,
- parentSelector?: () => HTMLElement,
- ...
- };
-
- declare type Props = DefaultProps & {
- style?: {
- content?: { [key: string]: string | number, ... },
- overlay?: { [key: string]: string | number, ... },
- ...
- },
- className?: string | {
- base: string,
- afterOpen: string,
- beforeClose: string,
- ...
- },
- overlayClassName?: string | {
- base: string,
- afterOpen: string,
- beforeClose: string,
- ...
- },
- appElement?: HTMLElement | string | null,
- onAfterOpen?: () => void | Promise,
- onRequestClose?: (SyntheticEvent<>) => void,
- aria?: { [key: string]: string, ... },
- role?: string,
- contentLabel?: string,
- ...
- };
-
- declare class Modal extends React$Component {
- static setAppElement(element: HTMLElement | string | null): void;
- }
-
- declare module.exports: typeof Modal;
-}
diff --git a/package.json b/package.json
index 509c04217..e57f4fe97 100644
--- a/package.json
+++ b/package.json
@@ -152,7 +152,6 @@
"react-helmet": "^5.2.0",
"react-i18next": "^11.7.3",
"react-keydown": "^1.7.3",
- "react-modal": "^3.1.2",
"react-portal": "^4.0.0",
"react-router-dom": "^5.2.0",
"react-virtualized-auto-sizer": "^1.0.2",
diff --git a/yarn.lock b/yarn.lock
index 5dba516ae..66467781c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5379,11 +5379,6 @@ execa@^4.0.0:
signal-exit "^3.0.2"
strip-final-newline "^2.0.0"
-exenv@^1.2.0:
- version "1.2.2"
- resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d"
- integrity sha1-KueOhdmJQVhnCwPUe+wfA72Ru50=
-
exif-parser@^0.1.12:
version "0.1.12"
resolved "https://registry.yarnpkg.com/exif-parser/-/exif-parser-0.1.12.tgz#58a9d2d72c02c1f6f02a0ef4a9166272b7760922"
@@ -10701,26 +10696,11 @@ react-keydown@^1.7.3:
dependencies:
core-js "^3.1.2"
-react-lifecycles-compat@^3.0.0:
- version "3.0.4"
- resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
- integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
-
react-medium-image-zoom@^3.0.16:
version "3.1.2"
resolved "https://registry.yarnpkg.com/react-medium-image-zoom/-/react-medium-image-zoom-3.1.2.tgz#5ac4441f1d424bd9680a25bfc2591be3d7704a42"
integrity sha512-werjufn5o4ytdyvJNzfqXCilovDhMyREH0qeJhCjV5brNAyfV7anZmvpFc3FApbuVXwBkzHMuQkV2z/GyEQatg==
-react-modal@^3.1.2:
- version "3.11.2"
- resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.11.2.tgz#bad911976d4add31aa30dba8a41d11e21c4ac8a4"
- integrity sha512-o8gvvCOFaG1T7W6JUvsYjRjMVToLZgLIsi5kdhFIQCtHxDkA47LznX62j+l6YQkpXDbvQegsDyxe/+JJsFQN7w==
- dependencies:
- exenv "^1.2.0"
- prop-types "^15.5.10"
- react-lifecycles-compat "^3.0.0"
- warning "^4.0.3"
-
react-portal@^4.0.0, react-portal@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/react-portal/-/react-portal-4.2.1.tgz#12c1599238c06fb08a9800f3070bea2a3f78b1a6"
@@ -13464,13 +13444,6 @@ walker@^1.0.7, walker@~1.0.5:
dependencies:
makeerror "1.0.x"
-warning@^4.0.3:
- version "4.0.3"
- resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
- integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
- dependencies:
- loose-envify "^1.0.0"
-
watchpack-chokidar2@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz#38500072ee6ece66f3769936950ea1771be1c957"