diff --git a/app/components/Sidebar/Settings.tsx b/app/components/Sidebar/Settings.tsx index b6047052ac..3b993e93dd 100644 --- a/app/components/Sidebar/Settings.tsx +++ b/app/components/Sidebar/Settings.tsx @@ -1,35 +1,30 @@ import { groupBy } from "es-toolkit/compat"; import { observer } from "mobx-react"; -import { BackIcon, SidebarIcon } from "outline-icons"; +import { BackIcon } from "outline-icons"; import { useCallback } from "react"; import { useTranslation } from "react-i18next"; import { useHistory, useLocation } from "react-router-dom"; import styled from "styled-components"; -import { metaDisplay } from "@shared/utils/keyboard"; import Flex from "~/components/Flex"; import Scrollable from "~/components/Scrollable"; import useSettingsConfig from "~/hooks/useSettingsConfig"; import useStores from "~/hooks/useStores"; import isCloudHosted from "~/utils/isCloudHosted"; import { settingsPath } from "~/utils/routeHelpers"; -import Tooltip from "../Tooltip"; import Sidebar from "./Sidebar"; 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 ToggleButton from "./components/ToggleButton"; import Version from "./components/Version"; -import useMobile from "~/hooks/useMobile"; function SettingsSidebar() { - const { ui, integrations } = useStores(); + const { integrations } = useStores(); const { t } = useTranslation(); const history = useHistory(); const location = useLocation(); const configs = useSettingsConfig(); - const isMobile = useMobile(); const groupedConfig = groupBy( configs.filter((item) => @@ -45,31 +40,12 @@ function SettingsSidebar() { }, [history]); return ( - + } onClick={returnToApp} - > - {isMobile ? null : ( - - } - style={{ paddingInline: 4 }} - onClick={() => { - ui.toggleCollapsedSidebar(); - (document.activeElement as HTMLElement)?.blur(); - }} - /> - - )} - + /> diff --git a/app/components/Sidebar/Sidebar.tsx b/app/components/Sidebar/Sidebar.tsx index 125962bee7..d086b27ed3 100644 --- a/app/components/Sidebar/Sidebar.tsx +++ b/app/components/Sidebar/Sidebar.tsx @@ -1,5 +1,6 @@ import { observer } from "mobx-react"; import * as React from "react"; +import { mergeRefs } from "react-merge-refs"; import { useWebHaptics } from "web-haptics/react"; import { useLocation } from "react-router-dom"; import styled, { css, useTheme } from "styled-components"; @@ -63,6 +64,8 @@ const Sidebar = React.forwardRef(function Sidebar_( const [hasPointerMoved, setPointerMoved] = React.useState(false); const isSmallerThanMinimum = width < minWidth; const hoverTimeoutRef = React.useRef(null); + const internalRef = React.useRef(null); + const mergedRef = React.useMemo(() => mergeRefs([internalRef, ref]), [ref]); const handleDrag = React.useCallback( (event: MouseEvent) => { @@ -174,6 +177,31 @@ const Sidebar = React.forwardRef(function Sidebar_( } }, [ui.sidebarIsClosed]); + // Reset stale hover state when the sidebar becomes visible after being + // hidden via display:none (e.g. returning from settings). Without this, a + // pointer-leave event never fires when navigating away while hovering, so + // isHovering stays true and the sidebar appears expanded until the cursor + // re-enters and leaves. + React.useEffect(() => { + const el = internalRef.current; + if (!el || typeof IntersectionObserver === "undefined") { + return; + } + let wasVisible = false; + const observer = new IntersectionObserver((entries) => { + for (const entry of entries) { + const nowVisible = entry.isIntersecting; + if (nowVisible && !wasVisible) { + setHovering(false); + setPointerMoved(false); + } + wasVisible = nowVisible; + } + }); + observer.observe(el); + return () => observer.disconnect(); + }, []); + React.useEffect(() => { if (isAnimating) { setTimeout(() => setAnimating(false), ANIMATION_MS); @@ -237,7 +265,7 @@ const Sidebar = React.forwardRef(function Sidebar_(