Merge pull request #271 from premiare/feat/mobileNavigation

feat: Mobile Navigation
This commit is contained in:
Lucas Smith
2023-08-21 20:01:08 +10:00
committed by GitHub
7 changed files with 192 additions and 8 deletions

View File

@ -37,4 +37,4 @@
"@types/react": "18.2.18", "@types/react": "18.2.18",
"@types/react-dom": "18.2.7" "@types/react-dom": "18.2.7"
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@ -3,7 +3,7 @@ import { HTMLAttributes } from 'react';
import Image from 'next/image'; import Image from 'next/image';
import Link from 'next/link'; import Link from 'next/link';
import { Github, Slack, Twitter } from 'lucide-react'; import { Github, MessagesSquare, Twitter } from 'lucide-react';
import { cn } from '@documenso/ui/lib/utils'; import { cn } from '@documenso/ui/lib/utils';
@ -36,11 +36,11 @@ export const Footer = ({ className, ...props }: FooterProps) => {
</Link> </Link>
<Link <Link
href="https://documenso.slack.com" href="https://documen.so/discord"
target="_blank" target="_blank"
className="hover:text-[#6D6D6D]" className="hover:text-[#6D6D6D]"
> >
<Slack className="h-6 w-6" /> <MessagesSquare className="h-6 w-6" />
</Link> </Link>
</div> </div>
</div> </div>

View File

@ -1,20 +1,27 @@
import { HTMLAttributes } from 'react'; 'use client';
import { HTMLAttributes, useState } from 'react';
import Image from 'next/image'; import Image from 'next/image';
import Link from 'next/link'; import Link from 'next/link';
import { cn } from '@documenso/ui/lib/utils'; import { cn } from '@documenso/ui/lib/utils';
import { HamburgerMenu } from './mobile-hamburger';
import { MobileNavigation } from './mobile-navigation';
export type HeaderProps = HTMLAttributes<HTMLElement>; export type HeaderProps = HTMLAttributes<HTMLElement>;
export const Header = ({ className, ...props }: HeaderProps) => { export const Header = ({ className, ...props }: HeaderProps) => {
const [isHamburgerMenuOpen, setIsHamburgerMenuOpen] = useState(false);
return ( return (
<header className={cn('flex items-center justify-between', className)} {...props}> <header className={cn('flex items-center justify-between', className)} {...props}>
<Link href="/"> <Link href="/" className="z-10" onClick={() => setIsHamburgerMenuOpen(false)}>
<Image src="/logo.png" alt="Documenso Logo" width={170} height={0}></Image> <Image src="/logo.png" alt="Documenso Logo" width={170} height={25} />
</Link> </Link>
<div className="flex items-center gap-x-6"> <div className="hidden items-center gap-x-6 md:flex">
<Link href="/pricing" className="text-sm font-semibold text-[#8D8D8D] hover:text-[#6D6D6D]"> <Link href="/pricing" className="text-sm font-semibold text-[#8D8D8D] hover:text-[#6D6D6D]">
Pricing Pricing
</Link> </Link>
@ -35,6 +42,15 @@ export const Header = ({ className, ...props }: HeaderProps) => {
Sign in Sign in
</Link> </Link>
</div> </div>
<HamburgerMenu
onToggleMenuOpen={() => setIsHamburgerMenuOpen((v) => !v)}
isMenuOpen={isHamburgerMenuOpen}
/>
<MobileNavigation
isMenuOpen={isHamburgerMenuOpen}
onMenuOpenChange={setIsHamburgerMenuOpen}
/>
</header> </header>
); );
}; };

View File

@ -0,0 +1,20 @@
'use client';
import { Menu, X } from 'lucide-react';
import { Button } from '@documenso/ui/primitives/button';
export interface HamburgerMenuProps {
isMenuOpen: boolean;
onToggleMenuOpen?: () => void;
}
export const HamburgerMenu = ({ isMenuOpen, onToggleMenuOpen }: HamburgerMenuProps) => {
return (
<div className="flex md:hidden">
<Button variant="outline" className="z-20 w-10 p-0" onClick={onToggleMenuOpen}>
{isMenuOpen ? <X /> : <Menu />}
</Button>
</div>
);
};

View File

@ -0,0 +1,121 @@
'use client';
import Image from 'next/image';
import Link from 'next/link';
import { motion, useReducedMotion } from 'framer-motion';
import { Github, MessagesSquare, Twitter } from 'lucide-react';
import { Sheet, SheetContent } from '@documenso/ui/primitives/sheet';
export type MobileNavigationProps = {
isMenuOpen: boolean;
onMenuOpenChange?: (_value: boolean) => void;
};
export const MENU_NAVIGATION_LINKS = [
{
href: '/blog',
text: 'Blog',
},
{
href: '/pricing',
text: 'Pricing',
},
{
href: 'https://status.documenso.com',
text: 'Status',
},
{
href: 'mailto:support@documenso.com',
text: 'Support',
},
{
href: '/privacy',
text: 'Privacy',
},
{
href: 'https://app.documenso.com/login',
text: 'Sign in',
},
];
export const MobileNavigation = ({ isMenuOpen, onMenuOpenChange }: MobileNavigationProps) => {
const shouldReduceMotion = useReducedMotion();
const handleMenuItemClick = () => {
onMenuOpenChange?.(false);
};
return (
<Sheet open={isMenuOpen} onOpenChange={onMenuOpenChange}>
<SheetContent className="w-full max-w-[400px]">
<Link href="/" className="z-10" onClick={handleMenuItemClick}>
<Image src="/logo.png" alt="Documenso Logo" width={170} height={25} />
</Link>
<motion.div
className="mt-12 flex w-full flex-col items-start gap-y-4"
initial="initial"
animate="animate"
transition={{
staggerChildren: 0.2,
}}
>
{MENU_NAVIGATION_LINKS.map(({ href, text }) => (
<motion.div
key={href}
variants={{
initial: {
opacity: 0,
x: shouldReduceMotion ? 0 : 100,
},
animate: {
opacity: 1,
x: 0,
transition: {
duration: 0.5,
},
},
}}
>
<Link
className="text-2xl font-semibold text-[#8D8D8D] hover:text-[#6D6D6D]"
href={href}
onClick={() => handleMenuItemClick()}
>
{text}
</Link>
</motion.div>
))}
</motion.div>
<div className="mx-auto mt-8 flex w-full flex-wrap items-center gap-x-4 gap-y-4 ">
<Link
href="https://twitter.com/documenso"
target="_blank"
className="text-[#8D8D8D] hover:text-[#6D6D6D]"
>
<Twitter className="h-6 w-6" />
</Link>
<Link
href="https://github.com/documenso/documenso"
target="_blank"
className="text-[#8D8D8D] hover:text-[#6D6D6D]"
>
<Github className="h-6 w-6" />
</Link>
<Link
href="https://documen.so/discord"
target="_blank"
className="text-[#8D8D8D] hover:text-[#6D6D6D]"
>
<MessagesSquare className="h-6 w-6" />
</Link>
</div>
</SheetContent>
</Sheet>
);
};

View File

@ -0,0 +1,27 @@
import { useEffect, useState } from 'react';
export function useWindowSize() {
const [size, setSize] = useState({
width: 0,
height: 0,
});
const onResize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight,
});
};
useEffect(() => {
onResize();
window.addEventListener('resize', onResize);
return () => {
window.removeEventListener('resize', onResize);
};
}, []);
return size;
}