Skip to content

Commit

Permalink
feat: transparent nav / nav color options
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremyagabriel committed Mar 2, 2025
1 parent 83dfe82 commit b65948e
Show file tree
Hide file tree
Showing 11 changed files with 339 additions and 69 deletions.
15 changes: 13 additions & 2 deletions app/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,28 @@ import {memo} from 'react';

import {usePromobar} from '~/hooks';

import {Navigation} from './Navigation';
import {Navigation, TransparentNavigation} from './Navigation';
import {DesktopMenu, MobileMenu} from './Menu';
import {Promobar} from './Promobar';
import {useDesktopMenu} from './useDesktopMenu';
import {useMobileMenu} from './useMobileMenu';

export const Header = memo(() => {
const {headerHeightClass} = usePromobar();
const {headerHeightClass, isTransparentNavPage} = usePromobar();
const desktopMenuContext = useDesktopMenu();
const mobileMenuContext = useMobileMenu();

/* Header is transparent for page handles set in site settings */
if (isTransparentNavPage) {
return (
<header
className={`fixed inset-x-0 top-0 z-20 flex flex-col transition-[height] duration-300 ease-out ${headerHeightClass}`}
>
<TransparentNavigation />
</header>
);
}

return (
<header
className={`fixed inset-x-0 top-0 z-20 flex flex-col bg-background transition-[height] duration-300 ease-out ${headerHeightClass}`}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import {memo} from 'react';
import {useCart} from '@shopify/hydrogen-react';

import {Link} from '~/components/Link';
import {Svg} from '~/components/Svg';
import {HEADER_NAVIGATION} from '~/lib/constants';
import {useCustomer, useMenu, useSettings} from '~/hooks';

import type {UseDesktopMenuReturn} from './useDesktopMenu';
import type {UseMobileMenuReturn} from './useMobileMenu';
import type {UseDesktopMenuReturn} from '../useDesktopMenu';
import type {UseMobileMenuReturn} from '../useMobileMenu';

import {NavigationCart} from './NavigationCart';
import {NavigationLogo} from './NavigationLogo';

type NavigationProps = Pick<
UseMobileMenuReturn,
Expand All @@ -30,11 +33,18 @@ export const Navigation = memo(
desktopMenuContent,
mobileMenuOpen,
}: NavigationProps) => {
const {totalQuantity = 0} = useCart();
const customer = useCustomer();
const {openCart, openSearch} = useMenu();
const {openSearch} = useMenu();
const {header} = useSettings();
const {logoPositionDesktop, navItems} = {...header?.menu};
const {
bgColor = 'var(--background)',
textColor = 'var(--text)',
iconColor = 'var(--text)',
logoPositionDesktop,
navItems,
} = {
...header?.menu,
};
const gridColsClassDesktop =
logoPositionDesktop === 'center'
? 'lg:grid-cols-[1fr_auto_1fr]'
Expand All @@ -46,17 +56,12 @@ export const Navigation = memo(

return (
<div
className={`px-contained relative z-[1] grid flex-1 grid-cols-[1fr_auto_1fr] gap-4 border-b border-b-border bg-background transition md:gap-8 ${gridColsClassDesktop}`}
className={`px-contained relative z-[1] grid flex-1 grid-cols-[1fr_auto_1fr] gap-4 border-b border-b-border transition md:gap-8 ${gridColsClassDesktop}`}
data-comp={HEADER_NAVIGATION}
style={{backgroundColor: bgColor, color: textColor}}
>
<div className={`order-2 flex items-center ${logoOrderClassDesktop}`}>
<Link aria-label="Go to homepage" to="/">
<Svg
className="h-8 text-text"
src="/svgs/pack-logo.svg#pack-logo"
title="Storefront logo"
viewBox="0 0 44 34"
/>
</Link>
<NavigationLogo color={iconColor} />
</div>

<div className={`order-1 flex items-center ${menuOrderClassDesktop}`}>
Expand All @@ -70,15 +75,15 @@ export const Navigation = memo(
<li key={index} className="flex">
<Link
aria-label={item.navItem?.text}
className={`group relative flex cursor-pointer items-center px-4 transition ${
isHovered ? 'bg-neutralLightest' : 'bg-background'
}`}
className="group relative flex cursor-pointer items-center px-4 transition"
to={item.navItem?.url}
onClick={handleDesktopMenuClose}
onMouseEnter={() => handleDesktopMenuHoverIn(index)}
onMouseLeave={handleDesktopMenuHoverOut}
>
<p className="text-nav">{item.navItem?.text}</p>
<p className="text-nav text-current">
{item.navItem?.text}
</p>

<div
className={`absolute left-0 top-[calc(100%_-_2px)] h-[3px] w-full origin-center scale-0 border-t-2 border-t-primary bg-transparent transition after:w-full group-hover:scale-100 ${
Expand All @@ -102,18 +107,19 @@ export const Navigation = memo(
if (mobileMenuOpen) handleCloseMobileMenu();
else handleOpenMobileMenu();
}}
style={{color: iconColor}}
type="button"
>
{mobileMenuOpen ? (
<Svg
className="w-full text-text"
className="w-full text-current"
src="/svgs/close.svg#close"
title="Close"
viewBox="0 0 24 24"
/>
) : (
<Svg
className="w-full text-text"
className="w-full text-current"
src="/svgs/menu.svg#menu"
title="Navigation"
viewBox="0 0 24 24"
Expand All @@ -125,10 +131,11 @@ export const Navigation = memo(
aria-label="Open search"
className="block w-5 md:hidden"
onClick={openSearch}
style={{color: iconColor}}
type="button"
>
<Svg
className="w-full text-text"
className="w-full text-current"
src="/svgs/search.svg#search"
title="Search"
viewBox="0 0 24 24"
Expand All @@ -142,10 +149,11 @@ export const Navigation = memo(
aria-label="Open search"
className="hidden w-5 md:block"
onClick={openSearch}
style={{color: iconColor}}
type="button"
>
<Svg
className="w-full text-text"
className="w-full text-current"
src="/svgs/search.svg#search"
title="Search"
viewBox="0 0 24 24"
Expand All @@ -154,35 +162,18 @@ export const Navigation = memo(

<Link
aria-label="Go to account page"
style={{color: iconColor}}
to={customer ? `/account/orders` : `/account/login`}
>
<Svg
className="w-5 text-text"
className="w-5 text-current"
src="/svgs/account.svg#account"
title="Account"
viewBox="0 0 24 24"
/>
</Link>

<div className="relative flex items-center">
<button
aria-label="Open cart"
className="w-5"
onClick={openCart}
type="button"
>
<Svg
className="w-full text-text"
src="/svgs/cart.svg#cart"
title="Cart"
viewBox="0 0 24 24"
/>
</button>

<p className="text-label-sm w-4 whitespace-nowrap pl-px font-bold">
({totalQuantity || 0})
</p>
</div>
<NavigationCart color={iconColor} />
</div>
</div>
);
Expand Down
40 changes: 40 additions & 0 deletions app/components/Header/Navigation/NavigationCart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {memo} from 'react';
import {useCart} from '@shopify/hydrogen-react';

import {Svg} from '~/components/Svg';
import {useMenu} from '~/hooks';

export const NavigationCart = memo(
({className = '', color}: {className?: string; color?: string}) => {
const {totalQuantity = 0} = useCart();
const {openCart} = useMenu();

return (
<div className="relative flex items-center">
<button
aria-label="Open cart"
className={`w-5 text-text ${className}`}
onClick={openCart}
style={{color}}
type="button"
>
<Svg
className="w-full text-current"
src="/svgs/cart.svg#cart"
title="Cart"
viewBox="0 0 24 24"
/>
</button>

<p
className="text-label-sm w-4 whitespace-nowrap pl-px font-bold text-current"
style={{color}}
>
({totalQuantity || 0})
</p>
</div>
);
},
);

NavigationCart.displayName = 'NavigationCart';
26 changes: 26 additions & 0 deletions app/components/Header/Navigation/NavigationLogo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {memo} from 'react';

import {Link} from '~/components/Link';
import {Svg} from '~/components/Svg';

export const NavigationLogo = memo(
({className = '', color}: {className?: string; color?: string}) => {
return (
<Link
aria-label="Go to homepage"
className={`text-text ${className}`}
style={{color}}
to="/"
>
<Svg
className="h-8 text-current"
src="/svgs/pack-logo.svg#pack-logo"
title="Storefront logo"
viewBox="0 0 44 34"
/>
</Link>
);
},
);

NavigationLogo.displayName = 'NavigationLogo';
77 changes: 77 additions & 0 deletions app/components/Header/Navigation/TransparentNavigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import {memo, useEffect, useState} from 'react';

import {HEADER_NAVIGATION} from '~/lib/constants';
import {useSettings} from '~/hooks';

import {NavigationCart} from './NavigationCart';
import {NavigationLogo} from './NavigationLogo';

export const TransparentNavigation = memo(() => {
const {header} = useSettings();

const [isScrolled, setIsScrolled] = useState(false);

const {logoPositionDesktop, transparentNav} = {
...header?.menu,
};
const {
iconColor = 'var(--white)',
isChangeColorsOnScroll = true,
pixelOffsetChangeColors = 100,
scrolledBgColor = 'var(--background)',
scrolledIconColor = 'var(--text)',
} = {...transparentNav};
const gridColsClassDesktop =
logoPositionDesktop === 'center'
? 'lg:grid-cols-[1fr_auto_1fr]'
: 'lg:grid-cols-[auto_1fr_auto]';
const logoOrderClassDesktop =
logoPositionDesktop === 'center' ? 'lg:order-2' : 'lg:order-1';
const menuOrderClassDesktop =
logoPositionDesktop === 'center' ? 'lg:order-1' : 'lg:order-2';

useEffect(() => {
const setScrolledNav = () => {
const scrolledNavIsActive = window.scrollY > pixelOffsetChangeColors;
if (isScrolled && !scrolledNavIsActive) setIsScrolled(false);
else if (!isScrolled && scrolledNavIsActive) setIsScrolled(true);
};

if (!isChangeColorsOnScroll) {
if (isScrolled) setIsScrolled(false);
window.removeEventListener('scroll', setScrolledNav);
return;
}

window.addEventListener('scroll', setScrolledNav);
return () => {
window.removeEventListener('scroll', setScrolledNav);
};
}, [isChangeColorsOnScroll, isScrolled, pixelOffsetChangeColors]);

return (
<nav
className={`px-contained relative z-[1] grid flex-1 grid-cols-[1fr_auto_1fr] gap-4 transition md:gap-8 ${gridColsClassDesktop}`}
data-comp={HEADER_NAVIGATION}
style={{backgroundColor: isScrolled ? scrolledBgColor : 'transparent'}}
>
<div className={`order-2 flex items-center ${logoOrderClassDesktop}`}>
<NavigationLogo
className="transition"
color={isScrolled ? scrolledIconColor : iconColor}
/>
</div>

<div className={`order-1 flex items-center ${menuOrderClassDesktop}`} />

<div className="order-3 flex items-center justify-end gap-4 md:gap-5">
<NavigationCart
className="transition"
color={isScrolled ? scrolledIconColor : iconColor}
/>
</div>
</nav>
);
});

TransparentNavigation.displayName = 'TransparentNavigation';
2 changes: 2 additions & 0 deletions app/components/Header/Navigation/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export {Navigation} from './Navigation';
export {TransparentNavigation} from './TransparentNavigation';
25 changes: 23 additions & 2 deletions app/contexts/SettingsProvider/SettingsProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {Context} from './useSettingsContext';

const settingsState = {
isPreviewModeEnabled: false,
isTransparentNavPage: false,
previewModeCustomer: undefined,
settings: {},
};
Expand All @@ -33,14 +34,34 @@ const actions = (dispatch: Dispatch) => ({
});

export function SettingsProvider({children}: {children: ReactNode}) {
const {isPreviewModeEnabled, siteSettings} = useRootLoaderData();
const {isPreviewModeEnabled, siteSettings, url} = useRootLoaderData();

const transparentNavPageHandles =
siteSettings?.data?.siteSettings?.settings?.header?.menu?.transparentNav
?.pageHandles;

const isTransparentNavPage = useMemo(() => {
if (!transparentNavPageHandles?.length) return false;
const {pathname} = new URL(url);
const [route, handle] = pathname.split('/').slice(1);
const pageHandle = route === 'pages' ? handle : undefined;
if (!pageHandle) return false;
return transparentNavPageHandles.includes(pageHandle);
}, [url, JSON.stringify(transparentNavPageHandles)]);

const [state, dispatch] = useReducer(reducer, {
...settingsState,
settings: siteSettings?.data?.siteSettings?.settings,
isPreviewModeEnabled,
});

const value = useMemo(() => ({state, actions: actions(dispatch)}), [state]);
const value = useMemo(
() => ({
state: {...state, isTransparentNavPage},
actions: actions(dispatch),
}),
[isTransparentNavPage, state],
);

return <Context.Provider value={value}>{children}</Context.Provider>;
}
Loading

0 comments on commit b65948e

Please sign in to comment.