Commit d054079e authored by Charles Bachmeier's avatar Charles Bachmeier Committed by GitHub

feat: add routing functionality to navbar (#4374)

* feat: add routingfunctionality to navbar

* update colors

* rename and pass strings directly

* rename symbol

* rename props

* better symbol name for mobile link

* more isPoolActive
Co-authored-by: default avatarCharles Bachmeier <charlie@genie.xyz>
parent fe6324f8
import { style } from '@vanilla-extract/css'
import { sprinkles, themeVars } from '../../nft/css/sprinkles.css'
export const MenuRow = style([
sprinkles({
color: 'blackBlue',
paddingY: '12',
width: 'max',
marginRight: '52',
}),
{
lineHeight: '24px',
textDecoration: 'none',
},
])
export const PrimaryText = style([
{
lineHeight: '24px',
},
])
export const SecondaryText = style([
sprinkles({
paddingY: '8',
color: 'darkGray',
}),
{
lineHeight: '20px',
},
])
export const Separator = style([
sprinkles({
height: '0',
}),
{
borderTop: 'solid',
borderColor: themeVars.colors.medGray,
borderWidth: '1px',
},
])
export const IconRow = style([
sprinkles({
paddingX: '16',
paddingY: '8',
}),
])
import FeatureFlagModal from 'components/FeatureFlagModal/FeatureFlagModal'
import { PrivacyPolicyModal } from 'components/PrivacyPolicy'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import { Box } from 'nft/components/Box'
import { Column, Row } from 'nft/components/Flex'
import {
BarChartIcon,
DiscordIconMenu,
EllipsisIcon,
GithubIconMenu,
GovernanceIcon,
TwitterIconMenu,
} from 'nft/components/icons'
import { body, bodySmall } from 'nft/css/common.css'
import { themeVars } from 'nft/css/sprinkles.css'
import { ReactNode, useReducer, useRef } from 'react'
import { NavLink, NavLinkProps } from 'react-router-dom'
import { isDevelopmentEnv, isStagingEnv } from 'utils/env'
import { useToggleModal } from '../../state/application/hooks'
import { ApplicationModal } from '../../state/application/reducer'
import * as styles from './MenuDropdown.css'
import { NavDropdown } from './NavDropdown'
import { NavIcon } from './NavIcon'
const PrimaryMenuRow = ({
to,
href,
close,
children,
}: {
to?: NavLinkProps['to']
href?: string
close?: () => void
children: ReactNode
}) => {
return (
<>
{to ? (
<NavLink to={to} className={styles.MenuRow}>
<Row onClick={close}>{children}</Row>
</NavLink>
) : (
<Row as="a" href={href} target={'_blank'} rel={'noopener noreferrer'} className={styles.MenuRow}>
{children}
</Row>
)}
</>
)
}
const PrimaryMenuRowText = ({ children }: { children: ReactNode }) => {
return <Box className={`${styles.PrimaryText} ${body}`}>{children}</Box>
}
PrimaryMenuRow.Text = PrimaryMenuRowText
const SecondaryLinkedText = ({
href,
onClick,
children,
}: {
href?: string
onClick?: () => void
children: ReactNode
}) => {
return (
<Box
as={href ? 'a' : 'div'}
href={href ?? undefined}
target={href ? '_blank' : undefined}
rel={href ? 'noopener noreferrer' : undefined}
className={`${styles.SecondaryText} ${bodySmall}`}
onClick={onClick}
cursor="pointer"
>
{children}
</Box>
)
}
const Separator = () => {
return <Box className={styles.Separator} />
}
const IconRow = ({ children }: { children: ReactNode }) => {
return <Row className={styles.IconRow}>{children}</Row>
}
const Icon = ({ href, children }: { href?: string; children: ReactNode }) => {
return (
<>
<Box
as={href ? 'a' : 'div'}
href={href ?? undefined}
target={href ? '_blank' : undefined}
rel={href ? 'noopener noreferrer' : undefined}
display="flex"
flexDirection="column"
color="blackBlue"
background="none"
border="none"
justifyContent="center"
textAlign="center"
marginRight="12"
>
{children}
</Box>
</>
)
}
export const MenuDropdown = () => {
const [isOpen, toggleOpen] = useReducer((s) => !s, false)
const togglePrivacyPolicy = useToggleModal(ApplicationModal.PRIVACY_POLICY)
const openFeatureFlagsModal = useToggleModal(ApplicationModal.FEATURE_FLAGS)
const ref = useRef<HTMLDivElement>(null)
useOnClickOutside(ref, isOpen ? toggleOpen : undefined)
return (
<>
<Box position="relative" ref={ref}>
<NavIcon onClick={toggleOpen}>
<EllipsisIcon width={28} height={28} />
</NavIcon>
{isOpen && (
<NavDropdown top={60}>
<Column gap="12">
<Column paddingX="16" gap="4">
<PrimaryMenuRow to="/vote" close={toggleOpen}>
<Icon>
<GovernanceIcon width={24} height={24} />
</Icon>
<PrimaryMenuRow.Text>Vote in governance</PrimaryMenuRow.Text>
</PrimaryMenuRow>
<PrimaryMenuRow href="https://info.uniswap.org/#/">
<Icon>
<BarChartIcon width={24} height={24} />
</Icon>
<PrimaryMenuRow.Text>View token analytics ↗</PrimaryMenuRow.Text>
</PrimaryMenuRow>
</Column>
<Separator />
<Column paddingX="16" gap="4">
<SecondaryLinkedText href="https://help.uniswap.org/en/">Help center ↗</SecondaryLinkedText>
<SecondaryLinkedText href="https://docs.uniswap.org/">Documentation ↗</SecondaryLinkedText>
<SecondaryLinkedText
onClick={() => {
toggleOpen()
togglePrivacyPolicy()
}}
>{`Legal & Privacy`}</SecondaryLinkedText>
{(isDevelopmentEnv() || isStagingEnv()) && (
<SecondaryLinkedText onClick={openFeatureFlagsModal}>{`Feature Flags`}</SecondaryLinkedText>
)}
</Column>
<IconRow>
<Icon href="https://discord.com/invite/FCfyBSbCU5">
<DiscordIconMenu width={24} height={24} color={themeVars.colors.darkGray} />
</Icon>
<Icon href="https://twitter.com/Uniswap">
<TwitterIconMenu width={24} height={24} color={themeVars.colors.darkGray} />
</Icon>
<Icon href="https://github.com/Uniswap">
<GithubIconMenu width={24} height={24} color={themeVars.colors.darkGray} />
</Icon>
</IconRow>
</Column>
</NavDropdown>
)}
</Box>
<PrivacyPolicyModal />
<FeatureFlagModal />
</>
)
}
import { style } from '@vanilla-extract/css'
import { subhead } from 'nft/css/common.css'
import { sprinkles } from '../../nft/css/sprinkles.css'
export const sidebar = style([
sprinkles({
display: 'flex',
position: 'fixed',
background: 'white',
height: 'full',
top: '0',
left: '0',
right: '0',
bottom: '0',
paddingBottom: '16',
justifyContent: 'space-between',
}),
{
zIndex: 20,
},
])
export const icon = style([
sprinkles({
width: '32',
height: '32',
}),
])
export const iconContainer = style([
sprinkles({
position: 'relative',
display: 'flex',
flexDirection: 'column',
color: 'darkGray',
background: 'none',
border: 'none',
justifyContent: 'flex-end',
textAlign: 'center',
cursor: 'pointer',
padding: '6',
}),
])
export const linkRow = style([
subhead,
sprinkles({
color: 'blackBlue',
width: 'full',
paddingLeft: '16',
paddingY: '12',
cursor: 'pointer',
}),
{
lineHeight: '24px',
textDecoration: 'none',
},
])
export const activeLinkRow = style([
linkRow,
sprinkles({
background: 'lightGrayButton',
}),
])
export const separator = style([
sprinkles({
height: '0',
borderStyle: 'solid',
borderColor: 'medGray',
borderWidth: '1px',
marginY: '8',
marginX: '16',
}),
])
export const extraLinkRow = style([
subhead,
sprinkles({
width: 'full',
color: 'blackBlue',
paddingY: '12',
paddingLeft: '16',
cursor: 'pointer',
}),
{
lineHeight: '24px',
textDecoration: 'none',
},
])
export const bottomExternalLinks = style([
sprinkles({
gap: '4',
paddingX: '4',
width: 'max',
flexWrap: 'wrap',
}),
])
export const bottomJointExternalLinksContainer = style([
sprinkles({
paddingX: '8',
paddingY: '4',
color: 'darkGray',
fontWeight: 'medium',
fontSize: '12',
}),
{
lineHeight: '20px',
},
])
export const IconRow = style([
sprinkles({
gap: '12',
width: 'max',
}),
])
import FeatureFlagModal from 'components/FeatureFlagModal/FeatureFlagModal'
import { PrivacyPolicyModal } from 'components/PrivacyPolicy'
import { Box } from 'nft/components/Box'
import { Portal } from 'nft/components/common/Portal'
import { Column, Row } from 'nft/components/Flex'
import {
BarChartIconMobile,
BulletIcon,
CloseIcon,
DiscordIconMenuMobile,
GithubIconMenuMobile,
GovernanceIconMobile,
HamburgerIcon,
TwitterIconMenuMobile,
} from 'nft/components/icons'
import { themeVars } from 'nft/css/sprinkles.css'
import { ReactNode, useReducer } from 'react'
import { NavLink, NavLinkProps, useLocation } from 'react-router-dom'
import { useToggleModal, useTogglePrivacyPolicy } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer'
import { isDevelopmentEnv, isStagingEnv } from 'utils/env'
import * as styles from './MobileSidebar.css'
import { NavIcon } from './NavIcon'
interface NavLinkRowProps {
href: string
id?: NavLinkProps['id']
isActive?: boolean
close: () => void
children: ReactNode
}
const NavLinkRow = ({ href, id, isActive, close, children }: NavLinkRowProps) => {
return (
<NavLink to={href} className={isActive ? styles.activeLinkRow : styles.linkRow} id={id} onClick={close}>
{children}
</NavLink>
)
}
const ExtraLinkRow = ({
to,
href,
close,
children,
}: {
to?: NavLinkProps['to']
href?: string
close: () => void
children: ReactNode
}) => {
return (
<>
{to ? (
<NavLink to={to} className={styles.extraLinkRow}>
<Row gap="12" onClick={close}>
{children}
</Row>
</NavLink>
) : (
<Row
as="a"
href={href}
target={'_blank'}
rel={'noopener noreferrer'}
gap="12"
onClick={close}
className={styles.extraLinkRow}
>
{children}
</Row>
)}
</>
)
}
const BottomExternalLink = ({
href,
onClick,
children,
}: {
href?: string
onClick?: () => void
children: ReactNode
}) => {
return (
<Box
as={href ? 'a' : 'div'}
href={href ?? undefined}
target={href ? '_blank' : undefined}
rel={href ? 'noopener noreferrer' : undefined}
className={`${styles.bottomJointExternalLinksContainer}`}
onClick={onClick}
cursor="pointer"
>
{children}
</Box>
)
}
const Icon = ({ href, children }: { href?: string; children: ReactNode }) => {
return (
<>
<Box
as={href ? 'a' : 'div'}
href={href ?? undefined}
target={href ? '_blank' : undefined}
rel={href ? 'noopener noreferrer' : undefined}
display="flex"
flexDirection="column"
color="blackBlue"
background="none"
border="none"
justifyContent="center"
textAlign="center"
>
{children}
</Box>
</>
)
}
const IconRow = ({ children }: { children: ReactNode }) => {
return <Row className={styles.IconRow}>{children}</Row>
}
const Seperator = () => {
return <Box className={styles.separator} />
}
export const MobileSideBar = () => {
const [isOpen, toggleOpen] = useReducer((s) => !s, false)
const togglePrivacyPolicy = useTogglePrivacyPolicy()
const openFeatureFlagsModal = useToggleModal(ApplicationModal.FEATURE_FLAGS)
const { pathname } = useLocation()
const isPoolActive =
pathname.startsWith('/pool') ||
pathname.startsWith('/add') ||
pathname.startsWith('/remove') ||
pathname.startsWith('/increase') ||
pathname.startsWith('/find')
return (
<>
<NavIcon onClick={toggleOpen}>
<HamburgerIcon width={28} height={28} />
</NavIcon>
{isOpen && (
<Portal>
<Column className={styles.sidebar}>
<Column>
<Row justifyContent="flex-end" marginTop="14" marginBottom="20" marginRight="8">
<Box as="button" onClick={toggleOpen} className={styles.iconContainer}>
<CloseIcon className={styles.icon} />
</Box>
</Row>
<Column gap="4">
<NavLinkRow href="/swap" close={toggleOpen} isActive={pathname.startsWith('/swap')}>
Swap
</NavLinkRow>
<NavLinkRow href="/explore" close={toggleOpen} isActive={pathname.startsWith('/explore')}>
Tokens
</NavLinkRow>
<NavLinkRow href="/pool" id={'pool-nav-link'} isActive={isPoolActive} close={toggleOpen}>
Pool
</NavLinkRow>
</Column>
<Seperator />
<Column gap="4">
<ExtraLinkRow to="/vote" close={toggleOpen}>
<Icon>
<GovernanceIconMobile width={24} height={24} />
</Icon>
Vote in governance
</ExtraLinkRow>
<ExtraLinkRow href="https://info.uniswap.org/#/" close={toggleOpen}>
<Icon>
<BarChartIconMobile width={24} height={24} />
</Icon>
View token analytics ↗
</ExtraLinkRow>
</Column>
</Column>
<Column>
<Row justifyContent="center" marginBottom="12" flexWrap="wrap">
<Row className={styles.bottomExternalLinks}>
<BottomExternalLink href="https://help.uniswap.org/en/" onClick={toggleOpen}>
Help center ↗
</BottomExternalLink>
<BulletIcon />
<BottomExternalLink href="https://docs.uniswap.org/" onClick={toggleOpen}>
Documentation ↗
</BottomExternalLink>
<BulletIcon />
<BottomExternalLink
onClick={() => {
toggleOpen()
togglePrivacyPolicy()
}}
>
{`Legal & Privacy`}
</BottomExternalLink>
</Row>
{(isDevelopmentEnv() || isStagingEnv()) && (
<>
<BulletIcon />
<BottomExternalLink onClick={openFeatureFlagsModal}>{`Feature Flags`}</BottomExternalLink>
</>
)}
</Row>
<Row justifyContent="center">
<IconRow>
<Icon href="https://discord.com/invite/FCfyBSbCU5">
<DiscordIconMenuMobile width={32} height={32} color={themeVars.colors.darkGray} />
</Icon>
<Icon href="https://twitter.com/Uniswap">
<TwitterIconMenuMobile width={32} height={32} color={themeVars.colors.darkGray} />
</Icon>
<Icon href="https://github.com/Uniswap">
<GithubIconMenuMobile width={32} height={32} color={themeVars.colors.darkGray} />
</Icon>
</IconRow>
</Row>
</Column>
</Column>
</Portal>
)}
<PrivacyPolicyModal />
<FeatureFlagModal />
</>
)
}
import { style } from '@vanilla-extract/css'
import { sprinkles } from '../../nft/css/sprinkles.css'
export const NavDropdown = style([
sprinkles({
position: 'absolute',
background: 'white95',
borderRadius: '12',
borderStyle: 'solid',
borderColor: 'medGray',
paddingY: '20',
borderWidth: '1px',
}),
{
boxShadow: '0px 4px 12px 0px #00000026',
zIndex: 10,
},
])
import { Box } from 'nft/components/Box'
import { ReactNode } from 'react'
import * as styles from './NavDropdown.css'
interface NavDropdownProps {
top: number
right?: number
leftAligned?: boolean
horizontalPadding?: boolean
centerHorizontally?: boolean
children: ReactNode
}
export const NavDropdown = ({
top,
centerHorizontally,
leftAligned,
horizontalPadding,
children,
}: NavDropdownProps) => {
return (
<Box
paddingX={horizontalPadding ? '16' : undefined}
style={{
top: `${top}px`,
left: centerHorizontally ? '50%' : leftAligned ? '0px' : 'auto',
right: centerHorizontally || leftAligned ? 'auto' : '10px',
transform: centerHorizontally ? 'translateX(-50%)' : 'unset',
zIndex: 3,
}}
className={styles.NavDropdown}
>
{children}
</Box>
)
}
import { style } from '@vanilla-extract/css'
import { sprinkles, themeVars } from '../../nft/css/sprinkles.css'
export const navIcon = style([
sprinkles({
position: 'relative',
display: 'flex',
flexDirection: 'column',
color: 'blackBlue',
background: 'none',
border: 'none',
justifyContent: 'center',
textAlign: 'center',
cursor: 'pointer',
padding: '8',
borderRadius: '8',
}),
{
':hover': {
background: themeVars.colors.lightGrayContainer,
},
zIndex: 2,
},
])
import { Box } from 'nft/components/Box'
import { ReactNode } from 'react'
import * as styles from './NavIcon.css'
interface NavIconProps {
children: ReactNode
onClick: () => void
}
export const NavIcon = ({ children, onClick }: NavIconProps) => {
return (
<Box as="button" className={styles.navIcon} onClick={onClick}>
{children}
</Box>
)
}
import { style } from '@vanilla-extract/css'
import { subhead } from '../../nft/css/common.css'
import { sprinkles } from '../../nft/css/sprinkles.css'
export const nav = style([
......@@ -9,14 +10,118 @@ export const nav = style([
width: 'full',
height: '72',
zIndex: '2',
borderStyle: 'solid',
background: 'white08',
}),
{
borderWidth: '0.5px',
backdropFilter: 'blur(24px)',
},
])
export const logoContainer = style([
sprinkles({
display: 'flex',
marginRight: { mobile: '12', desktopXl: '20' },
alignItems: 'center',
}),
])
export const logo = style([
sprinkles({
display: 'block',
color: 'blackBlue',
}),
])
export const baseContainer = style([
sprinkles({
display: 'flex',
alignItems: 'center',
}),
])
export const baseMobileContainer = style([
sprinkles({
display: 'flex',
width: 'full',
alignItems: 'center',
marginY: '2',
}),
])
export const baseSideContainer = style([
baseContainer,
sprinkles({
width: 'full',
flex: '1',
flexShrink: '2',
}),
])
export const leftSideContainer = style([
baseSideContainer,
sprinkles({
justifyContent: 'flex-start',
}),
])
export const leftSideMobileContainer = style([
baseMobileContainer,
sprinkles({
justifyContent: 'flex-start',
}),
])
export const middleContainer = style([
baseContainer,
sprinkles({
flex: '1',
flexShrink: '1',
justifyContent: 'center',
}),
])
export const rightSideContainer = style([
baseSideContainer,
sprinkles({
justifyContent: 'flex-end',
}),
])
const baseMenuItem = style([
subhead,
sprinkles({
paddingY: '8',
paddingX: '16',
marginY: '4',
borderRadius: '12',
}),
{
lineHeight: '24px',
textDecoration: 'none',
},
])
export const menuItem = style([
baseMenuItem,
sprinkles({
color: 'darkGray',
}),
])
export const rightSideMobileContainer = style([
baseMobileContainer,
sprinkles({
justifyContent: 'flex-end',
}),
])
export const activeMenuItem = style([
baseMenuItem,
sprinkles({
color: 'blackBlue',
}),
])
export const mobileWalletContainer = style([
sprinkles({
position: 'fixed',
......
import Web3Status from 'components/Web3Status'
import { useWindowSize } from 'hooks/useWindowSize'
import { ReactNode } from 'react'
import { NavLink, NavLinkProps, useLocation } from 'react-router-dom'
import { Box } from '../../nft/components/Box'
import { Row } from '../../nft/components/Flex'
import { UniIcon, UniIconMobile } from '../../nft/components/icons'
import { breakpoints } from '../../nft/css/sprinkles.css'
import { MenuDropdown } from './MenuDropdown'
import { MobileSideBar } from './MobileSidebar'
import * as styles from './Navbar.css'
interface MenuItemProps {
href: string
id?: NavLinkProps['id']
isActive?: boolean
children: ReactNode
}
const MenuItem = ({ href, id, isActive, children }: MenuItemProps) => {
return (
<NavLink
to={href}
className={isActive ? styles.activeMenuItem : styles.menuItem}
id={id}
style={{ textDecoration: 'none' }}
>
{children}
</NavLink>
)
}
const MobileNavbar = () => {
return (
<>
<nav className={styles.nav} />
<div className={styles.mobileWalletContainer}>
<nav className={styles.nav}>
<Box display="flex" height="full" flexWrap="nowrap" alignItems="stretch">
<Box className={styles.leftSideMobileContainer}>
<Box as="a" href="#/swap" className={styles.logoContainer}>
<UniIconMobile width="44" height="44" className={styles.logo} />
</Box>
{/* TODO add ChainSwitcher */}
</Box>
<Box className={styles.rightSideMobileContainer}>
<Row gap="16">
{/* TODO add Searchbar */}
<MobileSideBar />
</Row>
</Box>
</Box>
</nav>
<Box className={styles.mobileWalletContainer}>
<Web3Status />
</div>
</Box>
</>
)
}
const Navbar = () => {
const { width: windowWidth } = useWindowSize()
const { pathname } = useLocation()
if (windowWidth && windowWidth < breakpoints.desktopXl) {
return <MobileNavbar />
}
return <nav className={styles.nav} />
const isPoolActive =
pathname.startsWith('/pool') ||
pathname.startsWith('/add') ||
pathname.startsWith('/remove') ||
pathname.startsWith('/increase') ||
pathname.startsWith('/find')
return (
<nav className={styles.nav}>
<Box display="flex" height="full" flexWrap="nowrap" alignItems="stretch">
<Box className={styles.leftSideContainer}>
<Box as="a" href="#/swap" className={styles.logoContainer}>
<UniIcon width="48" height="48" className={styles.logo} />
</Box>
<Row gap="8">
<MenuItem href="/swap" isActive={pathname.startsWith('/swap')}>
Swap
</MenuItem>
<MenuItem href="/explore" isActive={pathname.startsWith('/explore')}>
Explore
</MenuItem>
<MenuItem href="/pool" id={'pool-nav-link'} isActive={isPoolActive}>
Pool
</MenuItem>
</Row>
</Box>
<Box className={styles.middleContainer}>{/* TODO add Searchbar */}</Box>
<Box className={styles.rightSideContainer}>
<Row gap="12">
<MenuDropdown />
{/* TODO add ChainSwitcher */}
<Web3Status />
</Row>
</Box>
</Box>
</nav>
)
}
export default Navbar
......@@ -32,6 +32,7 @@ const themeContractValues = {
white95: '',
white90: '',
white80: '',
white08: '',
},
shadows: {
......@@ -134,7 +135,6 @@ export const vars = createGlobalTheme(':root', {
dropShadow: '0px 4px 16px rgba(70, 115, 250, 0.4)',
green: '#209853',
orange: '#FA2C38',
// Pavel's colors, TODO probably remove them after Pavel continues Genie List
black: 'black',
whitesmoke: '#F5F5F5',
blue: '#4C82FB',
......
......@@ -29,6 +29,7 @@ export const darkTheme: Theme = {
white95: '#0E111AF2',
white90: '#000000E5',
white80: '#000000CC',
white08: '#0000000C',
},
shadows: {
menu: '0px 10px 30px rgba(0, 0, 0, 0.1)',
......
......@@ -29,6 +29,7 @@ export const lightTheme: Theme = {
white95: '#EDEFF7F2',
white90: '#FFFFFFE5',
white80: '#FFFFFFCC',
white08: '#29324908',
},
shadows: {
menu: '0px 10px 30px rgba(0, 0, 0, 0.1)',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment