Skip to content

Commit

Permalink
feat/fix: improve main navigation on mobile devices (#959)
Browse files Browse the repository at this point in the history
  • Loading branch information
grzegorzpokorski authored Oct 19, 2023
1 parent 75b295d commit 8c41975
Show file tree
Hide file tree
Showing 16 changed files with 307 additions and 121 deletions.
6 changes: 3 additions & 3 deletions src/app/(main)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type ReactNode } from "react";
import { Footer } from "@/ui/components/Footer";
import { Nav } from "@/ui/components/Nav";
import { Header } from "@/ui/components/Header";

export const metadata = {
title: "Saleor Storefront example",
Expand All @@ -10,8 +10,8 @@ export const metadata = {
export default function RootLayout(props: { children: ReactNode }) {
return (
<>
<Nav />
<div className="min-h-[calc(100vh-106px)] flex-grow">{props.children}</div>
<Header />
<main className="min-h-[calc(100vh-106px)] flex-grow">{props.children}</main>
<Footer />
</>
);
Expand Down
2 changes: 1 addition & 1 deletion src/app/checkout/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ export const metadata = {
};

export default function RootLayout(props: { children: ReactNode }) {
return <div>{props.children}</div>;
return <main>{props.children}</main>;
}
23 changes: 0 additions & 23 deletions src/ui/components/ActiveLink.tsx

This file was deleted.

19 changes: 0 additions & 19 deletions src/ui/components/CartNavItem.tsx

This file was deleted.

15 changes: 15 additions & 0 deletions src/ui/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Logo } from "./Logo";
import { Nav } from "./nav/Nav";

export function Header() {
return (
<header className="sticky top-0 z-20 bg-neutral-100/50 backdrop-blur-md">
<div className="mx-auto max-w-7xl px-3 sm:px-8">
<div className="flex h-16 justify-between gap-4 md:gap-8">
<Logo />
<Nav />
</div>
</div>
</header>
);
}
25 changes: 25 additions & 0 deletions src/ui/components/Logo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"use client";

import Link from "next/link";
import { usePathname } from "next/navigation";

const companyName = "ACME";

export const Logo = () => {
const pathname = usePathname();

if (pathname === "/") {
return (
<h1 className="flex items-center font-bold" aria-label="homepage">
{companyName}
</h1>
);
}
return (
<div className="flex items-center font-bold">
<Link aria-label="homepage" href="/">
{companyName}
</Link>
</div>
);
};
73 changes: 0 additions & 73 deletions src/ui/components/Nav.tsx

This file was deleted.

26 changes: 26 additions & 0 deletions src/ui/components/nav/Nav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Suspense } from "react";
import { AccountLink } from "./components/AccountLink";
import { CartNavItem } from "./components/CartNavItem";
import { NavLinks } from "./components/NavLinks";
import { MobileMenu } from "./components/MobileMenu";

export const Nav = () => {
return (
<nav className="flex w-full gap-4 lg:gap-8" aria-label="Main navigation">
<ul className="hidden gap-4 overflow-x-auto whitespace-nowrap md:flex lg:gap-8 lg:px-0">
<NavLinks />
</ul>
<div className="ml-auto flex items-center justify-center whitespace-nowrap">
<AccountLink />
</div>
<div className="flex items-center">
<Suspense fallback={<div className="w-6" />}>
<CartNavItem />
</Suspense>
</div>
<MobileMenu>
<NavLinks />
</MobileMenu>
</nav>
);
};
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React from "react";
import Link from "next/link";
import { UserIcon } from "@/checkout/ui-kit/icons/User";
import { UserIcon } from "lucide-react";

export function AccountLink() {
return (
<Link href="/login" className="h-6 w-6 flex-shrink-0">
<UserIcon />
<UserIcon className="h-6 w-6 shrink-0" aria-hidden="true" />
<span className="sr-only">Log in</span>
</Link>
);
}
31 changes: 31 additions & 0 deletions src/ui/components/nav/components/CartNavItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { cookies } from "next/headers";
import { ShoppingBagIcon } from "lucide-react";
import Link from "next/link";
import clsx from "clsx";
import * as Checkout from "@/lib/checkout";

export const CartNavItem = async () => {
const checkoutId = cookies().get("checkoutId")?.value || "";
const checkout = await Checkout.find(checkoutId);

const lineCount = checkout ? checkout.lines.reduce((result, line) => result + line.quantity, 0) : 0;

return (
<Link href="/cart" className="relative flex items-center">
<ShoppingBagIcon className="h-6 w-6 shrink-0" aria-hidden="true" />
{lineCount > 0 ? (
<div
className={clsx(
"absolute bottom-0 right-0 -mb-2 -mr-2 flex h-4 flex-col items-center justify-center rounded bg-neutral-900 text-xs font-medium text-white",
lineCount > 9 ? "w-[3ch]" : "w-[2ch]",
)}
>
{lineCount}
<span className="sr-only">items in cart, view bag</span>
</div>
) : (
<span className="sr-only">cart</span>
)}
</Link>
);
};
22 changes: 22 additions & 0 deletions src/ui/components/nav/components/CloseButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import clsx from "clsx";
import { XIcon } from "lucide-react";
import { type HTMLAttributes } from "react";

type Props = {
onClick: () => void;
} & Pick<HTMLAttributes<HTMLButtonElement>, "aria-controls">;

export const CloseButton = (props: Props) => {
return (
<button
className={clsx(
"top-0 ml-auto flex h-8 w-8 flex-col items-center justify-center gap-1.5 self-end self-center md:hidden",
)}
aria-controls={props["aria-controls"]}
aria-label="Close menu"
onClick={props.onClick}
>
<XIcon className="h-6 w-6 shrink-0" aria-hidden />
</button>
);
};
48 changes: 48 additions & 0 deletions src/ui/components/nav/components/MobileMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"use client";

import { Fragment, type ReactNode } from "react";
import { Dialog, Transition } from "@headlessui/react";
import { Logo } from "../../Logo";
import { useMobileMenu } from "./useMobileMenu";
import { OpenButton } from "./OpenButton";
import { CloseButton } from "./CloseButton";

type Props = {
children: ReactNode;
};

export const MobileMenu = ({ children }: Props) => {
const { closeMenu, openMenu, isOpen } = useMobileMenu({ breakpoint: 768 });

return (
<>
<OpenButton onClick={openMenu} aria-controls="mobile-menu" />
<Transition show={isOpen}>
<Dialog open={isOpen} onClose={closeMenu}>
<Dialog.Panel className="fixed inset-0 z-20 flex h-[100dvh] flex-col overflow-y-scroll bg-white">
<div className="sticky top-0 z-10 flex h-16 shrink-0 bg-neutral-100/50 px-3 backdrop-blur-md sm:px-8">
<Logo />
<CloseButton onClick={closeMenu} aria-controls="mobile-menu" />
</div>
<Transition.Child
as={Fragment}
enter="motion-safe:transition-all motion-safe:duration-300"
enterFrom="opacity-0 -translate-y-3"
enterTo="opacity-100 translate-y-0"
leave="motion-safe:transition-all motion-safe:duration-300"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 -translate-y-3"
>
<ul
className="flex flex-col divide-y divide-neutral-200 whitespace-nowrap p-3 pt-0 sm:p-8 sm:pt-0 [&>li]:py-3"
id="mobile-menu"
>
{children}
</ul>
</Transition.Child>
</Dialog.Panel>
</Dialog>
</Transition>
</>
);
};
25 changes: 25 additions & 0 deletions src/ui/components/nav/components/NavLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"use client";

import clsx from "clsx";
import Link from "next/link";
import { usePathname } from "next/navigation";

export function NavLink({ href, children }: { href: string; children: JSX.Element | string }) {
const pathname = usePathname();

const isActive = pathname === href;

return (
<li className="inline-flex">
<Link
href={href}
className={clsx(
isActive ? "border-neutral-900 text-neutral-900" : "border-transparent text-neutral-500",
"inline-flex items-center border-b-2 pt-px text-sm font-medium hover:text-neutral-700",
)}
>
{children}
</Link>
</li>
);
}
Loading

0 comments on commit 8c41975

Please sign in to comment.