Move sidebar toggle into the sidebar itself instead of overlaying document content (#5749)
This commit is contained in:
@@ -1,19 +1,18 @@
|
||||
import { observer } from "mobx-react";
|
||||
import { SubscribeIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import styled, { useTheme } from "styled-components";
|
||||
import styled from "styled-components";
|
||||
import { s } from "@shared/styles";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import Relative from "../Sidebar/components/Relative";
|
||||
|
||||
const NotificationIcon = () => {
|
||||
const { notifications } = useStores();
|
||||
const theme = useTheme();
|
||||
const count = notifications.approximateUnreadCount;
|
||||
|
||||
return (
|
||||
<Relative style={{ height: 24 }}>
|
||||
<SubscribeIcon color={theme.textTertiary} />
|
||||
<SubscribeIcon />
|
||||
{count > 0 && <Badge />}
|
||||
</Relative>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { observer } from "mobx-react";
|
||||
import { EditIcon, SearchIcon, ShapesIcon, HomeIcon } from "outline-icons";
|
||||
import {
|
||||
EditIcon,
|
||||
SearchIcon,
|
||||
ShapesIcon,
|
||||
HomeIcon,
|
||||
SidebarIcon,
|
||||
} from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { DndProvider } from "react-dnd";
|
||||
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||
@@ -14,7 +20,7 @@ import useCurrentUser from "~/hooks/useCurrentUser";
|
||||
import usePolicy from "~/hooks/usePolicy";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import OrganizationMenu from "~/menus/OrganizationMenu";
|
||||
import Desktop from "~/utils/Desktop";
|
||||
import { metaDisplay } from "~/utils/keyboard";
|
||||
import {
|
||||
homePath,
|
||||
draftsPath,
|
||||
@@ -22,23 +28,23 @@ import {
|
||||
searchPath,
|
||||
} from "~/utils/routeHelpers";
|
||||
import TeamLogo from "../TeamLogo";
|
||||
import Tooltip from "../Tooltip";
|
||||
import Sidebar from "./Sidebar";
|
||||
import ArchiveLink from "./components/ArchiveLink";
|
||||
import Collections from "./components/Collections";
|
||||
import DragPlaceholder from "./components/DragPlaceholder";
|
||||
import FullWidthButton, {
|
||||
FullWidthButtonProps,
|
||||
} from "./components/FullWidthButton";
|
||||
import HistoryNavigation from "./components/HistoryNavigation";
|
||||
import Section from "./components/Section";
|
||||
import SidebarAction from "./components/SidebarAction";
|
||||
import SidebarButton, { SidebarButtonProps } from "./components/SidebarButton";
|
||||
import SidebarLink from "./components/SidebarLink";
|
||||
import Starred from "./components/Starred";
|
||||
import ToggleButton from "./components/ToggleButton";
|
||||
import TrashLink from "./components/TrashLink";
|
||||
|
||||
function AppSidebar() {
|
||||
const { t } = useTranslation();
|
||||
const { documents } = useStores();
|
||||
const { documents, ui } = useStores();
|
||||
const team = useCurrentTeam();
|
||||
const user = useCurrentUser();
|
||||
const can = usePolicy(team);
|
||||
@@ -59,6 +65,15 @@ function AppSidebar() {
|
||||
[dndArea]
|
||||
);
|
||||
|
||||
const handleToggleSidebar = React.useCallback(
|
||||
(ev) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
ui.toggleCollapsedSidebar();
|
||||
},
|
||||
[ui]
|
||||
);
|
||||
|
||||
return (
|
||||
<Sidebar ref={handleSidebarRef}>
|
||||
<HistoryNavigation />
|
||||
@@ -67,23 +82,31 @@ function AppSidebar() {
|
||||
<DragPlaceholder />
|
||||
|
||||
<OrganizationMenu>
|
||||
{(props: FullWidthButtonProps) => (
|
||||
<FullWidthButton
|
||||
{(props: SidebarButtonProps) => (
|
||||
<SidebarButton
|
||||
{...props}
|
||||
title={team.name}
|
||||
image={
|
||||
<TeamLogo
|
||||
model={team}
|
||||
size={Desktop.hasInsetTitlebar() ? 24 : 32}
|
||||
size={24}
|
||||
alt={t("Logo")}
|
||||
style={{ marginLeft: 4 }}
|
||||
/>
|
||||
}
|
||||
style={
|
||||
// Move the logo over to align with smaller size
|
||||
Desktop.hasInsetTitlebar() ? { paddingLeft: 8 } : undefined
|
||||
}
|
||||
showDisclosure
|
||||
/>
|
||||
>
|
||||
<Tooltip
|
||||
tooltip={t("Toggle sidebar")}
|
||||
shortcut={`${metaDisplay}+.`}
|
||||
delay={500}
|
||||
>
|
||||
<ToggleButton
|
||||
position="bottom"
|
||||
image={<SidebarIcon />}
|
||||
onClick={handleToggleSidebar}
|
||||
/>
|
||||
</Tooltip>
|
||||
</SidebarButton>
|
||||
)}
|
||||
</OrganizationMenu>
|
||||
<Scrollable flex shadow>
|
||||
|
||||
@@ -11,10 +11,10 @@ import useSettingsConfig from "~/hooks/useSettingsConfig";
|
||||
import Desktop from "~/utils/Desktop";
|
||||
import isCloudHosted from "~/utils/isCloudHosted";
|
||||
import Sidebar from "./Sidebar";
|
||||
import FullWidthButton from "./components/FullWidthButton";
|
||||
import Header from "./components/Header";
|
||||
import HistoryNavigation from "./components/HistoryNavigation";
|
||||
import Section from "./components/Section";
|
||||
import SidebarButton from "./components/SidebarButton";
|
||||
import SidebarLink from "./components/SidebarLink";
|
||||
import Version from "./components/Version";
|
||||
|
||||
@@ -31,7 +31,7 @@ function SettingsSidebar() {
|
||||
return (
|
||||
<Sidebar>
|
||||
<HistoryNavigation />
|
||||
<FullWidthButton
|
||||
<SidebarButton
|
||||
title={t("Return to App")}
|
||||
image={<StyledBackIcon />}
|
||||
onClick={returnToApp}
|
||||
|
||||
@@ -11,9 +11,9 @@ import { homePath, sharedDocumentPath } from "~/utils/routeHelpers";
|
||||
import { useTeamContext } from "../TeamContext";
|
||||
import TeamLogo from "../TeamLogo";
|
||||
import Sidebar from "./Sidebar";
|
||||
import FullWidthButton from "./components/FullWidthButton";
|
||||
import Section from "./components/Section";
|
||||
import DocumentLink from "./components/SharedDocumentLink";
|
||||
import SidebarButton from "./components/SidebarButton";
|
||||
|
||||
type Props = {
|
||||
rootNode: NavigationNode;
|
||||
@@ -28,7 +28,7 @@ function SharedSidebar({ rootNode, shareId }: Props) {
|
||||
return (
|
||||
<Sidebar>
|
||||
{team && (
|
||||
<FullWidthButton
|
||||
<SidebarButton
|
||||
title={team.name}
|
||||
image={<TeamLogo model={team} size={32} alt={t("Logo")} />}
|
||||
onClick={() =>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Portal } from "react-portal";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import styled, { useTheme } from "styled-components";
|
||||
import styled, { css, useTheme } from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import { depths, s } from "@shared/styles";
|
||||
import Flex from "~/components/Flex";
|
||||
@@ -17,25 +16,23 @@ import Desktop from "~/utils/Desktop";
|
||||
import Avatar from "../Avatar";
|
||||
import NotificationIcon from "../Notifications/NotificationIcon";
|
||||
import NotificationsPopover from "../Notifications/NotificationsPopover";
|
||||
import FullWidthButton, {
|
||||
FullWidthButtonProps,
|
||||
} from "./components/FullWidthButton";
|
||||
import ResizeBorder from "./components/ResizeBorder";
|
||||
import Toggle, { ToggleButton, Positioner } from "./components/Toggle";
|
||||
import SidebarButton, { SidebarButtonProps } from "./components/SidebarButton";
|
||||
import ToggleButton from "./components/ToggleButton";
|
||||
|
||||
const ANIMATION_MS = 250;
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const Sidebar = React.forwardRef<HTMLDivElement, Props>(function _Sidebar(
|
||||
{ children }: Props,
|
||||
{ children, className }: Props,
|
||||
ref: React.RefObject<HTMLDivElement>
|
||||
) {
|
||||
const [isCollapsing, setCollapsing] = React.useState(false);
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const { ui, auth } = useStores();
|
||||
const location = useLocation();
|
||||
const previousLocation = usePrevious(location);
|
||||
@@ -48,6 +45,7 @@ const Sidebar = React.forwardRef<HTMLDivElement, Props>(function _Sidebar(
|
||||
|
||||
const setWidth = ui.setSidebarWidth;
|
||||
const [offset, setOffset] = React.useState(0);
|
||||
const [isHovering, setHovering] = React.useState(false);
|
||||
const [isAnimating, setAnimating] = React.useState(false);
|
||||
const [isResizing, setResizing] = React.useState(false);
|
||||
const isSmallerThanMinimum = width < minWidth;
|
||||
@@ -101,6 +99,22 @@ const Sidebar = React.forwardRef<HTMLDivElement, Props>(function _Sidebar(
|
||||
[width]
|
||||
);
|
||||
|
||||
const handlePointerMove = React.useCallback(() => {
|
||||
setHovering(true);
|
||||
}, []);
|
||||
|
||||
const handlePointerLeave = React.useCallback(
|
||||
(ev) => {
|
||||
setHovering(
|
||||
ev.pageX < width &&
|
||||
ev.pageX > 0 &&
|
||||
ev.pageY < window.innerHeight &&
|
||||
ev.pageY > 0
|
||||
);
|
||||
},
|
||||
[width]
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isAnimating) {
|
||||
setTimeout(() => setAnimating(false), ANIMATION_MS);
|
||||
@@ -149,23 +163,19 @@ const Sidebar = React.forwardRef<HTMLDivElement, Props>(function _Sidebar(
|
||||
[width]
|
||||
);
|
||||
|
||||
const toggleStyle = React.useMemo(
|
||||
() => ({
|
||||
right: "auto",
|
||||
marginLeft: `${collapsed ? theme.sidebarCollapsedWidth : width}px`,
|
||||
}),
|
||||
[width, theme.sidebarCollapsedWidth, collapsed]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container
|
||||
ref={ref}
|
||||
style={style}
|
||||
$isHovering={isHovering}
|
||||
$isAnimating={isAnimating}
|
||||
$isSmallerThanMinimum={isSmallerThanMinimum}
|
||||
$mobileSidebarVisible={ui.mobileSidebarVisible}
|
||||
$collapsed={collapsed}
|
||||
className={className}
|
||||
onPointerMove={handlePointerMove}
|
||||
onPointerLeave={handlePointerLeave}
|
||||
column
|
||||
>
|
||||
{ui.mobileSidebarVisible && (
|
||||
@@ -177,31 +187,32 @@ const Sidebar = React.forwardRef<HTMLDivElement, Props>(function _Sidebar(
|
||||
|
||||
{user && (
|
||||
<AccountMenu>
|
||||
{(props: FullWidthButtonProps) => (
|
||||
<FullWidthButton
|
||||
{(props: SidebarButtonProps) => (
|
||||
<SidebarButton
|
||||
{...props}
|
||||
showMoreMenu
|
||||
title={user.name}
|
||||
position="bottom"
|
||||
image={
|
||||
<StyledAvatar
|
||||
<Avatar
|
||||
alt={user.name}
|
||||
model={user}
|
||||
size={24}
|
||||
showBorder={false}
|
||||
style={{ marginLeft: 4 }}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<NotificationsPopover>
|
||||
{(rest: FullWidthButtonProps) => (
|
||||
<FullWidthButton
|
||||
{(rest: SidebarButtonProps) => (
|
||||
<SidebarButton
|
||||
{...rest}
|
||||
position="bottom"
|
||||
image={<NotificationIcon />}
|
||||
/>
|
||||
)}
|
||||
</NotificationsPopover>
|
||||
</FullWidthButton>
|
||||
</SidebarButton>
|
||||
)}
|
||||
</AccountMenu>
|
||||
)}
|
||||
@@ -209,28 +220,11 @@ const Sidebar = React.forwardRef<HTMLDivElement, Props>(function _Sidebar(
|
||||
onMouseDown={handleMouseDown}
|
||||
onDoubleClick={ui.sidebarIsClosed ? undefined : handleReset}
|
||||
/>
|
||||
{ui.sidebarIsClosed && (
|
||||
<Toggle
|
||||
onClick={ui.toggleCollapsedSidebar}
|
||||
direction={"right"}
|
||||
aria-label={t("Expand")}
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
<Toggle
|
||||
style={toggleStyle}
|
||||
onClick={ui.toggleCollapsedSidebar}
|
||||
direction={ui.sidebarIsClosed ? "right" : "left"}
|
||||
aria-label={ui.sidebarIsClosed ? t("Expand") : t("Collapse")}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
const StyledAvatar = styled(Avatar)`
|
||||
margin-left: 4px;
|
||||
`;
|
||||
|
||||
const Backdrop = styled.a`
|
||||
animation: ${fadeIn} 250ms ease-in-out;
|
||||
position: fixed;
|
||||
@@ -247,16 +241,33 @@ type ContainerProps = {
|
||||
$mobileSidebarVisible: boolean;
|
||||
$isAnimating: boolean;
|
||||
$isSmallerThanMinimum: boolean;
|
||||
$isHovering: boolean;
|
||||
$collapsed: boolean;
|
||||
};
|
||||
|
||||
const hoverStyles = (props: ContainerProps) => `
|
||||
transform: none;
|
||||
box-shadow: ${
|
||||
props.$collapsed
|
||||
? "rgba(0, 0, 0, 0.2) 1px 0 4px"
|
||||
: props.$isSmallerThanMinimum
|
||||
? "rgba(0, 0, 0, 0.1) inset -1px 0 2px"
|
||||
: "none"
|
||||
};
|
||||
|
||||
${ToggleButton} {
|
||||
opacity: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
const Container = styled(Flex)<ContainerProps>`
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
background: ${s("sidebarBackground")};
|
||||
transition: box-shadow 100ms ease-in-out, transform 100ms ease-out,
|
||||
transition: box-shadow 100ms ease-in-out, opacity 100ms ease-in-out,
|
||||
transform 100ms ease-out,
|
||||
${s("backgroundTransition")}
|
||||
${(props: ContainerProps) =>
|
||||
props.$isAnimating ? `,width ${ANIMATION_MS}ms ease-out` : ""};
|
||||
@@ -268,15 +279,15 @@ const Container = styled(Flex)<ContainerProps>`
|
||||
min-width: 280px;
|
||||
${fadeOnDesktopBackgrounded()}
|
||||
|
||||
${Positioner} {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media print {
|
||||
display: none;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
& > div {
|
||||
opacity: ${(props) => (props.$collapsed && !props.$isHovering ? "0" : "1")};
|
||||
}
|
||||
|
||||
${breakpoint("tablet")`
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
@@ -285,28 +296,14 @@ const Container = styled(Flex)<ContainerProps>`
|
||||
? `calc(-100% + ${Desktop.hasInsetTitlebar() ? 8 : 16}px)`
|
||||
: 0});
|
||||
|
||||
&:hover,
|
||||
${(props: ContainerProps) => props.$isHovering && css(hoverStyles)}
|
||||
|
||||
&:focus-within {
|
||||
transform: none;
|
||||
box-shadow: ${(props: ContainerProps) =>
|
||||
props.$collapsed
|
||||
? "rgba(0, 0, 0, 0.2) 1px 0 4px"
|
||||
: props.$isSmallerThanMinimum
|
||||
? "rgba(0, 0, 0, 0.1) inset -1px 0 2px"
|
||||
: "none"};
|
||||
${hoverStyles}
|
||||
|
||||
${Positioner} {
|
||||
display: block;
|
||||
}
|
||||
|
||||
${ToggleButton} {
|
||||
& > div {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:hover):not(:focus-within) > div {
|
||||
opacity: ${(props: ContainerProps) => (props.$collapsed ? "0" : "1")};
|
||||
transition: opacity 100ms ease-in-out;
|
||||
}
|
||||
}
|
||||
`};
|
||||
`;
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
15
app/components/Sidebar/components/ToggleButton.tsx
Normal file
15
app/components/Sidebar/components/ToggleButton.tsx
Normal 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;
|
||||
@@ -4,7 +4,8 @@ import Avatar from "./Avatar";
|
||||
|
||||
const TeamLogo = styled(Avatar)`
|
||||
border-radius: 4px;
|
||||
border: 1px solid ${s("divider")};
|
||||
box-shadow: inset 0 0 0 1px ${s("divider")};
|
||||
border: 0;
|
||||
`;
|
||||
|
||||
export default TeamLogo;
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
"natural-sort": "^1.0.0",
|
||||
"node-fetch": "2.6.12",
|
||||
"nodemailer": "^6.9.4",
|
||||
"outline-icons": "^2.3.0",
|
||||
"outline-icons": "^2.4.0",
|
||||
"oy-vey": "^0.12.0",
|
||||
"passport": "^0.6.0",
|
||||
"passport-google-oauth2": "^0.2.0",
|
||||
|
||||
135
yarn.lock
135
yarn.lock
@@ -5639,15 +5639,7 @@ define-lazy-prop@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f"
|
||||
integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==
|
||||
|
||||
define-properties@^1.1.3, define-properties@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1"
|
||||
integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==
|
||||
dependencies:
|
||||
has-property-descriptors "^1.0.0"
|
||||
object-keys "^1.1.1"
|
||||
|
||||
define-properties@^1.2.0:
|
||||
define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.0.tgz#52988570670c9eacedd8064f4a990f2405849bd5"
|
||||
integrity sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==
|
||||
@@ -6117,46 +6109,7 @@ error-ex@^1.3.1:
|
||||
dependencies:
|
||||
is-arrayish "^0.2.1"
|
||||
|
||||
es-abstract@^1.17.4, es-abstract@^1.18.0, es-abstract@^1.18.0-next.2, es-abstract@^1.18.2, es-abstract@^1.19.0, es-abstract@^1.20.4:
|
||||
version "1.21.1"
|
||||
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.21.1.tgz#e6105a099967c08377830a0c9cb589d570dd86c6"
|
||||
integrity sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==
|
||||
dependencies:
|
||||
available-typed-arrays "^1.0.5"
|
||||
call-bind "^1.0.2"
|
||||
es-set-tostringtag "^2.0.1"
|
||||
es-to-primitive "^1.2.1"
|
||||
function-bind "^1.1.1"
|
||||
function.prototype.name "^1.1.5"
|
||||
get-intrinsic "^1.1.3"
|
||||
get-symbol-description "^1.0.0"
|
||||
globalthis "^1.0.3"
|
||||
gopd "^1.0.1"
|
||||
has "^1.0.3"
|
||||
has-property-descriptors "^1.0.0"
|
||||
has-proto "^1.0.1"
|
||||
has-symbols "^1.0.3"
|
||||
internal-slot "^1.0.4"
|
||||
is-array-buffer "^3.0.1"
|
||||
is-callable "^1.2.7"
|
||||
is-negative-zero "^2.0.2"
|
||||
is-regex "^1.1.4"
|
||||
is-shared-array-buffer "^1.0.2"
|
||||
is-string "^1.0.7"
|
||||
is-typed-array "^1.1.10"
|
||||
is-weakref "^1.0.2"
|
||||
object-inspect "^1.12.2"
|
||||
object-keys "^1.1.1"
|
||||
object.assign "^4.1.4"
|
||||
regexp.prototype.flags "^1.4.3"
|
||||
safe-regex-test "^1.0.0"
|
||||
string.prototype.trimend "^1.0.6"
|
||||
string.prototype.trimstart "^1.0.6"
|
||||
typed-array-length "^1.0.4"
|
||||
unbox-primitive "^1.0.2"
|
||||
which-typed-array "^1.1.9"
|
||||
|
||||
es-abstract@^1.21.2, es-abstract@^1.22.1:
|
||||
es-abstract@^1.17.4, es-abstract@^1.18.0, es-abstract@^1.18.0-next.2, es-abstract@^1.18.2, es-abstract@^1.19.0, es-abstract@^1.20.4, es-abstract@^1.21.2, es-abstract@^1.22.1:
|
||||
version "1.22.1"
|
||||
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.1.tgz#8b4e5fc5cefd7f1660f0f8e1a52900dfbc9d9ccc"
|
||||
integrity sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==
|
||||
@@ -7140,12 +7093,7 @@ function.prototype.name@^1.1.2, function.prototype.name@^1.1.5:
|
||||
es-abstract "^1.19.0"
|
||||
functions-have-names "^1.2.2"
|
||||
|
||||
functions-have-names@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.2.tgz#98d93991c39da9361f8e50b337c4f6e41f120e21"
|
||||
integrity sha512-bLgc3asbWdwPbx2mNk2S49kmJCuQeu0nfmaOgbs8WIyzzkw3r4htszdIi9Q9EMezDPTYuJx2wvjZ/EwgAthpnA==
|
||||
|
||||
functions-have-names@^1.2.3:
|
||||
functions-have-names@^1.2.2, functions-have-names@^1.2.3:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
|
||||
integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
|
||||
@@ -7170,16 +7118,7 @@ get-caller-file@^2.0.5:
|
||||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
|
||||
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
||||
|
||||
get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f"
|
||||
integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
has "^1.0.3"
|
||||
has-symbols "^1.0.3"
|
||||
|
||||
get-intrinsic@^1.2.1:
|
||||
get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82"
|
||||
integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==
|
||||
@@ -7951,16 +7890,7 @@ is-arguments@^1.0.4:
|
||||
call-bind "^1.0.2"
|
||||
has-tostringtag "^1.0.0"
|
||||
|
||||
is-array-buffer@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.1.tgz#deb1db4fcae48308d54ef2442706c0393997052a"
|
||||
integrity sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
get-intrinsic "^1.1.3"
|
||||
is-typed-array "^1.1.10"
|
||||
|
||||
is-array-buffer@^3.0.2:
|
||||
is-array-buffer@^3.0.1, is-array-buffer@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe"
|
||||
integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==
|
||||
@@ -10218,10 +10148,10 @@ os-tmpdir@~1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
|
||||
integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==
|
||||
|
||||
outline-icons@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/outline-icons/-/outline-icons-2.3.0.tgz#f1a5910b77c1167ffa466951f4a3bcca182c3a8d"
|
||||
integrity sha512-DpTLh1YuflJ4+aO0U9DutbMJX86uIsG0rk0ONRxTtIbDIXZrkMXQ9pynssnI5FT9ZdQeNBx7AQjHOSRmxzT3HQ==
|
||||
outline-icons@^2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/outline-icons/-/outline-icons-2.4.0.tgz#820b4585ebcd4db789077574b918436cdb26d30b"
|
||||
integrity sha512-CdtyRrfwXPJWEALqpL01EbeHAR7XXwqUSSQV7+SK8u+2y1nzyYTZ6DD8dB0ledxRHI3+ErTFKcKrQK41hOxh9w==
|
||||
|
||||
oy-vey@^0.12.0:
|
||||
version "0.12.0"
|
||||
@@ -11509,16 +11439,7 @@ regenerator-transform@^0.15.2:
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.8.4"
|
||||
|
||||
regexp.prototype.flags@^1.4.3:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac"
|
||||
integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
define-properties "^1.1.3"
|
||||
functions-have-names "^1.2.2"
|
||||
|
||||
regexp.prototype.flags@^1.5.0:
|
||||
regexp.prototype.flags@^1.4.3, regexp.prototype.flags@^1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz#fe7ce25e7e4cca8db37b6634c8a2c7009199b9cb"
|
||||
integrity sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==
|
||||
@@ -11661,16 +11582,7 @@ resolve.exports@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.0.tgz#c1a0028c2d166ec2fbf7d0644584927e76e7400e"
|
||||
integrity sha512-6K/gDlqgQscOlg9fSRpWstA8sYe8rbELsSTNpx+3kTrsVCzvSl0zIvRErM7fdl9ERWDsKnrLnwB+Ne89918XOg==
|
||||
|
||||
resolve@^1.1.6, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.18.1, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.1:
|
||||
version "1.22.1"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
|
||||
integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
|
||||
dependencies:
|
||||
is-core-module "^2.9.0"
|
||||
path-parse "^1.0.7"
|
||||
supports-preserve-symlinks-flag "^1.0.0"
|
||||
|
||||
resolve@^1.22.4:
|
||||
resolve@^1.1.6, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.18.1, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.1, resolve@^1.22.4:
|
||||
version "1.22.4"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.4.tgz#1dc40df46554cdaf8948a486a10f6ba1e2026c34"
|
||||
integrity sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==
|
||||
@@ -12406,16 +12318,7 @@ string.prototype.matchall@^4.0.2, string.prototype.matchall@^4.0.6:
|
||||
regexp.prototype.flags "^1.4.3"
|
||||
side-channel "^1.0.4"
|
||||
|
||||
string.prototype.trim@^1.2.1:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.4.tgz#6014689baf5efaf106ad031a5fa45157666ed1bd"
|
||||
integrity sha512-hWCk/iqf7lp0/AgTF7/ddO1IWtSNPASjlzCicV5irAVdE1grjsneK26YG6xACMBEdCvO8fUST0UzDMh/2Qy+9Q==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
define-properties "^1.1.3"
|
||||
es-abstract "^1.18.0-next.2"
|
||||
|
||||
string.prototype.trim@^1.2.7:
|
||||
string.prototype.trim@^1.2.1, string.prototype.trim@^1.2.7:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz#a68352740859f6893f14ce3ef1bb3037f7a90533"
|
||||
integrity sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==
|
||||
@@ -13570,7 +13473,7 @@ which-boxed-primitive@^1.0.2:
|
||||
is-string "^1.0.5"
|
||||
is-symbol "^1.0.3"
|
||||
|
||||
which-typed-array@^1.1.10:
|
||||
which-typed-array@^1.1.10, which-typed-array@^1.1.2, which-typed-array@^1.1.9:
|
||||
version "1.1.11"
|
||||
resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.11.tgz#99d691f23c72aab6768680805a271b69761ed61a"
|
||||
integrity sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==
|
||||
@@ -13581,18 +13484,6 @@ which-typed-array@^1.1.10:
|
||||
gopd "^1.0.1"
|
||||
has-tostringtag "^1.0.0"
|
||||
|
||||
which-typed-array@^1.1.2, which-typed-array@^1.1.9:
|
||||
version "1.1.9"
|
||||
resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6"
|
||||
integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==
|
||||
dependencies:
|
||||
available-typed-arrays "^1.0.5"
|
||||
call-bind "^1.0.2"
|
||||
for-each "^0.3.3"
|
||||
gopd "^1.0.1"
|
||||
has-tostringtag "^1.0.0"
|
||||
is-typed-array "^1.1.10"
|
||||
|
||||
which@^2.0.1:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
|
||||
|
||||
Reference in New Issue
Block a user