Skip to content

Commit

Permalink
Merge pull request #244 from maryia-matskevich-deriv/maryia/adapt_not…
Browse files Browse the repository at this point in the history
…ifications

[BOT]maryia/BOT-2164/Adapt Notifications component for Bot announcements
  • Loading branch information
shayan-deriv authored Aug 29, 2024
2 parents 7962d82 + 01924b9 commit 6d91833
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 41 deletions.
3 changes: 2 additions & 1 deletion src/components/AppLayout/Notifications/Notification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const Notification = ({
buttonAction,
actionText,
}: TNotificationObject) => {
const haveAction = typeof buttonAction === 'function' && actionText;
return (
<div className="notification">
<div className="notification__container">
Expand All @@ -20,7 +21,7 @@ export const Notification = ({
</div>
</div>
</div>
{buttonAction && actionText && (
{haveAction && (
<div className="notification__button-container">
<button className="notification__button" onClick={buttonAction}>
{actionText}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ describe("Notification Component", () => {
it("renders the notification with title, message", async () => {
const { getByText } = render(
<Notification
id='0'
icon={<span>Icon</span>}
title="Test Title"
message="Test message"
Expand All @@ -22,6 +23,7 @@ describe("Notification Component", () => {
const mockAction = jest.fn();
const { getByRole } = render(
<Notification
id='0'
icon={<span>Icon</span>}
title="Test Title"
message="Test message"
Expand All @@ -39,6 +41,7 @@ describe("Notification Component", () => {
const mockAction = jest.fn();
const { getByRole } = render(
<Notification
id='0'
icon={<span>Icon</span>}
title="Test Title"
message="Test message"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ jest.mock("../../../../hooks", () => ({

function generateNotification(idx: number, action: () => void) {
return {
id: `${idx}`,
icon: <span>Icon{idx}</span>,
title: `Title ${idx}`,
message: `Message ${idx}`,
Expand All @@ -22,17 +23,18 @@ describe("Notifications Component", () => {
const { queryByText } = render(
<Notifications
notifications={[]}
clearNotificationsCallback={() => {}}
clearNotificationsCallback={() => { }}
isOpen={true}
setIsOpen={() => {}}
loadMoreFunction={() => {}}
setIsOpen={() => { }}
loadMoreFunction={() => { }}
isLoading={false}
componentConfig={{
clearButtonText: "Clear all",
modalTitle: "Notifications",
noNotificationsTitle: "No notifications",
noNotificationsMessage: "You have no notifications",
}}
excludedClickOutsideClass='notifications__label'
/>,
);
expect(queryByText("No notifications")).toBeInTheDocument();
Expand All @@ -48,17 +50,18 @@ describe("Notifications Component", () => {
const { getByText, getAllByRole } = render(
<Notifications
notifications={notifications}
clearNotificationsCallback={() => {}}
clearNotificationsCallback={() => { }}
isOpen={true}
setIsOpen={() => {}}
loadMoreFunction={() => {}}
setIsOpen={() => { }}
loadMoreFunction={() => { }}
isLoading={false}
componentConfig={{
clearButtonText: "Clear all",
modalTitle: "Notifications",
noNotificationsTitle: "No notifications",
noNotificationsMessage: "You have no notifications",
}}
excludedClickOutsideClass='notifications__label'
/>,
);

Expand All @@ -77,37 +80,38 @@ describe("Notifications Component", () => {
expect(mockAction2).toHaveBeenCalled();
});

it('displays a loading spinner when "isLoading" is true', async() => {
it('displays a loading spinner when "isLoading" is true', async () => {
const { queryByTestId } = render(
<Notifications
notifications={[]}
clearNotificationsCallback={() => {}}
clearNotificationsCallback={() => { }}
isOpen={true}
setIsOpen={() => {}}
loadMoreFunction={() => {}}
setIsOpen={() => { }}
loadMoreFunction={() => { }}
isLoading={true}
componentConfig={{
clearButtonText: "Clear all",
modalTitle: "Notifications",
noNotificationsTitle: "No notifications",
noNotificationsMessage: "You have no notifications",
}}
excludedClickOutsideClass='notifications_label'
/>,
);
expect(queryByTestId("notifications-loader")).toBeInTheDocument();
});

it('calls the "loadMoreFunction" when content is scrolled to the bottom', async() => {
it('calls the "loadMoreFunction" when content is scrolled to the bottom', async () => {
const mockLoadMore = jest.fn();

const notifications = Array.from({ length: 20 }, (_, idx) => generateNotification(idx, jest.fn()));

const { getByTestId } = render(
<Notifications
notifications={notifications}
clearNotificationsCallback={() => {}}
clearNotificationsCallback={() => { }}
isOpen={true}
setIsOpen={() => {}}
setIsOpen={() => { }}
loadMoreFunction={mockLoadMore}
isLoading={false}
componentConfig={{
Expand All @@ -116,6 +120,7 @@ describe("Notifications Component", () => {
noNotificationsTitle: "No notifications",
noNotificationsMessage: "You have no notifications",
}}
excludedClickOutsideClass='notifications_label'
/>,
);

Expand Down
28 changes: 16 additions & 12 deletions src/components/AppLayout/Notifications/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const Notifications = ({
setIsOpen,
componentConfig,
className,
excludedClickOutsideClass,
loadMoreFunction,
isLoading,
...rest
Expand All @@ -27,9 +28,12 @@ export const Notifications = ({
const notificationsRef = useRef(null);
const notificationsScrollRef = useRef(null);

useOnClickOutside(notificationsRef, (e) => {
useOnClickOutside(notificationsRef, (e: Event) => {
e.stopPropagation();
setIsOpen(false);
// To enable the button to open this component in the upper scope
if (!(e.target as HTMLElement).className.split(' ').includes(excludedClickOutsideClass)) {
setIsOpen(false);
}
});

const { fetchMoreOnBottomReached } = useFetchMore({
Expand Down Expand Up @@ -76,21 +80,21 @@ export const Notifications = ({
</Text>
</div>
)}
<div
className="notifications__content"
ref={notificationsScrollRef}
<div
className="notifications__content"
ref={notificationsScrollRef}
onScroll={(e) => fetchMoreOnBottomReached(e.target as HTMLDivElement)}
data-testid="notifications-content"
>
{notifications.map((notification) => (
<Notification
key={notification.title}
key={notification.id}
{...notification}
/>
))}
{isLoading && (
<div className="notifications__loader" data-testid="notifications-loader">
<Loader isFullScreen={false}/>
<Loader isFullScreen={false} />
</div>
)}
</div>
Expand Down Expand Up @@ -138,21 +142,21 @@ export const Notifications = ({
</Text>
</div>
)}
<div
className="notifications__content"
ref={notificationsScrollRef}
<div
className="notifications__content"
ref={notificationsScrollRef}
onScroll={(e) => fetchMoreOnBottomReached(e.target as HTMLDivElement)}
data-testid="notifications-content"
>
{notifications.map((notification) => (
<Notification
key={notification.title}
key={notification.id}
{...notification}
/>
))}
{isLoading && (
<div className="notifications__loader" data-testid="notifications-loader">
<Loader isFullScreen={false}/>
<Loader isFullScreen={false} />
</div>
)}
</div>
Expand Down
8 changes: 5 additions & 3 deletions src/components/AppLayout/Notifications/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { ComponentProps, ReactNode } from "react";

export type TNotificationObject = {
id: string;
icon: ReactNode;
title: string;
message: string;
buttonAction?: () => void;
title: string | React.ReactNode;
message: string | React.ReactNode;
buttonAction?: (() => void) | false | void;
actionText?: string;
};
export type TNotificationsProps = ComponentProps<"div"> & {
Expand All @@ -20,4 +21,5 @@ export type TNotificationsProps = ComponentProps<"div"> & {
noNotificationsTitle: string;
noNotificationsMessage: string;
};
excludedClickOutsideClass: string;
};
26 changes: 14 additions & 12 deletions stories/Notifications.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ export const Default: Story = {
noNotificationsMessage: "You currently have no notifications",
},

clearNotificationsCallback: () => {},
clearNotificationsCallback: () => { },
isOpen: true,
setIsOpen: () => {},
loadMoreFunction: () => {},
setIsOpen: () => { },
loadMoreFunction: () => { },
isLoading: false,
notifications: [
{
Expand Down Expand Up @@ -61,6 +61,7 @@ export const Default: Story = {
actionText: "Go to Trustpilot",
},
],
excludedClickOutsideClass: 'notifications__label',
},
render: (args) => {
const [isOpen, setIsOpen] = React.useState(args.isOpen);
Expand All @@ -86,11 +87,12 @@ export const Empty: Story = {
noNotificationsMessage: "You currently have no notifications",
},

clearNotificationsCallback: () => {},
clearNotificationsCallback: () => { },
isOpen: true,
setIsOpen: () => {},
setIsOpen: () => { },
notifications: [],
loadMoreFunction: () => {},
excludedClickOutsideClass: 'notifications__label',
loadMoreFunction: () => { },
isLoading: false,
},
render: (args) => {
Expand Down Expand Up @@ -118,9 +120,9 @@ export const LoadMore: Story = {
noNotificationsTitle: "No notifications",
noNotificationsMessage: "You currently have no notifications",
},
clearNotificationsCallback: () => {},
clearNotificationsCallback: () => { },
isOpen: true,
setIsOpen: () => {},
setIsOpen: () => { },
notifications: [
{
icon: <LegacyAnnouncementIcon width={16} height={16} />,
Expand Down Expand Up @@ -218,14 +220,14 @@ export const LoadMore: Story = {
console.log("Check Your Portfolio Performance Clicked");
},
actionText: "View Reports",
},
},
],
},
render: (args) => {
const [isOpen, setIsOpen] = React.useState(args.isOpen);
const [notifications, setNotifications] = React.useState(args.notifications);
const [isLoading, setIsLoading] = React.useState(false);

React.useEffect(() => {
setIsOpen(args.isOpen);
}, [args.isOpen])
Expand All @@ -235,10 +237,10 @@ export const LoadMore: Story = {
isOpen={isOpen}
setIsOpen={(state) => setIsOpen(state)}
notifications={notifications}
isLoading={isLoading}
isLoading={isLoading}
loadMoreFunction={() => {

function genNewNotification(idx: number) {
function genNewNotification(idx: number) {
return {
icon: <LegacyAnnouncementIcon width={16} height={16} />,
title: `New Notification #${idx}`,
Expand Down

0 comments on commit 6d91833

Please sign in to comment.