Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🎨 Created notification context to be able to access setIsOpen and… #448

Merged
merged 6 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {
createContext,
Dispatch,
FC,
ReactNode,
SetStateAction,
useContext,
useState,
} from 'react';

interface NotificationContext {
setIsOpen: Dispatch<SetStateAction<boolean>>;
isOpen: boolean;
}

export const NotificationContext = createContext<
AmandaElvkull marked this conversation as resolved.
Show resolved Hide resolved
NotificationContext | undefined
>(undefined);

export const useNotification = () => {
const context = useContext(NotificationContext);
if (context === undefined) {
throw new Error(
'useNotificationContext must be used within a Notification provider'
);
}
return context;
};

interface NotificationProviderProps {
children: ReactNode;
}

const NotificationProvider: FC<NotificationProviderProps> = ({ children }) => {
const [isOpen, setIsOpen] = useState(false);

return (
<NotificationContext.Provider value={{ isOpen, setIsOpen }}>
{children}
</NotificationContext.Provider>
);
};

export default NotificationProvider;
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from 'react';
import { notifications } from '@equinor/eds-icons';
import { tokens } from '@equinor/eds-tokens';
import { faker } from '@faker-js/faker';
import { renderHook } from '@testing-library/react';

import {
DefaultNotificationTypes,
Expand All @@ -15,7 +16,9 @@ import {
RequestReviewOrcaTypes,
ReviewQANotificationsTypes,
} from './NotificationsTemplate/Notifications.types';
import { Notifications, UnreadRedDot } from './Notifications';
import { useNotification } from 'src/components/Navigation/TopBar/Notifications/NotificationProvider';
import Notifications from 'src/components/Navigation/TopBar/Notifications/Notifications';
import { UnreadRedDot } from 'src/components/Navigation/TopBar/Notifications/NotificationsInner';
import { render, screen, userEvent } from 'src/tests/test-utils';
import { date } from 'src/utils';

Expand Down Expand Up @@ -154,6 +157,13 @@ const notificationsData: (
} as Due3WeeksTypes,
];

test('useNotification hook throws error if using outside of context', () => {
console.error = vi.fn();
expect(() => renderHook(() => useNotification())).toThrow(
'useNotificationContext must be used within a Notification provider'
);
});

