Renaming to common

This commit is contained in:
Belen Curcio
2020-10-29 12:10:53 -03:00
parent 629df7a9ab
commit 1e61afbbea
45 changed files with 14 additions and 8 deletions

View File

@@ -0,0 +1,25 @@
import cn from 'classnames'
import { FC, useState } from 'react'
import { getRandomPairOfColors } from '@lib/colors'
interface Props {
className?: string
children?: any
}
const Avatar: FC<Props> = ({}) => {
const [bg] = useState(getRandomPairOfColors)
return (
<div
className="inline-block h-8 w-8 rounded-full border-2 border-primary hover:border-secondary focus:border-secondary transition linear-out duration-150"
style={{
backgroundImage: `linear-gradient(140deg, ${bg[0]}, ${bg[1]} 100%)`,
}}
>
{/* Add an image - We're generating a gradient as placeholder <img></img> */}
</div>
)
}
export default Avatar

View File

@@ -0,0 +1 @@
export { default } from './Avatar'

View File

@@ -0,0 +1,42 @@
import { FC } from 'react'
import { useInView } from 'react-intersection-observer'
import Image from 'next/image'
type Props = Omit<
JSX.IntrinsicElements['img'],
'src' | 'srcSet' | 'ref' | 'width' | 'height' | 'loading'
> & {
src: string
quality?: string
priority?: boolean
loading?: readonly ['lazy', 'eager', undefined]
unoptimized?: boolean
} & (
| {
width: number | string
height: number | string
unsized?: false
}
| {
width?: number | string
height?: number | string
unsized: true
}
)
const EnhancedImage: FC<Props & JSX.IntrinsicElements['img']> = ({
...props
}) => {
const [ref] = useInView({
triggerOnce: true,
rootMargin: '220px 0px',
})
return (
<div ref={ref}>
<Image {...props} />
</div>
)
}
export default EnhancedImage

View File

@@ -0,0 +1 @@
export { default } from './EnhancedImage'

View File

@@ -0,0 +1,7 @@
.root {
@apply text-center p-6 bg-primary text-sm flex-row justify-center items-center font-medium fixed bottom-0 w-full z-30;
@screen md {
@apply flex text-left;
}
}

View File

@@ -0,0 +1,39 @@
import cn from 'classnames'
import { FC } from 'react'
import s from './Featurebar.module.css'
interface Props {
className?: string
title: string
description?: string
hide?: boolean
action?: React.ReactNode
}
const Featurebar: FC<Props> = ({
title,
description,
className,
action,
hide,
}) => {
const rootClassName = cn(
s.root,
{
'transition-transform transform duration-500 ease-out translate-y-full': hide,
},
className
)
return (
<div className={rootClassName}>
<span className="block md:inline">{title}</span>
<span className="block mb-6 md:inline md:mb-0 md:ml-2">
{description}
</span>
{action && action}
</div>
)
}
export default Featurebar

View File

@@ -0,0 +1 @@
export { default } from './Featurebar'

View File

@@ -0,0 +1,9 @@
.link {
& > svg {
@apply transform duration-75 ease-linear;
}
&:hover > svg {
@apply scale-110;
}
}

View File

