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
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,32 @@

## [Unreleased]

## [0.2.5] - 2026-01-18

### Added

- Modal 컴포넌트 actionButton에 아이콘 지원
- `leftIcon`, `rightIcon` prop 추가
- Button 컴포넌트와 동일한 방식으로 아이콘 렌더링
- 스토리북 예시 추가 (`WithActionButtonLeftIcon`, `WithActionButtonRightIcon`)
- Modal actionButton 테스트 추가
- leftIcon 단독 렌더링 테스트
- rightIcon 단독 렌더링 테스트
- 아이콘과 onClick 이벤트 통합 테스트

### Changed

- Modal actionButton 인터페이스 확장
- `actionButton.leftIcon?: ReactNode` - 버튼 왼쪽에 표시할 아이콘
- `actionButton.rightIcon?: ReactNode` - 버튼 오른쪽에 표시할 아이콘

### Fixed

- 잘못된 테스트 케이스 제거
- Button 컴포넌트는 leftIcon과 rightIcon을 동시에 지원하지 않는 구조
- leftIcon이 있으면 rightIcon은 무시되는 동작
- 동시 렌더링 테스트 케이스 제거

## [0.2.4] - 2026-01-18

### Added
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@team-numberone/daepiro-design-system",
"version": "0.2.4",
"version": "0.2.5",
"description": "대피로 디자인 시스템 - React 컴포넌트 라이브러리",
"private": false,
"packageManager": "[email protected]",
Expand Down
76 changes: 76 additions & 0 deletions src/components/modal/Modal.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,82 @@ export const WithActionButton: Story = {
},
};

export const WithActionButtonLeftIcon: Story = {
render: (args) => {
const [open, setOpen] = useState(false);

return (
<>
<Button onClick={() => setOpen(true)}>모달 열기</Button>
<Modal
{...args}
open={open}
onOpenChange={setOpen}
actionButton={{
label: "119 신고하기",
leftIcon: <span style={{ fontSize: "18px" }}>📞</span>,
onClick: () => {
alert("119에 신고되었습니다!");
setOpen(false);
},
}}
>
{args.children}
</Modal>
</>
);
},
args: {
size: "medium",
children: (
<div>
<h2 style={{ marginTop: 0, marginBottom: "16px" }}>맞아요!</h2>
<p style={{ margin: 0 }}>
119에 도움을 요청해보세요! (왼쪽 아이콘 포함)
</p>
</div>
),
},
};

export const WithActionButtonRightIcon: Story = {
render: (args) => {
const [open, setOpen] = useState(false);

return (
<>
<Button onClick={() => setOpen(true)}>모달 열기</Button>
<Modal
{...args}
open={open}
onOpenChange={setOpen}
actionButton={{
label: "다음 단계",
rightIcon: <span style={{ fontSize: "18px" }}>→</span>,
onClick: () => {
alert("다음 단계로 이동합니다!");
setOpen(false);
},
}}
>
{args.children}
</Modal>
</>
);
},
args: {
size: "medium",
children: (
<div>
<h2 style={{ marginTop: 0, marginBottom: "16px" }}>진행하시겠어요?</h2>
<p style={{ margin: 0 }}>
다음 단계로 진행하려면 버튼을 클릭하세요. (오른쪽 아이콘 포함)
</p>
</div>
),
},
};

export const AllSizes: Story = {
render: () => {
const sizes = ["small", "medium", "large"] as const;
Expand Down
62 changes: 62 additions & 0 deletions src/components/modal/Modal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -525,4 +525,66 @@ describe("Modal 컴파운드 패턴", () => {
expect(actionButton).toBeInTheDocument();
});
});

describe("actionButton 아이콘", () => {
it("actionButton에 leftIcon이 표시되는지", () => {
render(
<Modal
open={true}
onOpenChange={() => {}}
actionButton={{
label: "확인",
leftIcon: <span data-testid="left-icon">←</span>,
}}
>
모달 내용
</Modal>
);

expect(screen.getByTestId("left-icon")).toBeInTheDocument();
expect(screen.getByRole("button", { name: /확인/ })).toBeInTheDocument();
});

it("actionButton에 rightIcon이 표시되는지", () => {
render(
<Modal
open={true}
onOpenChange={() => {}}
actionButton={{
label: "확인",
rightIcon: <span data-testid="right-icon">→</span>,
}}
>
모달 내용
</Modal>
);

expect(screen.getByTestId("right-icon")).toBeInTheDocument();
expect(screen.getByRole("button", { name: /확인/ })).toBeInTheDocument();
});

it("actionButton의 아이콘과 onClick이 함께 동작하는지", async () => {
const handleActionClick = vi.fn();
const user = userEvent.setup();
render(
<Modal
open={true}
onOpenChange={() => {}}
actionButton={{
label: "확인",
onClick: handleActionClick,
leftIcon: <span data-testid="left-icon">✓</span>,
}}
>
모달 내용
</Modal>
);

const actionButton = screen.getByRole("button", { name: /확인/ });
expect(screen.getByTestId("left-icon")).toBeInTheDocument();

await user.click(actionButton);
expect(handleActionClick).toHaveBeenCalledTimes(1);
});
});
});
10 changes: 9 additions & 1 deletion src/components/modal/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ export interface ModalProps extends ModalRootProps {
actionButton?: {
label: string;
onClick?: () => void;
leftIcon?: ReactNode;
rightIcon?: ReactNode;
};
}

Expand All @@ -222,7 +224,13 @@ export function Modal({
display: "flex",
}}
>
<Button variant="primary" onClick={actionButton.onClick} full>
<Button
variant="primary"
onClick={actionButton.onClick}
leftIcon={actionButton.leftIcon}
rightIcon={actionButton.rightIcon}
full
>
{actionButton.label}
</Button>
</div>
Expand Down