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;
|
||||
|
||||
Reference in New Issue
Block a user