Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
813 changes: 587 additions & 226 deletions package-lock.json

Large diffs are not rendered by default.

34 changes: 34 additions & 0 deletions public/icons/IcClose.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { SVGProps } from 'react';

interface IcCloseProps extends SVGProps<SVGElement> {
width?: number;
height?: number;
isActive?: boolean;
color?: string;
}

function IcClose({
width = 24,
height = 24,
isActive = false,
color,
}: IcCloseProps) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={width}
height={height}
fill="none"
className={`${isActive ? color : 'stroke-gray-dark-02'}`}
>
<path
stroke={isActive ? color : '#909192'}
strokeLinecap="round"
strokeWidth="1.8"
d="m5 5 14.5 14.5M19.5 5 5 19.5"
/>
</svg>
);
}

export default IcClose;
1 change: 1 addition & 0 deletions public/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export { default as VisibilityOn } from './visibility_on';
export { default as VisibilityOff } from './visibility_off';
export { default as LocationIcon } from './LocationIcon';
export { default as HostIcon } from './HostIcon';
export { default as IcClose } from './IcClose';
58 changes: 58 additions & 0 deletions src/components/pop-up/PopUp.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { Meta, StoryObj } from '@storybook/react';
import PopUp from './PopUp';

const POPUP_LABEL = {
large: `정말 나가시겠어요?\n작성된 내용이 모두 삭제됩니다.`,
small: `가입이 완료되었습니다!`,
};

const meta: Meta<typeof PopUp> = {
title: 'components/PopUp',
component: PopUp,
parameters: {
layout: 'centerd',
},
};

type Story = StoryObj<typeof PopUp>;
export const LargeOneButton: Story = {
args: {
isOpen: true,
isLarge: true,
label: POPUP_LABEL.large,
handlePopUpClose: (e) => alert(e),
handlePopUpConfirm: (e) => alert(e),
},
};

export const LargeTwoButton: Story = {
args: {
isOpen: true,
isLarge: true,
isTwoButton: true,
label: POPUP_LABEL.large,
handlePopUpClose: (e) => alert(e),
handlePopUpConfirm: (e) => alert(e),
},
};

export const SmallOneButton: Story = {
args: {
isOpen: true,
label: POPUP_LABEL.small,
handlePopUpClose: (e) => alert(e),
handlePopUpConfirm: (e) => alert(e),
},
};

export const SmallTwoButton: Story = {
args: {
isOpen: true,
isTwoButton: true,
label: POPUP_LABEL.small,
handlePopUpClose: (e) => alert(e),
handlePopUpConfirm: (e) => alert(e),
},
};

export default meta;
82 changes: 82 additions & 0 deletions src/components/pop-up/PopUp.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import PopUp from './PopUp';

const POPUP_LABEL = {
large: `정말 나가시겠어요?\n작성된 내용이 모두 삭제됩니다.`,
small: `가입이 완료되었습니다!`,
};

const mockHandlePopUpClose = jest.fn();
const mockHandlePopUpConfirm = jest.fn();

describe('PopUp 렌더링 테스트', () => {
it('small, single button 렌더링 테스트', () => {
render(
<PopUp
isOpen={true}
label={POPUP_LABEL.small}
handlePopUpClose={mockHandlePopUpClose}
handlePopUpConfirm={mockHandlePopUpConfirm}
/>,
);
const popUp = screen.getByRole('pop-up');
const confirm = screen.getByText('확인');
const cancel = screen.queryByText('취소');
expect(popUp).toBeInTheDocument();
expect(confirm).toBeInTheDocument();
expect(cancel).not.toBeInTheDocument();
});
it('small, two button 렌더링 테스트', () => {
render(
<PopUp
isOpen={true}
isTwoButton={true}
label={POPUP_LABEL.small}
handlePopUpClose={mockHandlePopUpClose}
handlePopUpConfirm={mockHandlePopUpConfirm}
/>,
);
const popUp = screen.getByRole('pop-up');
const confirm = screen.getByText('확인');
const cancel = screen.getByText('취소');
expect(popUp).toBeInTheDocument();
expect(confirm).toBeInTheDocument();
expect(cancel).toBeInTheDocument();
});
it('large, single button 렌더링 테스트', () => {
render(
<PopUp
isOpen={true}
isLarge={true}
label={POPUP_LABEL.large}
handlePopUpClose={mockHandlePopUpClose}
handlePopUpConfirm={mockHandlePopUpConfirm}
/>,
);
const popUp = screen.getByRole('pop-up');
const confirm = screen.getByText('확인');
const cancel = screen.queryByText('취소');
expect(popUp).toBeInTheDocument();
expect(confirm).toBeInTheDocument();
expect(cancel).not.toBeInTheDocument();
});
it('large, two button 렌더링 테스트', () => {
render(
<PopUp
isOpen={true}
isLarge={false}
isTwoButton={true}
label={POPUP_LABEL.large}
handlePopUpClose={mockHandlePopUpClose}
handlePopUpConfirm={mockHandlePopUpConfirm}
/>,
);
const popUp = screen.getByRole('pop-up');
const confirm = screen.getByText('확인');
const cancel = screen.getByText('취소');
expect(popUp).toBeInTheDocument();
expect(confirm).toBeInTheDocument();
expect(cancel).toBeInTheDocument();
});
});
75 changes: 75 additions & 0 deletions src/components/pop-up/PopUp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { IcClose } from '../../../public/icons';
import Button from '../button/Button';