@@ -0,0 +1,150 @@
import { FC } from 'react'
import cn from 'classnames'
import Link from 'next/link'
import { useRouter } from 'next/router'
import type { Page } from '@bigcommerce/storefront-data-hooks/api/operations/get-all-pages'
import getSlug from '@lib/get-slug'
import { Github } from '@components/icons'
import { Logo, Container } from '@components/ui'
import { I18nWidget } from '@components/core'
import s from './Footer.module.css'
interface Props {
className?: string
children?: any
pages?: Page[]
}
const LEGAL_PAGES = ['terms-of-use', 'shipping-returns', 'privacy-policy']
const Footer: FC<Props> = ({ className, pages }) => {
const { sitePages, legalPages } = usePages(pages)
const rootClassName = cn(className)
return (
<footer className={rootClassName}>
<Container>
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 border-b border-accents-2 py-12 text-primary bg-primary transition-colors duration-150">
<div className="col-span-1 lg:col-span-2">
<Link href="/">
<a className="flex flex-initial items-center font-bold md:mr-24">
<span className="rounded-full border border-gray-700 mr-2">
<Logo />
</span>
<span>ACME</span>
</a>
</Link>
</div>
<div className="col-span-1 lg:col-span-2">
<ul className="flex flex-initial flex-col md:flex-1">
<li className="py-3 md:py-0 md:pb-4">
<Link href="/">
<a className="text-primary hover:text-accents-6 transition ease-in-out duration-150">
Home
</a>
</Link>
</li>
<li className="py-3 md:py-0 md:pb-4">
<Link href="/">
<a className="text-primary hover:text-accents-6 transition ease-in-out duration-150">
Careers
</a>
</Link>
</li>
<li className="py-3 md:py-0 md:pb-4">
<Link href="/blog">
<a className="text-primary hover:text-accents-6 transition ease-in-out duration-150">
Blog
</a>
</Link>
</li>
{sitePages.map((page) => (
<li key={page.url} className="py-3 md:py-0 md:pb-4">
<Link href={page.url!}>
<a className="text-primary hover:text-accents-6 transition ease-in-out duration-150">
{page.name}
</a>
</Link>
</li>
))}
</ul>
</div>
<div className="col-span-1 lg:col-span-2">
<ul className="flex flex-initial flex-col md:flex-1">
{legalPages.map((page) => (
<li key={page.url} className="py-3 md:py-0 md:pb-4">
<Link href={page.url!}>
<a className="text-primary hover:text-accents-6 transition ease-in-out duration-150">
{page.name}
</a>
</Link>
</li>
))}
</ul>
</div>
<div className="col-span-1 lg:col-span-6 flex items-start lg:justify-end text-primary">
<div className="flex space-x-6 items-center h-10">
<a href="https://github.com/vercel/commerce" className={s.link}>
<Github />
</a>
<I18nWidget />
</div>
</div>
</div>
<div className="py-12 flex flex-col md:flex-row justify-between items-center space-y-4">
<div>
<span>&copy; 2020 ACME, Inc. All rights reserved.</span>
</div>
<div className="flex items-center">
<span className="text-primary">Crafted by</span>
<a href="https://vercel.com" aria-label="Vercel.com Link">
<img
src="/vercel.svg"
alt="Vercel.com Logo"
className="inline-block h-6 ml-4 text-primary"
/>
</a>
</div>
</div>
</Container>
</footer>
)
}
function usePages(pages?: Page[]) {
const { locale } = useRouter()
const sitePages: Page[] = []
const legalPages: Page[] = []
if (pages) {
pages.forEach((page) => {
const slug = page.url && getSlug(page.url)
if (!slug) return
if (locale && !slug.startsWith(`${locale}/`)) return
if (isLegalPage(slug, locale)) {
legalPages.push(page)
} else {
sitePages.push(page)
}
})
}
return {
sitePages: sitePages.sort(bySortOrder),
legalPages: legalPages.sort(bySortOrder),
}
}
const isLegalPage = (slug: string, locale?: string) =>
locale
? LEGAL_PAGES.some((p) => `${locale}/${p}` === slug)
: LEGAL_PAGES.includes(slug)
// Sort pages by the sort order assigned in the BC dashboard
function bySortOrder(a: Page, b: Page) {
return (a.sort_order ?? 0) - (b.sort_order ?? 0)
}
export default Footer

View File

@@ -0,0 +1 @@
export { default } from './Footer'

View File

@@ -0,0 +1,26 @@
.root {
@apply text-lg leading-7 font-medium max-w-6xl mx-auto;
}
.root p {
@apply text-justify;
}
.root h1 {
@apply text-5xl mb-12;
}
.root h2 {
@apply text-3xl mt-12 mb-4 leading-snug;
}
.root h3 {
@apply text-2xl mt-8 mb-4 leading-snug;
}
.root p,
.root ul,
.root ol,
.root blockquote {
@apply mb-6;
}

