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:
parent
5364cb731e
commit
bd80434b83
|
@ -1,15 +1,24 @@
|
|||
import { Box, Flex, Input, InputGroup, Link, Stack, Text, useColorMode } from '@chakra-ui/react';
|
||||
import { FC } from 'react';
|
||||
import { Box, Flex, Link, Stack, Text, useColorMode } from '@chakra-ui/react';
|
||||
import NextLink from 'next/link';
|
||||
|
||||
import { HamburgerIcon, LensIcon, MoonIcon, SunIcon } from '../UI/icons';
|
||||
import { DOCS_PAGE, DOWNLOADS_PAGE } from '../../constants';
|
||||
import { MoonIcon, SunIcon } from '../UI/icons';
|
||||
import { Search } from './search';
|
||||
import { HeaderButtons } from './';
|
||||
import { MobileMenu } from '../layouts';
|
||||
|
||||
export const Header: FC = () => {
|
||||
const { colorMode, toggleColorMode } = useColorMode();
|
||||
const isDark = colorMode === 'dark';
|
||||
|
||||
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
|
||||
p={4}
|
||||
justifyContent='center'
|
||||
|
@ -26,71 +35,16 @@ export const Header: FC = () => {
|
|||
</Stack>
|
||||
|
||||
<Flex>
|
||||
{/* DOWNLOADS */}
|
||||
<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={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>
|
||||
{/* HEADER BUTTONS */}
|
||||
<Stack display={{base: 'none', md: 'block'}}>
|
||||
<HeaderButtons />
|
||||
</Stack>
|
||||
|
||||
{/* SEARCH */}
|
||||
<Stack
|
||||
p={4}
|
||||
display={{ base: 'none', md: 'block' }}
|
||||
borderRight='2px'
|
||||
borderColor='primary'
|
||||
>
|
||||
<InputGroup>
|
||||
<Input
|
||||
variant='unstyled'
|
||||
placeholder='search'
|
||||
size='md'
|
||||
_placeholder={{ color: 'primary', fontStyle: 'italic' }}
|
||||
/>
|
||||
|
||||
<Stack pl={4} justifyContent='center' alignItems='center'>
|
||||
<LensIcon color='primary' />
|
||||
</Stack>
|
||||
</InputGroup>
|
||||
<Search />
|
||||
</Stack>
|
||||
|
||||
{/* DARK MODE SWITCH */}
|
||||
|
@ -107,12 +61,11 @@ export const Header: FC = () => {
|
|||
>
|
||||
{isDark ? <SunIcon color='primary' /> : <MoonIcon color='primary' />}
|
||||
</Box>
|
||||
|
||||
{/* HAMBURGER MENU */}
|
||||
<Box p={4} display={{ base: 'block', md: 'none' }}>
|
||||
<HamburgerIcon color='primary' />
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
{/* MOBILE MENU */}
|
||||
<MobileMenu />
|
||||
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -1,4 +1,5 @@
|
|||
export * from './ButtonLinkSecondary';
|
||||
export * from './DataTable';
|
||||
export * from './Header';
|
||||
export * from './HeaderButtons';
|
||||
export * from './PageMetadata';
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
export * from './Search';
|
|
@ -12,7 +12,7 @@ interface Props {
|
|||
|
||||
export const Layout: FC<Props> = ({ children }) => {
|
||||
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 />
|
||||
|
||||
{children}
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -1 +1,3 @@
|
|||
export { Layout } from './Layout';
|
||||
export { Footer } from './Footer';
|
||||
export { MobileMenu } from './MobileMenu';
|
||||
|
|
|
@ -2,6 +2,8 @@ import React from 'react';
|
|||
import { IconProps } from '@chakra-ui/react';
|
||||
import { WindowsLogo, MacosLogo, LinuxPenguin, SourceBranch } from './components/UI/icons';
|
||||
|
||||
export const BORDER_WIDTH = '2px';
|
||||
|
||||
// internal pages
|
||||
export const DOWNLOADS_PAGE = '/downloads';
|
||||
export const DOCS_PAGE = '/docs';
|
||||
|
|
|
@ -90,6 +90,16 @@ export const textStyles = {
|
|||
textAlign: 'center',
|
||||
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': {
|
||||
fontFamily: '"JetBrains Mono", monospace',
|
||||
fontWeight: 400,
|
||||
|
|
Loading…
Reference in New Issue