Files
vercel/examples/hydrogen-2/app/components/Header.tsx
Nathan Rajlich 97659c687b [examples] Add "hydrogen-2" template (#10319)
Adds a Hydrogen v2 template which is the output of the `npm create @shopify/hydrogen@latest` command.

Note that a `vercel.json` file is being used to define the environment variables that are required at runtime. This is required for the template to deploy with zero configuration, however the user should update these values (including replacing the session secret) and migrate them to the Project settings in the Vercel dashboard.

[Live example](https://hydrogen-v2-template.vercel.app)
2023-08-10 00:11:33 +00:00

179 lines
4.1 KiB
TypeScript

import {Await, NavLink, useMatches} from '@remix-run/react';
import {Suspense} from 'react';
import type {LayoutProps} from './Layout';
type HeaderProps = Pick<LayoutProps, 'header' | 'cart' | 'isLoggedIn'>;
type Viewport = 'desktop' | 'mobile';
export function Header({header, isLoggedIn, cart}: HeaderProps) {
const {shop, menu} = header;
return (
<header className="header">
<NavLink prefetch="intent" to="/" style={activeLinkStyle} end>
<strong>{shop.name}</strong>
</NavLink>
<HeaderMenu menu={menu} viewport="desktop" />
<HeaderCtas isLoggedIn={isLoggedIn} cart={cart} />
</header>
);
}
export function HeaderMenu({
menu,
viewport,
}: {
menu: HeaderProps['header']['menu'];
viewport: Viewport;
}) {
const [root] = useMatches();
const publicStoreDomain = root?.data?.publicStoreDomain;
const className = `header-menu-${viewport}`;
function closeAside(event: React.MouseEvent<HTMLAnchorElement>) {
if (viewport === 'mobile') {
event.preventDefault();
window.location.href = event.currentTarget.href;
}
}
return (
<nav className={className} role="navigation">
{viewport === 'mobile' && (
<NavLink
end
onClick={closeAside}
prefetch="intent"
style={activeLinkStyle}
to="/"
>
Home
</NavLink>
)}
{(menu || FALLBACK_HEADER_MENU).items.map((item) => {
if (!item.url) return null;
// if the url is internal, we strip the domain
const url =
item.url.includes('myshopify.com') ||
item.url.includes(publicStoreDomain)
? new URL(item.url).pathname
: item.url;
return (
<NavLink
className="header-menu-item"
end
key={item.id}
onClick={closeAside}
prefetch="intent"
style={activeLinkStyle}
to={url}
>
{item.title}
</NavLink>
);
})}
</nav>
);
}
function HeaderCtas({
isLoggedIn,
cart,
}: Pick<HeaderProps, 'isLoggedIn' | 'cart'>) {
return (
<nav className="header-ctas" role="navigation">
<HeaderMenuMobileToggle />
<NavLink prefetch="intent" to="/account" style={activeLinkStyle}>
{isLoggedIn ? 'Account' : 'Sign in'}
</NavLink>
<SearchToggle />
<CartToggle cart={cart} />
</nav>
);
}
function HeaderMenuMobileToggle() {
return (
<a className="header-menu-mobile-toggle" href="#mobile-menu-aside">
<h3></h3>
</a>
);
}
function SearchToggle() {
return <a href="#search-aside">Search</a>;
}
function CartBadge({count}: {count: number}) {
return <a href="#cart-aside">Cart {count}</a>;
}
function CartToggle({cart}: Pick<HeaderProps, 'cart'>) {
return (
<Suspense fallback={<CartBadge count={0} />}>
<Await resolve={cart}>
{(cart) => {
if (!cart) return <CartBadge count={0} />;
return <CartBadge count={cart.totalQuantity || 0} />;
}}
</Await>
</Suspense>
);
}
const FALLBACK_HEADER_MENU = {
id: 'gid://shopify/Menu/199655587896',
items: [
{
id: 'gid://shopify/MenuItem/461609500728',
resourceId: null,
tags: [],
title: 'Collections',
type: 'HTTP',
url: '/collections',
items: [],
},
{
id: 'gid://shopify/MenuItem/461609533496',
resourceId: null,
tags: [],
title: 'Blog',
type: 'HTTP',
url: '/blogs/journal',
items: [],
},
{
id: 'gid://shopify/MenuItem/461609566264',
resourceId: null,
tags: [],
title: 'Policies',
type: 'HTTP',
url: '/policies',
items: [],
},
{
id: 'gid://shopify/MenuItem/461609599032',
resourceId: 'gid://shopify/Page/92591030328',
tags: [],
title: 'About',
type: 'PAGE',
url: '/pages/about',
items: [],
},
],
};
function activeLinkStyle({
isActive,
isPending,
}: {
isActive: boolean;
isPending: boolean;
}) {
return {
fontWeight: isActive ? 'bold' : '',
color: isPending ? 'grey' : 'black',
};
}