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 { 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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 './ButtonLinkSecondary';
|
||||||
export * from './DataTable';
|
export * from './DataTable';
|
||||||
export * from './Header';
|
export * from './Header';
|
||||||
|
export * from './HeaderButtons';
|
||||||
export * from './PageMetadata';
|
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 }) => {
|
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}
|
||||||
|
|
|
@ -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 { 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 { 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';
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue