-
push Toast 메시지 만들기Language/React 2024. 7. 9. 14:43
iOS 포그라운드 상태에서 푸시를 수신 후 React에서 푸시메시지를 토스트 형태로 표시하는 작업에 대한 정리이다.
소스코드
css
.push-toast-wrap { position: fixed; top: 0px; width: 100%; z-index: 99999; } .push-toast-wrap .toast-items { display: flex; flex-direction: column; gap: 10px; } .push-toast-wrap .toast-items .item { display: flex; align-items: flex-start; /* width: 344px; */ width: 720px; padding: 14px 14px 12px 14px; border-radius: 24px; background: linear-gradient(0deg, #333 0%, #333 100%), rgba(245, 245, 245, 0.45); gap: 10px; background-blend-mode: screen, normal; box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, 0.15); backdrop-filter: blur(75px); } .push-toast-wrap .toast-items .item.show { /* animation: toast-show-001 0.7s ease-in-out infinite alternate; */ animation: toast-show-001 0.5s ease-in-out; transform: translate3d(0, 15px, 0); } .push-toast-wrap .toast-items .item.hide { animation: toast-hide-001 0.5s ease-in-out; transform: translate3d(0, -15px, 0); } @keyframes toast-show-001 { 0% { transform: translate3d(0, 0, 0); } 100% { transform: translate3d(0, 15px, 0); } } @-webkit-keyframes toast-show-001 { 0% { transform: translate3d(0, 0, 0); } 100% { transform: translate3d(0, 15px, 0); } } @-moz-keyframes toast-show-001 { 0% { transform: translate3d(0, 0, 0); } 100% { transform: translate3d(0, 15px, 0); } } @keyframes toast-hide-001 { 0% { transform: translate3d(0, 15px, 0); } 100% { transform: translate3d(0, -1000px, 0); } } @-webkit-keyframes toast-hide-001 { 0% { transform: translate3d(0, 15px, 0); } 100% { transform: translate3d(0, -1000px, 0); } } @-moz-keyframes toast-hide-001 { 0% { transform: translate3d(0, 15px, 0); } 100% { transform: translate3d(0, -1000px, 0); } } .push-toast-wrap .toast-items .item .icon { width: 38px; height: 38px; flex-shrink: 0; fill: var(--color-black-and-white-white, #FFF); } .push-toast-wrap .toast-items .item .text-group { display: flex; flex-direction: column; align-items: flex-start; flex: 1 0 0; align-self: stretch; } .push-toast-wrap .toast-items .item .text-group > .title-group { display: flex; align-items: flex-start; gap: 16px; align-self: stretch; } .push-toast-wrap .toast-items .item .text-group > .title-group > .title { color: var(--Labels-Primary, #000); font-weight: 600; font-size: 14px; 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; } .push-toast-wrap .toast-items .item .text-group > .title-group > .time { color: var(--Labels-Vibrant-Secondary, rgba(127, 127, 127, 0.50)); font-weight: 400; font-size: 12px; line-height: 20px; /* 153.846% */ font-family: "SF Pro Text"; text-align: right; font-feature-settings: 'clig' off, 'liga' off; font-style: normal; } .push-toast-wrap .toast-items .item .text-group > .message { color: var(--Labels-Primary, #000); font-weight: 400; font-size: 14px; 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; }
_app.jsx
import { useCallback, useEffect, useRef, useState } from 'react'; import NFPushToastProvider from '../components/common/NFPushToastProvider'; function App({ Component, pageProps }) { return ( <> ... <NFPushToastProvider /> ... </> ); } export default App;
PushStore.tsx
import { create } from 'zustand'; type PushType = { id: string; title: string; message: string; click?: () => void; }; interface PushStore { pushs: PushType[] | null; setPushs: (pushs: PushType[]) => void; addPush: (push: PushType) => void; removePush: (id: string) => void; } export const usePushStore = create<PushStore>((set) => ({ pushs: null, // pushs: [ // { id: '111', title: 'success', message: 'success message' }, // { id: '222', title: 'error', message: 'error message' }, // { id: '333', title: 'info', message: 'info message' }, // ], // setPushs: (pushs) => set((state) => ({ ...state, pushs: pushs })), setPushs: (pushs) => set({ pushs }), // 직접 pushs를 설정하도록 변경 addPush: (push) => set((state) => ({ // pushs: state.pushs ? [...state.pushs, push] : [push], // pushs: state.pushs ? [push].concat(...state.pushs) : [push], pushs: state.pushs ? [push, ...state.pushs] : [push], })), removePush: (id) => set((state) => ({ pushs: state.pushs ? state.pushs.filter((push) => push.id !== id) : [], })), }));
NFPushToast.tsx
import React, { useEffect, useState } from 'react'; import { usePushStore } from 'store/PushStore'; 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 NFPushToastProps { id: string; title: string; message: string; click?: () => void; } const NFPushToast = ({ title, id, message, ...props }: NFPushToastProps) => { const { removePush } = usePushStore(); const [isShow, setIsShow] = useState(true); useEffect(() => { setIsShow(true); const timeoutForRemove = setTimeout(() => { removePush(id); }, TOAST_DURATION); const timeoutForVisible = setTimeout(() => { setIsShow(false); }, TOAST_DURATION - ANIMATION_DURATION); return () => { clearTimeout(timeoutForRemove); clearTimeout(timeoutForVisible); }; }, [id, removePush]); return ( <div className={cxCommon('item', { show: isShow, hide: !isShow })} onClick={props.click}> <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(NFPushToast);
NFPushToastProvider.tsx
import React from 'react'; import NFPushToast from './NFPushToast'; import { usePushStore } from 'store/PushStore'; import classNames from 'classnames/bind'; import commonStyles from '../../styles/common.module.css'; const cxCommon = classNames.bind(commonStyles); const Toaster = () => { const { pushs } = usePushStore(); return ( <div className={cxCommon('push-toast-wrap')}> <div className={cxCommon('toast-items')}> {pushs?.map((push: any, index: number) => ( <NFPushToast key={push.id} {...push} /> ))} </div> </div> ); }; export default Toaster;
commonHook.tsx
error, info, warning, success등 나중에라도 추가하기 구멍함수를 남겨둔다.
export const usePushToast = () => { const { setPushs } = usePushStore(); const id = uuidv4(); return { pushToast: { info: ({ title, message, click }) => { // addPush({ id: id, title: title || 'info', message: message, click: click }); setPushs([{ id: id, title: title || 'info', message: message, click: click }]); }, // success: ({ title, message, click }) => { // addPush({ id: id, title: title || 'success', message: message, click: click }); // }, // error: ({ title, message, click }) => { // addPush({ id: id, title: title || 'error', message: message, click: click }); // }, }, }; };
'Language > React' 카테고리의 다른 글
Swiper - Cannot read properties of undefined (reading 'autoplay') (1) 2024.11.07 Promise 동시에 여러번 호출 (0) 2024.10.15 Toast 만들기 (0) 2024.07.09 nextJS 사용하는 iOS 단말기에서 뒤로가기 페이지 흰색 이슈 (0) 2024.06.20 nextjs에서 svg 컴포넌트로 사용하는 방법 (0) 2024.04.04