React์—์„œ ๋งŽ์€ Modal์„ "์ž˜" ๊ด€๋ฆฌํ•ด ๋ณด์ž

์ „์—ญ ์ƒํƒœ๋ฅผ ์ด์šฉํ•ด์„œ React Modal์„ ๋” ์ž˜ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•์„ ์†Œ๊ฐœํ•ฉ๋‹ˆ๋‹ค.

Profile Picture
Yoon-Hae-Min
2023-12-06

์ด ๊ฒŒ์‹œ๊ธ€์˜ ์›๋ณธ ๋ธ”๋กœ๊ทธ์ž…๋‹ˆ๋‹ค.

์„œ๋ก 

ํ”„๋กœ์ ํŠธ๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ์ ์  ๋” ๋งŽ์€ modal์ด ์ƒ๊ฒจ๋‚˜๊ธฐ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ์กด์— ์กด์žฌํ–ˆ๋˜ 10๊ฐœ ํŒŒ์ผ์ด ์žˆ์—ˆ๊ณ  ์ถ”๊ฐ€๋กœ ์‚ฌ์šฉํ•˜๋Š” Modal๊นŒ์ง€ ๋„ฃ๋Š”๋‹ค๋ฉด ๋งค์šฐ ๋งŽ์€ modal์ด ํ”„๋กœ์ ํŠธ ๋‚ด์— ์‚ฌ์šฉ๋˜๊ณ  ์žˆ๋Š” ๊ฒƒ์ด์—ˆ์ฃ .

๋ณดํ†ต modal์„ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ํ•˜๋‚˜์˜ ํŽ˜์ด์ง€ ๋‚ด๋ถ€์— state๋กœ modal์˜ on off๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์‹์œผ๋กœ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ์ €ํฌ ํ”„๋กœ์ ํŠธ ๋˜ํ•œ ์ด๋ ‡๊ฒŒ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

const [isOpen,setIsOpen] = useState(false);

const closeModal = ()=>{
	setIsOpen(false);
}

const openModal = ()=>{
	setIsOpen(true)
}

return (
<Modal1/>
<Modal2/>
...
)

ํ•˜์ง€๋งŒ ์ด ํŽ˜์ด์ง€๋‚˜ ์ปดํฌ๋„ŒํŠธ ์•ˆ์— ์žˆ๋Š” modal ๋‘˜์€ ์•ฝ๊ฐ„์˜ ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š”๋ฐ์š”. ์ปดํฌ๋„ŒํŠธ๋‚˜ ํŽ˜์ด์ง€์— ์ข…์†์ ์ด๋‹ค ๋ณด๋‹ˆ modal์˜ ๋ ˆ์ด์•„์›ƒ์ด ๋ฐ–์˜ css์— ์˜ํ–ฅ์„ ๋ฐ›๋Š” ๊ฒฝ์šฐ๋„ ์ข…์ข… ์ƒ๊น๋‹ˆ๋‹ค.

์ผ์ฐจ์  ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ๋ฐฉ๋ฒ•์ด react์˜ createPortal์ž…๋‹ˆ๋‹ค. ํ•ด๋‹น dom์„ ํŠน์ • ๊ตฌ์—ญ์— ๋ Œ๋”๋ง์‹œํ‚ค๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

๋‹ค์Œ์€ root element ๋ฐ”๋กœ ๋ฐ‘์— modal์„ ๋ Œ๋”๋ง ์‹œํ‚ค๋Š” ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

const Modal = ({ children, isOpen }) => {
  if (!isOpen) {
    return null;
  }

  return ReactDOM.createPortal(
    <div>
      <div>{children}</div>
      <div></div>
    </div>,
    document.getElementById("root")
  );
};

const App = () => {
  const [isModalOpen, setIsModalOpen] = useState(false);

  const closeModal = () => {
    setIsOpen(false);
  };

  const openModal = () => {
    setIsOpen(true);
  };

  return (
    <div>
      <button onClick={openModal}>๋ชจ๋‹ฌ ์—ด๊ธฐ</button>
      <Modal isOpen={isModalOpen}>
        <h1>๋ชจ๋‹ฌ ์ œ๋ชฉ</h1>
        <p>์—ฌ๊ธฐ์— ๋‚ด์šฉ์„ ์ฑ„์›Œ๋„ฃ์œผ์„ธ์š”.</p>
        <button onClick={closeModal}>๋‹ซ๊ธฐ</button>
      </Modal>
    </div>
  );
};

