diff --git a/components/common/I18nWidget/I18nWidget.module.css b/components/common/I18nWidget/I18nWidget.module.css index 6e8883521..07a1aeba7 100644 --- a/components/common/I18nWidget/I18nWidget.module.css +++ b/components/common/I18nWidget/I18nWidget.module.css @@ -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); +} diff --git a/components/common/I18nWidget/I18nWidget.tsx b/components/common/I18nWidget/I18nWidget.tsx index d533e8c5e..92b40322d 100644 --- a/components/common/I18nWidget/I18nWidget.tsx +++ b/components/common/I18nWidget/I18nWidget.tsx @@ -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(null) return ( - + ) : null} + + + ) } diff --git a/components/common/UserNav/DropdownMenu.tsx b/components/common/UserNav/DropdownMenu.tsx index 99b92dff0..fb3ddc7de 100644 --- a/components/common/UserNav/DropdownMenu.tsx +++ b/components/common/UserNav/DropdownMenu.tsx @@ -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 = ({ open = false }) => { const { theme, setTheme } = useTheme() const [display, setDisplay] = useState(false) const { closeSidebarIfPresent } = useUI() + const ref = useRef() as React.MutableRefObject + + useEffect(() => { + if (ref.current) { + if (display) { + disableBodyScroll(ref.current) + } else { + enableBodyScroll(ref.current) + } + } + return () => { + clearAllBodyScrollLocks() + } + }, [display]) return ( -
- - - {display && ( - + )} +
+ ) } diff --git a/components/icons/ChevronUp.tsx b/components/icons/ChevronUp.tsx new file mode 100644 index 000000000..1a3e2556c --- /dev/null +++ b/components/icons/ChevronUp.tsx @@ -0,0 +1,20 @@ +const ChevronUp = ({ ...props }) => { + return ( + + + + ) +} + +export default ChevronUp diff --git a/components/icons/index.ts b/components/icons/index.ts index 1ce388914..e72660e83 100644 --- a/components/icons/index.ts +++ b/components/icons/index.ts @@ -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' diff --git a/lib/click-outside/click-outside.tsx b/lib/click-outside/click-outside.tsx new file mode 100644 index 000000000..d3bba953c --- /dev/null +++ b/lib/click-outside/click-outside.tsx @@ -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 | 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 diff --git a/lib/click-outside/has-parent.js b/lib/click-outside/has-parent.js new file mode 100644 index 000000000..06cd3ca91 --- /dev/null +++ b/lib/click-outside/has-parent.js @@ -0,0 +1,5 @@ +import isInDOM from './is-in-dom' + +export default function hasParent(element, root) { + return root && root.contains(element) && isInDOM(element) +} diff --git a/lib/click-outside/index.ts b/lib/click-outside/index.ts new file mode 100644 index 000000000..2df916f9c --- /dev/null +++ b/lib/click-outside/index.ts @@ -0,0 +1 @@ +export { default } from './click-outside' diff --git a/lib/click-outside/is-in-dom.js b/lib/click-outside/is-in-dom.js new file mode 100644 index 000000000..5d7438ed7 --- /dev/null +++ b/lib/click-outside/is-in-dom.js @@ -0,0 +1,3 @@ +export default function isInDom(obj) { + return Boolean(obj.closest('body')) +}