Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Menu #715

Merged
merged 7 commits into from
Jun 21, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 165 additions & 0 deletions src/custom/components/Header/MenuTree/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { HeaderLinks as Wrapper, StyledNavLink } from '../styled'
import MenuDropdown from 'components/MenuDropdown'
import { MenuTitle, MenuSection } from 'components/MenuDropdown/styled'
import SVG from 'react-inlinesvg'
import {
MAIN_MENU,
MenuTreeItem,
MenuItemKind,
InternalLink,
ExternalLink,
DropDownItem,
MenuLink,
} from 'constants/mainMenu'
import { ExternalLink as ExternalLinkComponent } from 'theme/components'

// Assets
import IMAGE_MOON from 'assets/cow-swap/moon.svg'
import IMAGE_SUN from 'assets/cow-swap/sun.svg'

interface MenuImageProps {
title: string
iconSVG?: string
icon?: string
}

function MenuImage(props: MenuImageProps) {
const { title, iconSVG, icon } = props

if (iconSVG) {
return <SVG src={iconSVG} description={`${title} icon`} />
} else if (icon) {
return <img src={icon} alt={`${title} icon`} />
} else {
return null
}
}

interface InternalExternalLinkProps {
link: InternalLink | ExternalLink
handleMobileMenuOnClick: () => void
}

function InternalExternalLink({ link, handleMobileMenuOnClick }: InternalExternalLinkProps) {
const { kind, title, url, iconSVG, icon } = link
const menuImage = <MenuImage title={title} icon={icon} iconSVG={iconSVG} />
const isExternal = kind === MenuItemKind.EXTERNAL_LINK

if (isExternal) {
return (
<ExternalLinkComponent href={url} onClickOptional={handleMobileMenuOnClick}>
{menuImage}
{title}
</ExternalLinkComponent>
)
} else {
return (
<StyledNavLink exact to={url} onClick={handleMobileMenuOnClick}>
{menuImage}
{title}
</StyledNavLink>
)
}
}

interface ContextProps {
anxolin marked this conversation as resolved.
Show resolved Hide resolved
darkMode: boolean
toggleDarkMode: () => void
handleMobileMenuOnClick: () => void
}

type DarkModeButtonProps = {
context: ContextProps
}

function DarkModeButton({ context }: DarkModeButtonProps) {
const { darkMode, toggleDarkMode, handleMobileMenuOnClick } = context
const description = `${darkMode ? 'Sun/light' : 'Moon/dark'} mode icon`
const label = (darkMode ? 'Light' : 'Dark') + ' Mode'
return (
<button
onClick={() => {
handleMobileMenuOnClick()
toggleDarkMode()
}}
>
<SVG src={darkMode ? IMAGE_SUN : IMAGE_MOON} description={description} /> {label}
</button>
)
}

interface LinkProps {
link: MenuLink
context: ContextProps
}

function Link({ link, context }: LinkProps) {
switch (link.kind) {
case MenuItemKind.DARK_MODE_BUTTON:
return <DarkModeButton context={context} />

default:
return <InternalExternalLink link={link} handleMobileMenuOnClick={context.handleMobileMenuOnClick} />
}
}

interface DropdownProps {
item: DropDownItem
context: ContextProps
}

const DropDown = ({ item, context }: DropdownProps) => {
const { title, items } = item

return (
<MenuDropdown title={title}>
{items?.map((item, index) => {
const { sectionTitle, links } = item
return (
<MenuSection key={index}>
{sectionTitle && <MenuTitle>{sectionTitle}</MenuTitle>}
anxolin marked this conversation as resolved.
Show resolved Hide resolved
{links.map((link, linkIndex) => (
<Link key={linkIndex} link={link} context={context} />
))}
</MenuSection>
)
})}
</MenuDropdown>
)
}

interface MenuItemWithDropDownProps {
menuItem: MenuTreeItem
context: ContextProps
}

function MenuItemWithDropDown(props: MenuItemWithDropDownProps) {
const { menuItem, context } = props

switch (menuItem.kind) {
case MenuItemKind.DROP_DOWN:
return <DropDown item={menuItem} context={context} />

case undefined: // INTERNAL
case MenuItemKind.EXTERNAL_LINK: // EXTERNAL
// Render Internal/External links
return <InternalExternalLink link={menuItem} handleMobileMenuOnClick={context.handleMobileMenuOnClick} />
default:
return null
}
}

export interface MenuTreeProps extends ContextProps {
isMobileMenuOpen: boolean
}