ํ•˜์ง€๋งŒ ์ด๊ฒƒ์€ modal์˜ ๋ Œ๋”๋ง ์œ„์น˜๋ฅผ ๋ฐ”๊พธ์–ด์ค€๋‹ค์ผ๋ฟ ์ฝ”๋“œ ๊ตฌ์กฐ์ƒ์œผ๋กœ๋Š” ๋ฐ”๋€๊ฒŒ ์—†์Šต๋‹ˆ๋‹ค. ์—ฌ์ „ํžˆ modal์˜ on off๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” state๋Š” ํ•ด๋‹น ํŽ˜์ด์ง€๋‚˜ ์ปดํฌ๋„ŒํŠธ์— ์กด์žฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์ด์ฐจ์  ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

์ผ๋‹จ์€ ์ € closeModal openModal์ด modal์„ ์‚ฌ์šฉํ•˜๋Š” ํŒŒ์ผ๋งˆ๋‹ค ๋ชจ๋‘ ์ค‘๋ณตํ•ด์„œ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค๋Š” ๋ถˆํŽธํ•œ ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ €๊ฒƒ์„ useModal hook์œผ๋กœ ๋งŒ๋“ค์–ด ๋ณด๊ณ ์ž ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

const useModal = (initialState = false) => {
  const [isOpen, setIsOpen] = useState(initialState);

  const openModal = () => setIsOpen(true);
  const closeModal = () => setIsOpen(false);

  return { isOpen, openModal, closeModal };
};

export default useModal;

const App = () => {
  const { isModalOpen, closeModal, openModal } = useModal(false);

  return (
    <div>
      <button onClick={openModal}>๋ชจ๋‹ฌ ์—ด๊ธฐ</button>
      <Modal isOpen={isModalOpen}>
        <h1>๋ชจ๋‹ฌ ์ œ๋ชฉ</h1>
        <p>์—ฌ๊ธฐ์— ๋‚ด์šฉ์„ ์ฑ„์›Œ๋„ฃ์œผ์„ธ์š”.</p>
        <button onClick={closeModal}>๋‹ซ๊ธฐ</button>
      </Modal>
    </div>
  );
};

ํ•˜์ง€๋งŒ ์ค‘๋ณต๋˜๋Š” ์ฝ”๋“œ๋Š” ์ค„์ผ ์ˆ˜๋Š” ์žˆ์–ด๋„ ๊ทผ๋ณธ์ ์ธ ๋ฌธ์ œ๋Š” ํ•ด๊ฒฐํ•˜์ง€ ๋ชปํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๋ฐ”๋กœ modal์€ ํŽ˜์ด์ง€๋‚˜ ์ปดํฌ๋„ŒํŠธ์— ์ข…์†์ ์ด์ง€ ์•Š์€๋ฐ JSX๋Š” ๊ฒฐ๊ตญ ํ•ด๋‹น ํŽ˜์ด์ง€๋‚˜ ์ปดํฌ๋„ŒํŠธ์˜ JSX์— ์žˆ์–ด์•ผ ํ•œ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค.

์ด๋Ÿฐ ์‹์˜ ์ฝ”๋“œ๋Š” ๊ฒฐ๊ตญ ์ฝ”๋“œ๊ฐ€ ๊ธธ์–ด์กŒ์„ ๋•Œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ตฌ์กฐ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฐ ๊ตฌ์กฐ๋Š” 2๊ฐ€์ง€ ์ด์ƒํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋Š” ์ ์ด ์žˆ์—ˆ๋Š”๋ฐ์š”. ์ฒซ ๋ฒˆ์งธ๋Š” ํŽ˜์ด์ง€๋‚˜ ์ปดํฌ๋„ŒํŠธ์—์„œ ๊ด€์‹ฌ์ด ์žˆ๋Š” ๊ฒƒ์€ ์ด modal์„ ์—ด์–ด์•ผ ํ• ์ง€ ๋‹ซ์•„์•ผ ํ• ์ง€์— ๋Œ€ํ•œ ์ฑ…์ž„๋งŒ ํ•„์š”ํ•˜๋‹ค ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‘ ๋ฒˆ์งธ๋กœ๋Š” createPortal์„ ์‚ฌ์šฉํ•˜๋ฉด dom ์ž์ฒด๋Š” root ๋ฐ”๋กœ ๋ฐ‘์—์„œ ๋ Œ๋”๋ง ๋˜์ง€๋งŒ ์‹ค์ œ jsx ์—ฐ๊ฒฐ๋ถ€๋Š” ํŽ˜์ด์ง€๋‚˜ ์ปดํฌ๋„ŒํŠธ์—ฌ์•ผ ํ•œ๋‹ค๋Š” ์‚ฌ์‹ค์ด ์ด์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

