feat: Show mobile-style (slide from bottom) menus on mobile (#2025)
* feat: Show mobile-style (slide from bottom) menus at responsive viewport sizes * More mobile improvements * fix: Safari compatability
This commit is contained in:
@ -3,6 +3,7 @@ import { CheckmarkIcon } from "outline-icons";
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { MenuItem as BaseMenuItem } from "reakit/Menu";
|
import { MenuItem as BaseMenuItem } from "reakit/Menu";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
import breakpoint from "styled-components-breakpoint";
|
||||||
|
|
||||||
type Props = {|
|
type Props = {|
|
||||||
onClick?: (SyntheticEvent<>) => void | Promise<void>,
|
onClick?: (SyntheticEvent<>) => void | Promise<void>,
|
||||||
@ -72,7 +73,7 @@ export const MenuAnchor = styled.a`
|
|||||||
display: flex;
|
display: flex;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
padding: 6px 12px;
|
padding: 12px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
background: none;
|
background: none;
|
||||||
@ -80,7 +81,7 @@ export const MenuAnchor = styled.a`
|
|||||||
props.disabled ? props.theme.textTertiary : props.theme.textSecondary};
|
props.disabled ? props.theme.textTertiary : props.theme.textSecondary};
|
||||||
justify-content: left;
|
justify-content: left;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 15px;
|
font-size: 16px;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
@ -115,6 +116,11 @@ export const MenuAnchor = styled.a`
|
|||||||
background: ${props.theme.primary};
|
background: ${props.theme.primary};
|
||||||
}
|
}
|
||||||
`};
|
`};
|
||||||
|
|
||||||
|
${breakpoint("tablet")`
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 15px;
|
||||||
|
`};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default MenuItem;
|
export default MenuItem;
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import { rgba } from "polished";
|
import { rgba } from "polished";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { Portal } from "react-portal";
|
||||||
import { Menu } from "reakit/Menu";
|
import { Menu } from "reakit/Menu";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { fadeAndScaleIn } from "shared/styles/animations";
|
import breakpoint from "styled-components-breakpoint";
|
||||||
|
import { fadeAndScaleIn, fadeAndSlideIn } from "shared/styles/animations";
|
||||||
|
import Fade from "components/Fade";
|
||||||
import usePrevious from "hooks/usePrevious";
|
import usePrevious from "hooks/usePrevious";
|
||||||
|
|
||||||
type Props = {|
|
type Props = {|
|
||||||
@ -37,27 +40,60 @@ export default function ContextMenu({
|
|||||||
}, [onOpen, onClose, previousVisible, rest.visible]);
|
}, [onOpen, onClose, previousVisible, rest.visible]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu hideOnClickOutside preventBodyScroll {...rest}>
|
<>
|
||||||
{(props) => (
|
<Menu hideOnClickOutside preventBodyScroll {...rest}>
|
||||||
<Position {...props}>
|
{(props) => (
|
||||||
<Background>
|
<Position {...props}>
|
||||||
{rest.visible || rest.animating ? children : null}
|
<Background>
|
||||||
</Background>
|
{rest.visible || rest.animating ? children : null}
|
||||||
</Position>
|
</Background>
|
||||||
|
</Position>
|
||||||
|
)}
|
||||||
|
</Menu>
|
||||||
|
{(rest.visible || rest.animating) && (
|
||||||
|
<Portal>
|
||||||
|
<Fade timing="200ms">
|
||||||
|
<Backdrop />
|
||||||
|
</Fade>
|
||||||
|
</Portal>
|
||||||
)}
|
)}
|
||||||
</Menu>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Backdrop = styled.div`
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: ${(props) => props.theme.shadow};
|
||||||
|
z-index: ${(props) => props.theme.depths.menu - 1};
|
||||||
|
|
||||||
|
${breakpoint("tablet")`
|
||||||
|
display: none;
|
||||||
|
`};
|
||||||
|
`;
|
||||||
|
|
||||||
const Position = styled.div`
|
const Position = styled.div`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: ${(props) => props.theme.depths.menu};
|
z-index: ${(props) => props.theme.depths.menu};
|
||||||
|
|
||||||
|
${breakpoint("mobile", "tablet")`
|
||||||
|
position: fixed !important;
|
||||||
|
transform: none !important;
|
||||||
|
top: auto !important;
|
||||||
|
right: 8px !important;
|
||||||
|
bottom: 8px !important;
|
||||||
|
left: 8px !important;
|
||||||
|
`};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Background = styled.div`
|
const Background = styled.div`
|
||||||
animation: ${fadeAndScaleIn} 200ms ease;
|
animation: ${fadeAndSlideIn} 200ms ease;
|
||||||
transform-origin: ${(props) => (props.left !== undefined ? "25%" : "75%")} 0;
|
transform-origin: 50% 100%;
|
||||||
background: ${(props) => rgba(props.theme.menuBackground, 0.95)};
|
max-width: 100%;
|
||||||
|
background: ${(props) => props.theme.menuBackground};
|
||||||
border: ${(props) =>
|
border: ${(props) =>
|
||||||
props.theme.menuBorder ? `1px solid ${props.theme.menuBorder}` : "none"};
|
props.theme.menuBorder ? `1px solid ${props.theme.menuBorder}` : "none"};
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
@ -66,7 +102,6 @@ const Background = styled.div`
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
max-height: 75vh;
|
max-height: 75vh;
|
||||||
max-width: 276px;
|
|
||||||
box-shadow: ${(props) => props.theme.menuShadow};
|
box-shadow: ${(props) => props.theme.menuShadow};
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
@ -74,4 +109,12 @@ const Background = styled.div`
|
|||||||
@media print {
|
@media print {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
${breakpoint("tablet")`
|
||||||
|
animation: ${fadeAndScaleIn} 200ms ease;
|
||||||
|
transform-origin: ${(props) =>
|
||||||
|
props.left !== undefined ? "25%" : "75%"} 0;
|
||||||
|
max-width: 276px;
|
||||||
|
background: ${(props) => rgba(props.theme.menuBackground, 0.95)};
|
||||||
|
`};
|
||||||
`;
|
`;
|
||||||
|
@ -77,7 +77,7 @@ const Actions = styled(Flex)`
|
|||||||
const Wrapper = styled(Flex)`
|
const Wrapper = styled(Flex)`
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 2;
|
z-index: ${(props) => props.theme.depths.header};
|
||||||
background: ${(props) => transparentize(0.2, props.theme.background)};
|
background: ${(props) => transparentize(0.2, props.theme.background)};
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
transition: all 100ms ease-out;
|
transition: all 100ms ease-out;
|
||||||
|
@ -211,7 +211,7 @@ const Background = styled.a`
|
|||||||
right: 0;
|
right: 0;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
z-index: ${(props) => props.theme.depths.sidebar - 1};
|
z-index: ${(props) => props.theme.depths.sidebar - 1};
|
||||||
background: rgba(0, 0, 0, 0.5);
|
background: ${(props) => props.theme.sidebarShadow};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Container = styled(Flex)`
|
const Container = styled(Flex)`
|
||||||
|
@ -126,7 +126,7 @@ const Link = styled(NavLink)`
|
|||||||
props.$isActiveDrop ? props.theme.slateDark : "inherit"};
|
props.$isActiveDrop ? props.theme.slateDark : "inherit"};
|
||||||
color: ${(props) =>
|
color: ${(props) =>
|
||||||
props.$isActiveDrop ? props.theme.white : props.theme.sidebarText};
|
props.$isActiveDrop ? props.theme.white : props.theme.sidebarText};
|
||||||
font-size: 15px;
|
font-size: 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
@ -158,6 +158,7 @@ const Link = styled(NavLink)`
|
|||||||
|
|
||||||
${breakpoint("tablet")`
|
${breakpoint("tablet")`
|
||||||
padding: 4px 16px;
|
padding: 4px 16px;
|
||||||
|
font-size: 15px;
|
||||||
`}
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -152,7 +152,6 @@ function CollectionScene() {
|
|||||||
<CollectionMenu
|
<CollectionMenu
|
||||||
collection={collection}
|
collection={collection}
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
modal={false}
|
|
||||||
label={(props) => (
|
label={(props) => (
|
||||||
<Button
|
<Button
|
||||||
icon={<MoreIcon />}
|
icon={<MoreIcon />}
|
||||||
|
@ -4,6 +4,7 @@ import { observer } from "mobx-react";
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import Textarea from "react-autosize-textarea";
|
import Textarea from "react-autosize-textarea";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
import breakpoint from "styled-components-breakpoint";
|
||||||
import { MAX_TITLE_LENGTH } from "shared/constants";
|
import { MAX_TITLE_LENGTH } from "shared/constants";
|
||||||
import parseTitle from "shared/utils/parseTitle";
|
import parseTitle from "shared/utils/parseTitle";
|
||||||
import Document from "models/Document";
|
import Document from "models/Document";
|
||||||
@ -165,11 +166,9 @@ const StarButton = styled(Star)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const Title = styled(Textarea)`
|
const Title = styled(Textarea)`
|
||||||
z-index: 1;
|
|
||||||
line-height: 1.25;
|
line-height: 1.25;
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
margin-left: ${(props) => (props.$startsWithEmojiAndSpace ? "-1.2em" : 0)};
|
|
||||||
background: ${(props) => props.theme.background};
|
background: ${(props) => props.theme.background};
|
||||||
transition: ${(props) => props.theme.backgroundTransition};
|
transition: ${(props) => props.theme.backgroundTransition};
|
||||||
color: ${(props) => props.theme.text};
|
color: ${(props) => props.theme.text};
|
||||||
@ -186,6 +185,10 @@ const Title = styled(Textarea)`
|
|||||||
-webkit-text-fill-color: ${(props) => props.theme.placeholder};
|
-webkit-text-fill-color: ${(props) => props.theme.placeholder};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
${breakpoint("tablet")`
|
||||||
|
margin-left: ${(props) => (props.$startsWithEmojiAndSpace ? "-1.2em" : 0)};
|
||||||
|
`};
|
||||||
|
|
||||||
${AnimatedStar} {
|
${AnimatedStar} {
|
||||||
opacity: ${(props) => (props.$isStarred ? "1 !important" : 0)};
|
opacity: ${(props) => (props.$isStarred ? "1 !important" : 0)};
|
||||||
}
|
}
|
||||||
|
@ -107,6 +107,7 @@ export const base = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
depths: {
|
depths: {
|
||||||
|
header: 900,
|
||||||
sidebar: 1000,
|
sidebar: 1000,
|
||||||
modalOverlay: 2000,
|
modalOverlay: 2000,
|
||||||
modal: 3000,
|
modal: 3000,
|
||||||
@ -133,6 +134,7 @@ export const light = {
|
|||||||
sidebarBackground: colors.warmGrey,
|
sidebarBackground: colors.warmGrey,
|
||||||
sidebarItemBackground: colors.black10,
|
sidebarItemBackground: colors.black10,
|
||||||
sidebarText: "rgb(78, 92, 110)",
|
sidebarText: "rgb(78, 92, 110)",
|
||||||
|
sidebarShadow: "rgba(0, 0, 0, 0.2)",
|
||||||
shadow: "rgba(0, 0, 0, 0.2)",
|
shadow: "rgba(0, 0, 0, 0.2)",
|
||||||
|
|
||||||
menuBackground: colors.white,
|
menuBackground: colors.white,
|
||||||
@ -192,6 +194,7 @@ export const dark = {
|
|||||||
sidebarBackground: colors.veryDarkBlue,
|
sidebarBackground: colors.veryDarkBlue,
|
||||||
sidebarItemBackground: colors.transparent,
|
sidebarItemBackground: colors.transparent,
|
||||||
sidebarText: colors.slate,
|
sidebarText: colors.slate,
|
||||||
|
sidebarShadow: "rgba(255, 255, 255, 0.07)",
|
||||||
shadow: "rgba(0, 0, 0, 0.6)",
|
shadow: "rgba(0, 0, 0, 0.6)",
|
||||||
|
|
||||||
menuBorder: lighten(0.1, colors.almostBlack),
|
menuBorder: lighten(0.1, colors.almostBlack),
|
||||||
|
Reference in New Issue
Block a user