diff --git a/src/custom/components/Header/MenuTree/index.tsx b/src/custom/components/Header/MenuTree/index.tsx new file mode 100644 index 0000000000..f6b3d8732f --- /dev/null +++ b/src/custom/components/Header/MenuTree/index.tsx @@ -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 + } else if (icon) { + return {`${title} + } else { + return null + } +} + +interface InternalExternalLinkProps { + link: InternalLink | ExternalLink + handleMobileMenuOnClick: () => void +} + +function InternalExternalLink({ link, handleMobileMenuOnClick }: InternalExternalLinkProps) { + const { kind, title, url, iconSVG, icon } = link + const menuImage = + const isExternal = kind === MenuItemKind.EXTERNAL_LINK + + if (isExternal) { + return ( + + {menuImage} + {title} + + ) + } else { + return ( + + {menuImage} + {title} + + ) + } +} + +interface ContextProps { + 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 ( + + ) +} + +interface LinkProps { + link: MenuLink + context: ContextProps +} + +function Link({ link, context }: LinkProps) { + switch (link.kind) { + case MenuItemKind.DARK_MODE_BUTTON: + return + + default: + return + } +} + +interface DropdownProps { + item: DropDownItem + context: ContextProps +} + +const DropDown = ({ item, context }: DropdownProps) => { + const { title, items } = item + + return ( + + {items?.map((item, index) => { + const { sectionTitle, links } = item + return ( + + {sectionTitle && {sectionTitle}} + {links.map((link, linkIndex) => ( + + ))} + + ) + })} + + ) +} + +interface MenuItemWithDropDownProps { + menuItem: MenuTreeItem + context: ContextProps +} + +function MenuItemWithDropDown(props: MenuItemWithDropDownProps) { + const { menuItem, context } = props + + switch (menuItem.kind) { + case MenuItemKind.DROP_DOWN: + return + + case undefined: // INTERNAL + case MenuItemKind.EXTERNAL_LINK: // EXTERNAL + // Render Internal/External links + return + default: + return null + } +} + +export interface MenuTreeProps extends ContextProps { + isMobileMenuOpen: boolean +} + +export function MenuTree({ isMobileMenuOpen, darkMode, toggleDarkMode, handleMobileMenuOnClick }: MenuTreeProps) { + const context = { darkMode, toggleDarkMode, handleMobileMenuOnClick } + return ( + + {MAIN_MENU.map((menuItem, index) => ( + + ))} + + ) +} diff --git a/src/custom/components/Header/index.tsx b/src/custom/components/Header/index.tsx index 1720af975a..4d2b7cbdde 100644 --- a/src/custom/components/Header/index.tsx +++ b/src/custom/components/Header/index.tsx @@ -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' @@ -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', @@ -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 ? ( - - {title} - - ) : !items && externalURL && url ? ( - - {title} - - ) : items ? ( - - {items.map(({ sectionTitle, links }, index) => { - return ( - - {sectionTitle && {sectionTitle}} - {links.map(({ title, url, externalURL, icon, iconSVG, action }, index) => { - return action && action === 'setColorMode' ? ( - - ) : !externalURL && url ? ( - - {iconSVG ? ( - - ) : icon ? ( - {`${title} - ) : null}{' '} - {title} - - ) : url ? ( - - {iconSVG ? ( - - ) : icon ? ( - {`${title} - ) : null}{' '} - {title} - - ) : null - })} - - ) - })} - - ) : null - ), - [darkMode, handleMobileMenuOnClick, toggleDarkMode] - ) - return ( @@ -177,7 +107,12 @@ export default function Header() { - {getMainMenu} + diff --git a/src/custom/constants/mainMenu.ts b/src/custom/constants/mainMenu.ts index ed4288dce7..ceda12f23e 100644 --- a/src/custom/constants/mainMenu.ts +++ b/src/custom/constants/mainMenu.ts @@ -11,24 +11,44 @@ import IMAGE_PIE from 'assets/cow-swap/pie.svg' import IMAGE_SLICER from 'assets/cow-swap/ninja-cow.png' import IMAGE_GAME from 'assets/cow-swap/game.gif' -export interface MAIN_MENU_TYPE { +export enum MenuItemKind { + DROP_DOWN = 'DROP_DOWN', + EXTERNAL_LINK = 'EXTERNAL_LINK', + DARK_MODE_BUTTON = 'DARK_MODE_BUTTON', +} + +export interface BasicMenuLink { title: string - url?: string - externalURL?: boolean - items?: { - sectionTitle?: string - links: { - title?: string - url?: string // If URL is an internal route - externalURL?: boolean // If URL is external - icon?: string // If icon uses a regular tag - iconSVG?: string // If icon is a inline component - action?: string // Special purpose flag for non-regular links - }[] - }[] + url: string + icon?: string // If icon uses a regular tag + iconSVG?: string // If icon is a inline component } +export interface InternalLink extends BasicMenuLink { + kind?: undefined +} + +export interface ExternalLink extends BasicMenuLink { + kind: MenuItemKind.EXTERNAL_LINK +} + +export type DarkModeLink = { kind: MenuItemKind.DARK_MODE_BUTTON } + +export type MenuLink = InternalLink | ExternalLink | DarkModeLink + +export interface DropDownSubItem { + sectionTitle?: string + links: MenuLink[] +} + +export interface DropDownItem { + kind: MenuItemKind.DROP_DOWN + title: string + items: DropDownSubItem[] +} + +export type MenuTreeItem = InternalLink | ExternalLink | DropDownItem -export const FAQ_MENU = [ +export const FAQ_MENU: InternalLink[] = [ { title: 'Overview', url: Routes.FAQ }, { title: 'Protocol', url: Routes.FAQ_PROTOCOL }, { title: 'Token', url: Routes.FAQ_TOKEN }, @@ -36,10 +56,11 @@ export const FAQ_MENU = [ { title: 'Affiliate', url: Routes.FAQ_AFFILIATE }, ] -export const MAIN_MENU = [ +export const MAIN_MENU: MenuTreeItem[] = [ { title: 'Swap', url: Routes.SWAP }, { title: 'Account', url: Routes.ACCOUNT }, { + kind: MenuItemKind.DROP_DOWN, title: 'FAQ', items: [ { @@ -48,28 +69,29 @@ export const MAIN_MENU = [ ], }, { + kind: MenuItemKind.DROP_DOWN, title: 'More', items: [ { sectionTitle: 'Overview', links: [ - { title: 'Documentation', url: DOCS_LINK, externalURL: true, iconSVG: IMAGE_DOCS }, + { title: 'Documentation', url: DOCS_LINK, iconSVG: IMAGE_DOCS, kind: MenuItemKind.EXTERNAL_LINK }, { title: 'About', url: Routes.ABOUT, iconSVG: IMAGE_INFO }, - { title: 'Statistics', url: DUNE_DASHBOARD_LINK, externalURL: true, iconSVG: IMAGE_PIE }, - { title: 'Contract', url: CONTRACTS_CODE_LINK, externalURL: true, iconSVG: IMAGE_CODE }, + { title: 'Statistics', url: DUNE_DASHBOARD_LINK, iconSVG: IMAGE_PIE, kind: MenuItemKind.EXTERNAL_LINK }, + { title: 'Contract', url: CONTRACTS_CODE_LINK, iconSVG: IMAGE_CODE, kind: MenuItemKind.EXTERNAL_LINK }, ], }, { sectionTitle: 'Community', links: [ - { title: 'Discord', url: DISCORD_LINK, externalURL: true, iconSVG: IMAGE_DISCORD }, - { title: 'Twitter', url: TWITTER_LINK, externalURL: true, iconSVG: IMAGE_TWITTER }, + { title: 'Discord', url: DISCORD_LINK, iconSVG: IMAGE_DISCORD, kind: MenuItemKind.EXTERNAL_LINK }, + { title: 'Twitter', url: TWITTER_LINK, iconSVG: IMAGE_TWITTER, kind: MenuItemKind.EXTERNAL_LINK }, ], }, { sectionTitle: 'Other', links: [ - { action: 'setColorMode' }, + { kind: MenuItemKind.DARK_MODE_BUTTON }, { title: 'CoW Runner', url: Routes.PLAY_COWRUNNER, icon: IMAGE_GAME }, { title: 'MEV Slicer', url: Routes.PLAY_MEVSLICER, icon: IMAGE_SLICER }, { title: 'Terms and Conditions', url: Routes.TERMS_CONDITIONS, iconSVG: IMAGE_DOCS },