From 0540e8cfa06a8059b8b0accbab56fd59e082c235 Mon Sep 17 00:00:00 2001 From: Sumin Date: Wed, 6 Dec 2023 20:56:14 +0900 Subject: [PATCH 01/21] =?UTF-8?q?feat:=20=ED=86=A0=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EA=B4=80=EB=A6=AC=EB=A5=BC=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20EventManger=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../foundation/Toast/EventManger.ts | 55 +++++++++++++++++++ FE/src/components/foundation/Toast/type.ts | 42 ++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 FE/src/components/foundation/Toast/EventManger.ts create mode 100644 FE/src/components/foundation/Toast/type.ts diff --git a/FE/src/components/foundation/Toast/EventManger.ts b/FE/src/components/foundation/Toast/EventManger.ts new file mode 100644 index 0000000..27e8fd2 --- /dev/null +++ b/FE/src/components/foundation/Toast/EventManger.ts @@ -0,0 +1,55 @@ +import { EventManager } from '@foundation/Toast/type'; + +export const eventManager: EventManager = { + list: new Map(), // 이벤트 리스너 저장 + emitQueue: new Map(), // 이벤트 지연을 위한 큐 + + // 새로운 이벤트 리스너 등록 + on(event, callback) { + this.list.has(event) + ? this.list.get(event)!.push(callback) + : this.list.set(event, [callback]); + + return this; + }, + + // 콜백이 있는경우 해당하는 리스너 제거, 없는 경우 이벤트에 대한 모든 리스너 제거 + off(event, callback) { + if (callback) { + const cb = this.list.get(event)?.filter((cb) => cb !== callback); + cb && this.list.set(event, cb); + return this; + } + this.list.delete(event); + + return this; + }, + + //대기중인 이벤트를 취소시킬 때 필요함 + cancelEmit(event) { + const timers = this.emitQueue.get(event); + if (timers) { + timers.forEach(clearTimeout); + this.emitQueue.delete(event); + } + + return this; + }, + + // 이벤트 발생시키기 + // 타입문제는 일단 보류중! (라이브러리 내부도 해결 안되어있음) + emit(event, ...args: never[]) { + this.list.has(event) && + this.list.get(event)!.forEach((callback) => { + const timer = setTimeout(() => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + return callback(...args); + }, 0); + + this.emitQueue.has(event) + ? this.emitQueue.get(event)!.push(timer) + : this.emitQueue.set(event, [timer]); + }); + }, +}; diff --git a/FE/src/components/foundation/Toast/type.ts b/FE/src/components/foundation/Toast/type.ts new file mode 100644 index 0000000..0ab41ab --- /dev/null +++ b/FE/src/components/foundation/Toast/type.ts @@ -0,0 +1,42 @@ +import { ToastType } from '@foundation/Toast/Toast.styles'; + +export type ToastProps = { + toastId: string; + text: string; + autoClose?: boolean | number; + closeOnClick?: boolean; + type?: keyof typeof ToastType; + pauseOnHover?: boolean; +}; + +export const enum ToastEvent { + Add, + Update, + Delete, +} + +type OnAddCallback = (props: ToastProps) => void; +type OnUpdateCallback = (id: string, props: ToastProps) => void; +type OnDeleteCallback = (id: string) => void; + +export type Callback = OnAddCallback | OnUpdateCallback | OnDeleteCallback; + +type TimeoutId = ReturnType; + +export interface EventManager { + list: Map; + emitQueue: Map; + + on(event: ToastEvent.Update, callback: OnUpdateCallback): EventManager; + on(event: ToastEvent.Add, callback: OnAddCallback): EventManager; + on(event: ToastEvent.Delete, callback: OnDeleteCallback): EventManager; + + off(event: ToastEvent.Update, callback: OnUpdateCallback): EventManager; + off(event: ToastEvent.Add, callback: OnAddCallback): EventManager; + off(event: ToastEvent.Delete, callback: OnDeleteCallback): EventManager; + + cancelEmit(event: ToastEvent): EventManager; + emit(event: ToastEvent.Update, id: string, props: ToastProps): void; + emit(event: ToastEvent.Add, props: ToastProps): void; + emit(event: ToastEvent.Delete, id: string): void; +} From 25e58d26e6aaddcafde4208a9cc2d30b521f5b2f Mon Sep 17 00:00:00 2001 From: Sumin Date: Wed, 6 Dec 2023 21:17:34 +0900 Subject: [PATCH 02/21] =?UTF-8?q?feat:=20=ED=86=A0=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=8B=80=20=EA=B5=AC=ED=98=84=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/App.tsx | 2 + .../foundation/Toast/Toast.styles.ts | 9 +++ FE/src/components/foundation/Toast/Toast.tsx | 65 ++++++++++++++++ .../foundation/Toast/ToastContainer.tsx | 76 +++++++++++++++++++ .../components/foundation/Toast/toastUtils.ts | 10 +++ 5 files changed, 162 insertions(+) create mode 100644 FE/src/components/foundation/Toast/Toast.styles.ts create mode 100644 FE/src/components/foundation/Toast/Toast.tsx create mode 100644 FE/src/components/foundation/Toast/ToastContainer.tsx create mode 100644 FE/src/components/foundation/Toast/toastUtils.ts diff --git a/FE/src/App.tsx b/FE/src/App.tsx index ca07e6c..443aba9 100644 --- a/FE/src/App.tsx +++ b/FE/src/App.tsx @@ -7,6 +7,7 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import GlobalSVGProvider from '@/GlobalSvgProvider'; import AppRouter from '@/AppRouter'; import ModalProvider from './modalProvider'; +import { ToastContainer } from '@foundation/Toast/ToastContainer'; function App() { const queryClient = new QueryClient(); @@ -20,6 +21,7 @@ function App() { + diff --git a/FE/src/components/foundation/Toast/Toast.styles.ts b/FE/src/components/foundation/Toast/Toast.styles.ts new file mode 100644 index 0000000..78368c3 --- /dev/null +++ b/FE/src/components/foundation/Toast/Toast.styles.ts @@ -0,0 +1,9 @@ +import { css } from '@emotion/react'; + +export const ToastType = { + info: css``, + success: css``, + warning: css``, + error: css``, + default: css``, +}; diff --git a/FE/src/components/foundation/Toast/Toast.tsx b/FE/src/components/foundation/Toast/Toast.tsx new file mode 100644 index 0000000..0ed7690 --- /dev/null +++ b/FE/src/components/foundation/Toast/Toast.tsx @@ -0,0 +1,65 @@ +import { ToastType } from '@foundation/Toast/Toast.styles'; +import { useCallback, useEffect, useRef } from 'react'; +import { ToastEvent, ToastProps } from '@foundation/Toast/type'; +import { css } from '@emotion/react'; +import { eventManager } from '@foundation/Toast/EventManger'; +import { theme } from '@styles/theme'; +import { Box } from '@foundation/index'; + +const Toast: React.FC = ({ + toastId, + text, + autoClose = 3000, + closeOnClick = true, + type = 'default', + pauseOnHover = true, +}) => { + const timerRef = useRef(); + const onClose = useCallback(() => { + eventManager.emit(ToastEvent.Delete, toastId); + }, [toastId]); + + useEffect(() => { + if (autoClose) { + timerRef.current = window.setTimeout(onClose, Number(autoClose)); + return () => clearTimeout(timerRef.current); + } + }, [autoClose, onClose]); + + const handleClick = () => { + closeOnClick && onClose(); + }; + + const handleMouseEnter = () => { + pauseOnHover && clearTimeout(timerRef.current); + }; + + const handleMouseLeave = () => { + if (pauseOnHover) { + timerRef.current = window.setTimeout(onClose, Number(autoClose)); + } + }; + + return ( + + {text} + + ); +}; + +export default Toast; diff --git a/FE/src/components/foundation/Toast/ToastContainer.tsx b/FE/src/components/foundation/Toast/ToastContainer.tsx new file mode 100644 index 0000000..78f7630 --- /dev/null +++ b/FE/src/components/foundation/Toast/ToastContainer.tsx @@ -0,0 +1,76 @@ +import { useEffect, useRef, useState } from 'react'; +import { ToastEvent, ToastProps } from '@foundation/Toast/type'; +import { eventManager } from '@foundation/Toast/EventManger'; +import Toast from '@foundation/Toast/Toast'; +import { css } from '@emotion/react'; + +const useToastContainer = () => { + const [toastList, setToastList] = useState(new Map()); + + // 토스트 추가 + const addToast = (props: ToastProps) => { + setToastList((prev) => new Map(prev).set(props.toastId, props)); + }; + + // 토스트 업데이트 + const updateToast = (id: string, props: ToastProps) => { + setToastList((prev) => { + const newMap = new Map(prev); + newMap.set(id, { ...newMap.get(id), ...props }); + return newMap; + }); + }; + + // 토스트 삭제 + const deleteToast = (id: string) => { + setToastList((prev) => { + const newMap = new Map(prev); + newMap.delete(id); + return newMap; + }); + }; + + useEffect(() => { + eventManager.on(ToastEvent.Add, addToast); + eventManager.on(ToastEvent.Update, updateToast); + eventManager.on(ToastEvent.Delete, deleteToast); + + // 컴포넌트 언마운트 시 리스너 해제 + return () => { + eventManager.off(ToastEvent.Add, addToast); + eventManager.off(ToastEvent.Update, updateToast); + eventManager.off(ToastEvent.Delete, deleteToast); + }; + }, []); + + const getToastToRender = () => { + return Array.from(toastList); + }; + + return { getToastToRender }; +}; + +export const ToastContainer = () => { + const containerRef = useRef(null); + const { getToastToRender } = useToastContainer(); + + const list = getToastToRender(); + return ( +
+ {list.map(([toastId, toastProps]) => ( + + ))} +
+ ); +}; diff --git a/FE/src/components/foundation/Toast/toastUtils.ts b/FE/src/components/foundation/Toast/toastUtils.ts new file mode 100644 index 0000000..6e9d6df --- /dev/null +++ b/FE/src/components/foundation/Toast/toastUtils.ts @@ -0,0 +1,10 @@ +import { ToastEvent, ToastProps } from '@foundation/Toast/type'; +import { eventManager } from '@foundation/Toast/EventManger'; + +const generateUniqueId = () => { + return Date.now().toString(36) + Math.random().toString(36).substring(4); +}; +export const showToast = (toastProps: Omit) => { + const id = generateUniqueId(); + eventManager.emit(ToastEvent.Add, { ...toastProps, toastId: id }); +}; From 47f9fa0302b80b858944b06ece33b889456dd83e Mon Sep 17 00:00:00 2001 From: Sumin Date: Wed, 6 Dec 2023 21:50:14 +0900 Subject: [PATCH 03/21] =?UTF-8?q?design:=20=ED=86=A0=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=EB=B3=84=20=EC=95=84=EC=9D=B4=EC=BD=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/GlobalSvgProvider.tsx | 24 +++++++++++++++++++ .../foundation/Toast/Toast.styles.ts | 8 +++++++ FE/src/components/foundation/Toast/Toast.tsx | 16 +++++++++---- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/FE/src/GlobalSvgProvider.tsx b/FE/src/GlobalSvgProvider.tsx index 6aa1f86..8e2902b 100644 --- a/FE/src/GlobalSvgProvider.tsx +++ b/FE/src/GlobalSvgProvider.tsx @@ -322,6 +322,30 @@ const spliteSvgCode = ( strokeLinejoin="round" /> + + + + + + + + + + + + ); diff --git a/FE/src/components/foundation/Toast/Toast.styles.ts b/FE/src/components/foundation/Toast/Toast.styles.ts index 78368c3..90b2327 100644 --- a/FE/src/components/foundation/Toast/Toast.styles.ts +++ b/FE/src/components/foundation/Toast/Toast.styles.ts @@ -7,3 +7,11 @@ export const ToastType = { error: css``, default: css``, }; + +export const ToastTypeIcon = { + info: 'info-round', + success: 'check-round', + warning: 'warning', + error: 'bomb', + default: null, +}; diff --git a/FE/src/components/foundation/Toast/Toast.tsx b/FE/src/components/foundation/Toast/Toast.tsx index 0ed7690..d4b8dc8 100644 --- a/FE/src/components/foundation/Toast/Toast.tsx +++ b/FE/src/components/foundation/Toast/Toast.tsx @@ -1,10 +1,10 @@ -import { ToastType } from '@foundation/Toast/Toast.styles'; +import { ToastType, ToastTypeIcon } from '@foundation/Toast/Toast.styles'; import { useCallback, useEffect, useRef } from 'react'; import { ToastEvent, ToastProps } from '@foundation/Toast/type'; import { css } from '@emotion/react'; import { eventManager } from '@foundation/Toast/EventManger'; import { theme } from '@styles/theme'; -import { Box } from '@foundation/index'; +import { Box, Icon } from '@foundation/index'; const Toast: React.FC = ({ toastId, @@ -40,6 +40,12 @@ const Toast: React.FC = ({ } }; + const IconType = () => + type === 'default' ? null : ( + + ); + + console.log(IconType); return ( = ({ css={[ css` display: flex; - flex-direction: column; + align-items: center; + column-gap: 1rem; padding: 1rem; min-width: 20rem; - border-radius: 1rem; + border-radius: 0.5rem; background-color: ${theme.colors.surface.default}; `, ToastType[type], ]} > + {text} ); From 3edd41d4de5d22a2c2fc43757fd9eca40de15571 Mon Sep 17 00:00:00 2001 From: Sumin Date: Wed, 6 Dec 2023 21:51:34 +0900 Subject: [PATCH 04/21] =?UTF-8?q?fix:=20autoClose=20=EC=98=B5=EC=85=98=20?= =?UTF-8?q?=EA=BA=BC=EC=A0=B8=EC=9E=88=EC=9D=84=20=EB=95=8C=20=ED=98=B8?= =?UTF-8?q?=EB=B2=84=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=EC=8B=9C=20=ED=86=A0?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EB=B0=94=EB=A1=9C=20=EC=A2=85=EB=A3=8C?= =?UTF-8?q?=EB=90=98=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/foundation/Toast/Toast.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/FE/src/components/foundation/Toast/Toast.tsx b/FE/src/components/foundation/Toast/Toast.tsx index d4b8dc8..91892e6 100644 --- a/FE/src/components/foundation/Toast/Toast.tsx +++ b/FE/src/components/foundation/Toast/Toast.tsx @@ -31,11 +31,11 @@ const Toast: React.FC = ({ }; const handleMouseEnter = () => { - pauseOnHover && clearTimeout(timerRef.current); + pauseOnHover && autoClose && clearTimeout(timerRef.current); }; const handleMouseLeave = () => { - if (pauseOnHover) { + if (pauseOnHover && autoClose) { timerRef.current = window.setTimeout(onClose, Number(autoClose)); } }; @@ -45,7 +45,6 @@ const Toast: React.FC = ({ ); - console.log(IconType); return ( Date: Wed, 6 Dec 2023 22:44:31 +0900 Subject: [PATCH 05/21] =?UTF-8?q?feat:=20=ED=86=A0=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EC=A7=80=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../foundation/Toast/Toast.styles.ts | 36 +++++++++++- FE/src/components/foundation/Toast/Toast.tsx | 9 ++- .../foundation/Toast/ToastContainer.tsx | 55 ++++++++++++------- FE/src/components/foundation/Toast/type.ts | 9 ++- 4 files changed, 81 insertions(+), 28 deletions(-) diff --git a/FE/src/components/foundation/Toast/Toast.styles.ts b/FE/src/components/foundation/Toast/Toast.styles.ts index 90b2327..9d7892c 100644 --- a/FE/src/components/foundation/Toast/Toast.styles.ts +++ b/FE/src/components/foundation/Toast/Toast.styles.ts @@ -1,6 +1,7 @@ import { css } from '@emotion/react'; -export const ToastType = { +const TOAST_GAP = '0.75rem'; +export const ToastTypeStyle = { info: css``, success: css``, warning: css``, @@ -8,10 +9,41 @@ export const ToastType = { default: css``, }; -export const ToastTypeIcon = { +export const ToastTypeIconName = { info: 'info-round', success: 'check-round', warning: 'warning', error: 'bomb', default: null, }; + +export const ToastPositionStyle = { + topLeft: css` + top: ${TOAST_GAP}; + left: ${TOAST_GAP}; + `, + topRight: css` + top: ${TOAST_GAP}; + right: ${TOAST_GAP}; + `, + topCenter: css` + top: ${TOAST_GAP}; + left: 50%; + transform: translateX(-50%); + z-index: 10000; + `, + bottomLeft: css` + bottom: ${TOAST_GAP}; + left: ${TOAST_GAP}; + `, + bottomRight: css` + bottom: ${TOAST_GAP}; + right: ${TOAST_GAP}; + `, + bottomCenter: css` + bottom: ${TOAST_GAP}; + left: 50%; + transform: translateX(-50%); + z-index: 10000; + `, +}; diff --git a/FE/src/components/foundation/Toast/Toast.tsx b/FE/src/components/foundation/Toast/Toast.tsx index 91892e6..5e5c5ab 100644 --- a/FE/src/components/foundation/Toast/Toast.tsx +++ b/FE/src/components/foundation/Toast/Toast.tsx @@ -1,4 +1,7 @@ -import { ToastType, ToastTypeIcon } from '@foundation/Toast/Toast.styles'; +import { + ToastTypeIconName, + ToastTypeStyle, +} from '@foundation/Toast/Toast.styles'; import { useCallback, useEffect, useRef } from 'react'; import { ToastEvent, ToastProps } from '@foundation/Toast/type'; import { css } from '@emotion/react'; @@ -42,7 +45,7 @@ const Toast: React.FC = ({ const IconType = () => type === 'default' ? null : ( - + ); return ( @@ -60,7 +63,7 @@ const Toast: React.FC = ({ border-radius: 0.5rem; background-color: ${theme.colors.surface.default}; `, - ToastType[type], + ToastTypeStyle[type], ]} > diff --git a/FE/src/components/foundation/Toast/ToastContainer.tsx b/FE/src/components/foundation/Toast/ToastContainer.tsx index 78f7630..112dd33 100644 --- a/FE/src/components/foundation/Toast/ToastContainer.tsx +++ b/FE/src/components/foundation/Toast/ToastContainer.tsx @@ -1,8 +1,9 @@ -import { useEffect, useRef, useState } from 'react'; -import { ToastEvent, ToastProps } from '@foundation/Toast/type'; +import { useEffect, useState } from 'react'; +import { ToastEvent, ToastPosition, ToastProps } from '@foundation/Toast/type'; import { eventManager } from '@foundation/Toast/EventManger'; import Toast from '@foundation/Toast/Toast'; import { css } from '@emotion/react'; +import { ToastPositionStyle } from '@foundation/Toast/Toast.styles'; const useToastContainer = () => { const [toastList, setToastList] = useState(new Map()); @@ -43,34 +44,46 @@ const useToastContainer = () => { }; }, []); - const getToastToRender = () => { + const toastListToArray = () => { return Array.from(toastList); }; - return { getToastToRender }; + const getToastPositionGroupToRender = () => { + const list = toastListToArray(); + const positionGroup = new Map(); + list.forEach(([_, toastProps]) => { + const position = toastProps.position || 'topRight'; + positionGroup.has(position) + ? positionGroup.get(position)!.push(toastProps) + : positionGroup.set(position, [toastProps]); + }); + return positionGroup; + }; + + return { getToastPositionGroupToRender }; }; export const ToastContainer = () => { - const containerRef = useRef(null); - const { getToastToRender } = useToastContainer(); + const { getToastPositionGroupToRender } = useToastContainer(); + const positionGroup = getToastPositionGroupToRender(); - const list = getToastToRender(); - return ( + return Array.from(positionGroup).map(([position, toasts]) => (
- {list.map(([toastId, toastProps]) => ( - + {toasts.map((toastProps) => ( + ))}
- ); + )); }; diff --git a/FE/src/components/foundation/Toast/type.ts b/FE/src/components/foundation/Toast/type.ts index 0ab41ab..f11311c 100644 --- a/FE/src/components/foundation/Toast/type.ts +++ b/FE/src/components/foundation/Toast/type.ts @@ -1,12 +1,17 @@ -import { ToastType } from '@foundation/Toast/Toast.styles'; +import { + ToastPositionStyle, + ToastTypeStyle, +} from '@foundation/Toast/Toast.styles'; +export type ToastPosition = keyof typeof ToastPositionStyle; export type ToastProps = { toastId: string; text: string; autoClose?: boolean | number; closeOnClick?: boolean; - type?: keyof typeof ToastType; + type?: keyof typeof ToastTypeStyle; pauseOnHover?: boolean; + position?: ToastPosition; }; export const enum ToastEvent { From 0713addb534f6b7e7eff4a723e42e356f99e8279 Mon Sep 17 00:00:00 2001 From: Sumin Date: Wed, 6 Dec 2023 22:46:31 +0900 Subject: [PATCH 06/21] =?UTF-8?q?fix:=20autoClose=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit autoClose 속성이 true이면 토스트가 바로 사라지는 문제 발생 --- FE/src/components/foundation/Toast/type.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FE/src/components/foundation/Toast/type.ts b/FE/src/components/foundation/Toast/type.ts index f11311c..5902cf2 100644 --- a/FE/src/components/foundation/Toast/type.ts +++ b/FE/src/components/foundation/Toast/type.ts @@ -7,7 +7,7 @@ export type ToastPosition = keyof typeof ToastPositionStyle; export type ToastProps = { toastId: string; text: string; - autoClose?: boolean | number; + autoClose?: false | number; closeOnClick?: boolean; type?: keyof typeof ToastTypeStyle; pauseOnHover?: boolean; From cc83052f10ef88dd97f11d68e4a040374caf6957 Mon Sep 17 00:00:00 2001 From: Sumin Date: Wed, 6 Dec 2023 22:48:51 +0900 Subject: [PATCH 07/21] =?UTF-8?q?remove:=20=ED=86=A0=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 업데이트는 토스트를 사용해서 promise 등의 상태를 표시해줄 때 필요함. 추후 이 부분이 기획으로 추가된다면 그 때 개발하기 --- FE/src/components/foundation/Toast/ToastContainer.tsx | 11 ----------- FE/src/components/foundation/Toast/type.ts | 7 +------ 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/FE/src/components/foundation/Toast/ToastContainer.tsx b/FE/src/components/foundation/Toast/ToastContainer.tsx index 112dd33..7f44879 100644 --- a/FE/src/components/foundation/Toast/ToastContainer.tsx +++ b/FE/src/components/foundation/Toast/ToastContainer.tsx @@ -13,15 +13,6 @@ const useToastContainer = () => { setToastList((prev) => new Map(prev).set(props.toastId, props)); }; - // 토스트 업데이트 - const updateToast = (id: string, props: ToastProps) => { - setToastList((prev) => { - const newMap = new Map(prev); - newMap.set(id, { ...newMap.get(id), ...props }); - return newMap; - }); - }; - // 토스트 삭제 const deleteToast = (id: string) => { setToastList((prev) => { @@ -33,13 +24,11 @@ const useToastContainer = () => { useEffect(() => { eventManager.on(ToastEvent.Add, addToast); - eventManager.on(ToastEvent.Update, updateToast); eventManager.on(ToastEvent.Delete, deleteToast); // 컴포넌트 언마운트 시 리스너 해제 return () => { eventManager.off(ToastEvent.Add, addToast); - eventManager.off(ToastEvent.Update, updateToast); eventManager.off(ToastEvent.Delete, deleteToast); }; }, []); diff --git a/FE/src/components/foundation/Toast/type.ts b/FE/src/components/foundation/Toast/type.ts index 5902cf2..0feb423 100644 --- a/FE/src/components/foundation/Toast/type.ts +++ b/FE/src/components/foundation/Toast/type.ts @@ -16,15 +16,13 @@ export type ToastProps = { export const enum ToastEvent { Add, - Update, Delete, } type OnAddCallback = (props: ToastProps) => void; -type OnUpdateCallback = (id: string, props: ToastProps) => void; type OnDeleteCallback = (id: string) => void; -export type Callback = OnAddCallback | OnUpdateCallback | OnDeleteCallback; +export type Callback = OnAddCallback | OnDeleteCallback; type TimeoutId = ReturnType; @@ -32,16 +30,13 @@ export interface EventManager { list: Map; emitQueue: Map; - on(event: ToastEvent.Update, callback: OnUpdateCallback): EventManager; on(event: ToastEvent.Add, callback: OnAddCallback): EventManager; on(event: ToastEvent.Delete, callback: OnDeleteCallback): EventManager; - off(event: ToastEvent.Update, callback: OnUpdateCallback): EventManager; off(event: ToastEvent.Add, callback: OnAddCallback): EventManager; off(event: ToastEvent.Delete, callback: OnDeleteCallback): EventManager; cancelEmit(event: ToastEvent): EventManager; - emit(event: ToastEvent.Update, id: string, props: ToastProps): void; emit(event: ToastEvent.Add, props: ToastProps): void; emit(event: ToastEvent.Delete, id: string): void; } From d6c2f4af23e0fea018b80578ea767919f3c229a8 Mon Sep 17 00:00:00 2001 From: Sumin Date: Thu, 7 Dec 2023 01:06:42 +0900 Subject: [PATCH 08/21] =?UTF-8?q?feat:=20collapseToast=EC=9D=84=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=ED=95=B4=EC=84=9C=20=ED=86=A0=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EA=B0=80=20=EC=9E=90=EC=97=B0=EC=8A=A4=EB=9F=BD?= =?UTF-8?q?=EA=B2=8C=20=EC=82=AC=EB=9D=BC=EC=A7=80=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../foundation/Toast/Toast.styles.ts | 13 +++- FE/src/components/foundation/Toast/Toast.tsx | 78 +++++++++++++------ .../foundation/Toast/collapseToast.ts | 20 +++++ 3 files changed, 86 insertions(+), 25 deletions(-) create mode 100644 FE/src/components/foundation/Toast/collapseToast.ts diff --git a/FE/src/components/foundation/Toast/Toast.styles.ts b/FE/src/components/foundation/Toast/Toast.styles.ts index 9d7892c..b3fd46f 100644 --- a/FE/src/components/foundation/Toast/Toast.styles.ts +++ b/FE/src/components/foundation/Toast/Toast.styles.ts @@ -1,4 +1,4 @@ -import { css } from '@emotion/react'; +import { css, keyframes } from '@emotion/react'; const TOAST_GAP = '0.75rem'; export const ToastTypeStyle = { @@ -47,3 +47,14 @@ export const ToastPositionStyle = { z-index: 10000; `, }; + +export const ToastFadeOutUpAnimation = keyframes` + from { + opacity: 1; + transform: translateY(0); + } + to { + opacity: 0; + transform: translateY(-1.5rem); + } +`; diff --git a/FE/src/components/foundation/Toast/Toast.tsx b/FE/src/components/foundation/Toast/Toast.tsx index 5e5c5ab..940bdbb 100644 --- a/FE/src/components/foundation/Toast/Toast.tsx +++ b/FE/src/components/foundation/Toast/Toast.tsx @@ -1,13 +1,16 @@ import { + ToastFadeOutUpAnimation, ToastTypeIconName, ToastTypeStyle, } from '@foundation/Toast/Toast.styles'; -import { useCallback, useEffect, useRef } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { ToastEvent, ToastProps } from '@foundation/Toast/type'; import { css } from '@emotion/react'; + import { eventManager } from '@foundation/Toast/EventManger'; import { theme } from '@styles/theme'; import { Box, Icon } from '@foundation/index'; +import { collapseToast } from '@foundation/Toast/collapseToast'; const Toast: React.FC = ({ toastId, @@ -17,10 +20,30 @@ const Toast: React.FC = ({ type = 'default', pauseOnHover = true, }) => { + const toastRef = useRef(null); const timerRef = useRef(); - const onClose = useCallback(() => { - eventManager.emit(ToastEvent.Delete, toastId); - }, [toastId]); + const [isExiting, setIsExiting] = useState(false); + + const onClose = useCallback(() => setIsExiting(true), []); + + useEffect(() => { + const handleAnimationEnd = () => { + collapseToast(toastRef.current!, () => { + eventManager.emit(ToastEvent.Delete, toastId); + }); + }; + + const toastElement = toastRef.current; + if (toastElement) { + toastElement.addEventListener('animationend', handleAnimationEnd); + } + + return () => { + if (toastElement) { + toastElement.removeEventListener('animationend', handleAnimationEnd); + } + }; + }, [onClose, toastId]); useEffect(() => { if (autoClose) { @@ -49,26 +72,33 @@ const Toast: React.FC = ({ ); return ( - - - {text} - +
+ + + {text} + +
); }; diff --git a/FE/src/components/foundation/Toast/collapseToast.ts b/FE/src/components/foundation/Toast/collapseToast.ts new file mode 100644 index 0000000..118aff3 --- /dev/null +++ b/FE/src/components/foundation/Toast/collapseToast.ts @@ -0,0 +1,20 @@ +export function collapseToast( + node: HTMLDivElement, + done: () => void, + duration = 500 +) { + const { scrollHeight, style } = node; + + requestAnimationFrame(() => { + style.minHeight = 'initial'; + style.height = scrollHeight + 'px'; + style.transition = `all ${duration}ms`; + + requestAnimationFrame(() => { + style.height = '0'; + style.padding = '0'; + style.margin = '0'; + setTimeout(done, duration); + }); + }); +} From cb0330bd30d4bcb08ac6530c16dc105fdc712a73 Mon Sep 17 00:00:00 2001 From: Sumin Date: Thu, 7 Dec 2023 01:46:26 +0900 Subject: [PATCH 09/21] =?UTF-8?q?refactor:=20=EC=95=A0=EB=8B=88=EB=A9=94?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EB=81=9D=EB=82=98=EB=8A=94=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=ED=9B=85=EC=9C=BC=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/foundation/Toast/Toast.tsx | 24 ++++++-------------- FE/src/hooks/useAnimationEnd.ts | 20 ++++++++++++++++ 2 files changed, 27 insertions(+), 17 deletions(-) create mode 100644 FE/src/hooks/useAnimationEnd.ts diff --git a/FE/src/components/foundation/Toast/Toast.tsx b/FE/src/components/foundation/Toast/Toast.tsx index 940bdbb..19f880a 100644 --- a/FE/src/components/foundation/Toast/Toast.tsx +++ b/FE/src/components/foundation/Toast/Toast.tsx @@ -11,6 +11,7 @@ import { eventManager } from '@foundation/Toast/EventManger'; import { theme } from '@styles/theme'; import { Box, Icon } from '@foundation/index'; import { collapseToast } from '@foundation/Toast/collapseToast'; +import useAnimationEnd from '@hooks/useAnimationEnd'; const Toast: React.FC = ({ toastId, @@ -26,24 +27,13 @@ const Toast: React.FC = ({ const onClose = useCallback(() => setIsExiting(true), []); - useEffect(() => { - const handleAnimationEnd = () => { - collapseToast(toastRef.current!, () => { - eventManager.emit(ToastEvent.Delete, toastId); - }); - }; - - const toastElement = toastRef.current; - if (toastElement) { - toastElement.addEventListener('animationend', handleAnimationEnd); - } + const handleAnimationEnd = useCallback(() => { + collapseToast(toastRef.current!, () => { + eventManager.emit(ToastEvent.Delete, toastId); + }); + }, [toastId]); - return () => { - if (toastElement) { - toastElement.removeEventListener('animationend', handleAnimationEnd); - } - }; - }, [onClose, toastId]); + useAnimationEnd(toastRef, handleAnimationEnd, [toastId]); useEffect(() => { if (autoClose) { diff --git a/FE/src/hooks/useAnimationEnd.ts b/FE/src/hooks/useAnimationEnd.ts new file mode 100644 index 0000000..e758fc3 --- /dev/null +++ b/FE/src/hooks/useAnimationEnd.ts @@ -0,0 +1,20 @@ +import { RefObject, useEffect } from 'react'; + +const useAnimationEnd = ( + nodeRef: RefObject, + onEndCallback: () => void, + deps: unknown[] = [] +) => { + useEffect(() => { + const node = nodeRef.current; + if (node) { + node.addEventListener('animationend', onEndCallback); + + return () => { + node.removeEventListener('animationend', onEndCallback); + }; + } + }, [nodeRef, onEndCallback, ...deps]); +}; + +export default useAnimationEnd; From a47941e5a4440e882c7bf94824b59823597a2b9a Mon Sep 17 00:00:00 2001 From: Sumin Date: Thu, 7 Dec 2023 02:08:10 +0900 Subject: [PATCH 10/21] =?UTF-8?q?feat:=20=ED=86=A0=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=EA=B7=B8=EB=9E=98=EC=8A=A4=EB=B0=94=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../foundation/Toast/Toast.styles.ts | 9 +++++ FE/src/components/foundation/Toast/Toast.tsx | 37 ++++++++++++++++--- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/FE/src/components/foundation/Toast/Toast.styles.ts b/FE/src/components/foundation/Toast/Toast.styles.ts index b3fd46f..dd8da2c 100644 --- a/FE/src/components/foundation/Toast/Toast.styles.ts +++ b/FE/src/components/foundation/Toast/Toast.styles.ts @@ -58,3 +58,12 @@ export const ToastFadeOutUpAnimation = keyframes` transform: translateY(-1.5rem); } `; + +export const ToastProgressBarAnimation = keyframes` + from { + transform: scaleX(1); + } + to { + transform: scaleX(0); + } +`; diff --git a/FE/src/components/foundation/Toast/Toast.tsx b/FE/src/components/foundation/Toast/Toast.tsx index 19f880a..3113895 100644 --- a/FE/src/components/foundation/Toast/Toast.tsx +++ b/FE/src/components/foundation/Toast/Toast.tsx @@ -1,5 +1,6 @@ import { ToastFadeOutUpAnimation, + ToastProgressBarAnimation, ToastTypeIconName, ToastTypeStyle, } from '@foundation/Toast/Toast.styles'; @@ -69,12 +70,13 @@ const Toast: React.FC = ({ onMouseLeave={handleMouseLeave} css={[ css` + position: relative; display: flex; - align-items: center; - column-gap: 1rem; - padding: 1rem; + flex-direction: column; + row-gap: 0.5rem; min-width: 20rem; border-radius: 0.5rem; + overflow: hidden; background-color: ${theme.colors.surface.default}; animation: ${isExiting ? css` @@ -85,8 +87,33 @@ const Toast: React.FC = ({ ToastTypeStyle[type], ]} > - - {text} +
+ + {text} +
+
); From 85e5a083a0a7430a3675d05f41970d0715911da7 Mon Sep 17 00:00:00 2001 From: Sumin Date: Thu, 7 Dec 2023 02:23:00 +0900 Subject: [PATCH 11/21] =?UTF-8?q?feat:=20=EB=A7=88=EC=9A=B0=EC=8A=A4=20?= =?UTF-8?q?=ED=98=B8=EB=B2=84=EC=8B=9C=20=ED=86=A0=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=83=80=EC=9D=B4=EB=A8=B8=20=EB=A9=88=EC=B6=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/foundation/Toast/Toast.tsx | 29 ++++++++++---------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/FE/src/components/foundation/Toast/Toast.tsx b/FE/src/components/foundation/Toast/Toast.tsx index 3113895..2bc50b9 100644 --- a/FE/src/components/foundation/Toast/Toast.tsx +++ b/FE/src/components/foundation/Toast/Toast.tsx @@ -4,7 +4,7 @@ import { ToastTypeIconName, ToastTypeStyle, } from '@foundation/Toast/Toast.styles'; -import { useCallback, useEffect, useRef, useState } from 'react'; +import { useCallback, useRef, useState } from 'react'; import { ToastEvent, ToastProps } from '@foundation/Toast/type'; import { css } from '@emotion/react'; @@ -23,37 +23,36 @@ const Toast: React.FC = ({ pauseOnHover = true, }) => { const toastRef = useRef(null); - const timerRef = useRef(); + const progressRef = useRef(null); const [isExiting, setIsExiting] = useState(false); + const [isPaused, setIsPaused] = useState(false); - const onClose = useCallback(() => setIsExiting(true), []); + const onProgressAnimationEnd = useCallback(() => setIsExiting(true), []); - const handleAnimationEnd = useCallback(() => { + const onExitingAnimationEnd = useCallback(() => { collapseToast(toastRef.current!, () => { eventManager.emit(ToastEvent.Delete, toastId); }); }, [toastId]); - useAnimationEnd(toastRef, handleAnimationEnd, [toastId]); + useAnimationEnd(toastRef, onExitingAnimationEnd, [toastId]); - useEffect(() => { - if (autoClose) { - timerRef.current = window.setTimeout(onClose, Number(autoClose)); - return () => clearTimeout(timerRef.current); - } - }, [autoClose, onClose]); + useAnimationEnd(progressRef, () => autoClose && onProgressAnimationEnd, [ + autoClose, + onProgressAnimationEnd, + ]); const handleClick = () => { - closeOnClick && onClose(); + closeOnClick && onProgressAnimationEnd(); }; const handleMouseEnter = () => { - pauseOnHover && autoClose && clearTimeout(timerRef.current); + pauseOnHover && autoClose && setIsPaused(true); }; const handleMouseLeave = () => { if (pauseOnHover && autoClose) { - timerRef.current = window.setTimeout(onClose, Number(autoClose)); + setIsPaused(false); } }; @@ -99,6 +98,7 @@ const Toast: React.FC = ({ {text}
= ({ forwards ` : 'none'}; + animation-play-state: ${isPaused ? 'paused' : 'running'}; `} /> From f5f55f36c3d867e5580489da67771fc5785fd8c0 Mon Sep 17 00:00:00 2001 From: Sumin Date: Thu, 7 Dec 2023 02:39:31 +0900 Subject: [PATCH 12/21] =?UTF-8?q?refactor:=20useToastContainer=20=ED=9B=85?= =?UTF-8?q?=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../foundation/Toast/ToastContainer.tsx | 51 +----------------- .../foundation/Toast/useToastContainer.ts | 52 +++++++++++++++++++ 2 files changed, 53 insertions(+), 50 deletions(-) create mode 100644 FE/src/components/foundation/Toast/useToastContainer.ts diff --git a/FE/src/components/foundation/Toast/ToastContainer.tsx b/FE/src/components/foundation/Toast/ToastContainer.tsx index 7f44879..87df73a 100644 --- a/FE/src/components/foundation/Toast/ToastContainer.tsx +++ b/FE/src/components/foundation/Toast/ToastContainer.tsx @@ -1,56 +1,7 @@ -import { useEffect, useState } from 'react'; -import { ToastEvent, ToastPosition, ToastProps } from '@foundation/Toast/type'; -import { eventManager } from '@foundation/Toast/EventManger'; import Toast from '@foundation/Toast/Toast'; import { css } from '@emotion/react'; import { ToastPositionStyle } from '@foundation/Toast/Toast.styles'; - -const useToastContainer = () => { - const [toastList, setToastList] = useState(new Map()); - - // 토스트 추가 - const addToast = (props: ToastProps) => { - setToastList((prev) => new Map(prev).set(props.toastId, props)); - }; - - // 토스트 삭제 - const deleteToast = (id: string) => { - setToastList((prev) => { - const newMap = new Map(prev); - newMap.delete(id); - return newMap; - }); - }; - - useEffect(() => { - eventManager.on(ToastEvent.Add, addToast); - eventManager.on(ToastEvent.Delete, deleteToast); - - // 컴포넌트 언마운트 시 리스너 해제 - return () => { - eventManager.off(ToastEvent.Add, addToast); - eventManager.off(ToastEvent.Delete, deleteToast); - }; - }, []); - - const toastListToArray = () => { - return Array.from(toastList); - }; - - const getToastPositionGroupToRender = () => { - const list = toastListToArray(); - const positionGroup = new Map(); - list.forEach(([_, toastProps]) => { - const position = toastProps.position || 'topRight'; - positionGroup.has(position) - ? positionGroup.get(position)!.push(toastProps) - : positionGroup.set(position, [toastProps]); - }); - return positionGroup; - }; - - return { getToastPositionGroupToRender }; -}; +import useToastContainer from '@foundation/Toast/useToastContainer'; export const ToastContainer = () => { const { getToastPositionGroupToRender } = useToastContainer(); diff --git a/FE/src/components/foundation/Toast/useToastContainer.ts b/FE/src/components/foundation/Toast/useToastContainer.ts new file mode 100644 index 0000000..4c8c53b --- /dev/null +++ b/FE/src/components/foundation/Toast/useToastContainer.ts @@ -0,0 +1,52 @@ +import { useEffect, useState } from 'react'; +import { ToastEvent, ToastPosition, ToastProps } from '@foundation/Toast/type'; +import { eventManager } from '@foundation/Toast/EventManger'; + +const useToastContainer = () => { + const [toastList, setToastList] = useState(new Map()); + + // 토스트 추가 + const addToast = (props: ToastProps) => { + setToastList((prev) => new Map(prev).set(props.toastId, props)); + }; + + // 토스트 삭제 + const deleteToast = (id: string) => { + setToastList((prev) => { + const newMap = new Map(prev); + newMap.delete(id); + return newMap; + }); + }; + + useEffect(() => { + eventManager.on(ToastEvent.Add, addToast); + eventManager.on(ToastEvent.Delete, deleteToast); + + // 컴포넌트 언마운트 시 리스너 해제 + return () => { + eventManager.off(ToastEvent.Add, addToast); + eventManager.off(ToastEvent.Delete, deleteToast); + }; + }, []); + + const toastListToArray = () => { + return Array.from(toastList); + }; + + const getToastPositionGroupToRender = () => { + const list = toastListToArray(); + const positionGroup = new Map(); + list.forEach(([_, toastProps]) => { + const position = toastProps.position || 'topRight'; + positionGroup.has(position) + ? positionGroup.get(position)!.push(toastProps) + : positionGroup.set(position, [toastProps]); + }); + return positionGroup; + }; + + return { getToastPositionGroupToRender }; +}; + +export default useToastContainer; From 32b7734aba26b3a5638c66d27603dba8116c680e Mon Sep 17 00:00:00 2001 From: Sumin Date: Thu, 7 Dec 2023 02:40:13 +0900 Subject: [PATCH 13/21] =?UTF-8?q?rename:=20EventManger=20->=20eventManger?= =?UTF-8?q?=CB=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../foundation/Toast/{EventManger.ts => eventManger.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename FE/src/components/foundation/Toast/{EventManger.ts => eventManger.ts} (100%) diff --git a/FE/src/components/foundation/Toast/EventManger.ts b/FE/src/components/foundation/Toast/eventManger.ts similarity index 100% rename from FE/src/components/foundation/Toast/EventManger.ts rename to FE/src/components/foundation/Toast/eventManger.ts From a85d9cf9933f03dd9c5d0de163c02e392ed3c4f4 Mon Sep 17 00:00:00 2001 From: Sumin Date: Thu, 7 Dec 2023 02:41:21 +0900 Subject: [PATCH 14/21] =?UTF-8?q?rename:=20EventManger=20->=20eventManger?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=20=ED=9B=84=20import=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/foundation/Toast/Toast.tsx | 3 +-- FE/src/components/foundation/Toast/toastUtils.ts | 2 +- FE/src/components/foundation/Toast/useToastContainer.ts | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/FE/src/components/foundation/Toast/Toast.tsx b/FE/src/components/foundation/Toast/Toast.tsx index 2bc50b9..2013008 100644 --- a/FE/src/components/foundation/Toast/Toast.tsx +++ b/FE/src/components/foundation/Toast/Toast.tsx @@ -7,12 +7,11 @@ import { import { useCallback, useRef, useState } from 'react'; import { ToastEvent, ToastProps } from '@foundation/Toast/type'; import { css } from '@emotion/react'; - -import { eventManager } from '@foundation/Toast/EventManger'; import { theme } from '@styles/theme'; import { Box, Icon } from '@foundation/index'; import { collapseToast } from '@foundation/Toast/collapseToast'; import useAnimationEnd from '@hooks/useAnimationEnd'; +import { eventManager } from '@foundation/Toast/eventManger'; const Toast: React.FC = ({ toastId, diff --git a/FE/src/components/foundation/Toast/toastUtils.ts b/FE/src/components/foundation/Toast/toastUtils.ts index 6e9d6df..0339a48 100644 --- a/FE/src/components/foundation/Toast/toastUtils.ts +++ b/FE/src/components/foundation/Toast/toastUtils.ts @@ -1,5 +1,5 @@ import { ToastEvent, ToastProps } from '@foundation/Toast/type'; -import { eventManager } from '@foundation/Toast/EventManger'; +import { eventManager } from '@foundation/Toast/eventManger'; const generateUniqueId = () => { return Date.now().toString(36) + Math.random().toString(36).substring(4); diff --git a/FE/src/components/foundation/Toast/useToastContainer.ts b/FE/src/components/foundation/Toast/useToastContainer.ts index 4c8c53b..9c93173 100644 --- a/FE/src/components/foundation/Toast/useToastContainer.ts +++ b/FE/src/components/foundation/Toast/useToastContainer.ts @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import { ToastEvent, ToastPosition, ToastProps } from '@foundation/Toast/type'; -import { eventManager } from '@foundation/Toast/EventManger'; +import { eventManager } from '@foundation/Toast/eventManger'; const useToastContainer = () => { const [toastList, setToastList] = useState(new Map()); From 98ba9035da7cbaac3839ad8b39c8619a45c1e146 Mon Sep 17 00:00:00 2001 From: Sumin Date: Thu, 7 Dec 2023 02:44:11 +0900 Subject: [PATCH 15/21] rename: Toast -> ToastItem --- FE/src/components/foundation/Toast/ToastContainer.tsx | 4 ++-- .../components/foundation/Toast/{Toast.tsx => ToastItem.tsx} | 4 ++-- .../components/foundation/Toast/{toastUtils.ts => toast.ts} | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename FE/src/components/foundation/Toast/{Toast.tsx => ToastItem.tsx} (97%) rename FE/src/components/foundation/Toast/{toastUtils.ts => toast.ts} (82%) diff --git a/FE/src/components/foundation/Toast/ToastContainer.tsx b/FE/src/components/foundation/Toast/ToastContainer.tsx index 87df73a..54714b8 100644 --- a/FE/src/components/foundation/Toast/ToastContainer.tsx +++ b/FE/src/components/foundation/Toast/ToastContainer.tsx @@ -1,7 +1,7 @@ -import Toast from '@foundation/Toast/Toast'; import { css } from '@emotion/react'; import { ToastPositionStyle } from '@foundation/Toast/Toast.styles'; import useToastContainer from '@foundation/Toast/useToastContainer'; +import ToastItem from '@foundation/Toast/ToastItem'; export const ToastContainer = () => { const { getToastPositionGroupToRender } = useToastContainer(); @@ -22,7 +22,7 @@ export const ToastContainer = () => { ]} > {toasts.map((toastProps) => ( - + ))}
)); diff --git a/FE/src/components/foundation/Toast/Toast.tsx b/FE/src/components/foundation/Toast/ToastItem.tsx similarity index 97% rename from FE/src/components/foundation/Toast/Toast.tsx rename to FE/src/components/foundation/Toast/ToastItem.tsx index 2013008..47af32c 100644 --- a/FE/src/components/foundation/Toast/Toast.tsx +++ b/FE/src/components/foundation/Toast/ToastItem.tsx @@ -13,7 +13,7 @@ import { collapseToast } from '@foundation/Toast/collapseToast'; import useAnimationEnd from '@hooks/useAnimationEnd'; import { eventManager } from '@foundation/Toast/eventManger'; -const Toast: React.FC = ({ +const ToastItem: React.FC = ({ toastId, text, autoClose = 3000, @@ -119,4 +119,4 @@ const Toast: React.FC = ({ ); }; -export default Toast; +export default ToastItem; diff --git a/FE/src/components/foundation/Toast/toastUtils.ts b/FE/src/components/foundation/Toast/toast.ts similarity index 82% rename from FE/src/components/foundation/Toast/toastUtils.ts rename to FE/src/components/foundation/Toast/toast.ts index 0339a48..f5e5ad4 100644 --- a/FE/src/components/foundation/Toast/toastUtils.ts +++ b/FE/src/components/foundation/Toast/toast.ts @@ -4,7 +4,7 @@ import { eventManager } from '@foundation/Toast/eventManger'; const generateUniqueId = () => { return Date.now().toString(36) + Math.random().toString(36).substring(4); }; -export const showToast = (toastProps: Omit) => { +export const toast = (toastProps: Omit) => { const id = generateUniqueId(); eventManager.emit(ToastEvent.Add, { ...toastProps, toastId: id }); }; From 020f7737a18147bc9b384b79f8aaa778380d4f33 Mon Sep 17 00:00:00 2001 From: Sumin Date: Thu, 7 Dec 2023 03:14:04 +0900 Subject: [PATCH 16/21] =?UTF-8?q?refactor:=20toast.type(text)=20=ED=98=95?= =?UTF-8?q?=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EC=B6=9C=EB=A0=A5=ED=95=A0=20?= =?UTF-8?q?=EC=88=98=20=EC=9E=88=EA=B2=8C=20=ED=95=A8=EC=88=98=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/foundation/Toast/toast.ts | 26 ++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/FE/src/components/foundation/Toast/toast.ts b/FE/src/components/foundation/Toast/toast.ts index f5e5ad4..fe42de9 100644 --- a/FE/src/components/foundation/Toast/toast.ts +++ b/FE/src/components/foundation/Toast/toast.ts @@ -1,10 +1,30 @@ -import { ToastEvent, ToastProps } from '@foundation/Toast/type'; +import { ToastEvent, ToastProps, ToastType } from '@foundation/Toast/type'; import { eventManager } from '@foundation/Toast/eventManger'; +type ToastFunctionProps = Omit; +type ToastOptions = Omit; const generateUniqueId = () => { return Date.now().toString(36) + Math.random().toString(36).substring(4); }; -export const toast = (toastProps: Omit) => { + +const emitToast = (type: ToastType, toastProps: ToastFunctionProps) => { const id = generateUniqueId(); - eventManager.emit(ToastEvent.Add, { ...toastProps, toastId: id }); + eventManager.emit(ToastEvent.Add, { + ...toastProps, + toastId: id, + type, + }); +}; + +export const toast = { + default: (text: string, toastOptions: ToastOptions) => + emitToast('default', { text: text, ...toastOptions }), + info: (text: string, toastOptions: ToastOptions) => + emitToast('info', { text: text, ...toastOptions }), + success: (text: string, toastOptions: ToastOptions) => + emitToast('success', { text: text, ...toastOptions }), + warning: (text: string, toastOptions: ToastOptions) => + emitToast('warning', { text: text, ...toastOptions }), + error: (text: string, toastOptions: ToastOptions) => + emitToast('error', { text: text, ...toastOptions }), }; From c37e73d2885f4cadbafb774a07a10321897f8d4a Mon Sep 17 00:00:00 2001 From: Sumin Date: Thu, 7 Dec 2023 03:14:20 +0900 Subject: [PATCH 17/21] =?UTF-8?q?design:=20=ED=86=A0=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=EC=97=90=20=EB=94=B0=EB=9D=BC=EC=84=9C=20?= =?UTF-8?q?=EB=8B=A4=EB=A5=B8=20=ED=94=84=EB=A1=9C=EA=B7=B8=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=EB=B0=94=20=EC=83=89=EC=83=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../foundation/Toast/Toast.styles.ts | 23 +++++++--- .../components/foundation/Toast/ToastItem.tsx | 45 ++++++++++--------- FE/src/components/foundation/Toast/type.ts | 5 ++- FE/src/styles/_colors.ts | 18 ++++++++ 4 files changed, 61 insertions(+), 30 deletions(-) diff --git a/FE/src/components/foundation/Toast/Toast.styles.ts b/FE/src/components/foundation/Toast/Toast.styles.ts index dd8da2c..6e2fcc9 100644 --- a/FE/src/components/foundation/Toast/Toast.styles.ts +++ b/FE/src/components/foundation/Toast/Toast.styles.ts @@ -1,12 +1,23 @@ import { css, keyframes } from '@emotion/react'; +import { theme } from '@styles/theme'; const TOAST_GAP = '0.75rem'; -export const ToastTypeStyle = { - info: css``, - success: css``, - warning: css``, - error: css``, - default: css``, +export const ToastProgressBarStyle = { + info: css` + background-color: ${theme.colors.toast.info}; + `, + success: css` + background-color: ${theme.colors.toast.success}; + `, + warning: css` + background-color: ${theme.colors.toast.warning}; + `, + error: css` + background-color: ${theme.colors.toast.error}; + `, + default: css` + background-color: ${theme.colors.toast.default}; + `, }; export const ToastTypeIconName = { diff --git a/FE/src/components/foundation/Toast/ToastItem.tsx b/FE/src/components/foundation/Toast/ToastItem.tsx index 47af32c..f05e267 100644 --- a/FE/src/components/foundation/Toast/ToastItem.tsx +++ b/FE/src/components/foundation/Toast/ToastItem.tsx @@ -1,9 +1,3 @@ -import { - ToastFadeOutUpAnimation, - ToastProgressBarAnimation, - ToastTypeIconName, - ToastTypeStyle, -} from '@foundation/Toast/Toast.styles'; import { useCallback, useRef, useState } from 'react'; import { ToastEvent, ToastProps } from '@foundation/Toast/type'; import { css } from '@emotion/react'; @@ -12,6 +6,12 @@ import { Box, Icon } from '@foundation/index'; import { collapseToast } from '@foundation/Toast/collapseToast'; import useAnimationEnd from '@hooks/useAnimationEnd'; import { eventManager } from '@foundation/Toast/eventManger'; +import { + ToastFadeOutUpAnimation, + ToastProgressBarAnimation, + ToastProgressBarStyle, + ToastTypeIconName, +} from '@foundation/Toast/Toast.styles'; const ToastItem: React.FC = ({ toastId, @@ -82,7 +82,6 @@ const ToastItem: React.FC = ({ ` : 'none'}; `, - ToastTypeStyle[type], ]} >
= ({
diff --git a/FE/src/components/foundation/Toast/type.ts b/FE/src/components/foundation/Toast/type.ts index 0feb423..85cfe80 100644 --- a/FE/src/components/foundation/Toast/type.ts +++ b/FE/src/components/foundation/Toast/type.ts @@ -1,15 +1,16 @@ import { ToastPositionStyle, - ToastTypeStyle, + ToastProgressBarStyle, } from '@foundation/Toast/Toast.styles'; export type ToastPosition = keyof typeof ToastPositionStyle; +export type ToastType = keyof typeof ToastProgressBarStyle; export type ToastProps = { toastId: string; text: string; autoClose?: false | number; closeOnClick?: boolean; - type?: keyof typeof ToastTypeStyle; + type?: ToastType; pauseOnHover?: boolean; position?: ToastPosition; }; diff --git a/FE/src/styles/_colors.ts b/FE/src/styles/_colors.ts index d097515..6cbab46 100644 --- a/FE/src/styles/_colors.ts +++ b/FE/src/styles/_colors.ts @@ -56,6 +56,17 @@ const colorChips = { red700: '#98382C', red800: '#742B22', red900: '#511E17', + + yellow50: '#F9EFCB', + yellow100: '#F7EABC', + yellow200: '#F4E19E', + yellow300: '#F1D881', + yellow400: '#EDCF63', + yellow500: '#EAC645', + yellow600: '#C5A63A', + yellow700: '#9F872F', + yellow800: '#7A6724', + yellow900: '#544719', } as const; export const colors = { @@ -103,4 +114,11 @@ export const colors = { backdrop: { default: colorChips.shadow400, }, + toast: { + info: colorChips.blue500, + success: colorChips.green500, + warning: colorChips.yellow500, + error: colorChips.red500, + default: colorChips.blue500, + }, }; From 3ada210838c5b43379794a4520e94d99ca01aff1 Mon Sep 17 00:00:00 2001 From: Sumin Date: Thu, 7 Dec 2023 10:40:36 +0900 Subject: [PATCH 18/21] =?UTF-8?q?chore:=20=ED=86=A0=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=98=B5=EC=85=98=EC=9D=98=20=ED=83=80=EC=9E=85=EC=9D=84=20?= =?UTF-8?q?=EC=98=B5=EC=85=94=EB=84=90=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/foundation/Toast/toast.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/FE/src/components/foundation/Toast/toast.ts b/FE/src/components/foundation/Toast/toast.ts index fe42de9..8672b62 100644 --- a/FE/src/components/foundation/Toast/toast.ts +++ b/FE/src/components/foundation/Toast/toast.ts @@ -17,14 +17,14 @@ const emitToast = (type: ToastType, toastProps: ToastFunctionProps) => { }; export const toast = { - default: (text: string, toastOptions: ToastOptions) => + default: (text: string, toastOptions?: ToastOptions) => emitToast('default', { text: text, ...toastOptions }), - info: (text: string, toastOptions: ToastOptions) => + info: (text: string, toastOptions?: ToastOptions) => emitToast('info', { text: text, ...toastOptions }), - success: (text: string, toastOptions: ToastOptions) => + success: (text: string, toastOptions?: ToastOptions) => emitToast('success', { text: text, ...toastOptions }), - warning: (text: string, toastOptions: ToastOptions) => + warning: (text: string, toastOptions?: ToastOptions) => emitToast('warning', { text: text, ...toastOptions }), - error: (text: string, toastOptions: ToastOptions) => + error: (text: string, toastOptions?: ToastOptions) => emitToast('error', { text: text, ...toastOptions }), }; From 5a453711eaec2f4a90a14e42bb215483c205fb2c Mon Sep 17 00:00:00 2001 From: Sumin Date: Thu, 7 Dec 2023 13:57:42 +0900 Subject: [PATCH 19/21] =?UTF-8?q?refactor:=20useAnimationEnd=20=ED=9B=85?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0=ED=95=98=EA=B3=A0=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=EC=97=90=20=EC=A7=81=EC=A0=91=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=EB=84=88=20=EB=8B=AC=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit react는 바닐라가 아니야!!! --- .../components/foundation/Toast/ToastItem.tsx | 12 ++--------- FE/src/hooks/useAnimationEnd.ts | 20 ------------------- 2 files changed, 2 insertions(+), 30 deletions(-) delete mode 100644 FE/src/hooks/useAnimationEnd.ts diff --git a/FE/src/components/foundation/Toast/ToastItem.tsx b/FE/src/components/foundation/Toast/ToastItem.tsx index f05e267..95dac60 100644 --- a/FE/src/components/foundation/Toast/ToastItem.tsx +++ b/FE/src/components/foundation/Toast/ToastItem.tsx @@ -4,7 +4,6 @@ import { css } from '@emotion/react'; import { theme } from '@styles/theme'; import { Box, Icon } from '@foundation/index'; import { collapseToast } from '@foundation/Toast/collapseToast'; -import useAnimationEnd from '@hooks/useAnimationEnd'; import { eventManager } from '@foundation/Toast/eventManger'; import { ToastFadeOutUpAnimation, @@ -22,7 +21,6 @@ const ToastItem: React.FC = ({ pauseOnHover = true, }) => { const toastRef = useRef(null); - const progressRef = useRef(null); const [isExiting, setIsExiting] = useState(false); const [isPaused, setIsPaused] = useState(false); @@ -34,13 +32,6 @@ const ToastItem: React.FC = ({ }); }, [toastId]); - useAnimationEnd(toastRef, onExitingAnimationEnd, [toastId]); - - useAnimationEnd(progressRef, () => autoClose && onProgressAnimationEnd, [ - autoClose, - onProgressAnimationEnd, - ]); - const handleClick = () => { closeOnClick && onProgressAnimationEnd(); }; @@ -66,6 +57,7 @@ const ToastItem: React.FC = ({ onClick={handleClick} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} + onAnimationEnd={onExitingAnimationEnd} css={[ css` position: relative; @@ -96,7 +88,7 @@ const ToastItem: React.FC = ({ {text}
autoClose && onProgressAnimationEnd} css={[ css` position: absolute; diff --git a/FE/src/hooks/useAnimationEnd.ts b/FE/src/hooks/useAnimationEnd.ts deleted file mode 100644 index e758fc3..0000000 --- a/FE/src/hooks/useAnimationEnd.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { RefObject, useEffect } from 'react'; - -const useAnimationEnd = ( - nodeRef: RefObject, - onEndCallback: () => void, - deps: unknown[] = [] -) => { - useEffect(() => { - const node = nodeRef.current; - if (node) { - node.addEventListener('animationend', onEndCallback); - - return () => { - node.removeEventListener('animationend', onEndCallback); - }; - } - }, [nodeRef, onEndCallback, ...deps]); -}; - -export default useAnimationEnd; From cfc0ed69441a46c82abbfcbf8a730142a27c3d67 Mon Sep 17 00:00:00 2001 From: Sumin Date: Thu, 7 Dec 2023 14:01:15 +0900 Subject: [PATCH 20/21] =?UTF-8?q?rename:=20=ED=95=B8=EB=93=A4=EB=9F=AC=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/foundation/Toast/ToastItem.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/FE/src/components/foundation/Toast/ToastItem.tsx b/FE/src/components/foundation/Toast/ToastItem.tsx index 95dac60..4e9f832 100644 --- a/FE/src/components/foundation/Toast/ToastItem.tsx +++ b/FE/src/components/foundation/Toast/ToastItem.tsx @@ -1,4 +1,4 @@ -import { useCallback, useRef, useState } from 'react'; +import { useRef, useState } from 'react'; import { ToastEvent, ToastProps } from '@foundation/Toast/type'; import { css } from '@emotion/react'; import { theme } from '@styles/theme'; @@ -24,16 +24,18 @@ const ToastItem: React.FC = ({ const [isExiting, setIsExiting] = useState(false); const [isPaused, setIsPaused] = useState(false); - const onProgressAnimationEnd = useCallback(() => setIsExiting(true), []); - - const onExitingAnimationEnd = useCallback(() => { + const handleExitingAnimationEnd = () => { collapseToast(toastRef.current!, () => { eventManager.emit(ToastEvent.Delete, toastId); }); - }, [toastId]); + }; + + const handleProgressAnimationEnd = () => { + autoClose && setIsExiting(true); + }; const handleClick = () => { - closeOnClick && onProgressAnimationEnd(); + closeOnClick && handleProgressAnimationEnd(); }; const handleMouseEnter = () => { @@ -57,7 +59,7 @@ const ToastItem: React.FC = ({ onClick={handleClick} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} - onAnimationEnd={onExitingAnimationEnd} + onAnimationEnd={handleExitingAnimationEnd} css={[ css` position: relative; @@ -88,7 +90,7 @@ const ToastItem: React.FC = ({ {text}
autoClose && onProgressAnimationEnd} + onAnimationEnd={handleProgressAnimationEnd} css={[ css` position: absolute; From 98537869e292d71c4279a5baad7461b17290c342 Mon Sep 17 00:00:00 2001 From: Sumin Date: Thu, 7 Dec 2023 16:37:15 +0900 Subject: [PATCH 21/21] =?UTF-8?q?design:=20=ED=86=A0=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=95=A0=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=20=EC=A1=B0?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/foundation/Toast/collapseToast.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/FE/src/components/foundation/Toast/collapseToast.ts b/FE/src/components/foundation/Toast/collapseToast.ts index 118aff3..c4ca689 100644 --- a/FE/src/components/foundation/Toast/collapseToast.ts +++ b/FE/src/components/foundation/Toast/collapseToast.ts @@ -1,19 +1,16 @@ export function collapseToast( node: HTMLDivElement, done: () => void, - duration = 500 + duration = 1000 ) { const { scrollHeight, style } = node; requestAnimationFrame(() => { - style.minHeight = 'initial'; style.height = scrollHeight + 'px'; style.transition = `all ${duration}ms`; requestAnimationFrame(() => { style.height = '0'; - style.padding = '0'; - style.margin = '0'; setTimeout(done, duration); }); });