์ตœ์ข… ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

๊ทธ๋ž˜์„œ ์ƒ๊ฐํ•œ ๊ฒŒ modal์˜ JSX๊ฐ€ ๊ตณ์ด ์—ฌ๊ธฐ์— ์กด์žฌํ•˜์—ฌ์•ผ ํ•˜๋Š”๊ฐ€์˜€์Šต๋‹ˆ๋‹ค. ์ด๊ฑธ hook์ด ๊ด€๋ฆฌํ•˜๋ฉด ๋˜์ง€ ์•Š์„๊นŒ ์ƒ๊ฐํ•˜์˜€๊ณ  ๋งˆ์นจ ์ „์—ญ ์ƒํƒœ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์–ด ์ด๋ฅผ ์ด์šฉํ•ด ๋ณด๊ณ ์ž ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

์ด๋Ÿฐ ์‹์œผ๋กœ useModal์„ ์ด์šฉํ•ด์„œ ์ „์—ญ ์ƒํƒœ์— modal ์ปดํฌ๋„ŒํŠธ ์ž์ฒด๋ฅผ ์ƒํƒœ๋กœ ์˜ฌ๋ ค๋†“๊ณ  root ๋ฐ”๋กœ ์•„๋ž˜์—์„œ ๋ Œ๋”๋ง ์‹œ์ผœ์ฃผ๋ ค ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋ฉด useModal hook์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ตฌ์กฐ๋กœ ์“ธ ์ˆ˜ ์žˆ์ฃ 

const { isOpen, openModal, closeModal } = useModal(() => {
  return (
    <AnswerSelectionModal
      workbookId={workbookId}
      question={question}
      closeModal={closeModal}
    />
  );
});

์ด๋Ÿฐ ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ• ๋•Œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ถ€๋ถ„์„ ๊ณ ๋ คํ•ด ์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  1. useModal์—์„œ ์ธ์ž๋กœ ๋ฐ›์€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ „์—ญ ์ƒํƒœ๋กœ ์„ค์ •ํ•œ๋‹ค
  2. modal์€ root ๋ฐ”๋กœ ๋ฐ‘์—์„œ ๋ Œ๋”๋งํ•˜๊ณ  ์žˆ๋‹ค
  3. ์ „์—ญ ์ƒํƒœ์— ์žˆ๋Š” modal์€ ํ•ญ์ƒ ์—ด๋ฆฐ ์ƒํƒœ์ด๋‹ค.

state

export const modalState = atom<{ id: string; element: React.FC }[]>({
  key: 'modalState',
  default: [],
});

๋จผ์ € ์ €ํฌ ํ”„๋กœ์ ํŠธ๋Š” recoil์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ์œผ๋ฏ€๋กœ atom์„ ์ด์šฉํ•œ ์ „์—ญ ์ƒํƒœ๋ฅผ ์ •์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •์˜๋ฅผ ํ•˜์˜€์ฃ . ๊ฐ’์€ id์™€ functional component๋ฅผ ์ „์—ญ์œผ๋กœ ๋ฐ›๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

id๋ฅผ ์ด์šฉํ•˜์—ฌ modal์„ ์‹๋ณ„ํ•  ์ˆ˜ ์žˆ๊ณ  ๋ฐฐ์—ด๋กœ ๋งŒ๋“ค์–ด modal์ด ์—ฌ๋Ÿฌ ๊ฐœ๋ฅผ openํ•ด๋„ ์ค‘์ฒฉ์œผ๋กœ modal์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

useModal

