4
0
forked from crowetic/commerce

Update to new design. (#1103)

This commit is contained in:
Lee Robinson
2023-07-24 21:40:29 -05:00
committed by GitHub
parent d918fcc895
commit 59fc2bc2e9
61 changed files with 1002 additions and 1247 deletions

View File

@@ -0,0 +1,46 @@
'use client';
import clsx from 'clsx';
import { Menu } from 'lib/shopify/types';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { useEffect, useState } from 'react';
const FooterMenuItem = ({ item }: { item: Menu }) => {
const pathname = usePathname();
const [active, setActive] = useState(pathname === item.path);
useEffect(() => {
setActive(pathname === item.path);
}, [pathname, item.path]);
return (
<li className="mt-2 first:mt-1">
<Link
href={item.path}
className={clsx(
'underline-offset-4 hover:text-black hover:underline dark:hover:text-neutral-300',
{
'text-black dark:text-neutral-300': active
}
)}
>
{item.title}
</Link>
</li>
);
};
export default function FooterMenu({ menu }: { menu: Menu[] }) {
if (!menu.length) return null;
return (
<nav>
<ul>
{menu.map((item: Menu) => {
return <FooterMenuItem key={item.title} item={item} />;
})}
</ul>
</nav>
);
}

View File

@@ -1,68 +1,68 @@
import Link from 'next/link';
import GitHubIcon from 'components/icons/github';
import LogoIcon from 'components/icons/logo';
import VercelIcon from 'components/icons/vercel';
import FooterMenu from 'components/layout/footer-menu';
import LogoSquare from 'components/logo-square';
import { getMenu } from 'lib/shopify';
import { Menu } from 'lib/shopify/types';
import { Suspense } from 'react';
const { SITE_NAME } = process.env;
const { COMPANY_NAME, SITE_NAME } = process.env;
export default async function Footer() {
const currentYear = new Date().getFullYear();
const copyrightDate = 2023 + (currentYear > 2023 ? `-${currentYear}` : '');
const skeleton = 'w-full h-6 animate-pulse rounded bg-neutral-200 dark:bg-neutral-700';
const menu = await getMenu('next-js-frontend-footer-menu');
const copyrightName = COMPANY_NAME || SITE_NAME || '';
return (
<footer className="border-t border-gray-700 bg-white text-black dark:bg-black dark:text-white">
<div className="mx-auto w-full max-w-7xl px-6">
<div className="grid grid-cols-1 gap-8 border-b border-gray-700 py-12 transition-colors duration-150 lg:grid-cols-12">
<div className="col-span-1 lg:col-span-3">
<a className="flex flex-initial items-center font-bold md:mr-24" href="/">
<span className="mr-2">
<LogoIcon className="h-8" />
</span>
<span>{SITE_NAME}</span>
</a>
</div>
{menu.length ? (
<nav className="col-span-1 lg:col-span-7">
<ul className="grid md:grid-flow-col md:grid-cols-3 md:grid-rows-4">
{menu.map((item: Menu) => (
<li key={item.title} className="py-3 md:py-0 md:pb-4">
<Link
href={item.path}
className="text-gray-800 transition duration-150 ease-in-out hover:text-gray-300 dark:text-gray-100"
>
{item.title}
</Link>
</li>
))}
</ul>
</nav>
) : null}
<div className="col-span-1 text-black dark:text-white lg:col-span-2">
<a aria-label="Github Repository" href="https://github.com/vercel/commerce">
<GitHubIcon className="h-6" />
</a>
</div>
<footer className="text-sm text-neutral-400 dark:text-neutral-600">
<div className="mx-auto flex w-full max-w-7xl flex-col gap-6 border-t border-neutral-200 px-6 py-12 text-sm dark:border-neutral-700 md:flex-row md:gap-12 md:px-4 xl:px-0">
<div>
<Link className="flex items-center gap-2 text-black dark:text-white" href="/">
<LogoSquare size="sm" />
<span className="font-bold uppercase">{SITE_NAME}</span>
</Link>
</div>
<div className="flex flex-col items-center justify-between space-y-4 pb-10 pt-6 text-sm md:flex-row">
<Suspense
fallback={
<div className="flex h-[188px] w-[200px] flex-col gap-2">
<div className={skeleton} />
<div className={skeleton} />
<div className={skeleton} />
<div className={skeleton} />
<div className={skeleton} />
<div className={skeleton} />
</div>
}
>
<FooterMenu menu={menu} />
</Suspense>
<div className="md:ml-auto">
<a
className="flex items-center gap-2 hover:text-black dark:hover:text-neutral-300"
aria-label="Github Repository"
href="https://github.com/vercel/commerce"
>
<GitHubIcon className="h-6" />
<p>Source</p>
</a>
</div>
</div>
<div className="border-t border-neutral-200 py-6 text-sm dark:border-neutral-700">
<div className="mx-auto flex w-full max-w-7xl flex-col items-center gap-1 md:flex-row md:gap-0">
<p>
&copy; {copyrightDate} {SITE_NAME}. All rights reserved.
&copy; {copyrightDate} {copyrightName}
{copyrightName.length && !copyrightName.endsWith('.') ? '.' : ''} All rights reserved.
</p>
<div className="flex items-center text-sm text-white dark:text-black">
<span className="text-black dark:text-white">Created by</span>
<a
rel="noopener noreferrer"
href="https://vercel.com"
aria-label="Vercel.com Link"
target="_blank"
className="text-black dark:text-white"
>
<VercelIcon className="ml-3 inline-block h-6" />
<hr className="mx-4 hidden h-4 w-[1px] border-l border-neutral-400 md:inline-block" />
<p>Designed in California</p>
<p className="md:ml-auto">
Crafted by{' '}
<a href="https://vercel.com" className="text-black dark:text-white">
Vercel
</a>
</div>
</p>
</div>
</div>
</footer>

View File

@@ -1,51 +1,57 @@
import Link from 'next/link';
import { Suspense } from 'react';
import Cart from 'components/cart';
import CartIcon from 'components/icons/cart';
import LogoIcon from 'components/icons/logo';
import OpenCart from 'components/cart/open-cart';
import LogoSquare from 'components/logo-square';
import { getMenu } from 'lib/shopify';
import { Menu } from 'lib/shopify/types';
import Link from 'next/link';
import { Suspense } from 'react';
import MobileMenu from './mobile-menu';
import Search from './search';
const { SITE_NAME } = process.env;
export default async function Navbar() {
const menu = await getMenu('next-js-frontend-header-menu');
return (
<nav className="relative flex items-center justify-between bg-white p-4 dark:bg-black lg:px-6">
<div className="block w-1/3 md:hidden">
<nav className="relative flex items-center justify-between p-4 lg:px-6">
<div className="block flex-none md:hidden">
<MobileMenu menu={menu} />
</div>
<div className="flex justify-self-center md:w-1/3 md:justify-self-start">
<div className="md:mr-4">
<Link href="/" aria-label="Go back home">
<LogoIcon className="h-8 transition-transform hover:scale-110" />
<div className="flex w-full items-center">
<div className="flex w-full md:w-1/3">
<Link
href="/"
aria-label="Go back home"
className="mr-2 flex w-full items-center justify-center md:w-auto lg:mr-6"
>
<LogoSquare />
<div className="ml-2 flex-none text-sm font-medium uppercase md:hidden lg:block">
{SITE_NAME}
</div>
</Link>
{menu.length ? (
<ul className="hidden text-sm md:flex md:items-center">
{menu.map((item: Menu) => (
<li key={item.title}>
<Link
href={item.path}
className="mr-3 text-neutral-400 underline-offset-4 hover:text-black hover:underline dark:text-neutral-600 dark:hover:text-neutral-300 lg:mr-8"
>
{item.title}
</Link>
</li>
))}
</ul>
) : null}
</div>
<div className="hidden justify-center md:flex md:w-1/3">
<Search />
</div>
<div className="flex justify-end md:w-1/3">
<Suspense fallback={<OpenCart className="h-6" />}>
<Cart />
</Suspense>
</div>
{menu.length ? (
<ul className="hidden md:flex md:items-center">
{menu.map((item: Menu) => (
<li key={item.title}>
<Link
href={item.path}
className="rounded-lg px-2 py-1 text-gray-800 hover:text-gray-500 dark:text-gray-200 dark:hover:text-gray-400"
>
{item.title}
</Link>
</li>
))}
</ul>
) : null}
</div>
<div className="hidden w-1/3 md:block">
<Search />
</div>
<div className="flex w-1/3 justify-end">
<Suspense fallback={<CartIcon className="h-6" />}>
<Cart />
</Suspense>
</div>
</nav>
);

View File

@@ -5,8 +5,7 @@ import Link from 'next/link';
import { usePathname, useSearchParams } from 'next/navigation';
import { Fragment, useEffect, useState } from 'react';
import CloseIcon from 'components/icons/close';
import MenuIcon from 'components/icons/menu';
import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline';
import { Menu } from 'lib/shopify/types';
import Search from './search';
@@ -33,13 +32,8 @@ export default function MobileMenu({ menu }: { menu: Menu[] }) {
return (
<>
<button
onClick={openMobileMenu}
aria-label="Open mobile menu"
className="md:hidden"
data-testid="open-mobile-menu"
>
<MenuIcon className="h-6" />
<button onClick={openMobileMenu} aria-label="Open mobile menu" className="md:hidden">
<Bars3Icon className="h-6" />
</button>
<Transition show={isOpen}>
<Dialog onClose={closeMobileMenu} className="relative z-50">
@@ -65,13 +59,8 @@ export default function MobileMenu({ menu }: { menu: Menu[] }) {
>
<Dialog.Panel className="fixed bottom-0 left-0 right-0 top-0 flex h-full w-full flex-col bg-white pb-6 dark:bg-black">
<div className="p-4">
<button
className="mb-4"
onClick={closeMobileMenu}
aria-label="Close mobile menu"
data-testid="close-mobile-menu"
>
<CloseIcon className="h-6" />
<button className="mb-4" onClick={closeMobileMenu} aria-label="Close mobile menu">
<XMarkIcon className="h-6" />
</button>
<div className="mb-4 w-full">
@@ -83,7 +72,7 @@ export default function MobileMenu({ menu }: { menu: Menu[] }) {
<li key={item.title}>
<Link
href={item.path}
className="rounded-lg py-1 text-xl text-black transition-colors hover:text-gray-500 dark:text-white"
className="rounded-lg py-1 text-xl text-black transition-colors hover:text-neutral-500 dark:text-white"
onClick={closeMobileMenu}
>
{item.title}

View File

@@ -1,13 +1,19 @@
'use client';
import { useRouter, useSearchParams } from 'next/navigation';
import { useEffect, useState } from 'react';
import SearchIcon from 'components/icons/search';
import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
import { createUrl } from 'lib/utils';
export default function Search() {
const router = useRouter();
const searchParams = useSearchParams();
const [searchValue, setSearchValue] = useState('');
useEffect(() => {
setSearchValue(searchParams?.get('q') || '');
}, [searchParams, setSearchValue]);
function onSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
@@ -26,20 +32,18 @@ export default function Search() {
}
return (
<form
onSubmit={onSubmit}
className="relative m-0 flex w-full items-center border border-gray-200 bg-transparent p-0 dark:border-gray-500"
>
<form onSubmit={onSubmit} className="relative w-full lg:w-[320px]">
<input
type="text"
name="search"
placeholder="Search for products..."
autoComplete="off"
defaultValue={searchParams?.get('q') || ''}
className="w-full px-4 py-2 text-black dark:bg-black dark:text-gray-100"
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
className="w-full rounded-lg border bg-white px-4 py-2 text-sm text-black placeholder:text-neutral-800 dark:border-neutral-800 dark:bg-transparent dark:text-neutral-500 dark:placeholder:text-neutral-500"
/>
<div className="absolute right-0 top-0 mr-3 flex h-full items-center">
<SearchIcon className="h-5" />
<MagnifyingGlassIcon className="h-4" />
</div>
</form>
);

View File

@@ -8,11 +8,10 @@ export default function ProductGridItems({ products }: { products: Product[] })
<>
{products.map((product) => (
<Grid.Item key={product.handle} className="animate-fadeIn">
<Link className="h-full w-full" href={`/product/${product.handle}`}>
<Link className="inline-block h-full w-full" href={`/product/${product.handle}`}>
<GridTileImage
alt={product.title}
labels={{
isSmall: true,
label={{
title: product.title,
amount: product.priceRange.maxVariantPrice.amount,
currencyCode: product.priceRange.maxVariantPrice.currencyCode

View File

@@ -10,14 +10,14 @@ async function CollectionList() {
}
const skeleton = 'mb-3 h-4 w-5/6 animate-pulse rounded';
const activeAndTitles = 'bg-gray-800 dark:bg-gray-300';
const items = 'bg-gray-400 dark:bg-gray-700';
const activeAndTitles = 'bg-neutral-800 dark:bg-neutral-300';
const items = 'bg-neutral-400 dark:bg-neutral-700';
export default function Collections() {
return (
<Suspense
fallback={
<div className="col-span-2 hidden h-[400px] w-full flex-none py-4 pl-10 lg:block">
<div className="col-span-2 hidden h-[400px] w-full flex-none py-4 lg:block">
<div className={clsx(skeleton, activeAndTitles)} />
<div className={clsx(skeleton, activeAndTitles)} />
<div className={clsx(skeleton, items)} />

View File

@@ -3,7 +3,7 @@
import { usePathname, useSearchParams } from 'next/navigation';
import { useEffect, useRef, useState } from 'react';
import Caret from 'components/icons/caret-right';
import { ChevronDownIcon } from '@heroicons/react/24/outline';
import type { ListItem } from '.';
import { FilterItem } from './item';
@@ -45,7 +45,7 @@ export default function FilterItemDropdown({ list }: { list: ListItem[] }) {
className="flex w-full items-center justify-between rounded border border-black/30 px-4 py-2 text-sm dark:border-white/30"
>
<div>{active}</div>
<Caret className="h-4 rotate-90" />
<ChevronDownIcon className="h-4" />
</div>
{openSelect && (
<div

View File

@@ -7,21 +7,19 @@ export type PathFilterItem = { title: string; path: string };
function FilterItemList({ list }: { list: ListItem[] }) {
return (
<div className="hidden md:block">
<>
{list.map((item: ListItem, i) => (
<FilterItem key={i} item={item} />
))}
</div>
</>
);
}
export default function FilterList({ list, title }: { list: ListItem[]; title?: string }) {
return (
<>
<nav className="col-span-2 w-full flex-none px-6 py-2 md:py-4 md:pl-10">
{title ? (
<h3 className="hidden font-semibold text-black dark:text-white md:block">{title}</h3>
) : null}
<nav>
{title ? <h3 className="hidden text-xs text-neutral-500 md:block">{title}</h3> : null}
<ul className="hidden md:block">
<FilterItemList list={list} />
</ul>

View File

@@ -13,6 +13,7 @@ function PathFilterItem({ item }: { item: PathFilterItem }) {
const searchParams = useSearchParams();
const [active, setActive] = useState(pathname === item.path);
const newParams = new URLSearchParams(searchParams.toString());
const DynamicTag = active ? 'p' : Link;
newParams.delete('q');
@@ -21,16 +22,18 @@ function PathFilterItem({ item }: { item: PathFilterItem }) {
}, [pathname, item.path]);
return (
<li className="mt-2 flex text-sm text-gray-400" key={item.title}>
<Link
<li className="mt-2 flex text-black dark:text-white" key={item.title}>
<DynamicTag
href={createUrl(item.path, newParams)}
className={clsx('w-full hover:text-gray-800 dark:hover:text-gray-100', {
'text-gray-600 dark:text-gray-400': !active,
'font-semibold text-black dark:text-white': active
})}
className={clsx(
'w-full text-sm underline-offset-4 hover:underline dark:hover:text-neutral-100',
{
'underline underline-offset-4': active
}
)}
>
{item.title}
</Link>
</DynamicTag>
</li>
);
}
@@ -40,34 +43,30 @@ function SortFilterItem({ item }: { item: SortFilterItem }) {
const searchParams = useSearchParams();
const [active, setActive] = useState(searchParams.get('sort') === item.slug);
const q = searchParams.get('q');
const href = createUrl(
pathname,
new URLSearchParams({
...(q && { q }),
...(item.slug && item.slug.length && { sort: item.slug })
})
);
const DynamicTag = active ? 'p' : Link;
useEffect(() => {
setActive(searchParams.get('sort') === item.slug);
}, [searchParams, item.slug]);
const href =
item.slug && item.slug.length
? createUrl(
pathname,
new URLSearchParams({
...(q && { q }),
sort: item.slug
})
)
: pathname;
return (
<li className="mt-2 flex text-sm text-gray-400" key={item.title}>
<Link
prefetch={false}
<li className="mt-2 flex text-sm text-black dark:text-white" key={item.title}>
<DynamicTag
prefetch={!active ? false : undefined}
href={href}
className={clsx('w-full hover:text-gray-800 dark:hover:text-gray-100', {
'text-gray-600 dark:text-gray-400': !active,
'font-semibold text-black dark:text-white': active
className={clsx('w-full', {
'underline underline-offset-4': active
})}
>
{item.title}
</Link>
</DynamicTag>
</li>
);
}