From 8ad0fd6cda4d81562fb8bdf64ec8a1743ee2e26b Mon Sep 17 00:00:00 2001 From: bluecco Date: Thu, 28 Dec 2023 15:25:20 +0100 Subject: [PATCH 1/6] feat: notification component ui --- packages/ui/postcss.config.cjs | 4 +- .../ui/src/components/NotificationButton.tsx | 29 -------- .../Notifications/NotificationButton.tsx | 72 +++++++++++++++++++ .../Notifications/NotificationItem.tsx | 70 ++++++++++++++++++ .../Notifications/NotificationMenu.tsx | 64 +++++++++++++++++ packages/ui/src/icons/CloseIcon.tsx | 19 +++++ packages/ui/src/types/Notification.ts | 0 .../storybook/NotificationButton.stories.ts | 41 ++++++++++- .../ui/storybook/NotificationItem.stories.ts | 31 ++++++++ .../ui/storybook/NotificationMenu.stories.ts | 60 ++++++++++++++++ packages/ui/tailwind.config.cjs | 8 +++ 11 files changed, 366 insertions(+), 32 deletions(-) delete mode 100644 packages/ui/src/components/NotificationButton.tsx create mode 100644 packages/ui/src/components/Notifications/NotificationButton.tsx create mode 100644 packages/ui/src/components/Notifications/NotificationItem.tsx create mode 100644 packages/ui/src/components/Notifications/NotificationMenu.tsx create mode 100644 packages/ui/src/icons/CloseIcon.tsx create mode 100644 packages/ui/src/types/Notification.ts create mode 100644 packages/ui/storybook/NotificationItem.stories.ts create mode 100644 packages/ui/storybook/NotificationMenu.stories.ts diff --git a/packages/ui/postcss.config.cjs b/packages/ui/postcss.config.cjs index 33ad091d..21efa6a4 100644 --- a/packages/ui/postcss.config.cjs +++ b/packages/ui/postcss.config.cjs @@ -1,6 +1,8 @@ module.exports = { plugins: { - tailwindcss: {}, + tailwindcss: { + config: "tailwind.config.cjs", + }, autoprefixer: {}, }, } diff --git a/packages/ui/src/components/NotificationButton.tsx b/packages/ui/src/components/NotificationButton.tsx deleted file mode 100644 index c219cc14..00000000 --- a/packages/ui/src/components/NotificationButton.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { FC } from "react" -import { BellIcon } from "../icons/BellIcon" - -interface NotificationButtonProps { - address?: string - hasNotifications?: boolean -} - -const NotificationButton: FC = ({ - hasNotifications, -}) => { - return ( - - ) -} - -export { NotificationButton } diff --git a/packages/ui/src/components/Notifications/NotificationButton.tsx b/packages/ui/src/components/Notifications/NotificationButton.tsx new file mode 100644 index 00000000..9f66a843 --- /dev/null +++ b/packages/ui/src/components/Notifications/NotificationButton.tsx @@ -0,0 +1,72 @@ +import { FC, useEffect, useRef, useState } from "react" +import { BellIcon } from "../../icons/BellIcon" +import { NotificationMenu } from "./NotificationMenu" + +interface Notification { + action: string + description: string + isUnread?: boolean + time: string +} + +interface NotificationButtonProps { + address?: string + hasNotifications?: boolean + notifications: Notification[] +} + +const NotificationButton: FC = ({ + hasNotifications, + notifications, +}) => { + const [isOpen, setIsOpen] = useState(false) + const ref = useRef(null) + + const toggleMenu = () => { + setIsOpen((prev) => !prev) + } + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (ref.current && !ref.current.contains(event.target as Node)) { + setIsOpen(false) + } + } + + document.addEventListener("mousedown", handleClickOutside) + + return () => { + document.removeEventListener("mousedown", handleClickOutside) + } + }, []) + + return ( +
+ +
+ +
+
+ ) +} + +export { NotificationButton } diff --git a/packages/ui/src/components/Notifications/NotificationItem.tsx b/packages/ui/src/components/Notifications/NotificationItem.tsx new file mode 100644 index 00000000..9865be3c --- /dev/null +++ b/packages/ui/src/components/Notifications/NotificationItem.tsx @@ -0,0 +1,70 @@ +import { FC } from "react" + +interface NotificationItemProps { + action: string + description: string + isUnread?: boolean + time: string +} + +const NotificationItem: FC = ({ + action, + description, + isUnread, + time, +}) => { + return ( + + ) +} + +export { NotificationItem } diff --git a/packages/ui/src/components/Notifications/NotificationMenu.tsx b/packages/ui/src/components/Notifications/NotificationMenu.tsx new file mode 100644 index 00000000..676d5efd --- /dev/null +++ b/packages/ui/src/components/Notifications/NotificationMenu.tsx @@ -0,0 +1,64 @@ +import { FC, useMemo } from "react" +import { CloseIcon } from "../../icons/CloseIcon" +import { NotificationItem } from "./NotificationItem" + +// TODO: discuss structure +interface Notification { + action: string + description: string + isUnread?: boolean + time: string +} + +interface NotificationMenuProps { + notifications: Notification[] + toggleMenu: () => void +} + +const NotificationMenu: FC = ({ + notifications, + toggleMenu, +}) => { + const totalUnread = useMemo( + () => + notifications?.filter((notification) => notification.isUnread).length ?? + 0, + [notifications], + ) + + return ( +
+
+
+
Notifications
+ {totalUnread > 0 && ( +
+ + {totalUnread} + +
+ )} +
+ +
+ + {notifications?.length > 0 ? ( +
+ {notifications?.map((notification) => ( + + ))} +
+ ) : ( +

+ No new notifications +

+ )} +
+
+ ) +} + +export { NotificationMenu } diff --git a/packages/ui/src/icons/CloseIcon.tsx b/packages/ui/src/icons/CloseIcon.tsx new file mode 100644 index 00000000..202f271c --- /dev/null +++ b/packages/ui/src/icons/CloseIcon.tsx @@ -0,0 +1,19 @@ +import type { SVGProps } from "react" + +const CloseIcon = (props: SVGProps) => ( + + + +) + +export { CloseIcon } diff --git a/packages/ui/src/types/Notification.ts b/packages/ui/src/types/Notification.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/ui/storybook/NotificationButton.stories.ts b/packages/ui/storybook/NotificationButton.stories.ts index 0e26ac62..cc46b2d0 100644 --- a/packages/ui/storybook/NotificationButton.stories.ts +++ b/packages/ui/storybook/NotificationButton.stories.ts @@ -1,6 +1,6 @@ import type { Meta, StoryObj } from "@storybook/react" -import { NotificationButton } from "../src/components/NotificationButton" +import { NotificationButton } from "../src/components/Notifications/NotificationButton" // More on how to set up stories at: https://storybook.js.org/docs/svelte/writing-stories/introduction const meta: Meta = { @@ -14,11 +14,48 @@ type Story = StoryObj // More on writing stories with args: https://storybook.js.org/docs/svelte/writing-stories/args export const Base: Story = { - args: {}, + args: { + notifications: [ + { + action: "Send", + description: "0.005 ETH was sent to 0x1234...5678", + time: "Just now", + }, + { + action: "Mint", + description: "A cool nft was minted", + time: "2 minutes ago", + }, + { + action: "Adding liquidity", + description: "0.05 ETH and 100 DAI are being added to the pool", + time: "3 Oct 2023, 11:40 AM", + }, + ], + }, } export const WithNotifications: Story = { args: { hasNotifications: true, + notifications: [ + { + action: "Send", + description: "0.005 ETH was sent to 0x1234...5678", + time: "Just now", + isUnread: true, + }, + { + action: "Mint", + description: "A cool nft was minted", + time: "2 minutes ago", + }, + { + action: "Adding liquidity", + description: "0.05 ETH and 100 DAI are being added to the pool", + time: "3 Oct 2023, 11:40 AM", + isUnread: true, + }, + ], }, } diff --git a/packages/ui/storybook/NotificationItem.stories.ts b/packages/ui/storybook/NotificationItem.stories.ts new file mode 100644 index 00000000..5e2c2717 --- /dev/null +++ b/packages/ui/storybook/NotificationItem.stories.ts @@ -0,0 +1,31 @@ +import type { Meta, StoryObj } from "@storybook/react" + +import { NotificationItem } from "../src/components/Notifications/NotificationItem" + +// More on how to set up stories at: https://storybook.js.org/docs/svelte/writing-stories/introduction +const meta: Meta = { + component: NotificationItem, + argTypes: {}, +} + +export default meta +type Story = StoryObj + +// More on writing stories with args: https://storybook.js.org/docs/svelte/writing-stories/args + +export const Base: Story = { + args: { + action: "Send", + description: "0.005 ETH was sent to 0x1234...5678", + time: "just now", + }, +} + +export const Unread: Story = { + args: { + action: "Send", + description: "0.005 ETH was sent to 0x1234...5678", + time: "just now", + isUnread: true, + }, +} diff --git a/packages/ui/storybook/NotificationMenu.stories.ts b/packages/ui/storybook/NotificationMenu.stories.ts new file mode 100644 index 00000000..31a5d8e3 --- /dev/null +++ b/packages/ui/storybook/NotificationMenu.stories.ts @@ -0,0 +1,60 @@ +import type { Meta, StoryObj } from "@storybook/react" + +import { NotificationMenu } from "../src/components/Notifications/NotificationMenu" + +// More on how to set up stories at: https://storybook.js.org/docs/svelte/writing-stories/introduction +const meta: Meta = { + component: NotificationMenu, + argTypes: {}, +} + +export default meta +type Story = StoryObj + +// More on writing stories with args: https://storybook.js.org/docs/svelte/writing-stories/args + +export const Base: Story = { + args: { + notifications: [ + { + action: "Send", + description: "0.005 ETH was sent to 0x1234...5678", + time: "Just now", + }, + { + action: "Mint", + description: "A cool nft was minted", + time: "2 minutes ago", + }, + { + action: "Adding liquidity", + description: "0.05 ETH and 100 DAI are being added to the pool", + time: "3 Oct 2023, 11:40 AM", + }, + ], + }, +} + +export const WithNotifications: Story = { + args: { + notifications: [ + { + action: "Send", + description: "0.005 ETH was sent to 0x1234...5678", + time: "Just now", + isUnread: true, + }, + { + action: "Mint", + description: "A cool nft was minted", + time: "2 minutes ago", + }, + { + action: "Adding liquidity", + description: "0.05 ETH and 100 DAI are being added to the pool", + time: "3 Oct 2023, 11:40 AM", + isUnread: true, + }, + ], + }, +} diff --git a/packages/ui/tailwind.config.cjs b/packages/ui/tailwind.config.cjs index c4b596cd..12f033f6 100644 --- a/packages/ui/tailwind.config.cjs +++ b/packages/ui/tailwind.config.cjs @@ -17,6 +17,14 @@ module.exports = { }, width: { 50: "12.5rem", + 112.5: "28.125rem", + }, + textColor: { + "neutrals.400": "#8C8C8C", + "neutrals.600": "#595959", + }, + leading: { + 3.5: "0.875rem", }, borderColor: { "neutrals.200": "#F0F0F0", From 6c1abbe1d386e829c59c4e1a54b83b00267d1997 Mon Sep 17 00:00:00 2001 From: bluecco Date: Thu, 28 Dec 2023 15:32:05 +0100 Subject: [PATCH 2/6] chore: update NotificationItem style --- packages/ui/src/components/Notifications/NotificationItem.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/Notifications/NotificationItem.tsx b/packages/ui/src/components/Notifications/NotificationItem.tsx index 9865be3c..22618cb4 100644 --- a/packages/ui/src/components/Notifications/NotificationItem.tsx +++ b/packages/ui/src/components/Notifications/NotificationItem.tsx @@ -15,8 +15,8 @@ const NotificationItem: FC = ({ }) => { return ( + ) +} + +const ActivityToastStory = () => { + // Sets the hooks for both the label and primary props + + const [showToast, setShowToast] = React.useState(false) + const [vertical, setVertical] = React.useState<"top" | "center" | "bottom">( + "bottom", + ) + const [horizontal, setHorizontal] = React.useState< + "left" | "center" | "right" + >("center") + + const clickButton = ( + v: "top" | "center" | "bottom", + h: "left" | "center" | "right", + ) => { + setVertical(v) + setHorizontal(h) + setShowToast(true) + } + + return ( + <> +
+
+ clickButton("top", "left")} + label="Top left" + /> + clickButton("top", "center")} + label="Top center" + /> + clickButton("top", "right")} + label="Top right" + /> +
+
+ clickButton("bottom", "left")} + label="Bottom left" + /> + clickButton("bottom", "center")} + label="Bottom center" + /> + clickButton("bottom", "right")} + label="Bottom right" + /> +
+
+ {" "} + clickButton("center", "center")} + label="Center" + /> +
+ {showToast && ( + setShowToast(false)} + vertical={vertical} + horizontal={horizontal} + duration={1000} + /> + )} +
+ + ) +} + +export const Base: Story = { + render: () => , +} diff --git a/packages/ui/tailwind.config.cjs b/packages/ui/tailwind.config.cjs index 12f033f6..2eba889a 100644 --- a/packages/ui/tailwind.config.cjs +++ b/packages/ui/tailwind.config.cjs @@ -29,6 +29,12 @@ module.exports = { borderColor: { "neutrals.200": "#F0F0F0", }, + transitionDuration: { + 2000: "2000ms", + 3000: "3000ms", + 4000: "4000ms", + 5000: "5000ms", + }, }, }, plugins: [], From edc90a67e4d4a46db2afa1ea7ad5e3498a728558 Mon Sep 17 00:00:00 2001 From: bluecco Date: Thu, 4 Jan 2024 19:11:13 +0100 Subject: [PATCH 5/6] chore: refactor with SuccessIcon --- .../ActivityToast/ActivityToast.tsx | 22 ++----------- .../Notifications/NotificationItem.tsx | 33 ++++++++----------- .../Notifications/NotificationMenu.tsx | 4 ++- packages/ui/src/icons/SuccessIcon.tsx | 25 ++++++++++++++ 4 files changed, 44 insertions(+), 40 deletions(-) create mode 100644 packages/ui/src/icons/SuccessIcon.tsx diff --git a/packages/ui/src/components/ActivityToast/ActivityToast.tsx b/packages/ui/src/components/ActivityToast/ActivityToast.tsx index 5891bb6b..acd1faa3 100644 --- a/packages/ui/src/components/ActivityToast/ActivityToast.tsx +++ b/packages/ui/src/components/ActivityToast/ActivityToast.tsx @@ -1,5 +1,6 @@ import { FC, useEffect, useMemo, useState } from "react" import { CloseIcon } from "../../icons/CloseIcon" +import { SuccessIcon } from "../../icons/SuccessIcon" type vertical = "top" | "center" | "bottom" type horizontal = "left" | "center" | "right" @@ -103,25 +104,8 @@ const ActivityToastComponent: FC = ({
- {/* TODO: remove hardcode - decide how to manage icons in notifications */} - - - - + {/* TODO: remove hardcoded icon - wait for transaction info */} +
diff --git a/packages/ui/src/components/Notifications/NotificationItem.tsx b/packages/ui/src/components/Notifications/NotificationItem.tsx index 22618cb4..fa8c4597 100644 --- a/packages/ui/src/components/Notifications/NotificationItem.tsx +++ b/packages/ui/src/components/Notifications/NotificationItem.tsx @@ -1,10 +1,13 @@ import { FC } from "react" +import { SuccessIcon } from "../../icons/SuccessIcon" interface NotificationItemProps { action: string description: string isUnread?: boolean time: string + // eslint-disable-next-line @typescript-eslint/no-explicit-any + onNotificationClick?: (notification: any) => void // TODO: remove any } const NotificationItem: FC = ({ @@ -12,33 +15,23 @@ const NotificationItem: FC = ({ description, isUnread, time, + onNotificationClick, }) => { + const txHash = "0x123" // TODO: remove hardcoded txHash and get from transaction + return (