useModal์˜ ๊ตฌํ˜„๋ถ€๋ฅผ ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

const isArrEmpty = (arr: unknown[]) => arr.length === 0;

const useModal = (component: React.FC) => {
  const [modalElements, setModal] = useRecoilState(modalState);
  // ์ „์—ญ ์ƒํƒœ์— element ์ž์ฒด๋ฅผ ์ƒํƒœ๋กœ ์ €์žฅํ•œ๋‹ค.
  const [isOpen, setIsOpen] = useState(false);
  // ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์—ด๋ ธ๋Š”์ง€ ์•ˆ์—ด๋ ธ๋Š”์ง€ ์•Œ๋ ค์ฃผ๋Š” ์ƒํƒœ
  const id = useId();
  // ์ปดํฌ๋„ŒํŠธ์— id๋ฅผ ๋ถ€์—ฌํ•ด์„œ ์‹๋ณ„ํ•œ๋‹ค.

  const modalComponent = useMemo(() => component, []);

  const openModal = useCallback(() => {
    setIsOpen(true);
    setModal((pre) => [...pre, { id: id, element: modalComponent }]);
    // modal์„ ์ „์—ญ์ƒํƒœ์— ์ถ”๊ฐ€ํ•œ๋‹ค
    document.body.style.overflow = "hidden";
    // modal์ด open๋˜๋ฉด ๋ฐฐ๊ฒฝ์˜ ์Šคํฌ๋กค์„ ๋ง‰์•„์•ผํ•จ
  }, []);

  const closeModal = useCallback(() => {
    setIsOpen(false);
    setModal((pre) => pre.filter((c) => c.id !== id));
    // modal์„ ์ „์—ญ์ƒํƒœ์—์„œ ์ œ๊ฑฐํ•œ๋‹ค.

    if (isArrEmpty(modalElements)) document.body.style.overflow = "unset";
    // modal์ด ๋ชจ๋‘ ๊บผ์ง€๋ฉด ๋ฐฐ๊ฒฝ์˜ ์Šคํฌ๋กค์ด ๊ฐ€๋Šฅํ•ด์•ผํ•จ
  }, []);

  return { isOpen, openModal, closeModal };
};

export default useModal;

useModal์€ ์ „์—ญ ์ƒํƒœ์— ํ•ด๋‹น modal ์ปดํฌ๋„ŒํŠธ๋“ค์„ ์ถ”๊ฐ€ ๋˜๋Š” ์ œ๊ฑฐํ•ด์„œ modal์ด ์—ด๋ ธ๋Š”์ง€ ๋‹ซํ˜”๋Š”์ง€๋ฅผ ๊ด€๋ฆฌํ•ด ์ฃผ๋Š” hook์ž…๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ๊ณ ๋ ค์‚ฌํ•ญ์ด์ฃ 

ModalProvider

์ž ๊ทธ๋Ÿฌ๋ฉด ์ด์ œ ์ „์—ญ ์ƒํƒœ์ธ modal ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋žœ๋”๋ง ํ•ด์„œ modal์ด root ๋ฐ”๋กœ ๋ฐ‘์—์„œ ์—ด๋ฆฐ๋‹ค๋Š” ๋‘ ๋ฒˆ์งธ ๊ณ ๋ ค ์‚ฌํ•ญ์„ ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ €๋Š” ์ด๋ฅผ ModalProvider๋กœ ํ•ด๊ฒฐํ–ˆ๋Š”๋ฐ์š”

import { modalState } from "@atoms/modal";
import { useRecoilState } from "recoil";

const ModalProvider = () => {
  const [state] = useRecoilState(modalState);
  return (
    <>
      {state.map(({ id, element }) => {
        return <Component key={id} component={element} />;
      })}
    </>
  );
};

const Component = ({ component, ...rest }: { component: React.FC }) => {
  return component({ ...rest });
};

export default ModalProvider;

