Mobile menu (#39)

* add MobileMenu component

* implement MobileMenu via Header

* close menu with links

* move all menu logic to MobileMenu component

* refactor MobileMenu to use Modals

* remove unneeded motion params

* remove animation on fixed modal

* abstract out a HeaderButtons component

* abstract out Search component

* move BORDER_WIDTH to constants

* hover fixes

* change requests

* fix: Link should wrap header buttons

Co-authored-by: Corwin Smith <cssmittys@gmail.com>
Co-authored-by: Nicolás Quiroz <nh.quiroz@gmail.com>
This commit is contained in:
Paul Wackerow 2022-11-23 20:57:49 +01:00 committed by GitHub
parent 5364cb731e
commit bd80434b83
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 187 additions and 69 deletions

View File

@ -1,15 +1,24 @@
import { Box, Flex, Input, InputGroup, Link, Stack, Text, useColorMode } from '@chakra-ui/react';
import { FC } from 'react'; import { FC } from 'react';
import { Box, Flex, Link, Stack, Text, useColorMode } from '@chakra-ui/react';
import NextLink from 'next/link'; import NextLink from 'next/link';
import { HamburgerIcon, LensIcon, MoonIcon, SunIcon } from '../UI/icons'; import { MoonIcon, SunIcon } from '../UI/icons';
import { DOCS_PAGE, DOWNLOADS_PAGE } from '../../constants'; import { Search } from './search';
import { HeaderButtons } from './';
import { MobileMenu } from '../layouts';
export const Header: FC = () => { export const Header: FC = () => {
const { colorMode, toggleColorMode } = useColorMode(); const { colorMode, toggleColorMode } = useColorMode();
const isDark = colorMode === 'dark'; const isDark = colorMode === 'dark';
return ( return (
<Flex mb={4} border='2px solid' borderColor='primary' justifyContent='space-between'> <Flex
mb={4}
border='2px'
borderColor='primary'
justifyContent='space-between'
position='relative'
>
<Stack <Stack
p={4} p={4}
justifyContent='center' justifyContent='center'
@ -26,71 +35,16 @@ export const Header: FC = () => {
</Stack> </Stack>
<Flex> <Flex>
{/* DOWNLOADS */} {/* HEADER BUTTONS */}
<Stack <Stack display={{base: 'none', md: 'block'}}>
p={4} <HeaderButtons />
justifyContent='center'
borderRight='2px'
borderColor='primary'
display={{ base: 'none', md: 'block' }}
color='primary'
_hover={{
textDecoration: 'none',
bg: 'primary',
color: 'bg !important'
}}
>
<NextLink href={DOWNLOADS_PAGE} passHref>
<Link _hover={{ textDecoration: 'none' }}>
<Text textStyle='header-font' textTransform='uppercase'>
downloads
</Text>
</Link>
</NextLink>
</Stack>
{/* DOCUMENTATION */}
<Stack
p={4}
justifyContent='center'
borderRight='2px'
borderColor='primary'
display={{ base: 'none', md: 'block' }}
color='primary'
_hover={{
textDecoration: 'none',
bg: 'primary',
color: 'bg !important'
}}
>
<NextLink href={DOCS_PAGE} passHref>
<Link _hover={{ textDecoration: 'none' }}>
<Text textStyle='header-font' textTransform='uppercase'>
documentation
</Text>
</Link>
</NextLink>
</Stack> </Stack>
{/* SEARCH */} {/* SEARCH */}
<Stack <Stack
p={4}
display={{ base: 'none', md: 'block' }} display={{ base: 'none', md: 'block' }}
borderRight='2px'
borderColor='primary'
> >
<InputGroup> <Search />
<Input
variant='unstyled'
placeholder='search'
size='md'
_placeholder={{ color: 'primary', fontStyle: 'italic' }}
/>
<Stack pl={4} justifyContent='center' alignItems='center'>
<LensIcon color='primary' />
</Stack>
</InputGroup>
</Stack> </Stack>
{/* DARK MODE SWITCH */} {/* DARK MODE SWITCH */}
@ -107,12 +61,11 @@ export const Header: FC = () => {
> >
{isDark ? <SunIcon color='primary' /> : <MoonIcon color='primary' />} {isDark ? <SunIcon color='primary' /> : <MoonIcon color='primary' />}
</Box> </Box>
{/* HAMBURGER MENU */}
<Box p={4} display={{ base: 'block', md: 'none' }}>
<HamburgerIcon color='primary' />
</Box>
</Flex> </Flex>
{/* MOBILE MENU */}
<MobileMenu />
</Flex> </Flex>
); );
}; };

View File

@ -0,0 +1,48 @@
import { FC, MouseEventHandler } from 'react';
import { Flex, Link, Stack, Text } from '@chakra-ui/react';
import NextLink from 'next/link';
import { BORDER_WIDTH, DOCS_PAGE, DOWNLOADS_PAGE } from '../../constants';
interface Props {
close?: MouseEventHandler<HTMLAnchorElement>;
}
export const HeaderButtons: FC<Props> = ({ close }) => {
const menuItemStyles = {
p: { base: 8, md: 4 },
borderBottom: { base: BORDER_WIDTH, md: 'none' },
borderRight: { base: 'none', md: BORDER_WIDTH },
borderColor: { base: 'bg', md: 'primary' },
color: { base: 'bg', md: 'primary' },
alignItems: 'center',
_hover: {
textDecoration: 'none',
bg: 'primary',
color: 'bg !important'
}
};
return (
<Flex direction={{ base: 'column', md: 'row' }}>
{/* DOWNLOADS */}
<NextLink href={DOWNLOADS_PAGE} passHref>
<Link _hover={{ textDecoration: 'none' }} onClick={close}>
<Stack {...menuItemStyles}>
<Text textStyle={{ base: 'header-mobile-button', md: 'header-button' }}>downloads</Text>
</Stack>
</Link>
</NextLink>
{/* DOCUMENTATION */}
<NextLink href={DOCS_PAGE} passHref>
<Link _hover={{ textDecoration: 'none' }} onClick={close}>
<Stack {...menuItemStyles}>
<Text textStyle={{ base: 'header-mobile-button', md: 'header-button' }}>
documentation
</Text>
</Stack>
</Link>
</NextLink>
</Flex>
);
};

View File

@ -1,4 +1,5 @@
export * from './ButtonLinkSecondary'; export * from './ButtonLinkSecondary';
export * from './DataTable'; export * from './DataTable';
export * from './Header'; export * from './Header';
export * from './HeaderButtons';
export * from './PageMetadata'; export * from './PageMetadata';

View File

@ -0,0 +1,30 @@
import { FC } from 'react';
import { Input, InputGroup, Stack } from '@chakra-ui/react'
import { BORDER_WIDTH } from '../../../constants'
import { LensIcon } from '../icons';
export const Search: FC = () => {
return (
<Stack
borderBottom={{ base: BORDER_WIDTH, md: 'none' }}
borderRight={{ base: 'none', md: BORDER_WIDTH }}
borderColor={{ base: 'bg', md: 'primary' }}
px={4}
py={{ base: 8, md: 4 }}
_hover={{ base: {bg: 'primary'}, md: {bg: 'none'}}}
>
<InputGroup>
<Input
variant='unstyled'
placeholder='search'
size='md'
_placeholder={{ color: {base: 'bg', md: 'primary'}, fontStyle: 'italic' }}
/>
<Stack pl={4} justifyContent='center' alignItems='center'>
<LensIcon color={{ base: 'bg', md: 'primary' }} fontSize={{ base: '3xl', md: 'md' }} />
</Stack>
</InputGroup>
</Stack>
);
};

View File

@ -0,0 +1 @@
export * from './Search';

View File

@ -12,7 +12,7 @@ interface Props {
export const Layout: FC<Props> = ({ children }) => { export const Layout: FC<Props> = ({ children }) => {
return ( return (
<Container maxW={{ base: 'container.sm', md: 'container.2xl' }} my={7}> <Container maxW={{ base: 'container.sm', md: 'container.2xl' }} my={{ base: 4, md: 7 }}>
<Header /> <Header />
{children} {children}

View File

@ -0,0 +1,71 @@
import { Box, Flex, Modal, ModalContent, ModalOverlay, useDisclosure } from '@chakra-ui/react';
import { CloseIcon } from '@chakra-ui/icons';
import { HamburgerIcon } from '../UI/icons';
import { Search } from '../UI/search';
import { HeaderButtons } from '../UI';
import { BORDER_WIDTH } from '../../constants';
export const MobileMenu: React.FC = () => {
const { isOpen, onOpen, onClose } = useDisclosure();
return (
<>
{/* HAMBURGER MENU ICON */}
<Box
as='button'
p={4}
display={{ base: 'block', md: 'none' }}
color='primary'
_hover={{ bg: 'primary', color: 'bg' }}
onClick={onOpen}
>
<HamburgerIcon />
</Box>
{/* MODAL */}
<Modal isOpen={isOpen} onClose={onClose} motionPreset='none'>
<ModalOverlay />
<ModalContent>
{/* MOBILE MENU */}
<Flex
position='fixed'
maxW='min(calc(var(--chakra-sizes-container-sm) - 2rem), 100vw - 2rem)'
marginInline='auto'
inset='0'
top={4}
mb={4}
color='bg'
bg='secondary'
border={BORDER_WIDTH}
overflow='hidden'
direction='column'
>
<Flex borderBottom={BORDER_WIDTH} justify='flex-end'>
{/* CLOSE ICON */}
<Box
as='button'
p={4}
borderInlineStartWidth={BORDER_WIDTH}
borderColor='bg'
color='bg'
_hover={{ bg: 'primary' }}
onClick={onClose}
ms='auto'
>
<CloseIcon boxSize={5} />
</Box>
</Flex>
{/* HEADER BUTTONS */}
<HeaderButtons close={onClose} />
{/* SEARCH */}
<Search />
</Flex>
</ModalContent>
</Modal>
</>
);
};

View File

@ -1 +1,3 @@
export { Layout } from './Layout'; export { Layout } from './Layout';
export { Footer } from './Footer';
export { MobileMenu } from './MobileMenu';

View File

@ -2,6 +2,8 @@ import React from 'react';
import { IconProps } from '@chakra-ui/react'; import { IconProps } from '@chakra-ui/react';
import { WindowsLogo, MacosLogo, LinuxPenguin, SourceBranch } from './components/UI/icons'; import { WindowsLogo, MacosLogo, LinuxPenguin, SourceBranch } from './components/UI/icons';
export const BORDER_WIDTH = '2px';
// internal pages // internal pages
export const DOWNLOADS_PAGE = '/downloads'; export const DOWNLOADS_PAGE = '/downloads';
export const DOCS_PAGE = '/docs'; export const DOCS_PAGE = '/docs';

View File

@ -90,6 +90,16 @@ export const textStyles = {
textAlign: 'center', textAlign: 'center',
fontSize: 'sm' fontSize: 'sm'
}, },
'header-button': {
fontFamily: '"JetBrains Mono", monospace',
fontWeight: 700,
fontSize: { base: '0.86rem', sm: '1rem' },
},
'header-mobile-button': {
fontFamily: '"JetBrains Mono", monospace',
textTransform: 'uppercase',
fontSize: '2xl'
},
'inline-code-snippet': { 'inline-code-snippet': {
fontFamily: '"JetBrains Mono", monospace', fontFamily: '"JetBrains Mono", monospace',
fontWeight: 400, fontWeight: 400,