diff --git a/app/components/ContextMenu/index.js b/app/components/ContextMenu/index.js
index 70c31c55..5a3ec9cf 100644
--- a/app/components/ContextMenu/index.js
+++ b/app/components/ContextMenu/index.js
@@ -6,14 +6,16 @@ import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
import {
fadeIn,
- fadeAndScaleIn,
- fadeAndSlideIn,
+ fadeAndSlideUp,
+ fadeAndSlideDown,
+ mobileContextMenu,
} from "shared/styles/animations";
import usePrevious from "hooks/usePrevious";
type Props = {|
"aria-label": string,
visible?: boolean,
+ placement?: string,
animating?: boolean,
children: React.Node,
onOpen?: () => void,
@@ -44,13 +46,25 @@ export default function ContextMenu({
return (
<>
{(rest.visible || rest.animating) && (
@@ -91,7 +105,7 @@ const Position = styled.div`
`;
const Background = styled.div`
- animation: ${fadeAndSlideIn} 200ms ease;
+ animation: ${mobileContextMenu} 200ms ease;
transform-origin: 50% 100%;
max-width: 100%;
background: ${(props) => props.theme.menuBackground};
@@ -109,9 +123,10 @@ const Background = styled.div`
}
${breakpoint("tablet")`
- animation: ${fadeAndScaleIn} 200ms ease;
+ animation: ${(props) =>
+ props.topAnchor ? fadeAndSlideDown : fadeAndSlideUp} 200ms ease;
transform-origin: ${(props) =>
- props.left !== undefined ? "25%" : "75%"} 0;
+ props.rightAnchor === "bottom-end" ? "75%" : "25%"} 0;
max-width: 276px;
background: ${(props) => props.theme.menuBackground};
box-shadow: ${(props) => props.theme.menuShadow};
diff --git a/app/components/DocumentListItem.js b/app/components/DocumentListItem.js
index 5e0b8a6f..08986eb8 100644
--- a/app/components/DocumentListItem.js
+++ b/app/components/DocumentListItem.js
@@ -66,6 +66,9 @@ function DocumentListItem(props: Props, ref) {
!document.isDraft && !document.isArchived && !document.isTemplate;
const can = policies.abilities(currentTeam.id);
+ const handleMenuOpen = React.useCallback(() => setMenuOpen(true), []);
+ const handleMenuClosed = React.useCallback(() => setMenuOpen(false), []);
+
return (
setMenuOpen(true)}
- onClose={() => setMenuOpen(false)}
+ onOpen={handleMenuOpen}
+ onClose={handleMenuClosed}
modal={false}
/>
diff --git a/app/components/HoverPreview.js b/app/components/HoverPreview.js
index ff1df8bd..8db9781a 100644
--- a/app/components/HoverPreview.js
+++ b/app/components/HoverPreview.js
@@ -4,7 +4,7 @@ import { transparentize } from "polished";
import * as React from "react";
import { Portal } from "react-portal";
import styled from "styled-components";
-import { fadeAndSlideIn } from "shared/styles/animations";
+import { fadeAndSlideDown } from "shared/styles/animations";
import parseDocumentSlug from "shared/utils/parseDocumentSlug";
import DocumentsStore from "stores/DocumentsStore";
import HoverPreviewDocument from "components/HoverPreviewDocument";
@@ -136,7 +136,7 @@ function HoverPreview({ node, ...rest }: Props) {
}
const Animate = styled.div`
- animation: ${fadeAndSlideIn} 150ms ease;
+ animation: ${fadeAndSlideDown} 150ms ease;
@media print {
display: none;
diff --git a/app/components/PaginatedDocumentList.js b/app/components/PaginatedDocumentList.js
index f6fabd9a..17234a15 100644
--- a/app/components/PaginatedDocumentList.js
+++ b/app/components/PaginatedDocumentList.js
@@ -1,5 +1,4 @@
// @flow
-import { observer } from "mobx-react";
import * as React from "react";
import Document from "models/Document";
import DocumentListItem from "components/DocumentListItem";
@@ -19,24 +18,26 @@ type Props = {|
showTemplate?: boolean,
|};
-@observer
-class PaginatedDocumentList extends React.Component {
- render() {
- const { empty, heading, documents, fetch, options, ...rest } = this.props;
-
- return (
- (
-
- )}
- />
- );
- }
-}
+const PaginatedDocumentList = React.memo(function PaginatedDocumentList({
+ empty,
+ heading,
+ documents,
+ fetch,
+ options,
+ ...rest
+}: Props) {
+ return (
+ (
+
+ )}
+ />
+ );
+});
export default PaginatedDocumentList;
diff --git a/app/components/Tab.js b/app/components/Tab.js
index 5d771744..ece4546a 100644
--- a/app/components/Tab.js
+++ b/app/components/Tab.js
@@ -1,14 +1,26 @@
// @flow
+import { m } from "framer-motion";
import * as React from "react";
-import { NavLink } from "react-router-dom";
+import { NavLink, Route } from "react-router-dom";
import styled, { withTheme } from "styled-components";
import { type Theme } from "types";
type Props = {
theme: Theme,
+ children: React.Node,
};
-const TabLink = styled(NavLink)`
+const NavLinkWithChildrenFunc = ({ to, exact = false, children, ...rest }) => (
+
+ {({ match }) => (
+
+ {children(match)}
+
+ )}
+
+);
+
+const TabLink = styled(NavLinkWithChildrenFunc)`
position: relative;
display: inline-flex;
align-items: center;
@@ -20,19 +32,48 @@ const TabLink = styled(NavLink)`
&:hover {
color: ${(props) => props.theme.textSecondary};
- border-bottom: 3px solid ${(props) => props.theme.divider};
- padding-bottom: 5px;
}
`;
-function Tab({ theme, ...rest }: Props) {
+const Active = styled(m.div)`
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 3px;
+ width: 100%;
+ border-top-left-radius: 2px;
+ border-top-right-radius: 2px;
+ background: ${(props) => props.theme.textSecondary};
+`;
+
+const transition = {
+ type: "spring",
+ stiffness: 500,
+ damping: 30,
+};
+
+function Tab({ theme, children, ...rest }: Props) {
const activeStyle = {
- paddingBottom: "5px",
- borderBottom: `3px solid ${theme.textSecondary}`,
color: theme.textSecondary,
};
- return ;
+ return (
+
+ {(match) => (
+ <>
+ {children}
+ {match && (
+
+ )}
+ >
+ )}
+
+ );
}
export default withTheme(Tab);
diff --git a/app/components/Tabs.js b/app/components/Tabs.js
index 7eec5868..2d37ec70 100644
--- a/app/components/Tabs.js
+++ b/app/components/Tabs.js
@@ -1,4 +1,5 @@
// @flow
+import { AnimateSharedLayout } from "framer-motion";
import { transparentize } from "polished";
import * as React from "react";
import styled from "styled-components";
@@ -79,11 +80,13 @@ const Tabs = ({ children }: {| children: React.Node |}) => {
}, [width, updateShadows]);
return (
-
-
-
+
+
+
+
+
);
};
diff --git a/app/index.js b/app/index.js
index 48c15f6b..36a2f629 100644
--- a/app/index.js
+++ b/app/index.js
@@ -1,5 +1,6 @@
// @flow
import "focus-visible";
+import { LazyMotion } from "framer-motion";
import { createBrowserHistory } from "history";
import { Provider } from "mobx-react";
import * as React from "react";
@@ -49,6 +50,10 @@ if ("serviceWorker" in window.navigator) {
});
}
+// Make sure to return the specific export containing the feature bundle.
+const loadFeatures = () =>
+ import("./utils/motion.js").then((res) => res.default);
+
if (element) {
const App = () => (
@@ -56,15 +61,17 @@ if (element) {
-
- <>
-
-
-
-
-
- >
-
+
+
+ <>
+
+
+
+
+
+ >
+
+
diff --git a/app/utils/motion.js b/app/utils/motion.js
new file mode 100644
index 00000000..069e3f09
--- /dev/null
+++ b/app/utils/motion.js
@@ -0,0 +1,3 @@
+// @flow
+import { domMax } from "framer-motion";
+export default domMax;
diff --git a/package.json b/package.json
index 22ae5141..6dbbbac4 100644
--- a/package.json
+++ b/package.json
@@ -79,6 +79,7 @@
"flow-typed": "^3.3.1",
"focus-visible": "^5.1.0",
"fractional-index": "^1.0.0",
+ "framer-motion": "^4.1.17",
"fs-extra": "^4.0.2",
"http-errors": "1.4.0",
"i18next": "^19.8.3",
@@ -211,4 +212,4 @@
"js-yaml": "^3.13.1"
},
"version": "0.57.0"
-}
\ No newline at end of file
+}
diff --git a/shared/styles/animations.js b/shared/styles/animations.js
index 00dbb52a..d1472d15 100644
--- a/shared/styles/animations.js
+++ b/shared/styles/animations.js
@@ -18,7 +18,19 @@ export const fadeAndScaleIn = keyframes`
}
`;
-export const fadeAndSlideIn = keyframes`
+export const fadeAndSlideDown = keyframes`
+ from {
+ opacity: 0;
+ transform: scale(.98) translateY(-10px);
+ }
+
+ to {
+ opacity: 1;
+ transform: scale(1) translateY(0px);
+ }
+`;
+
+export const fadeAndSlideUp = keyframes`
from {
opacity: 0;
transform: scale(.98) translateY(10px);
@@ -30,6 +42,18 @@ export const fadeAndSlideIn = keyframes`
}
`;
+export const mobileContextMenu = keyframes`
+ from {
+ opacity: 0;
+ transform: scale(.98) translateY(10vh);
+ }
+
+ to {
+ opacity: 1;
+ transform: scale(1) translateY(0px);
+ }
+`;
+
export const bounceIn = keyframes`
from,
20%,
diff --git a/yarn.lock b/yarn.lock
index 0c454873..742204d0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1040,7 +1040,7 @@
exec-sh "^0.3.2"
minimist "^1.2.0"
-"@emotion/is-prop-valid@^0.8.8":
+"@emotion/is-prop-valid@^0.8.2", "@emotion/is-prop-valid@^0.8.8":
version "0.8.8"
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a"
integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==
@@ -6014,6 +6014,26 @@ fragment-cache@^0.2.1:
dependencies:
map-cache "^0.2.2"
+framer-motion@^4.1.17:
+ version "4.1.17"
+ resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-4.1.17.tgz#4029469252a62ea599902e5a92b537120cc89721"
+ integrity sha512-thx1wvKzblzbs0XaK2X0G1JuwIdARcoNOW7VVwjO8BUltzXPyONGAElLu6CiCScsOQRI7FIk/45YTFtJw5Yozw==
+ dependencies:
+ framesync "5.3.0"
+ hey-listen "^1.0.8"
+ popmotion "9.3.6"
+ style-value-types "4.1.4"
+ tslib "^2.1.0"
+ optionalDependencies:
+ "@emotion/is-prop-valid" "^0.8.2"
+
+framesync@5.3.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/framesync/-/framesync-5.3.0.tgz#0ecfc955e8f5a6ddc8fdb0cc024070947e1a0d9b"
+ integrity sha512-oc5m68HDO/tuK2blj7ZcdEBRx3p1PjrgHazL8GYEpvULhrtGIFbQArN6cQS2QhW8mitffaB+VYzMjDqBxxQeoA==
+ dependencies:
+ tslib "^2.1.0"
+
fresh@~0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
@@ -6611,6 +6631,11 @@ helmet@^3.21.1:
referrer-policy "1.2.0"
x-xss-protection "1.3.0"
+hey-listen@^1.0.8:
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68"
+ integrity sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==
+
hide-powered-by@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/hide-powered-by/-/hide-powered-by-1.1.0.tgz#be3ea9cab4bdb16f8744be873755ca663383fa7a"
@@ -10352,6 +10377,16 @@ polished@3.6.5:
dependencies:
"@babel/runtime" "^7.9.2"
+popmotion@9.3.6:
+ version "9.3.6"
+ resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-9.3.6.tgz#b5236fa28f242aff3871b9e23721f093133248d1"
+ integrity sha512-ZTbXiu6zIggXzIliMi8LGxXBF5ST+wkpXGEjeTUDUOCdSQ356hij/xjeUdv0F8zCQNeqB1+PR5/BB+gC+QLAPw==
+ dependencies:
+ framesync "5.3.0"
+ hey-listen "^1.0.8"
+ style-value-types "4.1.4"
+ tslib "^2.1.0"
+
popper.js@^1.14.7:
version "1.16.1"
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b"
@@ -12627,6 +12662,14 @@ strip-json-comments@~2.0.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
+style-value-types@4.1.4:
+ version "4.1.4"
+ resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-4.1.4.tgz#80f37cb4fb024d6394087403dfb275e8bb627e75"
+ integrity sha512-LCJL6tB+vPSUoxgUBt9juXIlNJHtBMy8jkXzUJSBzeHWdBu6lhzHqCvLVkXFGsFIlNa2ln1sQHya/gzaFmB2Lg==
+ dependencies:
+ hey-listen "^1.0.8"
+ tslib "^2.1.0"
+
styled-components-breakpoint@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/styled-components-breakpoint/-/styled-components-breakpoint-2.1.1.tgz#37c1b92b0e96c1bbc5d293724d7a114daaa15fca"
@@ -13103,7 +13146,7 @@ tslib@^1.9.0, tslib@^1.9.3:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
-tslib@^2.0.3, tslib@^2.2.0:
+tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"
integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==