forked from crowetic/commerce
commit
7f8d37b3b9
@ -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);
|
||||||
|
}
|
||||||
|
@ -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>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
20
components/icons/ChevronUp.tsx
Normal file
20
components/icons/ChevronUp.tsx
Normal 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
|
@ -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'
|
||||||
|
45
lib/click-outside/click-outside.tsx
Normal file
45
lib/click-outside/click-outside.tsx
Normal 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
|
5
lib/click-outside/has-parent.js
Normal file
5
lib/click-outside/has-parent.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import isInDOM from './is-in-dom'
|
||||||
|
|
||||||
|
export default function hasParent(element, root) {
|
||||||
|
return root && root.contains(element) && isInDOM(element)
|
||||||
|
}
|
1
lib/click-outside/index.ts
Normal file
1
lib/click-outside/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './click-outside'
|
3
lib/click-outside/is-in-dom.js
Normal file
3
lib/click-outside/is-in-dom.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export default function isInDom(obj) {
|
||||||
|
return Boolean(obj.closest('body'))
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user