From a6d7d675c1007b15fcc55444507cd45f75570d3c Mon Sep 17 00:00:00 2001 From: Christiaan Scheermeijer Date: Mon, 2 Aug 2021 12:13:30 +0200 Subject: [PATCH] feat(project): update popover and close UserMenu after click --- src/components/Animation/Slide/Slide.tsx | 15 +++++--- .../DetectOutsideClick/DetectOutsideClick.tsx | 36 ++++++++++--------- src/components/Header/Header.tsx | 4 +-- src/components/MenuButton/MenuButton.tsx | 9 ++++- src/components/Popover/Popover.tsx | 18 +++++----- .../__snapshots__/Popover.test.tsx.snap | 2 +- src/components/UserMenu/UserMenu.tsx | 11 +++--- 7 files changed, 55 insertions(+), 40 deletions(-) diff --git a/src/components/Animation/Slide/Slide.tsx b/src/components/Animation/Slide/Slide.tsx index f4a21db08..657721594 100644 --- a/src/components/Animation/Slide/Slide.tsx +++ b/src/components/Animation/Slide/Slide.tsx @@ -9,7 +9,7 @@ type Props = { onOpenAnimationEnd?: () => void; onCloseAnimationEnd?: () => void; children: ReactNode; - fromRight?: boolean; + direction?: 'left' | 'top' | 'right' | 'bottom'; }; const Slide = ({ @@ -19,13 +19,20 @@ const Slide = ({ onOpenAnimationEnd, onCloseAnimationEnd, children, - fromRight, + direction = 'top', }: Props): JSX.Element | null => { const seconds = duration / 1000; - const transition = `transform ${seconds}s ease-out`; // todo: -webkit-transform; + const transition = `transform ${seconds}s ease, opacity ${seconds}s ease`; // todo: -webkit-transform; + const directions = { + left: 'translate(-15px, 0)', + top: 'translate(0, -15px)', + right: 'translate(15px, 0)', + bottom: 'translate(0, 15px)', + }; const createStyle = (status: Status): CSSProperties => ({ transition, - transform: status === 'opening' || status === 'open' ? 'translateY(0)' : `${fromRight ? 'translateX(15px)' : 'translateY(15px)'}`, + transform: status === 'opening' || status === 'open' ? 'translate(0, 0)' : directions[direction], + opacity: status === 'opening' || status === 'open' ? 1 : 0, zIndex: 15, }); diff --git a/src/components/DetectOutsideClick/DetectOutsideClick.tsx b/src/components/DetectOutsideClick/DetectOutsideClick.tsx index 0b22bbd53..bd0dac0fd 100644 --- a/src/components/DetectOutsideClick/DetectOutsideClick.tsx +++ b/src/components/DetectOutsideClick/DetectOutsideClick.tsx @@ -1,32 +1,34 @@ -import React, { useEffect, RefObject } from 'react'; +import React, { useEffect, useRef } from 'react'; type Prop = { - el?: RefObject; callback: () => void; - isActive: boolean; - children: React.ReactNode; + children: React.ReactElement; }; -const DetectOutsideClick = ({ el, callback, isActive, children }: Prop) => { +const DetectOutsideClick = ({ callback, children }: Prop) => { + const elementRef = useRef(); + useEffect(() => { - const onClick = (event: MouseEvent) => { - // If the active element exists and is clicked outside of - if (isActive && el?.current !== null && !el?.current?.contains(event.target as Node)) { + const handleClick = (event: MouseEvent) => { + if (!elementRef.current || !(event.target instanceof Node)) { + return; + } + + if (elementRef.current !== event.target && !elementRef.current?.contains(event.target)) { callback(); } }; - // If the item is active (ie open) then listen for clicks outside - if (isActive) { - setTimeout(() => window.addEventListener('click', onClick), 0); - } + document.addEventListener('click', handleClick); - return () => { - window.removeEventListener('click', onClick); - }; - }, [isActive, el, callback]); + return () => document.removeEventListener('click', handleClick); + }, [callback]); - return {children}; + return React.cloneElement(children, { + ref: (node: HTMLDivElement) => { + elementRef.current = node; + }, + }); }; export default DetectOutsideClick; diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index d4e58f7f2..121e61153 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -93,14 +93,14 @@ const Header: React.FC = ({ ); const userActions = - breakpoint >= Breakpoint.sm ? ( + breakpoint > Breakpoint.sm ? ( isLoggedIn ? ( toggleUserMenu(!userMenuOpen)}> toggleUserMenu(false)}> - + toggleUserMenu(false)} inPopover /> ) : ( diff --git a/src/components/MenuButton/MenuButton.tsx b/src/components/MenuButton/MenuButton.tsx index 16be091f2..cb1723a26 100644 --- a/src/components/MenuButton/MenuButton.tsx +++ b/src/components/MenuButton/MenuButton.tsx @@ -19,7 +19,14 @@ const MenuButton: React.FC = ({ label, to, onClick, tabIndex = 0, active if (to) { return ( - + {icon} {label} diff --git a/src/components/Popover/Popover.tsx b/src/components/Popover/Popover.tsx index 24ec5ece9..375f9ffc9 100644 --- a/src/components/Popover/Popover.tsx +++ b/src/components/Popover/Popover.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode, useRef } from 'react'; +import React, { ReactNode, useEffect, useRef, useState } from 'react'; import classNames from 'classnames'; import DetectOutsideClick from '../DetectOutsideClick/DetectOutsideClick'; @@ -13,17 +13,15 @@ type Props = { }; const Popover: React.FC = ({ children, isOpen, onClose }: Props) => { - const popoverRef = useRef(null); - - return isOpen ? ( - - onClose()} fromRight> -
+ return ( + + +
{children}
-
- - ) : null; + + + ); }; export default Popover; diff --git a/src/components/Popover/__snapshots__/Popover.test.tsx.snap b/src/components/Popover/__snapshots__/Popover.test.tsx.snap index 8d66c4593..31c17f58f 100644 --- a/src/components/Popover/__snapshots__/Popover.test.tsx.snap +++ b/src/components/Popover/__snapshots__/Popover.test.tsx.snap @@ -3,7 +3,7 @@ exports[` renders and matches snapshot 1`] = `
void; }; -const UserMenu = ({ inPopover = false }: Props) => { +const UserMenu = ({ inPopover = false, onClick }: Props) => { const { t } = useTranslation('user'); const menuItems = (
  • - } /> + } />
  • - } /> + } />
  • - } /> + } />

  • - } /> + } />
);