Move sidebar toggle into the sidebar itself instead of overlaying document content (#5749)

This commit is contained in:
Tom Moor
2023-08-29 21:45:03 -04:00
committed by GitHub
parent 864ddbd438
commit b7055ef853
11 changed files with 184 additions and 364 deletions

View File

@@ -1,64 +1,62 @@
import { ExpandedIcon, MoreIcon } from "outline-icons";
import { MoreIcon } from "outline-icons";
import * as React from "react";
import styled from "styled-components";
import { s } from "@shared/styles";
import Flex from "~/components/Flex";
import Text from "~/components/Text";
import { draggableOnDesktop, undraggableOnDesktop } from "~/styles";
import Desktop from "~/utils/Desktop";
export type FullWidthButtonProps = React.ComponentProps<typeof Button> & {
export type SidebarButtonProps = React.ComponentProps<typeof Button> & {
position: "top" | "bottom";
title: React.ReactNode;
image: React.ReactNode;
minHeight?: number;
rounded?: boolean;
showDisclosure?: boolean;
showMoreMenu?: boolean;
onClick: React.MouseEventHandler<HTMLButtonElement>;
children?: React.ReactNode;
};
const FullWidthButton = React.forwardRef<
HTMLButtonElement,
FullWidthButtonProps
>(function _FullWidthButton(
{
position = "top",
showDisclosure,
showMoreMenu,
image,
title,
minHeight = 0,
children,
...rest
}: FullWidthButtonProps,
ref
) {
return (
<Container
justify="space-between"
align="center"
shrink={false}
$position={position}
>
<Button
{...rest}
minHeight={minHeight}
as="button"
ref={ref}
role="button"
const SidebarButton = React.forwardRef<HTMLButtonElement, SidebarButtonProps>(
function _SidebarButton(
{
position = "top",
showMoreMenu,
image,
title,
minHeight = 0,
children,
...rest
}: SidebarButtonProps,
ref
) {
return (
<Container
justify="space-between"
align="center"
shrink={false}
$position={position}
>
<Title gap={8} align="center">
{image}
{title}
</Title>
{showDisclosure && <ExpandedIcon />}
{showMoreMenu && <MoreIcon />}
</Button>
{children}
</Container>
);
});
<Button
{...rest}
$minHeight={minHeight}
$position={position}
as="button"
ref={ref}
role="button"
>
<Title gap={8} align="center">
{image}
{title && <Text as="span">{title}</Text>}
</Title>
{showMoreMenu && <MoreIcon />}
</Button>
{children}
</Container>
);
}
);
const Container = styled(Flex)<{ $position: "top" | "bottom" }>`
padding-top: ${(props) =>
@@ -67,7 +65,6 @@ const Container = styled(Flex)<{ $position: "top" | "bottom" }>`
`;
const Title = styled(Flex)`
color: ${s("text")};
flex-shrink: 1;
flex-grow: 1;
text-overflow: ellipsis;
@@ -75,19 +72,22 @@ const Title = styled(Flex)`
overflow: hidden;
`;
const Button = styled(Flex)<{ minHeight: number }>`
const Button = styled(Flex)<{
$minHeight: number;
$position: "top" | "bottom";
}>`
flex: 1;
color: ${s("textTertiary")};
align-items: center;
padding: 8px 4px;
padding: 4px;
font-size: 15px;
font-weight: 500;
border-radius: 4px;
margin: 8px 0;
border: 0;
margin: ${(props) => (props.$position === "top" ? 16 : 8)}px 0;
background: none;
flex-shrink: 0;
min-height: ${(props) => props.minHeight}px;
min-height: ${(props) => props.$minHeight}px;
-webkit-appearance: none;
text-decoration: none;
@@ -114,4 +114,4 @@ const Button = styled(Flex)<{ minHeight: number }>`
}
`;
export default FullWidthButton;
export default SidebarButton;

View File

@@ -1,106 +0,0 @@
import * as React from "react";
import { useTranslation } from "react-i18next";
import styled, { css } from "styled-components";
import breakpoint from "styled-components-breakpoint";
import { s } from "@shared/styles";
import Arrow from "~/components/Arrow";
import useEventListener from "~/hooks/useEventListener";
type Props = {
direction: "left" | "right";
style?: React.CSSProperties;
onClick?: React.MouseEventHandler<HTMLButtonElement>;
};
const Toggle = React.forwardRef<HTMLButtonElement, Props>(function Toggle_(
{ direction = "left", onClick, style }: Props,
ref
) {
const { t } = useTranslation();
const [hovering, setHovering] = React.useState(false);
const positionRef = React.useRef<HTMLDivElement>(null);
// Not using CSS hover here so that we can disable pointer events on this
// div and allow click through to the editor elements behind.
useEventListener("mousemove", (event: MouseEvent) => {
if (!positionRef.current) {
return;
}
const bound = positionRef.current.getBoundingClientRect();
const withinBounds =
event.clientX >= bound.left && event.clientX <= bound.right;
if (withinBounds !== hovering) {
setHovering(withinBounds);
}
});
return (
<Positioner style={style} ref={positionRef} $hovering={hovering}>
<ToggleButton
ref={ref}
$direction={direction}
onClick={onClick}
aria-label={t("Toggle sidebar")}
>
<Arrow />
</ToggleButton>
</Positioner>
);
});
export const ToggleButton = styled.button<{ $direction?: "left" | "right" }>`
opacity: 0;
background: none;
transition: opacity 100ms ease-in-out;
transform: translateY(-50%)
scaleX(${(props) => (props.$direction === "left" ? 1 : -1)});
position: fixed;
top: 50vh;
padding: 8px;
border: 0;
pointer-events: none;
color: ${s("divider")};
&:active {
color: ${s("sidebarText")};
}
${breakpoint("tablet")`
pointer-events: all;
cursor: var(--pointer);
`}
@media (hover: none) {
opacity: 1;
}
`;
export const Positioner = styled.div<{ $hovering: boolean }>`
display: none;
z-index: 2;
position: absolute;
top: 0;
bottom: 0;
right: -30px;
width: 30px;
pointer-events: none;
&:focus-within ${ToggleButton} {
opacity: 1;
}
${(props) =>
props.$hovering &&
css`
${ToggleButton} {
opacity: 1;
}
`}
${breakpoint("tablet")`
display: block;
`}
`;
export default Toggle;

View File

@@ -0,0 +1,15 @@
import styled from "styled-components";
import { hover } from "~/styles";
import SidebarButton from "./SidebarButton";
const ToggleButton = styled(SidebarButton)`
opacity: 0;
transition: opacity 100ms ease-in-out;
&:${hover},
&:active {
opacity: 1;
}
`;
export default ToggleButton;