4
0
forked from crowetic/commerce

Merge pull request #110 from vercel/develop

New Updates
This commit is contained in:
B 2020-12-02 13:26:51 -03:00 committed by GitHub
commit 7f8d37b3b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 221 additions and 111 deletions

View File

@ -6,6 +6,10 @@
@apply h-10 px-2 rounded-md border border-accents-2 flex items-center justify-center; @apply h-10 px-2 rounded-md border border-accents-2 flex items-center justify-center;
} }
.button:hover {
@apply border-accents-4 shadow-sm;
}
.button:focus { .button:focus {
@apply outline-none; @apply outline-none;
} }
@ -18,6 +22,12 @@
} }
} }
.closeButton {
@screen md {
@apply hidden;
}
}
.item { .item {
@apply flex cursor-pointer px-6 py-3 flex transition ease-in-out duration-150 text-primary leading-6 font-medium items-center; @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; text-transform: capitalize;
@ -26,3 +36,7 @@
.item:hover { .item:hover {
@apply bg-accents-1; @apply bg-accents-1;
} }
.icon {
transform: rotate(180deg);
}

View File

@ -1,9 +1,10 @@
import cn from 'classnames' import cn from 'classnames'
import Link from 'next/link' import Link from 'next/link'
import { FC, useState } from 'react' import { useRef, FC, useState } from 'react'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import s from './I18nWidget.module.css' import s from './I18nWidget.module.css'
import { Cross } from '@components/icons' import { Cross, ChevronUp } from '@components/icons'
import ClickOutside from '@lib/click-outside'
interface LOCALE_DATA { interface LOCALE_DATA {
name: string name: string
img: { img: {
@ -37,63 +38,62 @@ const I18nWidget: FC = () => {
defaultLocale = 'en-US', defaultLocale = 'en-US',
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
const ref = useRef<HTMLDivElement | null>(null)
return ( return (
<nav className={s.root}> <ClickOutside active={display} onClick={() => setDisplay(false)} ref={ref}>
<div className="flex items-center relative"> <nav className={s.root}>
<button className={s.button} aria-label="Language selector" /> <div
<img className="flex items-center relative"
className="block mr-2 w-5" onClick={() => setDisplay(!display)}
src={`/${LOCALES_MAP[currentLocale].img.filename}`} >
alt={LOCALES_MAP[currentLocale].img.alt} <button className={s.button} aria-label="Language selector">
/> <img
{options && ( className="block mr-2 w-5"
<span className="cursor-pointer" onClick={() => setDisplay(!display)}> src={`/${LOCALES_MAP[currentLocale].img.filename}`}
<svg alt={LOCALES_MAP[currentLocale].img.alt}
viewBox="0 0 24 24" />
width="24" {options && (
height="24" <span className="cursor-pointer">
stroke="currentColor" <ChevronUp className={cn({ [s.icon]: display })} />
strokeWidth="1.5" </span>
strokeLinecap="round" )}
strokeLinejoin="round" </button>
fill="none" </div>
shapeRendering="geometricPrecision" <div className="absolute top-0 right-0">
> {options?.length && display ? (
<path d="M6 9l6 6 6-6" /> <div className={s.dropdownMenu}>
</svg> <div className="flex flex-row justify-end px-6">
</span> <button
)} onClick={() => setDisplay(false)}
</div> aria-label="Close panel"
<div className="absolute top-0 right-0"> className={s.closeButton}
{options?.length && display ? ( >
<div className={s.dropdownMenu}> <Cross className="h-6 w-6" />
<div className="flex flex-row justify-end px-6"> </button>
<button </div>
onClick={() => setDisplay(false)} <ul>
aria-label="Close panel" {options.map((locale) => (
className={s.closeButton} <li key={locale}>
> <Link href={currentPath} locale={locale}>
<Cross className="h-6 w-6" /> <a
</button> className={cn(s.item)}
onClick={() => setDisplay(false)}
>
{LOCALES_MAP[locale].name}
</a>
</Link>
</li>
))}
</ul>
</div> </div>
<ul> ) : null}
{options.map((locale) => ( </div>
<li key={locale}> </nav>
<Link href={currentPath} locale={locale}> </ClickOutside>
<a className={cn(s.item)} onClick={() => setDisplay(false)}>
{LOCALES_MAP[locale].name}
</a>
</Link>
</li>
))}
</ul>
</div>
) : null}
</div>
</nav>
) )
} }

View File

@ -1,12 +1,18 @@
import cn from 'classnames' import cn from 'classnames'
import Link from 'next/link' import Link from 'next/link'
import { FC, useState } from 'react' import { FC, useRef, useState, useEffect } from 'react'
import { useTheme } from 'next-themes' import { useTheme } from 'next-themes'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import s from './DropdownMenu.module.css' import s from './DropdownMenu.module.css'
import { Avatar } from '@components/common' 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 ClickOutside from '@lib/click-outside'
import {
disableBodyScroll,
enableBodyScroll,
clearAllBodyScrollLocks,
} from 'body-scroll-lock'
import useLogout from '@bigcommerce/storefront-data-hooks/use-logout' import useLogout from '@bigcommerce/storefront-data-hooks/use-logout'
interface DropdownMenuProps { interface DropdownMenuProps {
@ -34,65 +40,80 @@ const DropdownMenu: FC<DropdownMenuProps> = ({ open = false }) => {
const { theme, setTheme } = useTheme() const { theme, setTheme } = useTheme()
const [display, setDisplay] = useState(false) const [display, setDisplay] = useState(false)
const { closeSidebarIfPresent } = useUI() const { closeSidebarIfPresent } = useUI()
const ref = useRef() as React.MutableRefObject<HTMLUListElement>
useEffect(() => {
if (ref.current) {
if (display) {
disableBodyScroll(ref.current)
} else {
enableBodyScroll(ref.current)
}
}
return () => {
clearAllBodyScrollLocks()
}
}, [display])
return ( return (
<div> <ClickOutside active={display} onClick={() => setDisplay(false)}>
<button <div>
className={s.avatarButton} <button
onClick={() => setDisplay(!display)} className={s.avatarButton}
aria-label="Menu" onClick={() => setDisplay(!display)}
> aria-label="Menu"
<Avatar /> >
</button> <Avatar />
</button>
{display && ( {display && (
<ul className={s.dropdownMenu}> <ul className={s.dropdownMenu} ref={ref}>
{LINKS.map(({ name, href }) => ( {LINKS.map(({ name, href }) => (
<li key={href}> <li key={href}>
<div> <div>
<Link href={href}> <Link href={href}>
<a <a
className={cn(s.link, { className={cn(s.link, {
[s.active]: pathname === href, [s.active]: pathname === href,
})} })}
onClick={closeSidebarIfPresent} onClick={closeSidebarIfPresent}
> >
{name} {name}
</a> </a>
</Link> </Link>
</div> </div>
</li>
))}
<li>
<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>
</li> </li>
))} <li>
<li> <a
<a className={cn(s.link, 'border-t border-accents-2 mt-4')}
className={cn(s.link, 'justify-between')} onClick={() => logout()}
onClick={() => >
theme === 'dark' ? setTheme('light') : setTheme('dark') Logout
} </a>
> </li>
<div> </ul>
Theme: <strong>{theme}</strong>{' '} )}
</div> </div>
<div className="ml-3"> </ClickOutside>
{theme == 'dark' ? (
<Moon width={20} height={20} />
) : (
<Sun width="20" height={20} />
)}
</div>
</a>
</li>
<li>
<a
className={cn(s.link, 'border-t border-accents-2 mt-4')}
onClick={() => logout()}
>
Logout
</a>
</li>
</ul>
)}
</div>
) )
} }

View File

@ -0,0 +1,20 @@
const ChevronUp = ({ ...props }) => {
return (
<svg
viewBox="0 0 24 24"
width="24"
height="24"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
fill="none"
shape-rendering="geometricPrecision"
{...props}
>
<path d="M18 15l-6-6-6 6" />
</svg>
)
}
export default ChevronUp

View File

@ -12,3 +12,4 @@ export { default as Github } from './Github'
export { default as DoubleChevron } from './DoubleChevron' export { default as DoubleChevron } from './DoubleChevron'
export { default as RightArrow } from './RightArrow' export { default as RightArrow } from './RightArrow'
export { default as Info } from './Info' export { default as Info } from './Info'
export { default as ChevronUp } from './ChevronUp'

View File

@ -0,0 +1,45 @@
import React, { forwardRef, useEffect, Ref, MouseEvent } from 'react'
import hasParent from './has-parent'
interface ClickOutsideProps {
active: boolean
onClick: (e?: MouseEvent) => void
children: any
}
const ClickOutside = forwardRef(
(
{ active = true, onClick, children }: ClickOutsideProps,
ref: Ref<HTMLDivElement> | null | any = {}
) => {
const innerRef = ref?.current
const handleClick = (event: any) => {
if (!hasParent(event.target, innerRef)) {
if (typeof onClick === 'function') {
event.preventDefault()
event.stopImmediatePropagation()
onClick(event)
}
}
}
useEffect(() => {
if (active) {
document.addEventListener('mousedown', handleClick)
document.addEventListener('touchstart', handleClick)
}
return () => {
if (active) {
document.removeEventListener('mousedown', handleClick)
document.removeEventListener('touchstart', handleClick)
}
}
})
return React.cloneElement(children, { ref })
}
)
export default ClickOutside

View File

@ -0,0 +1,5 @@
import isInDOM from './is-in-dom'
export default function hasParent(element, root) {
return root && root.contains(element) && isInDOM(element)
}

View File

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

View File

@ -0,0 +1,3 @@
export default function isInDom(obj) {
return Boolean(obj.closest('body'))
}