Skip to content

Commit

Permalink
Add mobile nav
Browse files Browse the repository at this point in the history
Update padding

Add scroll y overflow
  • Loading branch information
thomasKn committed Feb 2, 2024
1 parent eca4b30 commit 6d8dbe2
Show file tree
Hide file tree
Showing 9 changed files with 284 additions and 14 deletions.
14 changes: 14 additions & 0 deletions app/components/icons/IconMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type {IconProps} from './Icon';

import {Icon} from './Icon';

export function IconMenu(props: IconProps) {
return (
<Icon {...props} stroke={props.stroke || 'currentColor'}>
<title>Menu</title>
<line strokeWidth="1.25" x1="3" x2="17" y1="6.375" y2="6.375" />
<line strokeWidth="1.25" x1="3" x2="17" y1="10.375" y2="10.375" />
<line strokeWidth="1.25" x1="3" x2="17" y1="14.375" y2="14.375" />
</Icon>
);
}
5 changes: 4 additions & 1 deletion app/components/layout/CartCount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,10 @@ function Badge(props: {cart?: CartType | null; count: number}) {
[count, isHydrated],
);

const buttonClass = cx(['relative flex size-8 items-center justify-center']);
const buttonClass = cx([
'relative flex size-8 items-center justify-center',
count > 0 && 'mr-3 md:mr-0',
]);

