Merge branch 'issue/79-fallback-image' of https://github.com/leahtard/commerce into issue/79-fallback-image

This commit is contained in:
Leah Wagner 2021-01-23 21:48:59 -08:00
commit 6d30d04fae
33 changed files with 449 additions and 2641 deletions

View File

@ -17,7 +17,7 @@ This project is currently <b>under development</b>.
- Responsive - Responsive
- UI Components - UI Components
- Theming - Theming
- Standarized Data Hooks - Standardized Data Hooks
- Integrations - Integrate seamlessly with the most common ecommerce platforms. - Integrations - Integrate seamlessly with the most common ecommerce platforms.
- Dark Mode Support - Dark Mode Support
@ -66,6 +66,15 @@ After Email confirmation, Checkout should be manually enabled through BigCommerc
BigCommerce team has been notified and they plan to add more detailed about this subject. BigCommerce team has been notified and they plan to add more detailed about this subject.
</details> </details>
<details>
<summary>I have issues with BigCommerce data hooks</summary>
<br>
Report issue with Data Hooks here: https://github.com/bigcommerce/storefront-data-hooks
</details>
## Contribute ## Contribute
Our commitment to Open Source can be found [here](https://vercel.com/oss). Our commitment to Open Source can be found [here](https://vercel.com/oss).

View File

@ -1,26 +0,0 @@
.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

@ -1,16 +0,0 @@
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

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

View File

@ -1,11 +1,9 @@
import { FC } from 'react'
import cn from 'classnames' import cn from 'classnames'
import { useRouter } from 'next/router'
import Link from 'next/link' import Link from 'next/link'
import { Menu } from '@headlessui/react' import { FC, useState } from 'react'
import { DoubleChevron } from '@components/icons' import { useRouter } from 'next/router'
import s from './I18nWidget.module.css' import s from './I18nWidget.module.css'
import { Cross } from '@components/icons'
interface LOCALE_DATA { interface LOCALE_DATA {
name: string name: string
img: { img: {
@ -32,6 +30,7 @@ const LOCALES_MAP: Record<string, LOCALE_DATA> = {
} }
const I18nWidget: FC = () => { const I18nWidget: FC = () => {
const [display, setDisplay] = useState(false)
const { const {
locale, locale,
locales, locales,
@ -39,42 +38,61 @@ const I18nWidget: FC = () => {
asPath: currentPath, asPath: currentPath,
} = useRouter() } = useRouter()
const options = locales?.filter((val) => val !== locale) const options = locales?.filter((val) => val !== locale)
const currentLocale = locale || defaultLocale const currentLocale = locale || defaultLocale
return ( return (
<nav className={s.root}> <nav className={s.root}>
<Menu> <div className="flex items-center relative">
<Menu.Button className={s.button} aria-label="Language selector"> <button className={s.button} aria-label="Language selector" />
<img <img
className="block mr-2 w-5" className="block mr-2 w-5"
src={`/${LOCALES_MAP[currentLocale].img.filename}`} src={`/${LOCALES_MAP[currentLocale].img.filename}`}
alt={LOCALES_MAP[currentLocale].img.alt} alt={LOCALES_MAP[currentLocale].img.alt}
/> />
<span className="mr-2">{LOCALES_MAP[currentLocale].name}</span>
{options && ( {options && (
<span> <span className="cursor-pointer" onClick={() => setDisplay(!display)}>
<DoubleChevron /> <svg
viewBox="0 0 24 24"
width="24"
height="24"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
fill="none"
shapeRendering="geometricPrecision"
>
<path d="M6 9l6 6 6-6" />
</svg>
</span> </span>
)} )}
</Menu.Button> </div>
<div className="absolute top-0 right-0">
{options?.length ? ( {options?.length && display ? (
<Menu.Items className={s.dropdownMenu}> <div className={s.dropdownMenu}>
<div className="flex flex-row justify-end px-6">
<button
onClick={() => setDisplay(false)}
aria-label="Close panel"
className={s.closeButton}
>
<Cross className="h-6 w-6" />
</button>
</div>
<ul>
{options.map((locale) => ( {options.map((locale) => (
<Menu.Item key={locale}> <li key={locale}>
{({ active }) => (
<Link href={currentPath} locale={locale}> <Link href={currentPath} locale={locale}>
<a className={cn(s.item, { [s.active]: active })}> <a className={cn(s.item)} onClick={() => setDisplay(false)}>
{LOCALES_MAP[locale].name} {LOCALES_MAP[locale].name}
</a> </a>
</Link> </Link>
)} </li>
</Menu.Item>
))} ))}
</Menu.Items> </ul>
</div>
) : null} ) : null}
</Menu> </div>
</nav> </nav>
) )
} }

View File

@ -5,7 +5,6 @@ import { useRouter } from 'next/router'
import React, { FC } from 'react' import React, { FC } from 'react'
import { useUI } from '@components/ui/context' import { useUI } from '@components/ui/context'
import { Navbar, Footer } from '@components/common' import { Navbar, Footer } from '@components/common'
import { usePreventScroll } from '@react-aria/overlays'
import { useAcceptCookies } from '@lib/hooks/useAcceptCookies' import { useAcceptCookies } from '@lib/hooks/useAcceptCookies'
import { CommerceProvider } from '@bigcommerce/storefront-data-hooks' import { CommerceProvider } from '@bigcommerce/storefront-data-hooks'
import { Sidebar, Button, Modal, LoadingDots } from '@components/ui' import { Sidebar, Button, Modal, LoadingDots } from '@components/ui'
@ -56,10 +55,6 @@ const Layout: FC<Props> = ({ children, pageProps }) => {
const { acceptedCookies, onAcceptCookies } = useAcceptCookies() const { acceptedCookies, onAcceptCookies } = useAcceptCookies()
const { locale = 'en-US' } = useRouter() const { locale = 'en-US' } = useRouter()
usePreventScroll({
isDisabled: !(displaySidebar || displayModal),
})
return ( return (
<CommerceProvider locale={locale}> <CommerceProvider locale={locale}>
<div className={cn(s.root)}> <div className={cn(s.root)}>

View File

@ -34,7 +34,7 @@ const Navbar: FC = () => {
</a> </a>
</Link> </Link>
<nav className="space-x-4 ml-6 hidden lg:block"> <nav className="space-x-4 ml-6 hidden lg:block">
<Link href="/"> <Link href="/search">
<a className={s.link}>All</a> <a className={s.link}>All</a>
</Link> </Link>
<Link href="/search?q=clothes"> <Link href="/search?q=clothes">

View File

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

View File

@ -1,55 +0,0 @@
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

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

View File

@ -1,8 +1,8 @@
.dropdownMenu { .dropdownMenu {
@apply fixed right-0 mt-7 origin-top-right outline-none bg-primary z-40 w-full h-full; @apply fixed right-0 mt-2 origin-top-right outline-none bg-primary z-40 w-full h-full;
@screen lg { @screen lg {
@apply absolute border border-accents-1 shadow-lg w-56 h-auto; @apply absolute top-10 border border-accents-1 shadow-lg w-56 h-auto;
} }
} }

View File

@ -1,16 +1,16 @@
import { FC } from 'react'
import Link from 'next/link'
import { useTheme } from 'next-themes'
import cn from 'classnames' import cn from 'classnames'
import Link from 'next/link'
import { FC, useState } from 'react'
import { useTheme } from 'next-themes'
import { useRouter } from 'next/router'
import s from './DropdownMenu.module.css' import s from './DropdownMenu.module.css'
import { Avatar } from '@components/common'
import { Moon, Sun } from '@components/icons' import { Moon, Sun } from '@components/icons'
import { useUI } from '@components/ui/context' 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'
import useLogout from '@bigcommerce/storefront-data-hooks/use-logout'
interface DropdownMenuProps { interface DropdownMenuProps {
open: boolean open?: boolean
} }
const LINKS = [ const LINKS = [
@ -29,25 +29,26 @@ const LINKS = [
] ]
const DropdownMenu: FC<DropdownMenuProps> = ({ open = false }) => { const DropdownMenu: FC<DropdownMenuProps> = ({ open = false }) => {
const { theme, setTheme } = useTheme()
const logout = useLogout() const logout = useLogout()
const { pathname } = useRouter() const { pathname } = useRouter()
const { theme, setTheme } = useTheme()
const [display, setDisplay] = useState(false)
const { closeSidebarIfPresent } = useUI() const { closeSidebarIfPresent } = useUI()
return ( return (
<Transition <div>
show={open} <button
enter="transition ease-out duration-150 z-20" className={s.avatarButton}
enterFrom="transform opacity-0 scale-95" onClick={() => setDisplay(!display)}
enterTo="transform opacity-100 scale-100" aria-label="Menu"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
> >
<Menu.Items className={s.dropdownMenu}> <Avatar />
</button>
{display && (
<ul className={s.dropdownMenu}>
{LINKS.map(({ name, href }) => ( {LINKS.map(({ name, href }) => (
<Menu.Item key={href}> <li key={href}>
<div> <div>
<Link href={href}> <Link href={href}>
<a <a
@ -60,9 +61,9 @@ const DropdownMenu: FC<DropdownMenuProps> = ({ open = false }) => {
</a> </a>
</Link> </Link>
</div> </div>
</Menu.Item> </li>
))} ))}
<Menu.Item> <li>
<a <a
className={cn(s.link, 'justify-between')} className={cn(s.link, 'justify-between')}
onClick={() => onClick={() =>
@ -80,17 +81,18 @@ const DropdownMenu: FC<DropdownMenuProps> = ({ open = false }) => {
)} )}
</div> </div>
</a> </a>
</Menu.Item> </li>
<Menu.Item> <li>
<a <a
className={cn(s.link, 'border-t border-accents-2 mt-4')} className={cn(s.link, 'border-t border-accents-2 mt-4')}
onClick={() => logout()} onClick={() => logout()}
> >
Logout Logout
</a> </a>
</Menu.Item> </li>
</Menu.Items> </ul>
</Transition> )}
</div>
) )
} }

View File

@ -3,12 +3,11 @@ import Link from 'next/link'
import cn from 'classnames' import cn from 'classnames'
import useCart from '@bigcommerce/storefront-data-hooks/cart/use-cart' import useCart from '@bigcommerce/storefront-data-hooks/cart/use-cart'
import useCustomer from '@bigcommerce/storefront-data-hooks/use-customer' import useCustomer from '@bigcommerce/storefront-data-hooks/use-customer'
import { Menu } from '@headlessui/react'
import { Heart, Bag } from '@components/icons' import { Heart, Bag } from '@components/icons'
import { Avatar } from '@components/common'
import { useUI } from '@components/ui/context' import { useUI } from '@components/ui/context'
import DropdownMenu from './DropdownMenu' import DropdownMenu from './DropdownMenu'
import s from './UserNav.module.css' import s from './UserNav.module.css'
import { Avatar } from '@components/common'
interface Props { interface Props {
className?: string className?: string
@ -21,9 +20,9 @@ const countItems = (count: number, items: any[]) =>
const UserNav: FC<Props> = ({ className, children, ...props }) => { const UserNav: FC<Props> = ({ className, children, ...props }) => {
const { data } = useCart() const { data } = useCart()
const { data: customer } = useCustomer() const { data: customer } = useCustomer()
const { toggleSidebar, closeSidebarIfPresent, openModal } = useUI() const { toggleSidebar, closeSidebarIfPresent, openModal } = useUI()
const itemsCount = Object.values(data?.line_items ?? {}).reduce(countItems, 0) const itemsCount = Object.values(data?.line_items ?? {}).reduce(countItems, 0)
return ( return (
<nav className={cn(s.root, className)}> <nav className={cn(s.root, className)}>
<div className={s.mainContainer}> <div className={s.mainContainer}>
@ -41,16 +40,7 @@ const UserNav: FC<Props> = ({ className, children, ...props }) => {
</li> </li>
<li className={s.item}> <li className={s.item}>
{customer ? ( {customer ? (
<Menu> <DropdownMenu />
{({ open }) => (
<>
<Menu.Button className={s.avatarButton} aria-label="Menu">
<Avatar />
</Menu.Button>
<DropdownMenu open={open} />
</>
)}
</Menu>
) : ( ) : (
<button <button
className={s.avatarButton} className={s.avatarButton}

View File

@ -5,7 +5,5 @@ export { default as Layout } from './Layout'
export { default as Navbar } from './Navbar' export { default as Navbar } from './Navbar'
export { default as Searchbar } from './Searchbar' export { default as Searchbar } from './Searchbar'
export { default as UserNav } from './UserNav' export { default as UserNav } from './UserNav'
export { default as Toggle } from './Toggle'
export { default as Head } from './Head' export { default as Head } from './Head'
export { default as HTMLContent } from './HTMLContent'
export { default as I18nWidget } from './I18nWidget' export { default as I18nWidget } from './I18nWidget'

View File

@ -1,4 +1,4 @@
const Sun = ({ ...props }) => { const Github = ({ ...props }) => {
return ( return (
<svg <svg
width="24" width="24"
@ -17,4 +17,4 @@ const Sun = ({ ...props }) => {
) )
} }
export default Sun export default Github

View File

@ -6,8 +6,7 @@ import { NextSeo } from 'next-seo'
import s from './ProductView.module.css' import s from './ProductView.module.css'
import { useUI } from '@components/ui/context' import { useUI } from '@components/ui/context'
import { Swatch, ProductSlider } from '@components/product' import { Swatch, ProductSlider } from '@components/product'
import { Button, Container } from '@components/ui' import { Button, Container, Text } from '@components/ui'
import { HTMLContent } from '@components/common'
import usePrice from '@bigcommerce/storefront-data-hooks/use-price' import usePrice from '@bigcommerce/storefront-data-hooks/use-price'
import useAddItem from '@bigcommerce/storefront-data-hooks/cart/use-add-item' import useAddItem from '@bigcommerce/storefront-data-hooks/cart/use-add-item'
@ -137,7 +136,7 @@ const ProductView: FC<Props> = ({ product }) => {
))} ))}
<div className="pb-14 break-words w-full max-w-xl"> <div className="pb-14 break-words w-full max-w-xl">
<HTMLContent html={product.description} /> <Text html={product.description} />
</div> </div>
</section> </section>
<div> <div>

View File

@ -6,7 +6,6 @@ import React, {
useRef, useRef,
} from 'react' } from 'react'
import mergeRefs from 'react-merge-refs' import mergeRefs from 'react-merge-refs'
import { useButton } from 'react-aria'
import s from './Button.module.css' import s from './Button.module.css'
import { LoadingDots } from '@components/ui' import { LoadingDots } from '@components/ui'
@ -34,19 +33,8 @@ const Button: React.FC<ButtonProps> = forwardRef((props, buttonRef) => {
loading = false, loading = false,
disabled = false, disabled = false,
style = {}, style = {},
...rest
} = props } = props
const ref = useRef<typeof Component>(null) const ref = useRef<typeof Component>(null)
const { buttonProps, isPressed } = useButton(
{
...rest,
// @ts-ignore onClick === onPress for our purposes
onPress: onClick,
isDisabled: disabled,
elementType: Component,
},
ref
)
const rootClassName = cn( const rootClassName = cn(
s.root, s.root,
@ -63,8 +51,6 @@ const Button: React.FC<ButtonProps> = forwardRef((props, buttonRef) => {
aria-pressed={active} aria-pressed={active}
data-variant={variant} data-variant={variant}
ref={mergeRefs([ref, buttonRef])} ref={mergeRefs([ref, buttonRef])}
{...buttonProps}
data-active={isPressed ? '' : undefined}
className={rootClassName} className={rootClassName}
disabled={disabled} disabled={disabled}
style={{ style={{

View File

@ -1,10 +1,13 @@
import { FC, useRef } from 'react' import { FC, useRef, useEffect } from 'react'
import s from './Modal.module.css'
import { FocusScope } from '@react-aria/focus'
import { Transition } from '@headlessui/react'
import { Cross } from '@components/icons'
import { useOverlay, OverlayContainer } from '@react-aria/overlays'
import Portal from '@reach/portal' import Portal from '@reach/portal'
import s from './Modal.module.css'
import { Cross } from '@components/icons'
import {
disableBodyScroll,
enableBodyScroll,
clearAllBodyScrollLocks,
} from 'body-scroll-lock'
interface Props { interface Props {
className?: string className?: string
children?: any children?: any
@ -12,32 +15,27 @@ interface Props {
onClose: () => void onClose: () => void
} }
const Modal: FC<Props> = ({ children, open = false, onClose, ...props }) => { const Modal: FC<Props> = ({ children, open, onClose }) => {
let ref = useRef() as React.MutableRefObject<HTMLInputElement> const ref = useRef() as React.MutableRefObject<HTMLDivElement>
let { overlayProps } = useOverlay(
{ useEffect(() => {
isOpen: open, if (ref.current) {
isDismissable: false, if (open) {
onClose: onClose, disableBodyScroll(ref.current)
...props, } else {
}, enableBodyScroll(ref.current)
ref }
) }
return () => {
clearAllBodyScrollLocks()
}
}, [open])
return ( return (
<Transition show={open}> <Portal>
<OverlayContainer> {open ? (
<FocusScope contain restoreFocus autoFocus> <div className={s.root} ref={ref}>
<div className={s.root}> <div className={s.modal}>
<Transition.Child
enter="transition-opacity ease-linear duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity ease-linear duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className={s.modal} {...overlayProps} ref={ref}>
<div className="h-7 flex items-center justify-end w-full"> <div className="h-7 flex items-center justify-end w-full">
<button <button
onClick={() => onClose()} onClick={() => onClose()}
@ -49,11 +47,9 @@ const Modal: FC<Props> = ({ children, open = false, onClose, ...props }) => {
</div> </div>
{children} {children}
</div> </div>
</Transition.Child>
</div> </div>
</FocusScope> ) : null}
</OverlayContainer> </Portal>
</Transition>
) )
} }

View File

@ -1,9 +1,11 @@
import { FC, useRef } from 'react'
import s from './Sidebar.module.css' import s from './Sidebar.module.css'
import { Transition } from '@headlessui/react'
import { useOverlay, OverlayContainer } from '@react-aria/overlays'
import { FocusScope } from '@react-aria/focus'
import Portal from '@reach/portal' import Portal from '@reach/portal'
import { FC, useEffect, useRef } from 'react'
import {
disableBodyScroll,
enableBodyScroll,
clearAllBodyScrollLocks,
} from 'body-scroll-lock'
interface Props { interface Props {
children: any children: any
@ -12,62 +14,40 @@ interface Props {
} }
const Sidebar: FC<Props> = ({ children, open = false, onClose }) => { const Sidebar: FC<Props> = ({ children, open = false, onClose }) => {
const ref = useRef<HTMLDivElement>(null) const ref = useRef() as React.MutableRefObject<HTMLDivElement>
const { overlayProps } = useOverlay(
{ useEffect(() => {
isOpen: open, if (ref.current) {
isDismissable: true, if (open) {
onClose: onClose, disableBodyScroll(ref.current)
}, } else {
ref enableBodyScroll(ref.current)
) }
}
return () => {
clearAllBodyScrollLocks()
}
}, [open])
return ( return (
<Portal> <Portal>
<Transition show={open}> {open ? (
<OverlayContainer> <div className={s.root} ref={ref}>
<FocusScope contain restoreFocus autoFocus>
<div className={s.root}>
<div className="absolute inset-0 overflow-hidden"> <div className="absolute inset-0 overflow-hidden">
<Transition.Child
enter="transition-opacity ease-linear duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity ease-linear duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div <div
className="absolute inset-0 bg-black bg-opacity-50 transition-opacity" className="absolute inset-0 bg-black bg-opacity-50 transition-opacity"
// Close the sidebar when clicking on the backdrop
onClick={onClose} onClick={onClose}
/> />
</Transition.Child> <section className="absolute inset-y-0 right-0 pl-10 max-w-full flex sm:pl-16 outline-none">
<section
className="absolute inset-y-0 right-0 pl-10 max-w-full flex sm:pl-16 outline-none"
{...overlayProps}
ref={ref}
>
<Transition.Child
enter="transition ease-in-out duration-300 transform"
enterFrom="translate-x-full"
enterTo="translate-x-0"
leave="transition ease-in-out duration-300 transform"
leaveFrom="translate-x-0"
leaveTo="translate-x-full"
>
<div className="h-full md:w-screen md:max-w-md"> <div className="h-full md:w-screen md:max-w-md">
<div className="h-full flex flex-col text-base bg-accents-1 shadow-xl overflow-y-auto"> <div className="h-full flex flex-col text-base bg-accents-1 shadow-xl overflow-y-auto">
{children} {children}
</div> </div>
</div> </div>
</Transition.Child>
</section> </section>
</div> </div>
</div> </div>
</FocusScope> ) : null}
</OverlayContainer>
</Transition>
</Portal> </Portal>
) )
} }

View File

@ -10,7 +10,8 @@ interface Props {
variant?: Variant variant?: Variant
className?: string className?: string
style?: CSSProperties style?: CSSProperties
children: React.ReactNode | any children?: React.ReactNode | any
html?: string
} }
type Variant = 'heading' | 'body' | 'pageHeading' | 'sectionHeading' type Variant = 'heading' | 'body' | 'pageHeading' | 'sectionHeading'
@ -20,6 +21,7 @@ const Text: FunctionComponent<Props> = ({
className = '', className = '',
variant = 'body', variant = 'body',
children, children,
html,
}) => { }) => {
const componentsMap: { const componentsMap: {
[P in Variant]: React.ComponentType<any> | string [P in Variant]: React.ComponentType<any> | string
@ -36,6 +38,12 @@ const Text: FunctionComponent<Props> = ({
| React.ComponentType<any> | React.ComponentType<any>
| string = componentsMap![variant!] | string = componentsMap![variant!]
const htmlContentProps = html
? {
dangerouslySetInnerHTML: { __html: html },
}
: {}
return ( return (
<Component <Component
className={cn( className={cn(
@ -49,6 +57,7 @@ const Text: FunctionComponent<Props> = ({
className className
)} )}
style={style} style={style}
{...htmlContentProps}
> >
{children} {children}
</Component> </Component>

View File

@ -1,9 +0,0 @@
.root {
}
.toast {
@apply absolute bg-primary text-primary flex items-center border border-accents-1
rounded-md z-50 shadow-2xl top-0 right-0 p-6 my-6 mx-3;
width: 420px;
z-index: 20000;
}

View File

@ -1,73 +0,0 @@
import cn from 'classnames'
import { FC, useRef, useEffect, useCallback } from 'react'
import s from './Toast.module.css'
import { useDialog } from '@react-aria/dialog'
import { FocusScope } from '@react-aria/focus'
import { Transition } from '@headlessui/react'
import { useOverlay, useModal, OverlayContainer } from '@react-aria/overlays'
interface Props {
className?: string
children?: any
open?: boolean
onClose: () => void
}
const Toast: FC<Props> = ({
className,
children,
open = false,
onClose,
...props
}) => {
const rootClassName = cn(s.root, className)
let ref = useRef() as React.MutableRefObject<HTMLInputElement>
let { modalProps } = useModal()
let { dialogProps } = useDialog({}, ref)
let { overlayProps } = useOverlay(
{
isOpen: open,
isDismissable: true,
onClose: onClose,
...props,
},
ref
)
// useEffect(() => {
// setTimeout(() => {
// useCallback(onClose, [])
// }, 400)
// })
return (
<Transition show={open}>
<OverlayContainer>
<FocusScope contain restoreFocus autoFocus>
<div className={rootClassName}>
<Transition.Child
enter="transition-opacity ease-linear duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity ease-linear duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div
className={s.toast}
{...overlayProps}
{...dialogProps}
{...modalProps}
ref={ref}
>
{children}
</div>
</Transition.Child>
</div>
</FocusScope>
</OverlayContainer>
</Transition>
)
}
export default Toast

View File

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

View File

@ -1,6 +1,5 @@
import React, { FC, useMemo } from 'react' import React, { FC, useMemo } from 'react'
import { ThemeProvider } from 'next-themes' import { ThemeProvider } from 'next-themes'
import { SSRProvider, OverlayProvider } from 'react-aria'
export interface State { export interface State {
displaySidebar: boolean displaySidebar: boolean
@ -181,10 +180,6 @@ export const useUI = () => {
export const ManagedUIContext: FC = ({ children }) => ( export const ManagedUIContext: FC = ({ children }) => (
<UIProvider> <UIProvider>
<ThemeProvider> <ThemeProvider>{children}</ThemeProvider>
<SSRProvider>
<OverlayProvider>{children}</OverlayProvider>
</SSRProvider>
</ThemeProvider>
</UIProvider> </UIProvider>
) )

View File

@ -10,4 +10,3 @@ export { default as Skeleton } from './Skeleton'
export { default as Modal } from './Modal' export { default as Modal } from './Modal'
export { default as Text } from './Text' export { default as Text } from './Text'
export { default as Input } from './Input' export { default as Input } from './Input'
export { default as Toast } from './Toast'

View File

@ -7,8 +7,7 @@ import usePrice from '@bigcommerce/storefront-data-hooks/use-price'
import useRemoveItem from '@bigcommerce/storefront-data-hooks/wishlist/use-remove-item' import useRemoveItem from '@bigcommerce/storefront-data-hooks/wishlist/use-remove-item'
import useAddItem from '@bigcommerce/storefront-data-hooks/cart/use-add-item' import useAddItem from '@bigcommerce/storefront-data-hooks/cart/use-add-item'
import { useUI } from '@components/ui/context' import { useUI } from '@components/ui/context'
import { Button } from '@components/ui' import { Button, Text } from '@components/ui'
import { HTMLContent } from '@components/common'
import { Trash } from '@components/icons' import { Trash } from '@components/icons'
import s from './WishlistCard.module.css' import s from './WishlistCard.module.css'
@ -72,7 +71,7 @@ const WishlistCard: FC<Props> = ({ item }) => {
</Link> </Link>
</h3> </h3>
<div className="mb-4"> <div className="mb-4">
<HTMLContent html={product.description!} /> <Text html={product.description} />
</div> </div>
<Button <Button
aria-label="Add to Cart" aria-label="Add to Cart"

14
lib/defaults.ts Normal file
View File

@ -0,0 +1,14 @@
// Fallback to CMS Data
export const defatultPageProps = {
header: {
links: [
{
link: {
title: 'New Arrivals',
url: '/',
},
},
],
},
}

21
license.md Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2020 Vercel, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -43,12 +43,12 @@
}, },
"dependencies": { "dependencies": {
"@bigcommerce/storefront-data-hooks": "^1.0.2", "@bigcommerce/storefront-data-hooks": "^1.0.2",
"@headlessui/react": "^0.2.0",
"@reach/portal": "^0.11.2", "@reach/portal": "^0.11.2",
"@react-aria/overlays": "^3.4.0",
"@tailwindcss/ui": "^0.6.2", "@tailwindcss/ui": "^0.6.2",
"@types/body-scroll-lock": "^2.6.1",
"@types/lodash.throttle": "^4.1.6", "@types/lodash.throttle": "^4.1.6",
"@vercel/fetch": "^6.1.0", "@vercel/fetch": "^6.1.0",
"body-scroll-lock": "^3.1.5",
"bowser": "^2.11.0", "bowser": "^2.11.0",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"email-validator": "^2.0.4", "email-validator": "^2.0.4",
@ -56,12 +56,11 @@
"keen-slider": "^5.2.4", "keen-slider": "^5.2.4",
"lodash.random": "^3.2.0", "lodash.random": "^3.2.0",
"lodash.throttle": "^4.1.1", "lodash.throttle": "^4.1.1",
"next": "^10.0.1-canary.7", "next": "^10.0.3-canary.3",
"next-seo": "^4.11.0", "next-seo": "^4.11.0",
"next-themes": "^0.0.4", "next-themes": "^0.0.4",
"postcss-nesting": "^7.0.1", "postcss-nesting": "^7.0.1",
"react": "^16.14.0", "react": "^16.14.0",
"react-aria": "^3.0.0",
"react-dom": "^16.14.0", "react-dom": "^16.14.0",
"react-intersection-observer": "^8.30.1", "react-intersection-observer": "^8.30.1",
"react-merge-refs": "^1.1.0", "react-merge-refs": "^1.1.0",

View File

@ -3,12 +3,14 @@ import type {
GetStaticPropsContext, GetStaticPropsContext,
InferGetStaticPropsType, InferGetStaticPropsType,
} from 'next' } from 'next'
import getSlug from '@lib/get-slug'
import { missingLocaleInPages } from '@lib/usage-warns'
import { Layout } from '@components/common'
import { Text } from '@components/ui'
import { getConfig } from '@bigcommerce/storefront-data-hooks/api' import { getConfig } from '@bigcommerce/storefront-data-hooks/api'
import getPage from '@bigcommerce/storefront-data-hooks/api/operations/get-page' import getPage from '@bigcommerce/storefront-data-hooks/api/operations/get-page'
import getAllPages from '@bigcommerce/storefront-data-hooks/api/operations/get-all-pages' import getAllPages from '@bigcommerce/storefront-data-hooks/api/operations/get-all-pages'
import getSlug from '@lib/get-slug' import { defatultPageProps } from '@lib/defaults'
import { missingLocaleInPages } from '@lib/usage-warns'
import { Layout, HTMLContent } from '@components/common'
export async function getStaticProps({ export async function getStaticProps({
preview, preview,
@ -32,7 +34,7 @@ export async function getStaticProps({
} }
return { return {
props: { pages, page }, props: { ...defatultPageProps, pages, page },
revalidate: 60 * 60, // Every hour revalidate: 60 * 60, // Every hour
} }
} }
@ -64,7 +66,7 @@ export default function Pages({
}: InferGetStaticPropsType<typeof getStaticProps>) { }: InferGetStaticPropsType<typeof getStaticProps>) {
return ( return (
<div className="max-w-2xl mx-auto py-20"> <div className="max-w-2xl mx-auto py-20">
{page?.body && <HTMLContent html={page.body} />} {page?.body && <Text html={page.body} />}
</div> </div>
) )
} }

View File

@ -51,8 +51,7 @@ export async function getStaticPaths({ locales }: GetStaticPathsContext) {
return arr return arr
}, []) }, [])
: products.map((product) => `/product${product.node.path}`), : products.map((product) => `/product${product.node.path}`),
// If your store has tons of products, enable fallback mode to improve build times! fallback: 'blocking',
fallback: false,
} }
} }

View File

@ -4,9 +4,9 @@ import getAllPages from '@bigcommerce/storefront-data-hooks/api/operations/get-a
import useWishlist from '@bigcommerce/storefront-data-hooks/wishlist/use-wishlist' import useWishlist from '@bigcommerce/storefront-data-hooks/wishlist/use-wishlist'
import { Layout } from '@components/common' import { Layout } from '@components/common'
import { Heart } from '@components/icons' import { Heart } from '@components/icons'
import { Container, Text } from '@components/ui' import { Text, Container } from '@components/ui'
import { WishlistCard } from '@components/wishlist' import { WishlistCard } from '@components/wishlist'
import { Transition } from '@headlessui/react' import { defatultPageProps } from '@lib/defaults'
export async function getStaticProps({ export async function getStaticProps({
preview, preview,
@ -15,7 +15,7 @@ export async function getStaticProps({
const config = getConfig({ locale }) const config = getConfig({ locale })
const { pages } = await getAllPages({ config, preview }) const { pages } = await getAllPages({ config, preview })
return { return {
props: { pages }, props: { ...defatultPageProps, pages },
} }
} }
@ -28,45 +28,22 @@ export default function Wishlist() {
<Text variant="pageHeading">My Wishlist</Text> <Text variant="pageHeading">My Wishlist</Text>
<div className="group flex flex-col"> <div className="group flex flex-col">
{isEmpty ? ( {isEmpty ? (
<Transition show>
<Transition.Child
enter="transition-opacity ease-linear duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity ease-linear duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="flex-1 px-12 py-24 flex flex-col justify-center items-center "> <div className="flex-1 px-12 py-24 flex flex-col justify-center items-center ">
<span className="border border-dashed border-secondary rounded-full flex items-center justify-center w-16 h-16 bg-primary p-12 rounded-lg text-primary"> <span className="border border-dashed border-secondary flex items-center justify-center w-16 h-16 bg-primary p-12 rounded-lg text-primary">
<Heart className="absolute" /> <Heart className="absolute" />
</span> </span>
<h2 className="pt-6 text-2xl font-bold tracking-wide text-center"> <h2 className="pt-6 text-2xl font-bold tracking-wide text-center">
Your wishlist is empty Your wishlist is empty
</h2> </h2>
<p className="text-accents-6 px-10 text-center pt-2"> <p className="text-accents-6 px-10 text-center pt-2">
Biscuit oat cake wafer icing ice cream tiramisu pudding Biscuit oat cake wafer icing ice cream tiramisu pudding cupcake.
cupcake.
</p> </p>
</div> </div>
</Transition.Child>
</Transition>
) : ( ) : (
<Transition show> data &&
{data &&
data.items?.map((item) => ( data.items?.map((item) => (
<Transition.Child
enter="transition-opacity ease-linear duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity ease-linear duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<WishlistCard key={item.id} item={item} /> <WishlistCard key={item.id} item={item} />
</Transition.Child> ))
))}
</Transition>
)} )}
</div> </div>
</div> </div>

2313
yarn.lock

File diff suppressed because it is too large Load Diff