View File

@@ -0,0 +1,16 @@
import cn from 'classnames'
import s from './HTMLContent.module.css'
type Props = {
className?: 'string'
html: string
}
export default function HTMLContent({ className, html }: Props) {
return (
<div
className={cn(s.root, className)}
dangerouslySetInnerHTML={{ __html: html }}
/>
)
}

View File

@@ -0,0 +1 @@
export { default } from './HTMLContent'

View File

@@ -0,0 +1,18 @@
import { FC } from 'react'
import NextHead from 'next/head'
import { DefaultSeo } from 'next-seo'
import config from '@config/seo.json'
const Head: FC = () => {
return (
<>
<DefaultSeo {...config} />
<NextHead>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="manifest" href="/site.webmanifest" key="site-manifest" />
</NextHead>
</>
)
}
export default Head

View File

@@ -0,0 +1 @@
export { default } from './Head'

View File

@@ -0,0 +1,23 @@
.root {
@apply py-12 flex flex-col w-full px-6;
@screen md {
@apply flex-row;
}
& .asideWrapper {
@apply pr-3 w-full relative;
@screen md {
@apply w-48;
}
}
& .aside {
@apply flex flex-row w-full justify-around mb-12;
@screen md {
@apply mb-0 block sticky top-32;
}
}
}

View File

@@ -0,0 +1,66 @@
import { FC } from 'react'
import Link from 'next/link'
import { getCategoryPath, getDesignerPath } from '@lib/search'
import { Grid } from '@components/ui'
import { ProductCard } from '@components/product'
import s from './HomeAllProductsGrid.module.css'
interface Props {
categories?: any
brands?: any
newestProducts?: any
}
const Head: FC<Props> = ({ categories, brands, newestProducts }) => {
return (
<div className={s.root}>
<div className={s.asideWrapper}>
<div className={s.aside}>
<ul className="mb-10">
<li className="py-1 text-base font-bold tracking-wide">
<Link href={getCategoryPath('')}>
<a>All Categories</a>
</Link>
</li>
{categories.map((cat: any) => (
<li key={cat.path} className="py-1 text-accents-8">
<Link href={getCategoryPath(cat.path)}>
<a>{cat.name}</a>
</Link>
</li>
))}
</ul>
<ul className="">
<li className="py-1 text-base font-bold tracking-wide">
<Link href={getDesignerPath('')}>
<a>All Designers</a>
</Link>
</li>
{brands.flatMap(({ node }: any) => (
<li key={node.path} className="py-1 text-accents-8">
<Link href={getDesignerPath(node.path)}>
<a>{node.name}</a>
</Link>
</li>
))}
</ul>
</div>
</div>
<div className="flex-1">
<Grid layout="normal">
{newestProducts.map(({ node }: any) => (
<ProductCard
key={node.path}
product={node}
variant="simple"
imgWidth={480}
imgHeight={480}
/>
))}
</Grid>
</div>
</div>
)
}
export default Head

View File

@@ -0,0 +1 @@
export { default } from './HomeAllProductsGrid'

View File

@@ -0,0 +1,28 @@
.root {
@apply relative;
}
.button {
@apply h-10 px-2 rounded-md border border-accents-2 flex items-center justify-center;
}
.button:focus {
@apply outline-none;
}
.dropdownMenu {
@apply fixed right-0 top-12 mt-2 origin-top-right outline-none bg-primary z-40 w-full h-full;
@screen lg {
@apply absolute border border-accents-1 shadow-lg w-56 h-auto;
}
}
.item {
@apply flex cursor-pointer px-6 py-3 flex transition ease-in-out duration-150 text-primary leading-6 font-medium items-center;
text-transform: capitalize;
}
.item:hover {
@apply bg-accents-1;
}

View File