interface PopUpProps {
isOpen: boolean;
label: string;
handlePopUpClose: (result: boolean) => void;
handlePopUpConfirm: (result: boolean) => void;
isLarge?: boolean;
isTwoButton?: boolean;
}

function PopUp({
isOpen,
isLarge = false,
isTwoButton = false,
label,
handlePopUpClose,
handlePopUpConfirm,
}: PopUpProps) {
const onClickClose = () => {
if (handlePopUpClose) {
handlePopUpClose(false);
}
};

const onClickConfirm = () => {
if (handlePopUpConfirm) {
handlePopUpConfirm(true);
}
};
Comment on lines +21 to +31
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p4: 이 부분은 혹시 그냥 props함수를 밑에 바로 버튼에 넘겨주면 동작을 안할까요? 그냥 넘겨주는거랑 현재 코드상의 로직이 어떤 차이인지 궁금합니다!

저는 이런식으로 사용하는걸 생각했는데

const [isPopUpOpen, setIsPopUpOpen] = useState(false);

  const handleClose = () => {
    setIsPopUpOpen(false);
  };

  const handleConfirm = () => {
    setIsPopUpOpen(false);
    // 추가 로직...
  };

이렇게 사용한다 가정하면 props로 넘긴 함수를 버튼에 바로 넣어도 되지 않을까?라는 생각이 들어서 여쭤봤습니다!

Copy link
Contributor Author

@sunnwave sunnwave Dec 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분은 저도 고민을 했는데 클릭 시 리턴하는 값은 팝업을 끄는 부분과는 별개로 클릭한 버튼에 따라 이후 부모 컴포넌트에서 동작하는 상황이 달라질 것 같아서 저렇게 구현했습니다

ex) 모임 만들기 페이지에서 정보를 입력하던 중 유저가 다른 페이지로 이동하면 '정말 나가시겠어요? 작성된 내용이 모두 삭제됩니다' 팝업창이 뜸

  1. 확인 버튼(return true)-> 팝업창이 사라지고 페이지를 이동
  2. 닫기 버튼/취소 버튼(return false)-> 팝업창이 사라지는 동작만 이루어짐

return (
<>
<div className={`${isOpen ? 'block' : 'hidden'}`}>
<div
className={`fixed left-0 top-0 h-screen w-screen bg-black bg-opacity-50`}
>
<div
role="pop-up"
className={`absolute left-2/4 top-2/4 flex -translate-x-2/4 -translate-y-2/4 flex-col items-center justify-between rounded-lg border opacity-100 ${isLarge ? 'h-[212px] w-[450px]' : 'h-[199px] w-[300px]'} bg-white p-[24px]`}
>
<button className="ml-auto" onClick={onClickClose}>
<IcClose role="close-icon" className="ml-auto" />
</button>
<div className="flex flex-col items-center whitespace-pre-wrap text-center text-[#111827]">
{label}
</div>
<div
className={`${isLarge && !isTwoButton ? 'ml-auto' : 'flex items-center gap-2'} `}
>
{isTwoButton ? (
<Button
text="취소"
size="modal"
fillType="outline"
themeColor="green-normal-01"
onClick={onClickClose}
/>
) : null}
<Button
text="확인"
size="modal"
fillType="solid"
themeColor="green-normal-01"
onClick={onClickConfirm}
/>
</div>
</div>
</div>
</div>
</>
);
}

export default PopUp;