Skip to content

Commit

Permalink
Improve notification popup position
Browse files Browse the repository at this point in the history
Co-Authored-By: Jonatan Asketorp <k3KAW8Pnf7mkmdSMPHz27@users.noreply.github.com>
  • Loading branch information
willemarcel and k3KAW8Pnf7mkmdSMPHz27 committed Nov 17, 2020
1 parent c7b5d88 commit 693562e
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 28 deletions.
15 changes: 13 additions & 2 deletions frontend/src/components/header/notificationBell.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef } from 'react';
import React, { useState, useEffect, useRef, useCallback } from 'react';
import { useSelector } from 'react-redux';

import { TopNavLink } from './NavLink';
Expand All @@ -9,11 +9,13 @@ import { useOnClickOutside } from '../../hooks/UseOnClickOutside';
import useForceUpdate from '../../hooks/UseForceUpdate';
import { useInboxQueryAPI } from '../../hooks/UseInboxQueryAPI';
import { pushToLocalJSONAPI } from '../../network/genericJSONRequest';
import { useOnResize } from '../../hooks/UseOnResize';

export const NotificationBell = (props) => {
const token = useSelector((state) => state.auth.get('token'));
const trigger = token !== null;
const [forceUpdated, forceUpdate] = useForceUpdate();
const [bellPosition, setBellPosition] = useState(0);
/* these below make the references stable so hooks doesn't re-request forever */
const notificationBellRef = useRef(null);
const params = useRef({ status: 'unread' });
Expand Down Expand Up @@ -51,6 +53,7 @@ export const NotificationBell = (props) => {
const handleBellClick = (e) => {
e.preventDefault();
e.stopPropagation();
setBellPosition(e.target.getBoundingClientRect().left);
setPopoutFocus(!isPopoutFocus);
if (unreadNotifications) {
forceUpdate(); // update the notifications when user clicks and there are unread messages
Expand All @@ -59,6 +62,13 @@ export const NotificationBell = (props) => {
}
};

const handleResizeCallback = useCallback(() => {
if (isPopoutFocus) {
setBellPosition(notificationBellRef.current.getBoundingClientRect().left);
}
}, [isPopoutFocus]);

useOnResize(notificationBellRef, handleResizeCallback);
useOnClickOutside(notificationBellRef, () => setPopoutFocus(false));

const isNotificationBellActive = ({ isCurrent }) => {
Expand All @@ -84,7 +94,7 @@ export const NotificationBell = (props) => {
>
<div className="relative dib">
<BellIcon aria-label="Notifications" role="button" />
{unreadNotifications && <div className="redicon"></div>}
{unreadNotifications && <div className="redicon" />}
</div>
</TopNavLink>
<NotificationPopout
Expand All @@ -93,6 +103,7 @@ export const NotificationBell = (props) => {
isPopoutFocus={isPopoutFocus}
setPopoutFocus={setPopoutFocus}
liveUnreadCount={liveUnreadCount}
position={bellPosition}
/>
</span>
);
Expand Down
24 changes: 24 additions & 0 deletions frontend/src/hooks/UseOnResize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useEffect } from 'react';

export function useOnResize(ref, handler) {
useEffect(
() => {
const listener = () => {
handler();
};

window.addEventListener('resize', listener);

return () => {
window.removeEventListener('resize', listener);
};
},
// Add ref and handler to effect dependencies
// It's worth noting that because passed in handler is a new ...
// ... function on every render that will cause this effect ...
// ... callback/cleanup to run every render. It's not a big deal ...
// ... but to optimize you can wrap handler in useCallback before ...
// ... passing it into this hook.
[ref, handler],
);
}
71 changes: 45 additions & 26 deletions frontend/src/views/notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,52 @@ import { useSetTitleTag } from '../hooks/UseMetaTags';
import { Login } from './login';

export const NotificationPopout = (props) => {
// Small screen size, as defined by tachyons
let smallScreenSize = !window.matchMedia('(min-width: 30em)').matches;
// Notification popout position and margin. The popout is anchored outside of the screen and centered on small screens.
const popoutPosition = {
left: `${smallScreenSize ? '-2rem' : Math.max(0, props.position - 320).toString() + 'px'}`,
right: `${smallScreenSize ? '-2rem' : 'auto'}`,
margin: `${smallScreenSize ? '.5rem auto 0' : '.5rem 0 0'}`,
};

return (
<div
style={{ minWidth: '390px', width: '390px', zIndex: '100', right: '4rem' }}
className={`fr ${props.isPopoutFocus ? '' : 'dn'} mt2 br2 absolute shadow-2 ph4 pb3 bg-white`}
>
<span className="absolute top-0 left-2 nt2 w1 h1 bg-white bl ml7 bt b--grey-light rotate-45"></span>
<InboxNavMini
newMsgCount={
props.state &&
props.state.notifications &&
props.state.notifications.filter((n) => !n.read).length
}
/>
<NotificationResultsMini
liveUnreadCount={props.liveUnreadCount}
retryFn={props.forceUpdate}
state={props.state}
className="tl"
/>
<InboxNavMiniBottom
className="tl"
setPopoutFocus={props.setPopoutFocus}
msgCount={props.state && props.state.notifications && props.state.notifications.length}
<>
<div
style={{
minWidth: '390px',
width: '390px',
zIndex: '100',
...popoutPosition,
}}
className={`fr ${props.isPopoutFocus ? '' : 'dn '}br2 absolute shadow-2 ph4 pb3 bg-white`}
>
<InboxNavMini
newMsgCount={
props.state &&
props.state.notifications &&
props.state.notifications.filter((n) => !n.read).length
}
/>
<NotificationResultsMini
liveUnreadCount={props.liveUnreadCount}
retryFn={props.forceUpdate}
state={props.state}
className="tl"
/>
<InboxNavMiniBottom
className="tl"
setPopoutFocus={props.setPopoutFocus}
msgCount={props.state && props.state.notifications && props.state.notifications.length}
/>
</div>
<div
style={{ zIndex: '100', left: `${props.position}px` }}
className={`${
props.isPopoutFocus ? '' : 'dn '
}absolute w1 h1 bg-white bl bt b--grey-light rotate-45`}
/>
</div>
</>
);
};

Expand Down Expand Up @@ -68,9 +89,7 @@ export const NotificationsPage = (props) => {
<div className="pt4-l pb5 ph5-l ph2 pt180 pull-center bg-tan">
{
props.children
/* This is where the full notification body component is rendered
using the router, as a child route.
*/
/* This is where the full notification body component is rendered using the router, as a child route. */
}
<section className="cf">
<InboxNav />
Expand Down

0 comments on commit 693562e

Please sign in to comment.