@@ -0,0 +1,82 @@
import { FC } from 'react'
import cn from 'classnames'
import { useRouter } from 'next/router'
import Link from 'next/link'
import { Menu } from '@headlessui/react'
import { DoubleChevron } from '@components/icons'
import s from './I18nWidget.module.css'
interface LOCALE_DATA {
name: string
img: {
filename: string
alt: string
}
}
const LOCALES_MAP: Record<string, LOCALE_DATA> = {
es: {
name: 'Español',
img: {
filename: 'flag-es-co.svg',
alt: 'Bandera Colombiana',
},
},
'en-US': {
name: 'English',
img: {
filename: 'flag-en-us.svg',
alt: 'US Flag',
},
},
}
const I18nWidget: FC = () => {
const {
locale,
locales,
defaultLocale = 'en-US',
asPath: currentPath,
} = useRouter()
const options = locales?.filter((val) => val !== locale)
const currentLocale = locale || defaultLocale
return (
<nav className={s.root}>
<Menu>
<Menu.Button className={s.button} aria-label="Language selector">
<img
className="block mr-2 w-5"
src={`/${LOCALES_MAP[currentLocale].img.filename}`}
alt={LOCALES_MAP[currentLocale].img.alt}
/>
<span className="mr-2">{LOCALES_MAP[currentLocale].name}</span>
{options && (
<span>
<DoubleChevron />
</span>
)}
</Menu.Button>
{options?.length ? (
<Menu.Items className={s.dropdownMenu}>
{options.map((locale) => (
<Menu.Item key={locale}>
{({ active }) => (
<Link href={currentPath} locale={locale}>
<a className={cn(s.item, { [s.active]: active })}>
{LOCALES_MAP[locale].name}
</a>
</Link>
)}
</Menu.Item>
))}
</Menu.Items>
) : null}
</Menu>
</nav>
)
}
export default I18nWidget

View File

@@ -0,0 +1 @@
export { default } from './I18nWidget'

View File

@@ -0,0 +1,4 @@
.root {
@apply h-full bg-primary mx-auto transition-colors duration-150;
max-width: 2460px;
}

View File

@@ -0,0 +1,96 @@
import { FC, useCallback, useEffect, useState } from 'react'
import cn from 'classnames'
import { useRouter } from 'next/router'
import type { Page } from '@bigcommerce/storefront-data-hooks/api/operations/get-all-pages'
import { CommerceProvider } from '@bigcommerce/storefront-data-hooks'
import { CartSidebarView } from '@components/cart'
import { Container, Sidebar, Button, Modal, Toast } from '@components/ui'
import { Navbar, Featurebar, Footer } from '@components/core'
import { LoginView, SignUpView, ForgotPassword } from '@components/auth'
import { useUI } from '@components/ui/context'
import { usePreventScroll } from '@react-aria/overlays'
import s from './Layout.module.css'
import debounce from 'lodash.debounce'
interface Props {
pageProps: {
pages?: Page[]
}
}
const Layout: FC<Props> = ({ children, pageProps }) => {
const {
displaySidebar,
displayModal,
closeSidebar,
closeModal,
modalView,
toastText,
closeToast,
displayToast,
} = useUI()
const [acceptedCookies, setAcceptedCookies] = useState(false)
const [hasScrolled, setHasScrolled] = useState(false)
const { locale = 'en-US' } = useRouter()
usePreventScroll({
isDisabled: !(displaySidebar || displayModal),
})
const handleScroll = useCallback(() => {
debounce(() => {
const offset = 0
const { scrollTop } = document.documentElement
if (scrollTop > offset) setHasScrolled(true)
else setHasScrolled(false)
}, 1)
}, [])
useEffect(() => {
document.addEventListener('scroll', handleScroll)
return () => {
document.removeEventListener('scroll', handleScroll)
}
}, [handleScroll])
return (
<CommerceProvider locale={locale}>
<div className={cn(s.root)}>
<header
className={cn(
'sticky top-0 bg-primary z-40 transition-all duration-150',
{ 'shadow-magical': hasScrolled }
)}
>
<Container>
<Navbar />
</Container>
</header>
<main className="fit">{children}</main>
<Footer pages={pageProps.pages} />
<Sidebar open={displaySidebar} onClose={closeSidebar}>
<CartSidebarView />
</Sidebar>
<Modal open={displayModal} onClose={closeModal}>
{modalView === 'LOGIN_VIEW' && <LoginView />}
{modalView === 'SIGNUP_VIEW' && <SignUpView />}
{modalView === 'FORGOT_VIEW' && <ForgotPassword />}
</Modal>
<Featurebar
title="This site uses cookies to improve your experience. By clicking, you agree to our Privacy Policy."
hide={acceptedCookies}
action={
<Button className="mx-5" onClick={() => setAcceptedCookies(true)}>
Accept cookies
</Button>
}
/>
{/* <Toast open={displayToast} onClose={closeModal}>
{toastText}
</Toast> */}
</div>
</CommerceProvider>
)
}
export default Layout