export function MenuTree({ isMobileMenuOpen, darkMode, toggleDarkMode, handleMobileMenuOnClick }: MenuTreeProps) {
const context = { darkMode, toggleDarkMode, handleMobileMenuOnClick }
return (
<Wrapper isMobileMenuOpen={isMobileMenuOpen}>
{MAIN_MENU.map((menuItem, index) => (
<MenuItemWithDropDown key={index} menuItem={menuItem} context={context} />
anxolin marked this conversation as resolved.
Show resolved Hide resolved
))}
</Wrapper>
)
}
83 changes: 9 additions & 74 deletions src/custom/components/Header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useEffect, useCallback, useMemo } from 'react'
import { useState, useEffect, useCallback } from 'react'
import { SupportedChainId as ChainId } from 'constants/chains'
import { Routes } from 'constants/routes'
import { useHistory } from 'react-router-dom'
Expand All @@ -8,40 +8,31 @@ import { useNativeCurrencyBalances } from 'state/wallet/hooks'
import { useDarkModeManager } from 'state/user/hooks'
import { useMediaQuery, upToSmall, upToMedium, upToLarge, LargeAndUp } from 'hooks/useMediaQuery'
import { AMOUNT_PRECISION } from 'constants/index'
import { MAIN_MENU, MAIN_MENU_TYPE } from 'constants/mainMenu'

import { supportedChainId } from 'utils/supportedChainId'
import { formatSmart } from 'utils/format'
import { addBodyClass, removeBodyClass } from 'utils/toggleBodyClass'
import SVG from 'react-inlinesvg'

// Components
import { ExternalLink } from 'theme/components'
import { HeaderRow } from './HeaderMod'
import {
Wrapper,
Title,
LogoImage,
HeaderLinks,
HeaderModWrapper,
UniIcon,
StyledNavLink,
AccountElement,
BalanceText,
HeaderControls,
HeaderElement,
} from './styled'
import { MenuTree } from './MenuTree'
import MobileMenuIcon from './MobileMenuIcon'
import MenuDropdown from 'components/MenuDropdown'
import { MenuTitle, MenuSection } from 'components/MenuDropdown/styled'
import Web3Status from 'components/Web3Status'
import OrdersPanel from 'components/OrdersPanel'
import NetworkSelector from 'components/Header/NetworkSelector'
import CowBalanceButton from 'components/CowBalanceButton'

// Assets
import IMAGE_MOON from 'assets/cow-swap/moon.svg'
import IMAGE_SUN from 'assets/cow-swap/sun.svg'

export const NETWORK_LABELS: { [chainId in ChainId]?: string } = {
[ChainId.RINKEBY]: 'Rinkeby',
// [ChainId.ROPSTEN]: 'Ropsten',
Expand Down Expand Up @@ -107,67 +98,6 @@ export default function Header() {
setIsTouch('ontouchstart' in document.documentElement)
}, [isOrdersPanelOpen, isMobileMenuOpen, isUpToLarge, isUpToMedium, isUpToSmall, isLargeAndUp])

const getMainMenu = useMemo(
() =>
MAIN_MENU.map(({ title, url, externalURL, items }: MAIN_MENU_TYPE, index) =>
!items && !externalURL && url ? (
<StyledNavLink key={index} exact to={url} onClick={handleMobileMenuOnClick}>
{title}
</StyledNavLink>
) : !items && externalURL && url ? (
<ExternalLink key={index} href={url} onClickOptional={handleMobileMenuOnClick}>
{title}
</ExternalLink>
) : items ? (
<MenuDropdown key={index} title={title}>
{items.map(({ sectionTitle, links }, index) => {
return (
<MenuSection key={index}>
{sectionTitle && <MenuTitle>{sectionTitle}</MenuTitle>}
{links.map(({ title, url, externalURL, icon, iconSVG, action }, index) => {
return action && action === 'setColorMode' ? (
<button
key={index}
onClick={() => {
handleMobileMenuOnClick()
toggleDarkMode()
}}
>
<SVG
src={darkMode ? IMAGE_SUN : IMAGE_MOON}
description={`${darkMode ? 'Sun/light' : 'Moon/dark'} mode icon`}
/>{' '}
{darkMode ? 'Light' : 'Dark'} Mode
</button>
) : !externalURL && url ? (
<StyledNavLink key={index} exact to={url} onClick={handleMobileMenuOnClick}>
{iconSVG ? (
<SVG src={iconSVG} description={`${title} icon`} />
) : icon ? (
<img src={icon} alt={`${title} icon`} />
) : null}{' '}
{title}
</StyledNavLink>
) : url ? (
<ExternalLink key={index} href={url} onClickOptional={handleMobileMenuOnClick}>
{iconSVG ? (
<SVG src={iconSVG} description={`${title} icon`} />
) : icon ? (
<img src={icon} alt={`${title} icon`} />
) : null}{' '}
{title}
</ExternalLink>
) : null
})}
</MenuSection>
)
})}
</MenuDropdown>
) : null
),
[darkMode, handleMobileMenuOnClick, toggleDarkMode]
)

return (
<Wrapper isMobileMenuOpen={isMobileMenuOpen}>
<HeaderModWrapper>
Expand All @@ -177,7 +107,12 @@ export default function Header() {
<LogoImage isMobileMenuOpen={isMobileMenuOpen} />
</UniIcon>
</Title>
<HeaderLinks isMobileMenuOpen={isMobileMenuOpen}>{getMainMenu}</HeaderLinks>
<MenuTree
isMobileMenuOpen={isMobileMenuOpen}
darkMode={darkMode}
toggleDarkMode={toggleDarkMode}
handleMobileMenuOnClick={handleMobileMenuOnClick}
/>
</HeaderRow>

<HeaderControls>
Expand Down
Loading