return isHydrated ? (
<Sheet onOpenChange={setCartOpen} open={cartOpen}>
Expand Down
23 changes: 15 additions & 8 deletions app/components/layout/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ import {Link} from '@remix-run/react';
import {vercelStegaCleanAll} from '@sanity/client/stega';
import {cx} from 'class-variance-authority';
import {LazyMotion, m, useTransform} from 'framer-motion';
import React from 'react';

import {useBoundedScroll} from '~/hooks/useBoundedScroll';
import {useLocalePath} from '~/hooks/useLocalePath';
import {useSanityRoot} from '~/hooks/useSanityRoot';
import {useSettingsCssVars} from '~/hooks/useSettingsCssVars';

import {headerVariants} from '../cva/header';
import {Navigation} from '../navigation/Navigation';
import {DesktopNavigation} from '../navigation/DesktopNavigation';
import {MobileNavigation} from '../navigation/MobileNavigation';
import {CartCount} from './CartCount';
import {Logo} from './Logo';

Expand All @@ -30,11 +32,18 @@ export function Header() {
return (
<HeaderWrapper>
<style dangerouslySetInnerHTML={{__html: cssVars}} />
<div className="container">
<div
className="md:container"
style={
{
'--mobileHeaderXPadding': '0.75rem',
} as React.CSSProperties
}
>
<div className="flex items-center justify-between">
<Link prefetch="intent" to={homePath}>
<Logo
className="h-auto w-[var(--logoWidth)]"
className="h-auto w-[var(--logoWidth)] pl-[var(--mobileHeaderXPadding)] md:pl-0"
sizes={logoWidth}
style={
{
Expand All @@ -43,12 +52,10 @@ export function Header() {
}
/>
</Link>
<div className="flex items-center gap-3">
{/* Desktop navigation */}
<div className="hidden md:block">
<Navigation data={header?.menu} />
</div>
<div className="flex items-center gap-0 md:gap-2">
<DesktopNavigation data={header?.menu} />
<CartCount />
<MobileNavigation data={header?.menu} />
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import {
import {NestedNavigation} from './NestedNavigation';

type HeaderQuery = InferType<typeof HEADER_QUERY>;
type NavigationProps = NonNullable<HeaderQuery>['menu'];
export type NavigationProps = NonNullable<HeaderQuery>['menu'];

export function Navigation(props: {data?: NavigationProps}) {
export function DesktopNavigation(props: {data?: NavigationProps}) {
const menuRef = useRef<HTMLUListElement>(null);
const [activeItem, setActiveItem] = useState<null | string | undefined>(null);
const dropdownWidth = 200;
Expand All @@ -28,7 +28,7 @@ export function Navigation(props: {data?: NavigationProps}) {
);

return (
<NavigationMenu id="header-nav">
<NavigationMenu className="hidden md:block" id="header-nav">
<CssVars
dropdownWidth={dropdownWidth}
viewportPosition={viewportPosition}
Expand Down
121 changes: 121 additions & 0 deletions app/components/navigation/MobileNavigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import {useCallback, useState} from 'react';

import type {NavigationProps} from './DesktopNavigation';
import type {SanityNestedNavigationProps} from './NestedNavigation';

import {IconMenu} from '../icons/IconMenu';
import {SanityExternalLink} from '../sanity/link/SanityExternalLink';
import {SanityInternalLink} from '../sanity/link/SanityInternalLink';
import {
MobileNavigationMenu,
MobileNavigationMenuContent,
MobileNavigationMenuItem,
MobileNavigationMenuLink,
MobileNavigationMenuList,
MobileNavigationMenuTrigger,
} from '../ui/MobileNavigationMenu';
import {Sheet, SheetContent, SheetTrigger} from '../ui/Sheet';

export function MobileNavigation(props: {data?: NavigationProps}) {
const [open, setOpen] = useState(false);
const handleClose = useCallback(() => setOpen(false), []);

if (!props.data) return null;

// Todo => Add <Navlink /> support
return (
<div className="md:hidden">
<Sheet onOpenChange={setOpen} open={open}>
<SheetTrigger className="flex items-center justify-center p-2 pr-[var(--mobileHeaderXPadding)] md:pr-0">
<IconMenu className="size-7" strokeWidth={1.5} />
</SheetTrigger>
<SheetContent
className="flex max-h-screen min-h-full w-screen flex-col gap-0 bg-background p-0 text-foreground sm:max-w-lg"
onCloseAutoFocus={(e) => e.preventDefault()}
>
<MobileNavigationMenu className="flex-1 overflow-y-scroll text-xl font-medium">
<MobileNavigationMenuList>
{props.data &&
props.data?.length > 0 &&
props.data?.map((item) => (
<MobileNavigationMenuItem key={item._key}>
{item._type === 'internalLink' && (
<MobileNavigationMenuLink asChild onClick={handleClose}>
<div>
<SanityInternalLink data={item} />
</div>
</MobileNavigationMenuLink>
)}
{item._type === 'externalLink' && (
<MobileNavigationMenuLink asChild onClick={handleClose}>
<div>
<SanityExternalLink data={item} />
</div>
</MobileNavigationMenuLink>
)}
{item._type === 'nestedNavigation' && (
<NestedMobileNavigation
data={item}
onClose={handleClose}
/>
)}
</MobileNavigationMenuItem>
))}
</MobileNavigationMenuList>
</MobileNavigationMenu>
</SheetContent>
</Sheet>
</div>
);
}

function NestedMobileNavigation(props: {
data?: SanityNestedNavigationProps;
onClose: () => void;
}) {
const {data} = props;

if (!data) return null;

const {childLinks} = data;

return data.name && childLinks && childLinks.length > 0 ? (
<>
<MobileNavigationMenuTrigger>{data.name}</MobileNavigationMenuTrigger>
<MobileNavigationMenuContent className="flex-1 overflow-y-scroll">
<MobileNavigationMenuList>
{childLinks &&
childLinks.length > 0 &&
childLinks.map((child) => (
<MobileNavigationMenuLink
asChild
key={child._key}
onClick={props.onClose}
>
<div>
{child._type === 'internalLink' ? (
<SanityInternalLink data={child} />
) : child._type === 'externalLink' ? (
<SanityExternalLink data={child} />
) : null}
</div>
</MobileNavigationMenuLink>
))}
</MobileNavigationMenuList>
</MobileNavigationMenuContent>
</>
) : data.link && data.name && (!childLinks || childLinks.length === 0) ? (
// Render internal link if no child links
<SanityInternalLink
data={{
_key: data._key,
_type: 'internalLink',
anchor: null,
link: data.link,
name: data.name,
}}
>
{data.name}
</SanityInternalLink>
) : null;
}
2 changes: 1 addition & 1 deletion app/components/navigation/NestedNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
navigationMenuTriggerStyle,
} from '../ui/NavigationMenu';

type SanityNestedNavigationProps = TypeFromSelection<
export type SanityNestedNavigationProps = TypeFromSelection<
typeof NESTED_NAVIGATION_FRAGMENT
>;

Expand Down
124 changes: 124 additions & 0 deletions app/components/ui/MobileNavigationMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu';
import {cn} from 'app/lib/utils';
import React, {forwardRef} from 'react';

import {IconChevron} from '../icons/IconChevron';

const MobileNavigationMenu = forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
>(({children, className, ...props}, ref) => (
<NavigationMenuPrimitive.Root
className={cn('container relative py-[var(--viewport-top)]', className)}
ref={ref}
{...props}
style={
{
'--viewport-top': '3.5rem',
} as React.CSSProperties
}
>
{children}
<MobileNavigationMenuViewport />
</NavigationMenuPrimitive.Root>
));
MobileNavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName;

const MobileNavigationMenuList = forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.List>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
>(({className, ...props}, ref) => (
<NavigationMenuPrimitive.List
className={cn('flex flex-col gap-4', className)}
ref={ref}
{...props}
/>
));
MobileNavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;

const MobileNavigationMenuItem = NavigationMenuPrimitive.Item;

const MobileNavigationMenuTrigger = forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
>(({children, className, ...props}, ref) => (
<NavigationMenuPrimitive.Trigger
className={cn(
'group data-[state=open]:fixed data-[state=open]:top-4 data-[state=open]:duration-300 data-[state=open]:animate-in data-[state=open]:fade-in',
className,
)}
ref={ref}
{...props}
>
<span className="flex items-center gap-2">
<span className="group-data-[state=open]:hidden">{children}</span>
<IconChevron
className="group-data-[state=open]:rotate-180"
direction="right"
/>
</span>
</NavigationMenuPrimitive.Trigger>
));
MobileNavigationMenuTrigger.displayName =
NavigationMenuPrimitive.Trigger.displayName;

const MobileNavigationMenuContent = forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
>(({className, ...props}, ref) => (
<NavigationMenuPrimitive.Content
className={cn(
[
'h-[calc(100vh-var(--viewport-top))] w-screen transition ease-in-out',
'group-data-[state=open]:duration-500',
'group-data-[state=open]:animate-in',
'group-data-[state=open]:fade-in',
],
className,
)}
onInteractOutside={(e) => e.preventDefault()}
ref={ref}
{...props}
>
<div className="flex h-full max-h-screen min-h-full w-screen flex-col gap-0">
<div className="container flex h-full flex-1 flex-col gap-3 overflow-y-scroll py-4">
{props.children}
</div>
</div>
</NavigationMenuPrimitive.Content>
));
MobileNavigationMenuContent.displayName =
NavigationMenuPrimitive.Content.displayName;

const MobileNavigationMenuLink = NavigationMenuPrimitive.Link;

const MobileNavigationMenuViewport = forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({className, ...props}, ref) => (
<NavigationMenuPrimitive.Viewport
className={cn([
'group absolute left-0 top-[var(--viewport-top)] z-50',
'h-[var(--radix-navigation-menu-viewport-height)] w-[var(--radix-navigation-menu-viewport-width)]',
'bg-background text-foreground transition ease-in-out',
'data-[state=closed]:duration-300',
'data-[state=closed]:animate-out',
'data-[state=closed]:fade-out',
className,
])}
ref={ref}
{...props}
/>
));
MobileNavigationMenuViewport.displayName =
NavigationMenuPrimitive.Viewport.displayName;

export {
MobileNavigationMenu,
MobileNavigationMenuContent,
MobileNavigationMenuItem,
MobileNavigationMenuLink,
MobileNavigationMenuList,
MobileNavigationMenuTrigger,
MobileNavigationMenuViewport,
};
2 changes: 1 addition & 1 deletion app/components/ui/NavigationMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu';
import {cn} from 'app/lib/utils';
import {cva} from 'class-variance-authority';
import {forwardRef, useEffect, useRef} from 'react';
import {forwardRef} from 'react';

import {IconChevron} from '../icons/IconChevron';

Expand Down
1 change: 1 addition & 0 deletions tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export default {
theme: {
container: {
center: true,
padding: '1rem',
},
extend: {
colors: {
Expand Down

0 comments on commit 6d8dbe2

Please sign in to comment.