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;
}
.button:hover {
@apply border-accents-4 shadow-sm;
}
.button:focus {
@apply outline-none;
}
@ -18,6 +22,12 @@
}
}
.closeButton {
@screen md {
@apply hidden;
}
}
.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;
@ -26,3 +36,7 @@
.item:hover {
@apply bg-accents-1;
}
.icon {
transform: rotate(180deg);
}

View File

@ -1,9 +1,10 @@
import cn from 'classnames'
import Link from 'next/link'
import { FC, useState } from 'react'
import { useRef, FC, useState } from 'react'
import { useRouter } from 'next/router'
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 {
name: string
img: {
@ -37,63 +38,62 @@ const I18nWidget: FC = () => {
defaultLocale = 'en-US',
asPath: currentPath,
} = useRouter()
const options = locales?.filter((val) => val !== locale)
const currentLocale = locale || defaultLocale
const ref = useRef<HTMLDivElement | null>(null)
return (
<nav className={s.root}>
<div className="flex items-center relative">
<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}
/>
{options && (
<span className="cursor-pointer" onClick={() => setDisplay(!display)}>
<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>
)}
</div>
<div className="absolute top-0 right-0">
{options?.length && display ? (
<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>
<ClickOutside active={display} onClick={() => setDisplay(false)} ref={ref}>
<nav className={s.root}>
<div
className="flex items-center relative"
onClick={() => setDisplay(!display)}
>
<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}
/>
{options && (
<span className="cursor-pointer">
<ChevronUp className={cn({ [s.icon]: display })} />
</span>
)}
</button>
</div>
<div className="absolute top-0 right-0">
{options?.length && display ? (
<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) => (
<li key={locale}>
<Link href={currentPath} locale={locale}>
<a
className={cn(s.item)}
onClick={() => setDisplay(false)}
>
{LOCALES_MAP[locale].name}
</a>
</Link>
</li>
))}
</ul>
</div>
<ul>
{options.map((locale) => (
<li key={locale}>
<Link href={currentPath} locale={locale}>
<a className={cn(s.item)} onClick={() => setDisplay(false)}>
{LOCALES_MAP[locale].name}
</a>
</Link>
</li>
))}
</ul>
</div>
) : null}
</div>
</nav>
) : null}
</div>
</nav>
</ClickOutside>
)
}

View File

@ -1,12 +1,18 @@
import cn from 'classnames'
import Link from 'next/link'
import { FC, useState } from 'react'
import { FC, useRef, useState, useEffect } from 'react'
import { useTheme } from 'next-themes'
import { useRouter } from 'next/router'
import s from './DropdownMenu.module.css'
import { Avatar } from '@components/common'
import { Moon, Sun } from '@components/icons'
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'
interface DropdownMenuProps {
@ -34,65 +40,80 @@ const DropdownMenu: FC<DropdownMenuProps> = ({ open = false }) => {
const { theme, setTheme } = useTheme()
const [display, setDisplay] = useState(false)
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 (
<div>
<button
className={s.avatarButton}
onClick={() => setDisplay(!display)}
aria-label="Menu"
>
<Avatar />
</button>
{display && (
<ul className={s.dropdownMenu}>
{LINKS.map(({ name, href }) => (
<li key={href}>
<div>
<Link href={href}>
<a
className={cn(s.link, {
[s.active]: pathname === href,
})}
onClick={closeSidebarIfPresent}
>
{name}
</a>
</Link>
</div>
<ClickOutside active={display} onClick={() => setDisplay(false)}>
<div>
<button
className={s.avatarButton}
onClick={() => setDisplay(!display)}
aria-label="Menu"
>
<Avatar />
</button>
{display && (
<ul className={s.dropdownMenu} ref={ref}>
{LINKS.map(({ name, href }) => (
<li key={href}>
<div>
<Link href={href}>
<a
className={cn(s.link, {
[s.active]: pathname === href,
})}
onClick={closeSidebarIfPresent}
>
{name}
</a>
</Link>
</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>
<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>
<a
className={cn(s.link, 'border-t border-accents-2 mt-4')}
onClick={() => logout()}
>
Logout
</a>
</li>
</ul>
)}
</div>
<li>
<a
className={cn(s.link, 'border-t border-accents-2 mt-4')}
onClick={() => logout()}
>
Logout
</a>
</li>
</ul>
)}
</div>
</ClickOutside>
)
}

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 RightArrow } from './RightArrow'
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'))
}