diff --git a/apps/explorer/src/app/components/header/header.tsx b/apps/explorer/src/app/components/header/header.tsx index 01bd98747..b21e84696 100644 --- a/apps/explorer/src/app/components/header/header.tsx +++ b/apps/explorer/src/app/components/header/header.tsx @@ -62,14 +62,14 @@ export const Header = () => { } - onResize={(width, ref) => { + onResize={(width, el) => { if (width < 1157) { // switch to magnifying glass trigger when widht < 1157 - ref.current?.classList.remove('nav-search-full'); - ref.current?.classList.add('nav-search-compact'); + el.classList.remove('nav-search-full'); + el.classList.add('nav-search-compact'); } else { - ref.current?.classList.remove('nav-search-compact'); - ref.current?.classList.add('nav-search-full'); + el.classList.remove('nav-search-compact'); + el.classList.add('nav-search-full'); } }} > diff --git a/apps/explorer/src/app/components/nav/index.tsx b/apps/explorer/src/app/components/nav/index.tsx deleted file mode 100644 index 38d674589..000000000 --- a/apps/explorer/src/app/components/nav/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export * from './nav'; diff --git a/apps/explorer/src/app/components/nav/nav.tsx b/apps/explorer/src/app/components/nav/nav.tsx deleted file mode 100644 index f54724894..000000000 --- a/apps/explorer/src/app/components/nav/nav.tsx +++ /dev/null @@ -1,181 +0,0 @@ -import { NavLink, useLocation } from 'react-router-dom'; -import type { Navigable } from '../../routes/router-config'; -import routerConfig from '../../routes/router-config'; -import classnames from 'classnames'; -import { create } from 'zustand'; -import { - useCallback, - useEffect, - useLayoutEffect, - useMemo, - useRef, -} from 'react'; -import { Icon } from '@vegaprotocol/ui-toolkit'; -import first from 'lodash/first'; -import last from 'lodash/last'; -import { BREAKPOINT_MD } from '../../config/breakpoints'; - -type NavStore = { - open: boolean; - toggle: () => void; - hide: () => void; -}; - -export const useNavStore = create()((set, get) => ({ - open: false, - toggle: () => set({ open: !get().open }), - hide: () => set({ open: false }), -})); - -const NavLinks = ({ links }: { links: Navigable[] }) => { - const navLinks = links.map((r) => ( -
  • - - classnames( - 'block mb-2 px-2', - 'text-lg hover:bg-vega-pink dark:hover:bg-vega-yellow hover:text-white dark:hover:text-black', - { - 'bg-vega-pink text-white dark:bg-vega-yellow dark:text-black': - isActive, - } - ) - } - > - {r.text} - -
  • - )); - - return ; -}; - -export const Nav = () => { - const [open, hide] = useNavStore((state) => [state.open, state.hide]); - const location = useLocation(); - - const navRef = useRef(null); - const btnRef = useRef(null); - - const focusable = useMemo( - () => - navRef.current - ? [ - ...(navRef.current.querySelectorAll( - 'a, button' - ) as NodeListOf), - ] - : [], - // eslint-disable-next-line react-hooks/exhaustive-deps - [navRef.current] // do not remove `navRef.current` from deps - ); - - const closeNav = useCallback(() => { - hide(); - console.log(focusable); - focusable.forEach((fe) => - fe.setAttribute( - 'tabindex', - window.innerWidth > BREAKPOINT_MD ? '0' : '-1' - ) - ); - }, [focusable, hide]); - - // close navigation when location changes - useEffect(() => { - closeNav(); - }, [closeNav, location]); - - useLayoutEffect(() => { - if (open) { - focusable.forEach((fe) => fe.setAttribute('tabindex', '0')); - } - - document.body.style.overflow = open ? 'hidden' : ''; - const offset = - document.querySelector('header')?.getBoundingClientRect().top || 0; - if (navRef.current) { - navRef.current.style.height = `calc(100vh - ${offset}px)`; - } - - // focus current by default - if (navRef.current && open) { - (navRef.current.querySelector('a[aria-current]') as HTMLElement)?.focus(); - } - - const closeOnEsc = (e: KeyboardEvent) => { - if (e.key === 'Escape') { - closeNav(); - } - }; - - // tabbing loop - const focusLast = (e: FocusEvent) => { - e.preventDefault(); - const isNavElement = - e.relatedTarget && navRef.current?.contains(e.relatedTarget as Node); - if (!isNavElement && open) { - last(focusable)?.focus(); - } - }; - const focusFirst = (e: FocusEvent) => { - e.preventDefault(); - const isNavElement = - e.relatedTarget && navRef.current?.contains(e.relatedTarget as Node); - if (!isNavElement && open) { - first(focusable)?.focus(); - } - }; - - const resetOnDesktop = () => { - focusable.forEach((fe) => - fe.setAttribute( - 'tabindex', - window.innerWidth > BREAKPOINT_MD ? '0' : '-1' - ) - ); - }; - - window.addEventListener('resize', resetOnDesktop); - - first(focusable)?.addEventListener('focusout', focusLast); - last(focusable)?.addEventListener('focusout', focusFirst); - - document.addEventListener('keydown', closeOnEsc); - return () => { - window.removeEventListener('resize', resetOnDesktop); - document.removeEventListener('keydown', closeOnEsc); - first(focusable)?.removeEventListener('focusout', focusLast); - last(focusable)?.removeEventListener('focusout', focusFirst); - }; - }, [closeNav, focusable, open]); - - return ( - - ); -}; diff --git a/apps/trading/components/navbar/navbar.tsx b/apps/trading/components/navbar/navbar.tsx index bc65bcaae..98c87c88d 100644 --- a/apps/trading/components/navbar/navbar.tsx +++ b/apps/trading/components/navbar/navbar.tsx @@ -1,6 +1,4 @@ -import { useState } from 'react'; -import classNames from 'classnames'; -import { NavLink, Link } from 'react-router-dom'; +import type { ComponentProps } from 'react'; import { DApp, NetworkSwitcher, @@ -11,32 +9,22 @@ import { t } from '@vegaprotocol/i18n'; import { useGlobalStore } from '../../stores'; import { VegaWalletConnectButton } from '../vega-wallet-connect-button'; import { - Drawer, - getNavLinkClassNames, - getActiveNavLinkClassNames, - Nav, - NewTab, ThemeSwitcher, + Navigation, + NavigationList, + NavigationItem, + NavigationLink, + ExternalLink, + Icon, + NavigationBreakpoint, } from '@vegaprotocol/ui-toolkit'; -import { Vega } from '../icons/vega'; -import type { HTMLAttributeAnchorTarget } from 'react'; + import { Links, Routes } from '../../pages/client-router'; -type NavbarTheme = 'inherit' | 'dark' | 'yellow'; -interface NavbarProps { - navbarTheme?: NavbarTheme; -} - -const LinkList = ({ - navbarTheme, - className = 'flex', - dataTestId = 'navbar-links', - onNavigate, +export const Navbar = ({ + theme = 'system', }: { - navbarTheme: NavbarTheme; - className?: string; - dataTestId?: string; - onNavigate?: () => void; + theme: ComponentProps['theme']; }) => { const tokenLink = useLinks(DApp.Token); const { marketId } = useGlobalStore((store) => ({ @@ -46,178 +34,47 @@ const LinkList = ({ ? Links[Routes.MARKET](marketId) : Links[Routes.MARKET](); return ( - - ); -}; - -const MobileMenuBar = ({ navbarTheme }: { navbarTheme: NavbarTheme }) => { - const [drawerOpen, setDrawerOpen] = useState(false); - const [container, setContainer] = useState(null); - - const menuButton = ( - - ); - - return ( -
    - -
    -
    -
    - -
    -
    - setDrawerOpen(false)} - /> -
    -
    - -
    -
    -
    -
    - ); -}; - -export const Navbar = ({ navbarTheme = 'inherit' }: NavbarProps) => { - const titleContent = ( -
    - -
    - ); - return ( - - ); -}; - -interface AppNavLinkProps { - name: string; - path: string; - navbarTheme: NavbarTheme; - testId?: string; - target?: HTMLAttributeAnchorTarget; - end?: boolean; - onClick?: () => void; -} - -const AppNavLink = ({ - name, - path, - navbarTheme, - target, - testId = name, - end, - onClick, -}: AppNavLinkProps) => { - const borderClasses = classNames( - 'absolute h-[2px] md:h-1 w-full bottom-[-1px] left-0', - { - 'bg-black dark:bg-vega-yellow': navbarTheme !== 'yellow', - 'bg-black dark:bg-vega-yellow md:dark:bg-black': navbarTheme === 'yellow', - } - ); - return ( - - {({ isActive }) => { - return ( - <> - {name} - {isActive && } - - ); - }} - + + + + + + + + + {t('Markets')} + + + + {t('Trading')} + + + + {t('Portfolio')} + + + + + + {t('Governance')}{' '} + + + + + + ); }; diff --git a/apps/trading/components/vega-wallet-connect-button/vega-wallet-connect-button.tsx b/apps/trading/components/vega-wallet-connect-button/vega-wallet-connect-button.tsx index a83a59479..342c55c86 100644 --- a/apps/trading/components/vega-wallet-connect-button/vega-wallet-connect-button.tsx +++ b/apps/trading/components/vega-wallet-connect-button/vega-wallet-connect-button.tsx @@ -54,7 +54,7 @@ const MobileWalletButton = ({ ? 'hidden' : isYellow ? 'fill-black' - : 'fill-white'; + : 'fill-black dark:fill-white'; const [container, setContainer] = useState(null); const walletButton = ( diff --git a/apps/trading/pages/_app.page.tsx b/apps/trading/pages/_app.page.tsx index 013ce58a4..c1905d8c1 100644 --- a/apps/trading/pages/_app.page.tsx +++ b/apps/trading/pages/_app.page.tsx @@ -1,6 +1,5 @@ import Head from 'next/head'; import type { AppProps } from 'next/app'; -import { Navbar } from '../components/navbar'; import { t } from '@vegaprotocol/i18n'; import { useEagerConnect as useVegaEagerConnect, @@ -33,6 +32,7 @@ import { ViewingBanner } from '../components/viewing-banner'; import { Banner } from '../components/banner'; import classNames from 'classnames'; import { AppLoader, DynamicLoader } from '../components/app-loader'; +import { Navbar } from '../components/navbar'; const DEFAULT_TITLE = t('Welcome to Vega trading!'); @@ -83,9 +83,7 @@ function AppBody({ Component }: AppProps) { <div className={gridClasses}> - <Navbar - navbarTheme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'dark'} - /> + <Navbar theme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'system'} /> <Banner /> <ViewingBanner /> <main data-testid={location.pathname}> diff --git a/libs/ui-toolkit/src/components/navigation/navigation.tsx b/libs/ui-toolkit/src/components/navigation/navigation.tsx index 1cc07cd8b..4849b0850 100644 --- a/libs/ui-toolkit/src/components/navigation/navigation.tsx +++ b/libs/ui-toolkit/src/components/navigation/navigation.tsx @@ -1,12 +1,11 @@ import classNames from 'classnames'; -import type { ComponentProps, ReactNode, RefObject } from 'react'; -import type { MutableRefObject } from 'react'; +import type { ComponentProps, ReactNode } from 'react'; +import { useLayoutEffect } from 'react'; import { createContext } from 'react'; import { useContext } from 'react'; import { useRef } from 'react'; import { VegaLogo } from '../vega-logo'; import * as NavigationMenu from '@radix-ui/react-navigation-menu'; -import { useResizeObserver } from '@vegaprotocol/react-helpers'; import { Icon } from '../icon'; import { Drawer, DrawerContext, useDrawer } from '../drawer'; import type { Link } from 'react-router-dom'; @@ -38,7 +37,8 @@ type NavigationProps = { * Size variants breakpoints */ breakpoints?: [number, number]; - onResize?: (width: number, ref: RefObject<HTMLElement>) => void; + fullWidth?: boolean; + onResize?: (width: number, navigationElement: HTMLElement) => void; }; export enum NavigationBreakpoint { @@ -310,6 +310,44 @@ export const NavigationContext = createContext<{ theme: NavigationProps['theme']; }>({ theme: 'system' }); +const setSizeVariantClasses = ( + breakpoints: [number, number], + currentWidth: number, + target: HTMLElement +) => { + if ( + currentWidth <= breakpoints[0] && + !target.classList.contains(NavigationBreakpoint.Small) + ) { + target.classList.remove( + NavigationBreakpoint.Full, + NavigationBreakpoint.Narrow + ); + target.classList.add(NavigationBreakpoint.Small); + } + if ( + currentWidth > breakpoints[0] && + currentWidth <= breakpoints[1] && + !target.classList.contains(NavigationBreakpoint.Narrow) + ) { + target.classList.remove( + NavigationBreakpoint.Full, + NavigationBreakpoint.Small + ); + target.classList.add(NavigationBreakpoint.Narrow); + } + if ( + currentWidth > breakpoints[1] && + !target.classList.contains(NavigationBreakpoint.Full) + ) { + target.classList.remove( + NavigationBreakpoint.Narrow, + NavigationBreakpoint.Small + ); + target.classList.add(NavigationBreakpoint.Full); + } +}; + export const Navigation = ({ appName, homeLink = '/', @@ -319,47 +357,28 @@ export const Navigation = ({ breakpoints = [478, 1000], onResize, }: NavigationProps) => { - const navigationRef = useRef<HTMLElement>( - null - ) as MutableRefObject<HTMLElement>; + const navigationRef = useRef<HTMLElement>(null); const actionsRef = useRef<HTMLDivElement>(null); - useResizeObserver(navigationRef.current, (entries) => { - if (entries.length === 0 || !navigationRef.current) return; - const w = entries[0].borderBoxSize[0].inlineSize; - if (onResize) onResize(w, navigationRef); - if ( - w <= breakpoints[0] && - !navigationRef.current.classList.contains(NavigationBreakpoint.Small) - ) { - navigationRef.current.classList.remove( - NavigationBreakpoint.Full, - NavigationBreakpoint.Narrow - ); - navigationRef.current.classList.add(NavigationBreakpoint.Small); - } - if ( - w > breakpoints[0] && - w <= breakpoints[1] && - !navigationRef.current.classList.contains(NavigationBreakpoint.Narrow) - ) { - navigationRef.current.classList.remove( - NavigationBreakpoint.Full, - NavigationBreakpoint.Small - ); - navigationRef.current.classList.add(NavigationBreakpoint.Narrow); - } - if ( - w > breakpoints[1] && - !navigationRef.current.classList.contains(NavigationBreakpoint.Full) - ) { - navigationRef.current.classList.remove( - NavigationBreakpoint.Narrow, - NavigationBreakpoint.Small - ); - navigationRef.current.classList.add(NavigationBreakpoint.Full); - } - }); + useLayoutEffect(() => { + if (!navigationRef.current) return; + const target = navigationRef.current; + const currentWidth = Math.min( + target.getBoundingClientRect().width, + window.innerWidth + ); + setSizeVariantClasses(breakpoints, currentWidth, target); + + const handler = () => { + const currentWidth = target.getBoundingClientRect().width; + setSizeVariantClasses(breakpoints, currentWidth, target); + onResize?.(currentWidth, target); + }; + window.addEventListener('resize', handler); + return () => { + window.removeEventListener('resize', handler); + }; + }, [breakpoints, onResize]); const [drawerOpen, setDrawerOpen] = useDrawer((state) => [ state.drawerOpen, @@ -368,9 +387,13 @@ export const Navigation = ({ const drawerTrigger = ( <button - className={classNames('px-2', `group-[.nav-size-full]:hidden`, { - 'z-20': drawerOpen, - })} + className={classNames( + 'px-2', + `hidden group-[.nav-size-narrow]:block group-[.nav-size-small]:block`, + { + 'z-[21]': drawerOpen, + } + )} onClick={() => { setDrawerOpen(!drawerOpen); }} @@ -415,7 +438,7 @@ export const Navigation = ({ className={classNames( 'drawer-content', 'border-l h-full relative overflow-auto', - 'px-4 font-alpha', + 'px-4 pb-8 font-alpha', // text { 'text-vega-light-300 dark:text-vega-dark-300': theme === 'system', @@ -442,7 +465,7 @@ export const Navigation = ({ }px`, }} > - <div className="flex flex-col gap-2 pr-10">{children}</div> + <div className="flex flex-col gap-2 pr-10 text-lg">{children}</div> </div> </DrawerContext.Provider> );