View File

@@ -0,0 +1 @@
export { default } from './Layout'

View File

@@ -0,0 +1,20 @@
.link {
@apply inline-flex items-center text-primary leading-6 font-medium transition ease-in-out duration-75 cursor-pointer text-accents-6;
}
.link:hover {
@apply text-accents-9;
}
.link:focus {
@apply outline-none text-accents-8;
}
.logo {
@apply cursor-pointer rounded-full border transform duration-100 ease-in-out;
&:hover {
@apply shadow-md;
transform: scale(1.05);
}
}

View File

@@ -0,0 +1,51 @@
import { FC } from 'react'
import Link from 'next/link'
import s from './Navbar.module.css'
import { Logo } from '@components/ui'
import { Searchbar, UserNav } from '@components/core'
interface Props {
className?: string
}
const Navbar: FC<Props> = ({ className }) => {
const rootClassName = className
return (
<div className={rootClassName}>
<div className="flex justify-between align-center flex-row py-4 md:py-6 relative">
<div className="flex flex-1 items-center">
<Link href="/">
<a className={s.logo} aria-label="Logo">
<Logo />
</a>
</Link>
<nav className="space-x-4 ml-6 hidden lg:block">
<Link href="/">
<a className={s.link}>All</a>
</Link>
<Link href="/search?q=clothes">
<a className={s.link}>Clothes</a>
</Link>
<Link href="/search?q=accessories">
<a className={s.link}>Accessories</a>
</Link>
</nav>
</div>
<div className="flex-1 justify-center hidden lg:flex">
<Searchbar />
</div>
<div className="flex flex-1 justify-end space-x-8">
<UserNav />
</div>
</div>
<div className="flex pb-4 lg:px-6 lg:hidden">
<Searchbar id="mobileSearch" />
</div>
</div>
)
}
export default Navbar

View File

@@ -0,0 +1 @@
export { default } from './Navbar'

View File

@@ -0,0 +1,19 @@
.input {
@apply bg-transparent px-3 py-2 appearance-none w-full transition duration-150 ease-in-out pr-10;
@screen sm {
min-width: 300px;
}
}
.input:focus {
@apply outline-none shadow-outline-2;
}
.iconContainer {
@apply absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none;
}
.icon {
@apply h-5 w-5;
}

View File

@@ -0,0 +1,62 @@
import { FC, useEffect } from 'react'
import cn from 'classnames'
import s from './Searchbar.module.css'
import { useRouter } from 'next/router'
interface Props {
className?: string
id?: string
}
const Searchbar: FC<Props> = ({ className, id = 'search' }) => {
const router = useRouter()
useEffect(() => {
router.prefetch('/search')
}, [])
return (
<div
className={cn(
'relative text-sm bg-accents-1 text-base w-full transition-colors duration-150',
className
)}
>
<label htmlFor={id}>
<input
id={id}
className={s.input}
placeholder="Search for products..."
defaultValue={router.query.q}
onKeyUp={(e) => {
e.preventDefault()
if (e.key === 'Enter') {
const q = e.currentTarget.value
router.push(
{
pathname: `/search`,
query: q ? { q } : {},
},
undefined,
{ shallow: true }
)
}
}}
/>
</label>
<div className={s.iconContainer}>
<svg className={s.icon} fill="currentColor" viewBox="0 0 20 20">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
/>
</svg>
</div>
</div>
)
}
export default Searchbar

