어쩌다 알게 된 ƪ(•̃͡•̃͡ ƪ

팝업 컴포넌트 5차 (position 개선_유니온 / enum 방법) 본문

개발/🔵 React-TypeScript

팝업 컴포넌트 5차 (position 개선_유니온 / enum 방법)

비니_ 2025. 9. 19. 15:55
728x90

 

 

 

팝업 컴포넌트 4차 (외부 감지 + 버튼에 토글 동작 겹치는 이슈 해결하기)

{isOpen && ( setI" data-og-host="dazzle-bini.tistory.com" data-og-source-url="https://dazzle-bini.tistory.com/287" data-og-url="https://dazzle-bini.tis" data-og-host="dazzle-bini.tistory.com" data-og-source-url="https://dazzl" data-og-host="dazzle-bini.tis

dazzle-bini.tistory.com

 

 

+ position 여러가지 쓸 수 있게 개선

"use client";

import React, { ReactNode, useEffect, useRef } from "react";
import { Button } from "./Buttons";

export enum PopupPosition { 💛 // 2번 방법 enum으로 만들어서 관리
    Top = 'top',
    Bottom = 'bottom',
    Left = 'left',
    Right = 'right',
    Center = 'center'
}

interface ButtonProps{
    text: string;
    className?: string;
    onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
}

interface DefaultPopupProps{
    text: string;
    isOpen: boolean;
    setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
    content: ReactNode;
    buttons?: ButtonProps[];
    // position?: 'top' | 'right' | 'bottom' | 'left' | 'center'; 💛 // 1번 방법: pos 유니온 값 주기
    position?: PopupPosition; 💛 // 2번 방법 enum으로 만들어서 관리
}

const DefaultPopup = ({
    text,
    isOpen,
    setIsOpen,
    content,
    buttons,
    // position = 'center' 💛 // 1번 방법
    position = PopupPosition.Center, 💛 // 2번 방법 enum으로 만들어서 관리
}: DefaultPopupProps) => {

    const outsideRef = useRef<HTMLDivElement>(null);
    useEffect(() => {
        // 외부 클릭시
        const handleClickOutside = (e: MouseEvent) => {
            const target = e.target as HTMLElement;
            if(target.closest(".btn_pop")) return; // 특정 버튼 제외
            if(outsideRef.current && !outsideRef.current.contains(e.target as Node)){
                setIsOpen(false);
            }
        }

        // esc 눌렀을 시
        const handleEsc = (e: KeyboardEvent) => {
            console.log(e.key);
            console.log(e.code);
            if(e.key === "Escape"){
                setIsOpen(false);
            }
        }

        document.addEventListener('mousedown', handleClickOutside);
        document.addEventListener('keydown', handleEsc);
        return() => {
            document.removeEventListener('mousedown', handleClickOutside);
            document.removeEventListener('keydown', handleEsc);
        }
    }, [setIsOpen]);

    return (
        <div className={`
            btn_pop_wrap
            ${position ? position.toLowerCase() : ''} 💛
        `}>
            <Button
                text={text}
                onClick={() => {
                    setIsOpen(prev => !prev)
                }}
                className="btn_pop"
            />
            {isOpen && (
                <div className="pop_wrap" ref={outsideRef}>
                    <div className="pop_header">
                        <Button
                            text="X"
                            onClick={() => setIsOpen(false)}
                        />
                    </div>
                    <div className="pop_content">
                        {content}
                    </div>
                    {buttons && buttons.length > 0 && (
                        <div className="pop_footer">
                            {buttons.map((v, index) => (
                                <Button
                                    key={index}
                                    className={v.className}
                                    text={v.text}
                                    onClick={v.onClick}
                                />
                            ))}
                        </div>
                    )}
                </div>
            )}
        </div>
    )
}

export { DefaultPopup };

 

💻 1번 방법: 유니온 타입 (props)

✔ position?: 'top' | 'right' | 'bottom' | 'left' | 'center'; 

=> props 의 interface 타입에 적어두면 저 안의 값만 들어올 수 있음

 

💻 2번 방법: enum 방법

✔ 해당 enum 생성 

export enum PopupPosition {
    Top = 'top',
    Bottom = 'bottom',
    Left = 'left',
    Right = 'right',
    Center = 'center'
}

  interface 타입에 position?: PopupPosition; 으로 지정

  함수 props 기본값 지정하려면 ex) position = PopupPosition.Center

 

 <div className={`
            btn_pop_wrap
            ${position ? position.toLowerCase() : ''}
    `}>

=> enum 값이 클래스로 들어오게 하기
=> 나중에 enum만 수정해주면 알아서 class도 생기기에 재사용성 좋음

 

 

💻 사용 방법

<DefaultPopup
    text="버튼 하단 배치"
    isOpen={isOpen2}
    setIsOpen={setIsOpen2}
    content='내용입니다.'
    buttons={[
        {
            text: '취소',
            className:'red',
            onClick:() => setIsOpen2(false)
        },
        {
            text: '확인',
            className:'green',
            onClick:() => setIsOpen2(false)
        }
    ]}
    position={PopupPosition.Bottom} // position props 안쓰면 center이 나오는 컴포넌트
/>

 

 

** css 참고

/* 버튼 */
.btn{
  padding:10px;
  border-radius:8px;
  background:#ccc;
  font-weight:bold;
  cursor:pointer;
}

.btn.blue{
  background:#9bafe7;
}

.btn.red{
  background:#f38db7;
}

.btn.green{
  background:#79cc84;
}

/* 팝업 */
.btn_pop_wrap{
  position:relative;
}

.btn_pop_wrap .btn{
  display:inline-block;
}

.pop_wrap{
  position:fixed;
  left:50%;
  top:50%;
  min-width:350px;
  border:1px solid #e1e1e1;
  border-radius:20px;
  background:#fefefe;
  transform:translate(-50%, -50%);
}

.bottom .pop_wrap{
  position:absolute;
  left:0;
  top:unset;
  transform:unset;
}

.pop_wrap .pop_header{
  padding:10px;
  text-align:right;
}

.pop_wrap .pop_content{
  overflow-y:auto;
  max-height:500px;
  padding:15px;
}

.pop_wrap .pop_content .tit{
  font-size:20px;
  font-weight:bold;
  text-align:center;
}

.pop_wrap .pop_content .desc{
  font-size:15px;
}

.pop_wrap .pop_footer{
  display:flex;
  justify-content:flex-end;
  gap:10px;
  padding:10px;
}

 

 

 

 

728x90
Comments