-
Toast 만들기Language/React 2024. 7. 9. 11:07
iOS에서 포그라운드 푸시메시지를 수신하는 화면을 React로 보여주는 기능을 넣어야 한다.작업방향은 Toast 형식으로 먼저 만들고 푸시수신되는 애니메이션 효과를 주면 어떨까 해서 만드는 도중에 토스트 메시지는 어느정도 완성 되었는데 푸시 수신 애니메이션을 넣으려고 하니 토스트 형태 소스에서 많이 달라질듯 하여
토스트까지만 작업한 부분을 정리해 두려고 한다.
테스트용 소스가 많이 들어가 있으니 지저분 하지만 여기까이 정리하자!
작업 소스
_app.jsx
import NFToastProvider from '../components/common/NFToastProvider'; function App({ Component, pageProps }) { return ( <> ... <NFToastProvider /> ... </> ); } export default App;
common.module.css
.fixed-top { position: fixed; top: 0px; width: 100%; z-index: 99999; } .toast-items { display: flex; flex-direction: column; gap: 10px; } .item { /* animation: down-001 0.7s ease-in-out infinite alternate; */ } @keyframes down-001 { 0% { transform: translate3d(0, 0, 0); } 100% { transform: translate3d(0, 24px, 0); } } /* @-webkit-keyframes down-001 { 0% { transform: translate3d(0, 0, 0); } 100% { transform: translate3d(0, 17px, 0); } } @-moz-keyframes down-001 { 0% { transform: translate3d(0, 0, 0); } 100% { transform: translate3d(0, 17px, 0); } } */ .toast-items .item { display: flex; align-items: flex-start; /* width: 377px; */ width: 720px; padding: 14px 14px 12px 14px; border-radius: 24px; background: var(--Materials-Thin, linear-gradient(0deg, #333 0%, #333 100%), rgba(166, 166, 166, 0.70)); /* background: #CCC; */ gap: 10px; background-blend-mode: color-dodge, normal; backdrop-filter: blur(75px); } .toast-items .item .icon { width: 38px; height: 38px; flex-shrink: 0; fill: var(--color-black-and-white-white, #FFF); } .toast-items .item .text-group { display: flex; flex-direction: column; align-items: flex-start; flex: 1 0 0; align-self: stretch; } .toast-items .item .text-group > .title-group { display: flex; align-items: flex-start; gap: 16px; align-self: stretch; } .toast-items .item .text-group > .title-group > .title { color: var(--Labels-Primary, #000); font-weight: 600; font-size: 15px; line-height: 20px; /* 133.333% */ font-family: "SF Pro Text"; letter-spacing: -0.4px; flex: 1 0 0; font-feature-settings: 'clig' off, 'liga' off; font-style: normal; } .toast-items .item .text-group > .title-group > .time { color: var(--Labels-Vibrant-Secondary, rgba(127, 127, 127, 0.50)); font-weight: 400; font-size: 13px; line-height: 20px; /* 153.846% */ font-family: "SF Pro Text"; text-align: right; font-feature-settings: 'clig' off, 'liga' off; font-style: normal; } .toast-items .item .text-group > .message { color: var(--Labels-Primary, #000); font-weight: 400; font-size: 15px; line-height: 20px; /* 133.333% */ font-family: "SF Pro Text"; letter-spacing: -0.4px; align-self: stretch; font-feature-settings: 'clig' off, 'liga' off; font-style: normal; }
NFToastProvider.tsx
import React from 'react'; import NFToast from './NFToast'; import { useToastStore } from 'store/ToastStore'; import classNames from 'classnames/bind'; import commonStyles from '../../styles/common.module.css'; const cxCommon = classNames.bind(commonStyles); const Toaster = () => { const { toasts } = useToastStore(); return ( <div className={cxCommon('fixed-top')}> <div className={cxCommon('toast-items')}> {toasts?.map((toast: any, index: number) => ( <NFToast key={toast.id} {...toast} /> ))} </div> </div> ); }; export default Toaster;
NFToast.tsx
import React, { useEffect, useState } from 'react'; import { useToastStore } from 'store/ToastStore'; import { ReactComponent as GobangIcon } from '/public/images/icon_notification_app_icon_24.svg'; import classNames from 'classnames/bind'; import commonStyles from '../../styles/common.module.css'; const cxCommon = classNames.bind(commonStyles); const TOAST_DURATION = 0 + 3000; const ANIMATION_DURATION = 350; interface NFToastProps { id: string; title: string; message: string; // type: ToastType; } const NFToast = ({ title, id, message /* , type */ }: NFToastProps) => { const { removeToast } = useToastStore(); const [opacity, setOpacity] = useState(0.2); useEffect(() => { setOpacity(1); const timeoutForRemove = setTimeout(() => { removeToast(id); }, TOAST_DURATION); const timeoutForVisible = setTimeout(() => { setOpacity(0); }, TOAST_DURATION - ANIMATION_DURATION); return () => { clearTimeout(timeoutForRemove); clearTimeout(timeoutForVisible); }; }, [id, removeToast]); return ( <div style={{ opacity: opacity, transition: 'all 0.35s ease-in-out', transform: `translateY(${opacity === 0 ? '-10px' : '10px'})`, }} className={cxCommon('item')} > <span className={cxCommon('icon')}> <GobangIcon /> </span> <div className={cxCommon('text-group')}> <span className={cxCommon('title-group')}> <span className={cxCommon('title')}>{title}</span> <span className={cxCommon('time')}>9:41 AM</span> </span> <span className={cxCommon('message')}>{message}</span> </div> </div> ); }; export default React.memo(NFToast);
ToastStore.tsx
import { create } from 'zustand'; type ToastType = { id: string; title: string; message: string; }; interface ToastStore { toasts: ToastType[] | null; setToasts: (toasts: ToastType[]) => void; addToast: (toast: ToastType) => void; removeToast: (id: string) => void; } export const useToastStore = create<ToastStore>((set) => ({ toasts: null, // toasts: [ // { id: '111', title: 'success', message: 'success message' }, // { id: '222', title: 'error', message: 'error message' }, // { id: '333', title: 'info', message: 'info message' }, // ], // setToasts: (toasts) => set((state) => ({ ...state, toasts: toasts })), setToasts: (toasts) => set({ toasts }), // 직접 toasts를 설정하도록 변경 addToast: (toast) => set((state) => ({ // toasts: state.toasts ? [...state.toasts, toast] : [toast], toasts: state.toasts ? [toast, ...state.toasts] : [toast], // toasts: state.toasts ? [toast].concat(...state.toasts) : [toast], })), removeToast: (id) => set((state) => ({ toasts: state.toasts ? state.toasts.filter((toast) => toast.id !== id) : [], })), }));
commonHook.tsx
... export const useToast = () => { const { addToast } = useToastStore(); return { toast: { success: () => { addToast({ id: uuidv4(), title: 'success', message: 'success message' }); }, info: () => { addToast({ id: uuidv4(), title: 'info', message: 'info message' }); }, error: () => { addToast({ id: uuidv4(), title: 'error', message: 'error message' }); }, }, }; }; ...
Test.tsx
import { useToast } from '../../hook/commonHook'; const Test = () => { const { toast } = useToast(); useEffect(() => { ... setTimeout(() => { toast.error({ title: 'error', message: 'error message' }); }, 300); }, []); return ( <> ... <div> <button type="button" onClick={() => toast.success({ title: 'success', message: 'success message' })}> success message </button> {/* <button type="button" onClick={() => toast.warning('messageText warning')}> warning message </button> */} <button type="button" onClick={() => toast.info({ title: 'info', message: 'info message' })}> info message </button> <button type="button" onClick={() => toast.error({ title: 'error', message: 'error message' })}> error message </button> </div> ... </> ); }; export default Test;
정리안된 소스라도 나중에 필요할 수 있으니!!
참고)
'Language > React' 카테고리의 다른 글
Promise 동시에 여러번 호출 (0) 2024.10.15 push Toast 메시지 만들기 (0) 2024.07.09 nextJS 사용하는 iOS 단말기에서 뒤로가기 페이지 흰색 이슈 (0) 2024.06.20 nextjs에서 svg 컴포넌트로 사용하는 방법 (0) 2024.04.04 react-hook-form typescript로 변환 및 컴포넌트 생성 (antd) (0) 2023.12.27