View File

@@ -0,0 +1 @@
export { default } from './Searchbar'

View File

@@ -0,0 +1,2 @@
.root {
}

View File

@@ -0,0 +1,55 @@
import React, { FC } from 'react'
import { Switch } from '@headlessui/react'
import { Moon, Sun } from '@components/icons'
interface Props {
className?: string
checked: boolean
onChange: any
}
const Toggle: FC<Props> = ({ className, checked, onChange }) => {
return (
<Switch
checked={checked}
onChange={onChange}
className="focus:outline-none"
>
<span
role="checkbox"
aria-checked="false"
tabIndex={0}
className={`${
checked ? 'bg-gray-800' : 'bg-gray-200'
} relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-150 focus:outline-none focus:shadow-outline`}
>
<span
aria-hidden="true"
className={`${
checked ? 'translate-x-5' : 'translate-x-0'
} translate-x-0 relative inline-block h-5 w-5 rounded-full bg-white shadow transform transition ease-in-out duration-150`}
>
<span
className={`${
checked
? 'opacity-0 ease-out duration-150'
: 'opacity-100 ease-in duration-150'
} absolute inset-0 h-full w-full flex items-center justify-center transition-opacity`}
>
<Sun className="h-3 w-3 text-accent-3" />
</span>
<span
className={`${
checked
? 'opacity-100 ease-in duration-150'
: 'opacity-0 ease-out duration-150'
} opacity-0 ease-out duration-150 absolute inset-0 h-full w-full flex items-center justify-center transition-opacity`}
>
<Moon className="h-3 w-3 text-yellow-400" />
</span>
</span>
</span>
</Switch>
)
}
export default Toggle

View File

@@ -0,0 +1 @@
export { default } from './Toggle'

View File

@@ -0,0 +1,24 @@
.dropdownMenu {
@apply fixed right-0 mt-7 origin-top-right outline-none bg-primary z-40 w-full h-full;
@screen lg {
@apply absolute border border-accents-1 shadow-lg w-56 h-auto;
}
}
.link {
@apply text-primary flex cursor-pointer px-6 py-3 flex transition ease-in-out duration-150 leading-6 font-medium items-center;
text-transform: capitalize;
}
.link:hover {
@apply bg-accents-1;
}
.link.active {
@apply font-bold bg-accents-2;
}
.off {
@apply hidden;
}

View File

@@ -0,0 +1,97 @@
import { FC } from 'react'
import Link from 'next/link'
import { useTheme } from 'next-themes'
import cn from 'classnames'
import s from './DropdownMenu.module.css'
import { Moon, Sun } from '@components/icons'
import { useUI } from '@components/ui/context'
import { Menu, Transition } from '@headlessui/react'
import useLogout from '@bigcommerce/storefront-data-hooks/use-logout'
import { useRouter } from 'next/router'
interface DropdownMenuProps {
open: boolean
}
const LINKS = [
{
name: 'My Orders',
href: '/orders',
},
{
name: 'My Profile',
href: '/profile',
},
{
name: 'My Cart',
href: '/cart',
},
]
const DropdownMenu: FC<DropdownMenuProps> = ({ open = false }) => {
const { theme, setTheme } = useTheme()
const logout = useLogout()
const { pathname } = useRouter()
const { closeSidebarIfPresent } = useUI()
return (
<Transition
show={open}
enter="transition ease-out duration-150 z-20"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className={s.dropdownMenu}>
{LINKS.map(({ name, href }) => (
<Menu.Item key={href}>
<div>
<Link href={href}>
<a
className={cn(s.link, {
[s.active]: pathname === href,
})}
onClick={closeSidebarIfPresent}
>
{name}
</a>
</Link>
</div>
</Menu.Item>
))}
<Menu.Item>
<a
className={cn(s.link, 'justify-between')}
onClick={() =>
theme === 'dark' ? setTheme('light') : setTheme('dark')
}
>
<div>
Theme: <strong>{theme}</strong>{' '}
</div>
<div className="ml-3">
{theme == 'dark' ? (
<Moon width={20} height={20} />
) : (
<Sun width="20" height={20} />
)}
</div>
</a>
</Menu.Item>
<Menu.Item>
<a
className={cn(s.link, 'border-t border-accents-2 mt-4')}
onClick={() => logout()}
>
Logout
</a>
</Menu.Item>
</Menu.Items>
</Transition>
)
}
export default DropdownMenu