์ „์—ญ ์ƒํƒœ๋ฅผ ๊ฐ€์ ธ์™€์„œ ๋‹จ์ˆœํ•˜๊ฒŒ ๋ Œ๋”๋ง ํ•ด์ฃผ๊ณ  ์žˆ๋Š” ์—ญํ• ์„ ํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐฐ์—ด๋กœ ๋˜์–ด ์žˆ์–ด ๋ฐฐ์—ด์—์„œ ๋’ค ์ˆœ์„œ๋กœ ์žˆ๋Š” modal์ด ์Œ“์ž„ ๋งฅ๋ฝ ๋•Œ๋ฌธ์— ์ด์ „ modal๋ณด๋‹ค ๋” ์œ„์— ์กด์žฌํ•˜๊ฒŒ ๋˜์–ด์„œ ์ค‘์ฒฉ modal์„ ์ด๋Ÿฐ ๋‹จ์ˆœ ๋ฐฐ์—ด๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•ด๋‹น ModalProvider์€ provider๋“ค์„ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ๋Š” App ์ปดํฌ๋„ŒํŠธ์—์„œ ์‹คํ–‰์ด ๋˜๊ณ  ์žˆ๋Š”๋ฐ์š”

...
<RecoilRoot>
  <ThemeProvider theme={theme}>
    <Global styles={_global} />
    <AppRouter queryClient={queryClient} />
    <GlobalSVGProvider />
    <ModalProvider />
  </ThemeProvider>
</RecoilRoot>
...

์ด๋Ÿฌ๋ฉด router๋ณด๋‹ค๋Š” ๋ฐ”๊นฅ ์˜์—ญ์ด๊ณ  root๋ณด๋‹ค๋Š” ์•„๋ž˜ ์˜์—ญ์ด๋‹ˆ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ณณ์—์„œ modal์ด ์ถ”๊ฐ€๊ฐ€ ๋œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฒฐ๊ตญ ํ•ด๋‹น ์ „์—ญ state์— ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ๊ทธ ์ฆ‰์‹œ root ํ•˜๋‹จ ๋ถ€์— ๋ฐ”๋กœ ๋ Œ๋”๋ง ๋˜๊ณ  ์žˆ๋Š” ๊ฑธ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฒฐ๋ก 

modal์„ ์ œ์–ดํ•˜๋Š” ๋ถ€๋ถ„์„ hook์œผ๋กœ ๋ชจ์œผ๋ฉด์„œ ์‘์ง‘๋„๋ฅผ ๋†’์ผ ์ˆ˜ ์žˆ๊ณ  ํ™•์‹คํ•œ ์ฑ…์ž„์„ ์ฃผ์—ˆ๋Š”๋ฐ์š”. ์ œ๊ฐ€ ์ด hook์„ ์‚ฌ์šฉํ•˜๋ฉด์„œ ๊ฐ€์žฅ ํฌ๊ฒŒ ์žฅ์ ์œผ๋กœ ๋Š๋‚€ ๊ฒƒ์€ modal์˜ ์ข…๋ฅ˜๊ฐ€ ๋งŽ์€ ํ”„๋กœ์ ํŠธ์— ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉ๋˜์—ˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๊ฐ๊ฐ์˜ modal์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋งŽ์€๋ฐ modal์„ ๋“ฑ๋กํ•˜๋Š” ์œ„์น˜๊ฐ€ ์ž์œ ๋กœ์šฐ๋‹ˆ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” ์„ ํƒ์ง€๊ฐ€ ๋งŽ์•„์ง„ ๊ฒƒ์ด์ฃ .


ํŠนํžˆ ๋ถˆํ•„์š”ํ•œ ์ฝ”๋“œ๋“ค์ด ์ค„์ด๋ฉด์„œ ๋” ์ด์ƒ ๊ฐœ๋ฐœ์ž๋Š” modal์˜ ์ƒํƒœ๋ฅผ ์ง€์ •ํ•˜๋Š” state modal์˜ ๋ Œ๋”๋ง ์œ„์น˜ ๋“ฑ์„ ์•Œ ํ•„์š”๊ฐ€ ์—†์–ด์กŒ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ƒฅ modal์„ ์ž˜ ๋””์ž์ธํ•˜๊ณ  ์ž˜ ๋™์ž‘์‹œํ‚ค๊ธฐ๋งŒ ํ•˜๋Š”๋ฐ ์ง‘์ค‘์„ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

๊ทน์ฐฌ์„ ๋ฐ›์•˜๋‹ค!!

ยฉ 2024 Adultlee. All rights reserved.Made with โค by ์ด์„ฑ์ธ