Skip to content

Commit

Permalink
feat(project): update popover and close UserMenu after click
Browse files Browse the repository at this point in the history
  • Loading branch information
ChristiaanScheermeijer committed Aug 2, 2021
1 parent ce0da5c commit a6d7d67
Show file tree
Hide file tree
Showing 7 changed files with 55 additions and 40 deletions.
15 changes: 11 additions & 4 deletions src/components/Animation/Slide/Slide.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type Props = {
onOpenAnimationEnd?: () => void;
onCloseAnimationEnd?: () => void;
children: ReactNode;
fromRight?: boolean;
direction?: 'left' | 'top' | 'right' | 'bottom';
};

const Slide = ({
Expand All @@ -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,
});

Expand Down
36 changes: 19 additions & 17 deletions src/components/DetectOutsideClick/DetectOutsideClick.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
import React, { useEffect, RefObject } from 'react';
import React, { useEffect, useRef } from 'react';

type Prop = {
el?: RefObject<HTMLDivElement>;
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<HTMLDivElement>();

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 <React.Fragment>{children}</React.Fragment>;
return React.cloneElement(children, {
ref: (node: HTMLDivElement) => {
elementRef.current = node;
},
});
};

export default DetectOutsideClick;
4 changes: 2 additions & 2 deletions src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,14 @@ const Header: React.FC<Props> = ({
);

const userActions =
breakpoint >= Breakpoint.sm ? (
breakpoint > Breakpoint.sm ? (
isLoggedIn ? (
<React.Fragment>
<IconButton className={styles.iconButton} aria-label={t('open_user_menu')} onClick={() => toggleUserMenu(!userMenuOpen)}>
<AccountCircle />
</IconButton>
<Popover isOpen={userMenuOpen} onClose={() => toggleUserMenu(false)}>
<UserMenu inPopover />
<UserMenu onClick={() => toggleUserMenu(false)} inPopover />
</Popover>
</React.Fragment>
) : (
Expand Down
9 changes: 8 additions & 1 deletion src/components/MenuButton/MenuButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@ const MenuButton: React.FC<Props> = ({ label, to, onClick, tabIndex = 0, active

if (to) {
return (
<NavLink className={classNames(styles.menuButton, { [styles.small]: small })} activeClassName={styles.active} to={to} tabIndex={tabIndex} exact>
<NavLink
className={classNames(styles.menuButton, { [styles.small]: small })}
onClick={onClick}
activeClassName={styles.active}
to={to}
tabIndex={tabIndex}
exact
>
{icon}
<span className={styles.label}>{label}</span>
</NavLink>
Expand Down
18 changes: 8 additions & 10 deletions src/components/Popover/Popover.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -13,17 +13,15 @@ type Props = {
};

const Popover: React.FC<Props> = ({ children, isOpen, onClose }: Props) => {
const popoverRef = useRef<HTMLDivElement>(null);

return isOpen ? (
<DetectOutsideClick isActive={isOpen} callback={onClose} el={popoverRef}>
<Slide open={isOpen} duration={300} onCloseAnimationEnd={() => onClose()} fromRight>
<div ref={popoverRef} className={classNames(styles.popover)}>
return (
<Slide open={isOpen} duration={250} direction="right">
<DetectOutsideClick callback={onClose}>
<div className={classNames(styles.popover)}>
{children}
</div>
</Slide>
</DetectOutsideClick>
) : null;
</DetectOutsideClick>
</Slide>
);
};

export default Popover;
2 changes: 1 addition & 1 deletion src/components/Popover/__snapshots__/Popover.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
exports[`<Popover> renders and matches snapshot 1`] = `
<div>
<div
style="transition: transform 0.3s ease-out; transform: translateX(15px); z-index: 15;"
style="transition: transform 0.25s ease, opacity 0.25s ease; transform: translate(15px, 0); opacity: 0; z-index: 15;"
>
<div
class="popover"
Expand Down
11 changes: 6 additions & 5 deletions src/components/UserMenu/UserMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,26 @@ import styles from './UserMenu.module.scss';

type Props = {
inPopover?: boolean;
onClick?: () => void;
};

const UserMenu = ({ inPopover = false }: Props) => {
const UserMenu = ({ inPopover = false, onClick }: Props) => {
const { t } = useTranslation('user');

const menuItems = (
<ul className={styles.menuItems}>
<li>
<MenuButton small={inPopover} to="/u/my-account" label={t('nav.account')} startIcon={<AccountCircle />} />
<MenuButton small={inPopover} onClick={onClick} to="/u/my-account" label={t('nav.account')} startIcon={<AccountCircle />} />
</li>
<li>
<MenuButton small={inPopover} to="/u/favorites" label={t('nav.favorites')} startIcon={<Favorite />} />
<MenuButton small={inPopover} onClick={onClick} to="/u/favorites" label={t('nav.favorites')} startIcon={<Favorite />} />
</li>
<li>
<MenuButton small={inPopover} to="/u/payments" label={t('nav.payments')} startIcon={<BalanceWallet />} />
<MenuButton small={inPopover} onClick={onClick} to="/u/payments" label={t('nav.payments')} startIcon={<BalanceWallet />} />
</li>
<hr className={classNames(styles.divider, { [styles.inPopover]: inPopover })} />
<li>
<MenuButton small={inPopover} to="/u/logout" label={t('nav.logout')} startIcon={<Exit />} />
<MenuButton small={inPopover} onClick={onClick} to="/u/logout" label={t('nav.logout')} startIcon={<Exit />} />
</li>
</ul>
);
Expand Down

0 comments on commit a6d7d67

Please sign in to comment.