View File

@@ -0,0 +1,36 @@
.root {
@apply relative;
}
.list {
@apply flex flex-row items-center justify-items-end h-full;
}
.item {
@apply mr-6 cursor-pointer relative transition ease-in-out duration-100 flex items-center outline-none text-primary;
&:hover {
@apply text-accents-6 transition scale-110 duration-100;
}
&:last-child {
@apply mr-0;
}
&:focus,
&:active {
@apply outline-none;
}
}
.bagCount {
@apply border border-accents-1 bg-secondary text-secondary h-4 w-4 absolute rounded-full right-3 top-3 flex items-center justify-center font-bold text-xs;
}
.avatarButton {
@apply inline-flex justify-center rounded-full;
}
.avatarButton:focus {
@apply outline-none;
}

View File

@@ -0,0 +1,70 @@
import { FC } from 'react'
import Link from 'next/link'
import cn from 'classnames'
import useCart from '@bigcommerce/storefront-data-hooks/cart/use-cart'
import useCustomer from '@bigcommerce/storefront-data-hooks/use-customer'
import { Menu } from '@headlessui/react'
import { Heart, Bag } from '@components/icons'
import { Avatar } from '@components/core'
import { useUI } from '@components/ui/context'
import DropdownMenu from './DropdownMenu'
import s from './UserNav.module.css'
interface Props {
className?: string
}
const countItem = (count: number, item: any) => count + item.quantity
const countItems = (count: number, items: any[]) =>
items.reduce(countItem, count)
const UserNav: FC<Props> = ({ className, children, ...props }) => {
const { data } = useCart()
const { data: customer } = useCustomer()
const { toggleSidebar, closeSidebarIfPresent, openModal } = useUI()
const itemsCount = Object.values(data?.line_items ?? {}).reduce(countItems, 0)
return (
<nav className={cn(s.root, className)}>
<div className={s.mainContainer}>
<ul className={s.list}>
<li className={s.item} onClick={toggleSidebar}>
<Bag />
{itemsCount > 0 && <span className={s.bagCount}>{itemsCount}</span>}
</li>
<li className={s.item}>
<Link href="/wishlist">
<a onClick={closeSidebarIfPresent}>
<Heart />
</a>
</Link>
</li>
<li className={s.item}>
{customer ? (
<Menu>
{({ open }) => (
<>
<Menu.Button className={s.avatarButton} aria-label="Menu">
<Avatar />
</Menu.Button>
<DropdownMenu open={open} />
</>
)}
</Menu>
) : (
<button
className={s.avatarButton}
aria-label="Menu"
onClick={() => openModal()}
>
<Avatar />
</button>
)}
</li>
</ul>
</div>
</nav>
)
}
export default UserNav

View File

@@ -0,0 +1 @@
export { default } from './UserNav'

View File

@@ -0,0 +1,12 @@
export { default as Avatar } from './Avatar'
export { default as Featurebar } from './Featurebar'
export { default as Footer } from './Footer'
export { default as Layout } from './Layout'
export { default as Navbar } from './Navbar'
export { default as Searchbar } from './Searchbar'
export { default as UserNav } from './UserNav'
export { default as Toggle } from './Toggle'
export { default as Head } from './Head'
export { default as HTMLContent } from './HTMLContent'
export { default as I18nWidget } from './I18nWidget'
export { default as EnhancedImage } from './EnhancedImage'