test('renders button and panel correctly', async () => {
render(<Notifications setAllAsRead={() => null} />);
const icons = screen.getAllByTestId('eds-icon-path');
Expand Down
188 changes: 11 additions & 177 deletions src/components/Navigation/TopBar/Notifications/Notifications.tsx
Original file line number Diff line number Diff line change
@@ -1,183 +1,17 @@
import { FC, ReactNode, useMemo, useRef, useState } from 'react';
import { FC } from 'react';

import { Icon } from '@equinor/eds-core-react';
import { notifications as notificationIcon } from '@equinor/eds-icons';
import { tokens } from '@equinor/eds-tokens';

import { TopBarButton } from '../TopBar.styles';
import NoNotifications from './NotificationsTemplate/NotificationElements/NoNotifications';
import NotificationProvider from 'src/components/Navigation/TopBar/Notifications/NotificationProvider';
import {
DefaultNotificationTypes,
Due3WeeksTypes,
ExperienceReadyToPublishTypes,
FilterNotification,
MergeBranchOrcaTypes,
ReadyToReportNotificationTypes,
RequestChangeOrcaTypes,
RequestReviewOrcaTypes,
ReviewQANotificationsTypes,
SortNotification,
} from './NotificationsTemplate/Notifications.types';
import NotificationTemplate from './NotificationsTemplate/NotificationTemplate';
import FilterOptions from './FilterOptions';
import TopBarMenu from 'src/components/Navigation/TopBar/TopBarMenu';

import styled from 'styled-components';

const { colors } = tokens;

export const UnreadRedDot = styled.div`
background-color: ${colors.interactive.danger__resting.rgba};
width: 10px;
height: 10px;
border-radius: 50%;
position: absolute;
right: 2px;
top: 4px;
border: 2px solid ${colors.text.static_icons__primary_white.rgba};
// Box-sizing is a quickfix for use in PWEX because of global styling
box-sizing: content-box;
`;

const FilterOptionsContainer = styled.div`
display: flex;
`;

const Content = styled.div`
overflow: auto;
height: 66vh;
width: 350px;
`;

interface NotificationsProps {
setAllAsRead: () => void;
hasUnread?: boolean;
showFilterOptions?: boolean;
children?: ReactNode;
notifications?: (
| ReadyToReportNotificationTypes
| RequestChangeOrcaTypes
| MergeBranchOrcaTypes
| Due3WeeksTypes
| ExperienceReadyToPublishTypes
| ReviewQANotificationsTypes
| DefaultNotificationTypes
| RequestReviewOrcaTypes
)[];
}

export const Notifications: FC<NotificationsProps> = ({
children,
hasUnread = false,
setAllAsRead,
showFilterOptions = false,
notifications,
}) => {
const buttonRef = useRef<HTMLDivElement | null>(null);
const filterMenuRef = useRef<HTMLDivElement | null>(null);
const sortMenuRef = useRef<HTMLDivElement | null>(null);

const [notificationsOpen, setNotificationsOpen] = useState(false);

const [filteringOn, setFilteringOn] = useState<FilterNotification[]>([]);

const [sortingOn, setSortingOn] = useState<SortNotification[]>([]);

const filteredAndSortedNotifications = useMemo(() => {
if (!notifications) return [];
let copy = [...notifications];

if (filteringOn.length > 0) {
copy = copy.filter((notification) => {
if (filteringOn.includes(FilterNotification.UNREAD)) {
return !notification.Read;
} else if (filteringOn.includes(FilterNotification.USER)) {
return notification.user;
} else if (filteringOn.includes(FilterNotification.SYSTEM)) {
return !notification.user;
}
});
}
if (sortingOn.length > 0) {
copy = copy.sort((a, b) => {
const aUnixTime = new Date(a.time).getTime();
const bUnixTime = new Date(b.time).getTime();

if (sortingOn.includes(SortNotification.OLD_NEWEST)) {
return bUnixTime - aUnixTime;
} else if (sortingOn.includes(SortNotification.UNREAD)) {
return a.Read === b.Read ? 0 : a.Read ? 1 : -1;
} else {
return aUnixTime - bUnixTime;
}
});
}

return copy;
}, [filteringOn, notifications, sortingOn]);

const handleButtonClick = () => {
if (notificationsOpen) {
onClose();
} else {
setNotificationsOpen(true);
}
};

const onClose = () => {
setAllAsRead();
setNotificationsOpen(false);
};
NotificationsInner,
NotificationsProps,
} from 'src/components/Navigation/TopBar/Notifications/NotificationsInner';

const Notifications: FC<NotificationsProps> = (props) => {
return (
<>
<TopBarButton
variant="ghost"
key="topbar-notifications"
ref={buttonRef}
onClick={handleButtonClick}
data-testid="show-hide-button"
$isSelected={notificationsOpen}
>
<Icon data={notificationIcon} size={24} />
{hasUnread && <UnreadRedDot data-testid="unread-dot" />}
</TopBarButton>
<TopBarMenu
open={notificationsOpen}
anchorEl={buttonRef.current}
contentPadding={false}
isNotification
onClose={onClose}
>
{showFilterOptions && (
<FilterOptionsContainer>
<FilterOptions
onFilter={setFilteringOn}
onSort={setSortingOn}
sortMenuRef={sortMenuRef}
filterMenuRef={filterMenuRef}
/>
</FilterOptionsContainer>
)}

{children ? (
<Content>{children}</Content>
) : (
<Content>
{filteredAndSortedNotifications &&
filteredAndSortedNotifications?.length > 0 ? (
filteredAndSortedNotifications.map((item) => {
return (
<NotificationTemplate {...item} key={item.SequenceNumber} />
);
})
) : (
<NoNotifications />
)}
</Content>
)}
</TopBarMenu>
</>
<NotificationProvider>
<NotificationsInner {...props} />
</NotificationProvider>
);
};
Notifications.displayName = 'TopBar.Notifications';

export default Notifications;
Loading
Loading