diff --git a/web/package.json b/web/package.json index 7fb6d1826..ea2cc5bb4 100644 --- a/web/package.json +++ b/web/package.json @@ -82,6 +82,8 @@ "graphql": "^16.7.1", "graphql-request": "~6.1.0", "moment": "^2.29.4", + "overlayscrollbars": "^2.3.0", + "overlayscrollbars-react": "^0.5.2", "react": "^18.2.0", "react-chartjs-2": "^4.3.1", "react-dom": "^18.2.0", diff --git a/web/src/components/Overlay.tsx b/web/src/components/Overlay.tsx index d60bd9cf2..76b4f3975 100644 --- a/web/src/components/Overlay.tsx +++ b/web/src/components/Overlay.tsx @@ -7,5 +7,5 @@ export const Overlay = styled.div` width: 100vw; height: 100vh; background-color: ${({ theme }) => theme.blackLowOpacity}; - z-index: 1; + z-index: 0; `; diff --git a/web/src/context/OverlayScrollContext.tsx b/web/src/context/OverlayScrollContext.tsx new file mode 100644 index 000000000..7b9629317 --- /dev/null +++ b/web/src/context/OverlayScrollContext.tsx @@ -0,0 +1,3 @@ +import { createContext, MutableRefObject } from "react"; + +export const OverlayScrollContext = createContext | null>(null); diff --git a/web/src/hooks/useLockOverlayScroll.ts b/web/src/hooks/useLockOverlayScroll.ts new file mode 100644 index 000000000..146cdcd2a --- /dev/null +++ b/web/src/hooks/useLockOverlayScroll.ts @@ -0,0 +1,28 @@ +import { useContext, useEffect, useCallback } from "react"; +import { OverlayScrollContext } from "context/OverlayScrollContext"; + +export const useLockOverlayScroll = (shouldLock: boolean) => { + const osInstanceRef = useContext(OverlayScrollContext); + + const lockScroll = useCallback(() => { + const osInstance = osInstanceRef?.current?.osInstance(); + if (osInstance) { + osInstance.options({ overflow: { x: "hidden", y: "hidden" } }); + } + }, [osInstanceRef]); + + const unlockScroll = useCallback(() => { + const osInstance = osInstanceRef?.current?.osInstance(); + if (osInstance) { + osInstance.options({ overflow: { x: "scroll", y: "scroll" } }); + } + }, [osInstanceRef]); + + useEffect(() => { + if (shouldLock) { + lockScroll(); + } else { + unlockScroll(); + } + }, [shouldLock, lockScroll, unlockScroll]); +}; diff --git a/web/src/layout/Header/navbar/DappList.tsx b/web/src/layout/Header/navbar/DappList.tsx index 81c69a38b..0a5137bc5 100644 --- a/web/src/layout/Header/navbar/DappList.tsx +++ b/web/src/layout/Header/navbar/DappList.tsx @@ -10,7 +10,6 @@ import Linguo from "svgs/icons/linguo.svg"; import POH from "svgs/icons/poh-image.png"; import Tokens from "svgs/icons/tokens.svg"; import Product from "./Product"; -import { Overlay } from "components/Overlay"; const Header = styled.h1` display: flex; @@ -28,7 +27,7 @@ const Container = styled.div` top: 5%; left: 50%; transform: translate(-50%); - z-index: 10; + z-index: 1; flex-direction: column; align-items: center; @@ -58,10 +57,6 @@ const ItemsDiv = styled.div` grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); `; -interface IDappList { - toggleSolution: () => void; -} - const ITEMS = [ { text: "Court v1", @@ -105,24 +100,25 @@ const ITEMS = [ }, ]; -const DappList: React.FC = ({ toggleSolution }) => { +interface IDappList { + toggleIsDappListOpen: () => void; +} + +const DappList: React.FC = ({ toggleIsDappListOpen }) => { const containerRef = useRef(null); useFocusOutside(containerRef, () => { - toggleSolution(); + toggleIsDappListOpen(); }); return ( - <> - - -
Kleros Solutions
- - {ITEMS.map((item) => { - return ; - })} - -
- + +
Kleros Solutions
+ + {ITEMS.map((item) => { + return ; + })} + +
); }; export default DappList; diff --git a/web/src/layout/Header/navbar/Menu/Help.tsx b/web/src/layout/Header/navbar/Menu/Help.tsx index 1559d6b68..4a90227be 100644 --- a/web/src/layout/Header/navbar/Menu/Help.tsx +++ b/web/src/layout/Header/navbar/Menu/Help.tsx @@ -7,7 +7,6 @@ import Bug from "svgs/icons/bug.svg"; import ETH from "svgs/icons/eth.svg"; import Faq from "svgs/menu-icons/help.svg"; import Telegram from "svgs/socialmedia/telegram.svg"; -import { Overlay } from "components/Overlay"; const Container = styled.div` display: flex; @@ -16,7 +15,7 @@ const Container = styled.div` top: 5%; left: 50%; transform: translate(-50%); - z-index: 10; + z-index: 1; padding: 27px 10px; gap: 23px; border: 1px solid ${({ theme }) => theme.stroke}; @@ -83,27 +82,24 @@ const ITEMS = [ ]; interface IHelp { - toggle: () => void; + toggleIsHelpOpen: () => void; } -const Help: React.FC = ({ toggle }) => { +const Help: React.FC = ({ toggleIsHelpOpen }) => { const containerRef = useRef(null); useFocusOutside(containerRef, () => { - toggle(); + toggleIsHelpOpen(); }); return ( - <> - - - {ITEMS.map((item) => ( - - - {item.text} - - ))} - - + + {ITEMS.map((item) => ( + + + {item.text} + + ))} + ); }; export default Help; diff --git a/web/src/layout/Header/navbar/Menu/Settings/index.tsx b/web/src/layout/Header/navbar/Menu/Settings/index.tsx index a6bf761d0..28b5aec52 100644 --- a/web/src/layout/Header/navbar/Menu/Settings/index.tsx +++ b/web/src/layout/Header/navbar/Menu/Settings/index.tsx @@ -1,14 +1,15 @@ -import React, { Dispatch, SetStateAction, useRef, useState } from "react"; +import React, { useRef, useState } from "react"; import styled from "styled-components"; import { Tabs } from "@kleros/ui-components-library"; import General from "./General"; import SendMeNotifications from "./SendMeNotifications"; import { useFocusOutside } from "hooks/useFocusOutside"; -import { Overlay } from "components/Overlay"; const Container = styled.div` display: flex; position: absolute; + max-height: 80vh; + overflow-y: auto; z-index: 1; background-color: ${({ theme }) => theme.whiteBackground}; flex-direction: column; @@ -45,29 +46,26 @@ const TABS = [ ]; interface ISettings { - setIsSettingsOpen: Dispatch>; + toggleIsSettingsOpen: () => void; } -const Settings: React.FC = ({ setIsSettingsOpen }) => { +const Settings: React.FC = ({ toggleIsSettingsOpen }) => { const [currentTab, setCurrentTab] = useState(0); const containerRef = useRef(null); - useFocusOutside(containerRef, () => setIsSettingsOpen(false)); + useFocusOutside(containerRef, () => toggleIsSettingsOpen()); return ( - <> - - - Settings - { - setCurrentTab(n); - }} - /> - {currentTab === 0 ? : } - - + + Settings + { + setCurrentTab(n); + }} + /> + {currentTab === 0 ? : } + ); }; diff --git a/web/src/layout/Header/navbar/Menu/index.tsx b/web/src/layout/Header/navbar/Menu/index.tsx index 2886c7c66..f6351f3db 100644 --- a/web/src/layout/Header/navbar/Menu/index.tsx +++ b/web/src/layout/Header/navbar/Menu/index.tsx @@ -1,14 +1,11 @@ -import React, { useState } from "react"; +import React from "react"; import styled from "styled-components"; -import { useToggle } from "react-use"; import LightButton from "components/LightButton"; -import Help from "./Help"; import DarkModeIcon from "svgs/menu-icons/dark-mode.svg"; import HelpIcon from "svgs/menu-icons/help.svg"; import LightModeIcon from "svgs/menu-icons/light-mode.svg"; import NotificationsIcon from "svgs/menu-icons/notifications.svg"; import SettingsIcon from "svgs/menu-icons/settings.svg"; -import Settings from "./Settings"; import { useToggleTheme } from "hooks/useToggleThemeContext"; const Container = styled.div``; @@ -19,10 +16,13 @@ const ButtonContainer = styled.div` align-items: center; `; -const Menu: React.FC = () => { +interface IMenu { + toggleIsSettingsOpen: () => void; + toggleIsHelpOpen: () => void; +} + +const Menu: React.FC = ({ toggleIsHelpOpen, toggleIsSettingsOpen }) => { const [theme, toggleTheme] = useToggleTheme(); - const [isHelpOpen, toggleIsHelpOpen] = useToggle(true); - const [isSettingsOpen, setIsSettingsOpen] = useState(false); const isLightTheme = theme === "light"; const buttons = [ @@ -30,7 +30,7 @@ const Menu: React.FC = () => { { text: "Settings", Icon: SettingsIcon, - onClick: () => setIsSettingsOpen(true), + onClick: () => toggleIsSettingsOpen(), }, { text: "Help", @@ -53,8 +53,6 @@ const Menu: React.FC = () => { ))} - {isHelpOpen && } - {isSettingsOpen && } ); }; diff --git a/web/src/layout/Header/navbar/index.tsx b/web/src/layout/Header/navbar/index.tsx index 684a36ebf..6ec1563eb 100644 --- a/web/src/layout/Header/navbar/index.tsx +++ b/web/src/layout/Header/navbar/index.tsx @@ -1,32 +1,36 @@ import React from "react"; import styled from "styled-components"; -import { useLockBodyScroll, useToggle } from "react-use"; -import ConnectWallet from "components/ConnectWallet"; -import LightButton from "components/LightButton"; -import KlerosSolutionsIcon from "svgs/menu-icons/kleros-solutions.svg"; +import { useToggle } from "react-use"; import { useOpenContext } from "../index"; import DappList from "./DappList"; import Explore from "./Explore"; +import ConnectWallet from "components/ConnectWallet"; +import LightButton from "components/LightButton"; +import { Overlay } from "components/Overlay"; +import KlerosSolutionsIcon from "svgs/menu-icons/kleros-solutions.svg"; import Menu from "./Menu"; import Debug from "./Debug"; +import Help from "./Menu/Help"; +import Settings from "./Menu/Settings"; +import { useLockOverlayScroll } from "hooks/useLockOverlayScroll"; const Container = styled.div<{ isOpen: boolean }>` position: absolute; top: 64px; left: 0; right: 0; + max-height: calc(100vh - 64px); + overflow-y: auto; z-index: 1; background-color: ${({ theme }) => theme.whiteBackground}; border: 1px solid ${({ theme }) => theme.stroke}; box-shadow: 0px 2px 3px ${({ theme }) => theme.defaultShadow}; - transform-origin: top; transform: scaleY(${({ isOpen }) => (isOpen ? "1" : "0")}); visibility: ${({ isOpen }) => (isOpen ? "visible" : "hidden")}; transition-property: transform, visibility; transition-duration: ${({ theme }) => theme.transitionSpeed}; transition-timing-function: ease; - padding: 24px; hr { @@ -34,30 +38,50 @@ const Container = styled.div<{ isOpen: boolean }>` } `; +const PopupContainer = styled.div` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 20; +`; + const NavBar: React.FC = () => { - const [isSolutionsOpen, toggleSolution] = useToggle(false); + const [isDappListOpen, toggleIsDappListOpen] = useToggle(false); + const [isHelpOpen, toggleIsHelpOpen] = useToggle(false); + const [isSettingsOpen, toggleIsSettingsOpen] = useToggle(false); const { isOpen } = useOpenContext(); - useLockBodyScroll(isOpen); + useLockOverlayScroll(isOpen); return ( - - { - toggleSolution(); - }} - Icon={KlerosSolutionsIcon} - /> - {isSolutionsOpen && } -
- -
- -
- -
- - + <> + + { + toggleIsDappListOpen(); + }} + Icon={KlerosSolutionsIcon} + /> +
+ +
+ +
+ +
+ + + {(isDappListOpen || isHelpOpen || isSettingsOpen) && ( + + + {isDappListOpen && } + {isHelpOpen && } + {isSettingsOpen && } + + )} + ); }; diff --git a/web/src/layout/index.tsx b/web/src/layout/index.tsx index 4a50039d3..ecfb3153a 100644 --- a/web/src/layout/index.tsx +++ b/web/src/layout/index.tsx @@ -1,7 +1,10 @@ -import React from "react"; +import React, { useRef } from "react"; import styled from "styled-components"; +import "overlayscrollbars/styles/overlayscrollbars.css"; import { Outlet } from "react-router-dom"; import { ToastContainer } from "react-toastify"; +import { OverlayScrollbarsComponent } from "overlayscrollbars-react"; +import { OverlayScrollContext } from "context/OverlayScrollContext"; import Header from "./Header"; import Footer from "./Footer"; @@ -10,18 +13,31 @@ const Container = styled.div` width: 100%; `; +const StyledOverlayScrollbarsComponent = styled(OverlayScrollbarsComponent)` + height: 100vh; + width: 100vw; +`; + const StyledToastContainer = styled(ToastContainer)` padding: 16px; padding-top: 70px; `; -const Layout: React.FC = () => ( - -
- - -