ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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 });
          // },
        },
      };
    };

     

    KakaoTalk_Video_2024-07-09-14-42-47.mp4
    2.02MB

     

     

     

     

    댓글

Designed by Tistory.