Skip to content

Commit

Permalink
feat: add new mobile nav bar (#6747)
Browse files Browse the repository at this point in the history
* add bottom sheet dialog

* try out vaul

* hook up send

* Create SubPage.tsx

* adjusting dialog header

* Update DialogCloseButton.tsx

* add new action menu

* updates

* update more height stuff

* use 100dvh instead of vh

* use 100vh

* anchor footer at the bottom of the screen

* update new asset search

* Update SelectAssets.tsx

* add min height for regular modals

* fix not connected button

* Delete SubPage.tsx

* remove mobile order

* Update main.json

* fix the qr code modal

* Update MobileNavBar.tsx

* Update MobileNavBar.tsx
  • Loading branch information
reallybeard authored Apr 24, 2024
1 parent 708b065 commit e8dc4bb
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 32 deletions.
8 changes: 4 additions & 4 deletions src/Routes/RoutesCommon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,14 @@ export const routes: NestedRoute[] = [
mobileNav: true,
hideDesktop: true,
main: Home,
priority: 0,
priority: 1,
},
{
path: '/trade',
label: 'navBar.trade',
shortLabel: 'navBar.tradeShort',
icon: <SwapIcon />,
mobileNav: true,
mobileNav: false,
priority: 2,
main: Trade,
category: RouteCategory.Featured,
Expand Down Expand Up @@ -165,7 +165,7 @@ export const routes: NestedRoute[] = [
main: StakingVaults,
category: RouteCategory.Featured,
mobileNav: true,
priority: 3,
priority: 4,
},
{
path: '/buy-crypto',
Expand All @@ -175,7 +175,7 @@ export const routes: NestedRoute[] = [
main: Buy,
category: RouteCategory.Featured,
mobileNav: true,
priority: 4,
priority: 5,
routes: assetIdPaths.map(assetIdPath => ({
label: 'Buy Asset',
path: assetIdPath,
Expand Down
24 changes: 23 additions & 1 deletion src/assets/translations/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,29 @@
"buyCryptoShort": "Buy/Sell",
"arkeo": "Arkeo Airdrop",
"lending": "Lending",
"pools": "Pools"
"pools": "Pools",
"actionMenu": {
"send": {
"title": "Send",
"body": "Send your crypto"
},
"receive": {
"title": "Receive",
"body": "Receive crypto"
},
"trade": {
"title": "Trade or Bridge",
"body": "Trade of bridge your crypto"
},
"buy": {
"title": "Buy or Sell",
"body": "Buy or sell crypto with credit card"
},
"qrcode": {
"title": "Scan QR Code",
"body": "Easily scan to send or receive crypto"
}
}
},
"transactionRow": {
"send": "Sent",
Expand Down
213 changes: 194 additions & 19 deletions src/components/Layout/Header/NavBar/MobileNavBar.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,97 @@
import { Flex, useColorModeValue } from '@chakra-ui/react'
import { ArrowDownIcon } from '@chakra-ui/icons'
import type { CenterProps } from '@chakra-ui/react'
import {
Button,
Center,
Flex,
IconButton,
SimpleGrid,
Stack,
useColorModeValue,
useDisclosure,
} from '@chakra-ui/react'
import { union } from 'lodash'
import { memo, useLayoutEffect, useMemo } from 'react'
import React, { memo, useCallback, useLayoutEffect, useMemo } from 'react'
import { FaRegCreditCard } from 'react-icons/fa'
import { useTranslate } from 'react-polyglot'
import { useHistory } from 'react-router'
import { routes } from 'Routes/RoutesCommon'
import { QRCodeIcon } from 'components/Icons/QRCode'
import { SendIcon } from 'components/Icons/SendIcon'
import { SwapIcon } from 'components/Icons/SwapIcon'
import { Dialog } from 'components/Modal/components/Dialog'
import { DialogBody } from 'components/Modal/components/DialogBody'
import { DialogHeader } from 'components/Modal/components/DialogHeader'
import { RawText } from 'components/Text'
import { usePlugins } from 'context/PluginProvider/PluginProvider'
import { useModal } from 'hooks/useModal/useModal'
import { useWallet } from 'hooks/useWallet/useWallet'
import { bnOrZero } from 'lib/bignumber/bignumber'

import { MobileNavLink } from './MobileNavLink'

const displayProp = { base: 'flex', md: 'none' }
const displayProp = { base: 'grid', md: 'none' }

const swapIcon = <SwapIcon />
const sendIcon = <SendIcon />
const receiveIcon = <ArrowDownIcon />
const buyIcon = <FaRegCreditCard />
const qrCodeIcon = <QRCodeIcon />

type ActionMenuButtonProps = {
icon?: JSX.Element
title: string
body: string
onClick?: () => void

iconColor?: CenterProps['bg']
isDisabled?: boolean
}

const actionMenuButtonHover = { bg: 'transparent' }
const actionButtonActive = { opacity: 0.5, bg: 'transparent' }

const ActionMenuButton: React.FC<ActionMenuButtonProps> = ({
icon,
title,
body,
iconColor = 'blue.500',
onClick,

isDisabled,
}) => {
return (
<Button
height='auto'
onClick={onClick}
variant='ghost'
display='flex'
alignItems='center'
width='full'
color='text.base'
justifyContent='flex-start'
textAlign='left'
gap={4}
px={0}
isDisabled={isDisabled}
_hover={actionMenuButtonHover}
_active={actionButtonActive}
>
<Center flexShrink={0} bg={iconColor} boxSize='48px' borderRadius='full' fontSize='xl'>
{icon}
</Center>
<Stack spacing={1} flex={1}>
<RawText fontWeight='semibold'>{title}</RawText>
<RawText color='text.subtle' fontWeight='normal'>
{body}
</RawText>
</Stack>
</Button>
)
}

export const MobileNavBar = memo(() => {
const translate = useTranslate()
const bg = useColorModeValue(
`linear-gradient(
to top,
Expand Down Expand Up @@ -50,7 +132,15 @@ export const MobileNavBar = memo(() => {
hsla(211, 11%, 7%, 0) 100%
);`,
)
const { isOpen, onClose, onOpen } = useDisclosure()
const {
state: { isConnected },
} = useWallet()
const send = useModal('send')
const receive = useModal('receive')
const qrCode = useModal('qrCode')
const { routes: pluginRoutes } = usePlugins()
const history = useHistory()
const allRoutes = useMemo(
() =>
union(routes, pluginRoutes)
Expand All @@ -77,22 +167,107 @@ export const MobileNavBar = memo(() => {
}
}, [])

const handleQrCodeClick = useCallback(() => {
onClose()
qrCode.open({})
}, [onClose, qrCode])

const handleSendClick = useCallback(() => {
onClose()
send.open({})
}, [onClose, send])

const handleReceiveClick = useCallback(() => {
onClose()
receive.open({})
}, [onClose, receive])

const handleTradeClick = useCallback(() => {
onClose()
history.push('/trade')
}, [history, onClose])

const handleBuyClick = useCallback(() => {
onClose()
history.push('/buy-crypto')
}, [history, onClose])

return (
<Flex
position='fixed'
bottom={0}
left={0}
width='100%'
bgImage={bg}
pt={6}
zIndex='banner'
paddingBottom='calc(env(safe-area-inset-bottom, 16px) - 16px)'
display={displayProp}
className='mobile-nav'
>
{allRoutes.map(route => (
<MobileNavLink key={route.path} {...route} />
))}
</Flex>
<>
<SimpleGrid
position='fixed'
bottom={0}
left={0}
width='100%'
gridTemplateColumns='1fr 1fr 1fr 1fr 1fr'
bgImage={bg}
pt={6}
zIndex='banner'
alignItems='center'
paddingBottom='calc(env(safe-area-inset-bottom, 16px) - 16px)'
display={displayProp}
className='mobile-nav'
>
{allRoutes.map(route => (
<MobileNavLink key={route.path} {...route} />
))}
<Flex alignItems='center' justifyContent='center' order={3}>
<IconButton
size='lg'
icon={swapIcon}
isRound
colorScheme='blue'
aria-label='action menu'
onClick={onOpen}
/>
</Flex>
</SimpleGrid>
<Dialog isOpen={isOpen} onClose={onClose} height='auto'>
<DialogHeader />
<DialogBody
pb='calc(env(safe-area-inset-bottom) + 2rem)'
display='flex'
flexDir='column'
gap={8}
px={6}
>
<ActionMenuButton
onClick={handleSendClick}
icon={sendIcon}
title={translate('navBar.actionMenu.send.title')}
body={translate('navBar.actionMenu.send.body')}
isDisabled={!isConnected}
/>
<ActionMenuButton
onClick={handleReceiveClick}
iconColor='green.600'
icon={receiveIcon}
title={translate('navBar.actionMenu.receive.title')}
body={translate('navBar.actionMenu.receive.body')}
isDisabled={!isConnected}
/>
<ActionMenuButton
onClick={handleTradeClick}
iconColor='purple.500'
icon={swapIcon}
title={translate('navBar.actionMenu.trade.title')}
body={translate('navBar.actionMenu.trade.body')}
/>
<ActionMenuButton
onClick={handleBuyClick}
icon={buyIcon}
title={translate('navBar.actionMenu.buy.title')}
body={translate('navBar.actionMenu.buy.body')}
/>
<ActionMenuButton
onClick={handleQrCodeClick}
icon={qrCodeIcon}
title={translate('navBar.actionMenu.qrcode.title')}
body={translate('navBar.actionMenu.qrcode.body')}
isDisabled={!isConnected}
/>
</DialogBody>
</Dialog>
</>
)
})
3 changes: 2 additions & 1 deletion src/components/Layout/Header/NavBar/MobileNavLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useTranslate } from 'react-polyglot'
import { Link as ReactRouterLink, matchPath, useLocation } from 'react-router-dom'
import type { Route } from 'Routes/helpers'

export const MobileNavLink = memo(({ label, shortLabel, path, icon }: Route) => {
export const MobileNavLink = memo(({ label, shortLabel, path, icon, priority }: Route) => {
const translate = useTranslate()
const location = useLocation()
const isActive = useMemo(() => {
Expand All @@ -30,6 +30,7 @@ export const MobileNavLink = memo(({ label, shortLabel, path, icon }: Route) =>
fontSize='24px'
gap={2}
height='auto'
order={priority}
variant='nav-link'
isActive={isActive}
fontWeight='medium'
Expand Down
10 changes: 3 additions & 7 deletions src/components/Modals/QrCode/QrCode.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { ModalContent, ModalOverlay } from '@chakra-ui/react'
import type { AccountId, AssetId } from '@shapeshiftoss/caip'
import { MemoryRouter } from 'react-router-dom'
import { Dialog } from 'components/Modal/components/Dialog'
Expand All @@ -19,12 +18,9 @@ export const QrCodeModal = ({ assetId, accountId }: QrCodeModalProps) => {

return (
<Dialog isOpen={isOpen} onClose={close} isFullScreen>
<ModalOverlay />
<ModalContent maxW='500px'>
<MemoryRouter initialEntries={entries}>
<Form assetId={assetId} accountId={accountId} />
</MemoryRouter>
</ModalContent>
<MemoryRouter initialEntries={entries}>
<Form assetId={assetId} accountId={accountId} />
</MemoryRouter>
</Dialog>
)
}

0 comments on commit e8dc4bb

Please sign in to comment.