From 96395f0ca1699c8d1ea933de75912ada35134605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Neves?= Date: Mon, 13 Nov 2023 19:18:41 +0100 Subject: [PATCH] Adding toasts support with event emitter --- src/components/Toast/Toast.tsx | 21 ++++++++++++++++--- src/components/Toast/toastEmitter.ts | 8 +++++++ src/components/Toast/useToast.tsx | 5 +++-- src/components/index.ts | 1 + src/lib/EventEmitter.tsx | 31 ++++++++++++++++++++++++++++ 5 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 src/components/Toast/toastEmitter.ts create mode 100644 src/lib/EventEmitter.tsx diff --git a/src/components/Toast/Toast.tsx b/src/components/Toast/Toast.tsx index 98a90834..2055522c 100644 --- a/src/components/Toast/Toast.tsx +++ b/src/components/Toast/Toast.tsx @@ -1,16 +1,17 @@ -import { ReactNode, createContext, useState } from "react"; +import { ReactNode, createContext, useEffect, useState } from "react"; import * as RadixUIToast from "@radix-ui/react-toast"; import { Button, ButtonProps, Icon, IconButton, IconName } from "@/components"; import styled, { keyframes } from "styled-components"; +import { toastsEventEmitter } from "./toastEmitter"; -interface ToastContextProps { +export interface ToastContextProps { createToast: (toast: ToastProps) => void; } export const ToastContext = createContext({ createToast: () => null, }); -type ToastType = "danger" | "warning" | "default" | "success"; +export type ToastType = "danger" | "warning" | "default" | "success"; export interface ToastProps { id?: string; type?: ToastType; @@ -192,6 +193,20 @@ const Viewport = styled(RadixUIToast.Viewport)` export const ToastProvider = ({ children, ...props }: RadixUIToast.ToastProps) => { const [toasts, setToasts] = useState>(new Map()); + useEffect(() => { + const listener = (toast: ToastProps) => { + setToasts(currentToasts => { + const newMap = new Map(currentToasts); + newMap.set(toast?.id ?? String(Date.now()), toast); + return newMap; + }); + } + + toastsEventEmitter.on(listener); + + return () => toastsEventEmitter.off(listener); + }, []); + const onClose = (id: string) => (open: boolean) => { if (!open) { setToasts(currentToasts => { diff --git a/src/components/Toast/toastEmitter.ts b/src/components/Toast/toastEmitter.ts new file mode 100644 index 00000000..49fdf827 --- /dev/null +++ b/src/components/Toast/toastEmitter.ts @@ -0,0 +1,8 @@ +import { EventEmitter } from "@/lib/EventEmitter"; +import type { ToastProps } from ".."; + +export const toastsEventEmitter = new EventEmitter(); + +export const createToast = (toast: ToastProps): void => { + toastsEventEmitter.emit(toast); +}; diff --git a/src/components/Toast/useToast.tsx b/src/components/Toast/useToast.tsx index adffbdae..95104153 100644 --- a/src/components/Toast/useToast.tsx +++ b/src/components/Toast/useToast.tsx @@ -1,6 +1,7 @@ import { useContext } from "react"; -import { ToastContext } from "./Toast"; -export const useToast = () => { +import { ToastContext, ToastContextProps } from "./Toast"; + +export const useToast = (): ToastContextProps => { const result = useContext(ToastContext); if (!result) { throw new Error("Context used outside of its Provider!"); diff --git a/src/components/index.ts b/src/components/index.ts index 6a44e525..bf401162 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -50,4 +50,5 @@ export { Title } from "./Typography/Title/Title"; export { Tooltip } from "./Tooltip/Tooltip"; export { default as ClickUIProvider } from "./ClickUIProvider/ClickUIProvider"; export { useToast } from "./Toast/useToast"; +export { createToast } from "./Toast/toastEmitter"; export { UserIcon as ProfileIcon } from "./icons/UserIcon"; diff --git a/src/lib/EventEmitter.tsx b/src/lib/EventEmitter.tsx new file mode 100644 index 00000000..ee9277b4 --- /dev/null +++ b/src/lib/EventEmitter.tsx @@ -0,0 +1,31 @@ +export interface Listener { + (event: T): void; +} + +export interface Disposable { + dispose: () => void; +} + +/** passes through events as they happen. You will not get events from before you start listening */ +export class EventEmitter { + private listeners: Listener[] = []; + + on = (listener: Listener): Disposable => { + this.listeners.push(listener); + return { + dispose: () => this.off(listener) + }; + } + + off = (listener: Listener) => { + const callbackIndex = this.listeners.indexOf(listener); + if (callbackIndex > -1) { + this.listeners.splice(callbackIndex, 1); + } + } + + emit = (event: T) => { + /** Update any general listeners */ + this.listeners.forEach((listener: Listener) => listener(event)); + } +}