Skip to content

[1단계 - 페이먼츠 모듈] 마빈(김재영) 미션 제출합니다. #91

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 117 commits into from
May 4, 2025

Conversation

spoyodevelop
Copy link

@spoyodevelop spoyodevelop commented May 1, 2025

안녕하세요 온스타! 다시 만나게 되는군요! 이번에도 좋은 고민거리를 가져왔다고 생각해요. 다만, 이번에는 코드리뷰에 적극적으로 커멘트를 달고 조금 더 유연한 의사소통을 진행하려고 합니다. 이번에도 코드리뷰 잘 부탁 드립니다!

🎯 페이먼츠 모듈

이번 미션을 통해 다음과 같은 학습 경험들을 쌓는 것을 목표로 합니다.

1단계 - Module

  • 재사용 가능한 모듈화된 컴포넌트를 개발하고 npm에 배포할 수 있다.
  • 재사용 가능한 커스텀 훅을 개발하고 npm에 배포할 수 있다.
  • Storybook을 사용하여 컴포넌트의 다양한 상태를 시각적으로 문서화하고 테스트할 수 있다.
  • React Testing Library를 사용하여 컴포넌트와 커스텀 훅에 대해 폭넓은 테스트를 작성할 수 있다.

🕵️ 셀프 리뷰(Self-Review)

제출 전 체크 리스트

리뷰 요청 & 논의하고 싶은 내용

1) 이번 단계에서 가장 많이 고민했던 문제와 해결 과정에서 배운 점

으.. 정말 끔찍한 방식으로 vite.config.js를 작성하는 법을 배웠어요.
이번 단계에서 가장 큰 골칫거리는 라이브러리로 배포할 때 CSS 모듈과 에셋(이미지)을 안전하게 포함시키는 설정이었어요. Vite의 기본 빌드 설정만으로는 CSS가 사라지거나, 트리 쉐이킹에 걸리거나, CommonJS 번들에서 제대로 로드되지 않는 문제가 발생했죠.

어떻게든 index.ts에:

import styles from 'styles.css'
export { default as Modal } from "./Modal";

을 하고, import된 styles가 트리 쉐이킹 과정에서 제거되지 않게 하려고 많은 노력을 했습니다. 그런데 어떻게 하던 style 줄이 사라지고, CSS import가 깨지는 현상이 발생했습니다.

다음과 같은 방식들을 시도했어요:

  1. export를 한번 더 해주어서, '사용하는 코드다!'라고 선언해 주는 방식
  2. sideEffects*.csspackage.json에 작성해서, 어떻게든 해당 코드를 트리 쉐이킹에 걸리지 않게 하는 방식

계속 되었다가 말았다 해서, 결국 cssInjectedByJs 롤업 플러그인을 사용해 JS에 인라인으로 삽입하도록 했습니다.

굉장히 많이 고생한 내용이라고 생각해요.

이 부분을 어디서부터 공부하면 좋을지 ChatGPT에 물어보니, 1주 정도 학습하는 플랜을 짜주더라고요.

인터넷에 자료가 webpack과 Rollup의 진짜 '사용자 친화적이지 않은 문서'들 밖에 없어서, 어디서부터 공부를 시작해야 할지 고민이 많습니다. 어떤 학습 루트를 추천해 주실 수 있을까요?


2) 이번 리뷰를 통해 논의하고 싶은 부분

고민거리들이 많이 있는데, 이 뒤부터는 짧게 대답하셔도 무관합니다. 중요도에 따라 순서대로 작성했습니다.

1. 컴포넌트 무게와 유연성

모달을 작성할 때, 얼마나 다양한 사용성을 가정해야 할까요? 버튼의 위치, 크기, 모양을 props로 조정할 수 있게 하는 크루들도 있었고, 폰트 사이즈 같은 세부 스타일을 넘겨주는 크루도 있었어요. 저는 '필수적인 모달 동작'만 보증하면 컴포넌트의 책임을 다했다고 생각했고, 내부 state도 보관하지 않고 하위에서 상태를 내려받는 'dumb component'로 설계했습니다. 이 모달이 적절한 구성이라고 생각하시는지, 아니면 조금 더 모달에 책임을 부여하는 것이 맞다고 보시는지 궁금합니다.

2. CSS Reset 전략

대부분 프로젝트에서 CSS Reset은 필수인데, component 배포 시 Reset은 누가 관리해야 할지 고민이에요. 컴포넌트와 함께 Reset을 포함하여 배포해야 할까요? 아니면 Reset은 소비자가 별도로 적용하도록 안내하는 것이 좋을까요? Reset 때문에 자체 서식이 적용되는 태그(h2 같은 태그)를 사용하는 것을 기피하게 돼, 시맨틱 마크업 활용에도 제약이 생기는데, 어떻게 해결하면 좋을까요?

3. 상태 관리 vs. 순수 유틸

useCardNumber 훅이 꼭 훅일 필요가 있을까 고민했어요. validator 함수 형태의 순수 유틸로도 충분할 것 같다는 생각이 들었죠. 필요하다면 에러 메시지 등 상태는 소비자가 직접 관리하도록 하고요. 하지만 실제로 사용해 보니 상태가 있으면 확실히 편리해서, 훅 형태도 나쁘지 않다고 느꼈습니다. 이 정체성 혼란을 어떻게 접근하면 좋을까요?

4. 테스트 코드 중복

RTL과 jest의 테스트 코드가 중복됩니다. 저는 , validation tool만 jest로 테스트 코드를 작성해도 좋다고 생각하지만, RTL을 쓰는게 요구 사항 이더군요. 혹시, 요구사항중에 '꼭 할필요는 없고 대체제가 있는데?'하는 요구사항이 있으면 어떻게 대처를 하시는 편인가요?


이상, 긴 본문 읽어주셔서 감사합니다. 비가 많이 내리네요. 감기 조심하시고, 즐거운 주말 보내세요, 온스타!

✅ 리뷰어 체크 포인트

1. 모달 컴포넌트의 유연성

  • 다양한 위치(중앙, 하단 등)와 내용을 prop으로 받아 모달을 유연하게 구성할 수 있는가?
  • 열기, 닫기, 확인 등의 사용자 정의 이벤트 핸들러가 자연스럽게 동작하는가?
  • Storybook을 통해 다양한 시나리오를 명확히 테스트했는가?

2. 커스텀 훅의 역할과 모듈화

  • 카드 정보 입력 유효성 검사 훅이 각각 명확한 책임을 가지도록 분리되어 있는가?
  • 훅이 유효성 검사 결과와 에러 메시지를 명확히 반환하는가?
  • 독립적인 테스트 케이스를 작성하여 정상/비정상 입력 모두를 폭넓게 검증했는가?

3. 모듈 배포

  • npm 배포를 위한 설정이 적절히 구성되어 있는가?

spoyodevelop and others added 30 commits April 29, 2025 15:53
Co-authored-by: mun-kyeong <[email protected]>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
components/src/App.tsx (1)

6-6: 초기 모달 상태 검토 필요

모달의 초기 상태가 true로 설정되어 있어 페이지 로드 시 자동으로 모달이 열립니다. 일반적인 UX 패턴에서는 모달이 초기에 닫혀 있는 것이 더 일반적입니다. 의도적인 설계가 아니라면 초기값을 false로 변경하는 것이 좋습니다.

-  const [isOpen, setIsOpen] = useState(true);
+  const [isOpen, setIsOpen] = useState(false);
🧹 Nitpick comments (7)
components/src/App.tsx (1)

14-23: 모달 위치와 타이틀의 불일치

현재 모달의 위치는 position="bottom"으로 설정되어 있지만, 모달 헤더의 텍스트는 "중앙 모달 제목입니다"로 되어 있습니다. 이는 사용자에게 혼란을 줄 수 있습니다. 모달 위치와 헤더 텍스트를 일치시키는 것이 좋습니다.

- <Modal.Header>중앙 모달 제목입니다</Modal.Header>
+ <Modal.Header>하단 모달 제목입니다</Modal.Header>
components/src/lib/Components/Modal/Container.tsx (1)

5-17: 컨텍스트 사용 패턴이 잘 구현되었습니다

컨텍스트 사용과 조건부 렌더링이 적절하게 구현되어 있습니다. e.stopPropagation()을 사용하여 모달 내부 클릭이 외부로 전파되지 않도록 한 것이 좋습니다.

다만, 더 나은 사용자 경험을 위해 컨텍스트가 없는 경우 에러 로깅 또는 개발 환경에서의 경고를 추가하는 것을 고려해 볼 수 있습니다:

  const ctx = useContext(ModalContext);
- if (!ctx) return null;
+ if (!ctx) {
+   if (process.env.NODE_ENV === 'development') {
+     console.warn('Modal.Container must be used within a Modal component');
+   }
+   return null;
+ }
components/src/lib/Components/Modal/Background.tsx (2)

6-7: 불필요한 주석

"배경" 이라는 주석은 코드의 가독성을 위해 존재하는 것으로 보이지만, 함수명 Background가 이미 그 역할을 명확하게 설명하고 있어 불필요합니다. 자기 문서화(self-documenting) 코드 원칙에 따라 제거하는 것이 좋습니다.

- // 배경
  function Background({ children }: { children: React.ReactNode }) {

1-19: 개발 과정 분석

커밋 기록을 살펴보면 개발자가 모달 컴포넌트를 구현하는 과정에서 다음과 같은 사고 흐름을 가졌음을 알 수 있습니다:

  1. 먼저 기본적인 모달 컨텍스트를 설계하여 공유 상태를 관리할 수 있는 기반을 마련
  2. 컴포넌트를 Background, Container, Header, Content로 분리하여 모듈화 설계
  3. 각 컴포넌트가 컨텍스트를 통해 상태를 공유하도록 구현
  4. 모달 위치 옵션(bottom/center)을 추가하여 유연성 부여

이는 컴포넌트 기반 아키텍처와 컨텍스트 API를 잘 활용한 접근 방식입니다. 다만, 컨텍스트의 null 체크와 같은 안전장치에 일관성이 부족한 점이 개선되면 더 견고한 컴포넌트가 될 것입니다.

React 생태계의 모범 사례에 따르면, 컴포넌트가 외부에서 제공된 컨텍스트에 의존할 때는 항상 컨텍스트의 존재 여부를 체크하는 것이 좋습니다. 이 점을 Background 컴포넌트에도 적용하면 좋겠습니다.

components/src/lib/Components/Modal/Modal.stories.tsx (2)

14-72: CenterModal 스토리에 잘 작성된 play 함수가 있습니다만, BottomModal 스토리에는 없습니다.

CenterModal 스토리에서는 모달이 열리고 닫히는 세 가지 방법(닫기 버튼, 배경 클릭, ESC 키)을 자동화된 테스트로 잘 구현하셨습니다. 하지만 BottomModal 스토리에는 play 함수가 누락되어 있습니다. 일관성을 위해 BottomModal에도 유사한 play 함수를 추가하는 것이 좋을 것 같습니다.

play: async ({ canvasElement }) => {
  const triggerButton = canvasElement.querySelector(
    "#trigger-button-bottom"
  ) as HTMLButtonElement;
  await userEvent.click(triggerButton);
  
  // 여기에 BottomModal에 대한 테스트 추가...
}

또한 BottomModal과 CenterModal의 render 함수 간에 중복된 코드가 많습니다. 공통 로직을 추출하여 재사용 가능한 함수로 만들면 코드 중복을 줄일 수 있습니다.


39-71: 테스트 시나리오가 복잡할수록 명확한 주석이 필요합니다.

현재 play 함수에서는 주석으로 닫기 방법을 설명하고 있지만, 각 테스트 시나리오의 시작과 기대 결과에 대한 주석이 더 명확하면 좋을 것 같습니다. 또한 테스트 코드에서 재사용 가능한 요소(예: waitFor와 expect 조합)를 함수로 추출하면 가독성이 향상될 것입니다.

다음과 같이 주석을 보강하는 것이 좋겠습니다:

// 시나리오 1: 모달 열기 및 내용 확인
await userEvent.click(triggerButton);
await waitFor(() => expect(modalContainer).toHaveTextContent("중앙 모달 제목입니다"));

// 시나리오 2: 닫기 버튼으로 모달 닫기
await userEvent.click(triggerButton); // 모달 다시 열기
const closeButton = document.body.querySelector("#modal-close-button") as HTMLImageElement;
await userEvent.click(closeButton);
await waitFor(() => expect(modalContainer).not.toBeInTheDocument());

또한 반복되는 검증 로직을 함수로 추출하는 것도 고려해 보세요.

components/src/lib/Components/Modal/index.tsx (1)

16-25: ESC 키 이벤트 핸들러를 커스텀 훅으로 추출하면 재사용성이 향상됩니다.

현재 ESC 키 이벤트 처리 로직이 모달 컴포넌트 내부에 직접 구현되어 있습니다. 이 로직을 별도의 커스텀 훅(useEscapeKey)으로 추출하면 다른 컴포넌트에서도 재사용할 수 있고, 모달 컴포넌트의 복잡성도 줄일 수 있습니다.

// useEscapeKey.ts
import { useEffect } from 'react';

export const useEscapeKey = (isActive: boolean, callback: () => void) => {
  useEffect(() => {
    if (!isActive) return;
    
    const handler = (e: KeyboardEvent) => {
      if (e.key === "Escape") callback();
    };
    
    window.addEventListener("keydown", handler);
    return () => window.removeEventListener("keydown", handler);
  }, [isActive, callback]);
};

// Modal 컴포넌트에서 사용:
// useEscapeKey(isOpen, onClose);

이렇게 하면 코드의 가독성과 재사용성이 향상됩니다.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Lite

📥 Commits

Reviewing files that changed from the base of the PR and between 6d18eec and 0f7711f.

📒 Files selected for processing (12)
  • components/src/App.tsx (1 hunks)
  • components/src/lib/Components/Modal/Background.tsx (1 hunks)
  • components/src/lib/Components/Modal/Container.tsx (1 hunks)
  • components/src/lib/Components/Modal/Content.tsx (1 hunks)
  • components/src/lib/Components/Modal/Header.tsx (1 hunks)
  • components/src/lib/Components/Modal/Modal.stories.tsx (1 hunks)
  • components/src/lib/Components/Modal/ModalBackground.module.css (1 hunks)
  • components/src/lib/Components/Modal/ModalContainer.module.css (1 hunks)
  • components/src/lib/Components/Modal/ModalContext.tsx (1 hunks)
  • components/src/lib/Components/Modal/ModalHeader.module.css (1 hunks)
  • components/src/lib/Components/Modal/index.tsx (1 hunks)
  • components/src/lib/index.ts (1 hunks)
✅ Files skipped from review due to trivial changes (5)
  • components/src/lib/Components/Modal/ModalBackground.module.css
  • components/src/lib/Components/Modal/Content.tsx
  • components/src/lib/Components/Modal/ModalHeader.module.css
  • components/src/lib/Components/Modal/Header.tsx
  • components/src/lib/Components/Modal/ModalContainer.module.css
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/src/lib/index.ts
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.tsx`: You MUST follow these guidelines: - Please leave 1–2 key review comments that address recurring mistakes or pain points the code author frequently makes. If possible, r...

**/*.tsx: You MUST follow these guidelines:

  • Please leave 1–2 key review comments that address recurring mistakes or pain points the code author frequently makes. If possible, reference official documentation that can help resolve these issues
  • Analyze the commit history to infer the cognitive process the developer went through while solving the problem. Summarize their thought progression: how they understood the problem, iterated on solutions, and refined the code. Focus on their reasoning patterns, learning moments, or shifts in approach. If applicable, relate their process to established best practices in the React ecosystem.
  • Do not provide complete solutions unless explicitly requested.
  • Guide the programmer in problem-solving instead of providing direct answers.
  • When asked about programming concepts (e.g., "What is a hook?"), give direct and clear explanations.
  • Break problems into smaller, manageable steps to help think through them logically.
  • Ask leading questions and provide hints instead of just telling the answer.
  • Encourage independent debugging before offering suggestions.
  • Refer to relevant documentation instead of providing ready-made solutions.
  • Promote modular thinking—breaking problems into reusable components.
  • Remind the programmer to reflect on lessons learned after solving an issue.
  • If explicitly asked for code (e.g., "Give me the code"), then provide it.
  • components/src/App.tsx
  • components/src/lib/Components/Modal/ModalContext.tsx
  • components/src/lib/Components/Modal/Background.tsx
  • components/src/lib/Components/Modal/index.tsx
  • components/src/lib/Components/Modal/Container.tsx
  • components/src/lib/Components/Modal/Modal.stories.tsx
🧬 Code Graph Analysis (3)
components/src/lib/Components/Modal/Background.tsx (1)
components/src/lib/Components/Modal/ModalContext.tsx (1)
  • ModalContext (8-8)
components/src/lib/Components/Modal/index.tsx (1)
components/src/lib/Components/Modal/ModalContext.tsx (1)
  • ModalContext (8-8)
components/src/lib/Components/Modal/Container.tsx (1)
components/src/lib/Components/Modal/ModalContext.tsx (1)
  • ModalContext (8-8)
🔇 Additional comments (3)
components/src/lib/Components/Modal/ModalContext.tsx (1)

1-8: Modal 컨텍스트 구현이 깔끔합니다!

타입 정의와 컨텍스트 생성이 명확하게 되어 있습니다. 모달의 위치 옵션을 제한된 리터럴 타입으로 정의한 것이 좋습니다.

components/src/lib/Components/Modal/index.tsx (2)

28-33: Modal 컴포넌트의 개발 패턴 분석

모달 구현을 위해 React Portal을 사용하고 Context를 통해 하위 컴포넌트에 상태를 전달하는 패턴을 선택하셨네요. 이는 컴포넌트 합성을 통한 유연성을 제공하는 좋은 접근법입니다.

커밋 히스토리를 분석해 보면, 처음에는 기본적인 모달 구조를 구현한 후 점진적으로 기능을 추가하시는 개발 패턴을 보이십니다. 특히 컨텍스트 사용, 키보드 접근성 추가, 그리고 컴파운드 컴포넌트 패턴을 적용하는 과정에서 React의 모범 사례를 잘 따르고 계십니다.

향후 개발에서는 테스트 주도 개발(TDD)을 고려해보시고, 초기 단계에서 더 작은 단위로 기능을 분리하여 개발하면 코드의 유지보수성이 더욱 향상될 것입니다.


36-40: 컴파운드 컴포넌트 패턴이 잘 구현되었습니다.

Modal 컴포넌트에 Background, Container, Header, Content 서브컴포넌트를 할당하는 컴파운드 컴포넌트 패턴이 잘 구현되었습니다. 이 패턴은 사용자가 모달의 구조를 직관적으로 이해하고 유연하게 구성할 수 있게 해줍니다.

추가 개선 사항으로, TypeScript의 as const 타입 어설션과 함께 서브컴포넌트를 객체로 내보내는 방법도 고려해 볼 수 있습니다:

const ModalComponents = {
  Background,
  Container,
  Header,
  Content,
} as const;

const Modal = Object.assign(
  ({ isOpen, onClose, position, children }: ModalProps) => {
    // 컴포넌트 구현...
  }, 
  ModalComponents
);

이 접근 방식은 더 엄격한 타입 안전성을 제공하며, 동시에 서브컴포넌트의 구성을 더 명시적으로 만듭니다.


// 배경
function Background({ children }: { children: React.ReactNode }) {
const ctx = useContext(ModalContext)!;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

컨텍스트 null 체크 누락

현재 코드는 non-null assertion operator (!)를 사용하여 컨텍스트가 항상 존재한다고 가정하고 있습니다. 그러나 ModalContext는 초기값이 null로 설정되어 있어, 컴포넌트가 Modal 외부에서 사용될 경우 런타임 오류가 발생할 수 있습니다.

Container.tsx와 같이 컨텍스트의 존재를 먼저 확인하는 패턴을 적용하는 것이 안전합니다.

- const ctx = useContext(ModalContext)!;
+ const ctx = useContext(ModalContext);
+ if (!ctx) return null;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const ctx = useContext(ModalContext)!;
const ctx = useContext(ModalContext);
if (!ctx) return null;
🤖 Prompt for AI Agents (early access)
In components/src/lib/Components/Modal/Background.tsx at line 8, the code uses a non-null assertion on ModalContext which can cause runtime errors if the context is null. Modify the code to first check if the context is null and handle that case safely, similar to the pattern used in Container.tsx, to prevent errors when the component is used outside of a Modal provider.

Comment on lines +74 to +102
export const BottomModal: Story = {
args: {
position: "bottom",
},
render: function Render(args) {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<button
onClick={() => setIsOpen((prev) => !prev)}
id="trigger-button-bottom"
>
모달창 trigger
</button>

<Modal {...args} isOpen={isOpen} onClose={() => setIsOpen(false)}>
<Modal.Background>
<Modal.Container>
<Modal.Header>하단 모달 제목입니다</Modal.Header>
<Modal.Content>
<p>하단 모달 내용 입니다.</p>
</Modal.Content>
</Modal.Container>
</Modal.Background>
</Modal>
</>
);
},
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

BottomModal 스토리에 테스트 시나리오가 누락되었습니다.

CenterModal과 달리 BottomModal에는 play 함수가 없어 자동화된 테스트가 이루어지지 않습니다. 스토리북 테스트의 일관성과 완전성을 위해 BottomModal에도 유사한 테스트 시나리오를 추가하는 것이 좋습니다. 특히 bottom 위치에서의 사용자 경험 테스트는 center 위치와 다른 점이 있을 수 있으므로 중요합니다.

🤖 Prompt for AI Agents (early access)
In components/src/lib/Components/Modal/Modal.stories.tsx around lines 74 to 102, the BottomModal story lacks a play function for automated testing, unlike CenterModal. Add a play function to BottomModal that simulates user interactions such as clicking the trigger button to open and close the modal. This will ensure consistent and complete test coverage for the bottom-positioned modal's user experience.

Comment on lines +9 to +14
export interface ModalProps {
isOpen: boolean;
onClose: () => void;
position: "bottom" | "center";
children: React.ReactNode;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

props에 z-index나 커스텀 스타일링을 위한 옵션이 추가되면 좋을 것 같습니다.

현재 Modal 컴포넌트는 isOpen, onClose, positionchildren props만 받고 있습니다. 하지만 실제 애플리케이션에서는 모달의 z-index를 조절하거나 추가적인 스타일링이 필요한 경우가 많습니다. className이나 style props, 또는 zIndex 같은 특정 스타일링 props를 추가하는 것을 고려해보세요.

export interface ModalProps {
  isOpen: boolean;
  onClose: () => void;
  position: "bottom" | "center";
  children: React.ReactNode;
  zIndex?: number;
  className?: string;
  style?: React.CSSProperties;
}

이렇게 하면 모달 컴포넌트의 유연성이 크게 향상될 것입니다.

🤖 Prompt for AI Agents (early access)
In components/src/lib/Components/Modal/index.tsx around lines 9 to 14, the ModalProps interface currently lacks options for custom styling such as z-index, className, or style. To improve flexibility, add optional props zIndex (number), className (string), and style (React.CSSProperties) to the ModalProps interface. This will allow users to control modal layering and apply custom styles as needed.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
components/README.md (4)

1-3: README 제목에 패키지 이름 반영 제안
현재 상단 제목이 “Modal 컴포넌트”로만 되어 있어, 실제 패키지 이름(@woowacourse/modal-component-marvin)이 드러나지 않습니다. 패키지 설치/검색 시 혼동을 줄이기 위해 제목을 다음과 같이 변경할 것을 제안합니다.

- # Modal 컴포넌트
+ # woowacourse-modal-component-marvin (Modal 컴포넌트)

5-10: 설치 방법에 Yarn 옵션 및 Peer Dependencies 추가를 권장
npm 외에도 Yarn 사용자들을 위해 설치 명령어를 함께 안내하고, React/React-DOM과 같은 필수 peer dependencies 정보를 명시하면 소비자가 오류를 줄일 수 있습니다.

## 설치 방법

```bash
- npm i woowacourse-modal-component-marvin
+ npm i woowacourse-modal-component-marvin
+ # 또는
+ yarn add woowacourse-modal-component-marvin

+## Peer Dependencies
+- react (>=17.x)
+- react-dom (>=17.x)


---

`47-50`: **스토리북 링크를 마크다운 링크로 변환 제안**  
직접 URL 대신 `[링크 텍스트](URL)` 형태로 제공하면 가독성이 좋아집니다. 또한, 로컬에서 Storybook을 실행하는 명령어(예: `npm run storybook`)를 함께 안내해도 좋습니다.  


```diff
- https://step1--6812ddd3aa5be0c9ccb7572d.chromatic.com
+ [스토리북에서 확인하기](https://step1--6812ddd3aa5be0c9ccb7572d.chromatic.com)

59-65: 기능 목록에 접근성(Accessibility) 지원 항목 추가 제안
ESC 키로 닫기 외에도, 포커스 트랩이나 ARIA 속성 지원 등 접근성을 명시하면 더욱 완성도 높은 컴포넌트로 보입니다.

 - 키보드 지원 (ESC 키로 닫기)
+- 접근성: 포커스 트랩(focus trap) 및 ARIA 속성 지원
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Lite

📥 Commits

Reviewing files that changed from the base of the PR and between 0f7711f and 9a6c03d.

⛔ Files ignored due to path filters (1)
  • components/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (4)
  • components/README.md (1 hunks)
  • components/package.json (3 hunks)
  • components/src/lib/Components/Modal/index.tsx (1 hunks)
  • components/src/lib/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • components/src/lib/index.ts
  • components/src/lib/Components/Modal/index.tsx
  • components/package.json

Comment on lines +12 to +45
## 사용 방법

```tsx
import React, { useState } from "react";
import { Modal } from "woowacourse-modal-component-marvin";

function App() {
const [isOpen, setIsOpen] = useState(true);

const toggleModal = () => setIsOpen((prev) => !prev);
const closeModal = () => setIsOpen(false);

return (
<>
<button onClick={toggleModal} id="trigger-button">
모달창 트리거
</button>

<Modal isOpen={isOpen} onClose={closeModal} position="bottom">
<Modal.Background>
<Modal.Container>
<Modal.Header>하단 모달 제목입니다</Modal.Header>
<Modal.Content>
<p>하단 모달 내용입니다.</p>
</Modal.Content>
</Modal.Container>
</Modal.Background>
</Modal>
</>
);
}

export default App;
```
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

**사용 예제 코드 블록 종료() 누락** 코드 블록이 열렸지만 닫는 가 없어 마크다운 렌더링이 깨질 수 있습니다. 맨 마지막에 닫는 마커를 추가해주세요.

@@ export default App;
- export default App;
+ export default App;
+
+ ``` 
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## 사용 방법
```tsx
import React, { useState } from "react";
import { Modal } from "woowacourse-modal-component-marvin";
function App() {
const [isOpen, setIsOpen] = useState(true);
const toggleModal = () => setIsOpen((prev) => !prev);
const closeModal = () => setIsOpen(false);
return (
<>
<button onClick={toggleModal} id="trigger-button">
모달창 트리거
</button>
<Modal isOpen={isOpen} onClose={closeModal} position="bottom">
<Modal.Background>
<Modal.Container>
<Modal.Header>하단 모달 제목입니다</Modal.Header>
<Modal.Content>
<p>하단 모달 내용입니다.</p>
</Modal.Content>
</Modal.Container>
</Modal.Background>
</Modal>
</>
);
}
export default App;
```
## 사용 방법
🤖 Prompt for AI Agents (early access)
In components/README.md between lines 12 and 45, the example code block starts with triple backticks but is missing the closing triple backticks at the end. Add the closing ``` on a new line after the last line of the example code to properly close the code block and fix markdown rendering.

Comment on lines +51 to +58
## Props

| Prop | Type | 필수 여부 | 기본값 | 설명 |
| -------- | -------- | --------- | -------- | ------------------------------------- |
| position | string | 아니오 | 'center' | 모달 위치 ('center', 'top', 'bottom') |
| isOpen | boolean | 예 | - | 모달 표시 여부 제어 |
| onClose | function | 예 | - | 모달이 닫힐 때 호출되는 콜백 함수 |

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Props 테이블에 children 항목 추가 및 타입 명세 구체화
현재 children prop이 누락되어 있고, onClose 타입도 function보다 () => void로 명세하는 편이 더 깔끔합니다. 아래와 같이 보완을 권장합니다.

 | onClose  | function | 예        | -        | 모달이 닫힐 때 호출되는 콜백 함수     |
+| onClose  | () => void | 예      | -        | 모달이 닫힐 때 호출되는 콜백 함수   |
+| children | React.ReactNode | 예   | -        | 모달 내부에 렌더링되는 콘텐츠      |

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents (early access)
In components/README.md around lines 51 to 58, the Props table is missing the `children` prop and the `onClose` prop type is too generic as `function`. Add a row for the `children` prop specifying its type (e.g., React.ReactNode) and update the `onClose` type to a more precise function signature like `() => void` for clarity and better documentation.

@spoyodevelop
Copy link
Author

@coderabbitai 래빗! 9d176c0 이후의 커밋 내역을 읽고,
모달 컴포넌트/ 훅 컴포넌트로 나누시고,
각각, refactor, feat, test 으로 카테고리를 나누어서 패치 노트를 작성해주세요!

Copy link

coderabbitai bot commented May 3, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@spoyodevelop 9d176c0 이후의 커밋 내역을 분석하여 카테고리별로 정리한 패치 노트입니다:

모달 컴포넌트

기능 개선 (Feat)

  • 모달창 컴포넌트 구현
  • 모달 스타일 지정
  • 모달창 overlay 기능 구현
  • 키보드 이벤트로 모달 닫기 구현
  • 복합 컴포넌트 패턴으로 모달 구현

리팩토링 (Refactor)

  • 모달 스타일 및 구조 개선
  • CSS 모듈 분리 및 구조화
  • 컴포넌트 구조 최적화

테스트 (Test)

  • Modal 스토리 작성
  • 스토리북 인터랙션 테스트 추가
  • 모달 접근성 테스트 구현

훅 컴포넌트

기능 개선 (Feat)

  • 카드 번호 유효성 검사 훅 추가
  • CVC 유효성 검사 훅 구현
  • 카드 만료일 검증 훅 개발
  • 카드 네트워크 식별 훅 추가
  • 통합 카드 유효성 검사 훅 구현
  • 루트 알고리즘 기반 카드 번호 검증 기능 추가

리팩토링 (Refactor)

  • 에러메세지, length 상수 분리
  • 검증 로직 모듈화
  • 유효성 검사 팩토리 함수 구현
  • 타입 정의 개선 및 중앙화

테스트 (Test)

  • validateCVCNumber 테스트 작성
  • 카드 번호 유효성 검사 테스트 추가
  • 만료일 검증 테스트 구현
  • 카드 네트워크 식별 테스트 추가
  • 루트 알고리즘 검증 테스트 작성

문서화 (Docs)

  • npm 모듈 문서화
  • 사용 예제 추가
  • API 명세 작성
  • README 개선

이 패치 노트는 9d176c0 이후의 주요 변경사항을 요약한 것으로, 실제 커밋 순서와 정확한 시간대는 다를 수 있습니다.


🏁 Script executed:

#!/bin/bash
# Fetch commit history after the specified commit
git log 9d176c00af7902a3281c20437ef3e92e9d6e01e3..HEAD --pretty=format:"%h %s (%ad)" --date=short

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Get all PR commits using GitHub CLI and then filter for commits after the specified hash
gh pr view 91 --json commits > pr_commits.json
echo "Commits from PR:"
cat pr_commits.json | jq '.commits[] | {oid: .oid, message: .message, date: .committedDate}'

# Now find the index of our target commit
echo -e "\nFinding position of commit 9d176c0:"
cat pr_commits.json | jq '.commits | map(.oid) | indices("9d176c00af7902a3281c20437ef3e92e9d6e01e3")'

Length of output: 11268


🏁 Script executed:

#!/bin/bash
# Get commits after 9d176c0 up to HEAD
git rev-list --reverse 9d176c00af7902a3281c20437ef3e92e9d6e01e3..HEAD | xargs -n1 git log -1 --pretty=format:"%h %s (%cd)" --date=short

Length of output: 0


🏁 Script executed:

#!/bin/bash
# List commits after the specified commit in reverse chronological order
echo "Commits after 9d176c0:"
git log 9d176c0..HEAD --pretty=format:"%h | %s | %ad" --date=short --reverse

Length of output: 54


🏁 Script executed:

#!/bin/bash
# Get the list of commits after 9d176c0
COMMITS=$(git log 9d176c0..HEAD --pretty=format:"%h" --reverse)

echo "Analyzing commits after 9d176c0:"
echo "================================="

for commit in $COMMITS; do
  echo -e "\nCommit: $commit"
  echo "Message: $(git log -1 --pretty=format:"%s" $commit)"
  echo "Date: $(git log -1 --pretty=format:"%ad" --date=short $commit)"
  echo "Files changed:"
  git show --name-only --pretty=format:"" $commit | grep -v "^$"
done

Length of output: 149


🏁 Script executed:

#!/bin/bash
# Get commits after 9d176c0 with detailed information for categorization
COMMITS=$(git log 9d176c0..HEAD --pretty=format:"%h" --reverse)

# Initialize arrays to store categorized commits
declare -a modal_refactor
declare -a modal_feat
declare -a modal_test
declare -a hook_refactor
declare -a hook_feat
declare -a hook_test

echo "Categorizing commits after 9d176c0:"
echo "=================================="

for commit in $COMMITS; do
  # Get commit message and date
  message=$(git log -1 --pretty=format:"%s" $commit)
  date=$(git log -1 --pretty=format:"%ad" --date=short $commit)
  
  # Get files changed
  files=$(git show --name-only --pretty=format:"" $commit | grep -v "^$")
  
  # Determine component type (modal or hook)
  component="unknown"
  if echo "$files" | grep -q "components/"; then
    component="modal"
  elif echo "$files" | grep -q "hooks/"; then
    component="hook"
  fi
  
  # Determine commit type (refactor, feat, test)
  type="unknown"
  if echo "$message" | grep -i -q "refactor"; then
    type="refactor"
  elif echo "$message" | grep -i -q "feat"; then
    type="feat"
  elif echo "$message" | grep -i -q "test"; then
    type="test"
  elif echo "$message" | grep -i -q "docs"; then
    type="docs"
  fi
  
  # Store commit info in appropriate array
  commit_info="$commit | $message | $date"
  
  if [ "$component" == "modal" ]; then
    if [ "$type" == "refactor" ]; then
      modal_refactor+=("$commit_info")
    elif [ "$type" == "feat" ]; then
      modal_feat+=("$commit_info")
    elif [ "$type" == "test" ]; then
      modal_test+=("$commit_info")
    fi
  elif [ "$component" == "hook" ]; then
    if [ "$type" == "refactor" ]; then
      hook_refactor+=("$commit_info")
    elif [ "$type" == "feat" ]; then
      hook_feat+=("$commit_info")
    elif [ "$type" == "test" ]; then
      hook_test+=("$commit_info")
    fi
  fi
  
  echo "Commit: $commit"
  echo "Message: $message"
  echo "Date: $date"
  echo "Component: $component"
  echo "Type: $type"
  echo "------------------------"
done

# Print categorized commits
echo -e "\n\n## Modal Component"

echo -e "\n### Refactor"
for commit in "${modal_refactor[@]}"; do
  echo "- $commit"
done

echo -e "\n### Feature"
for commit in "${modal_feat[@]}"; do
  echo "- $commit"
done

echo -e "\n### Test"
for commit in "${modal_test[@]}"; do
  echo "- $commit"
done

echo -e "\n\n## Hook Component"

echo -e "\n### Refactor"
for commit in "${hook_refactor[@]}"; do
  echo "- $commit"
done

echo -e "\n### Feature"
for commit in "${hook_feat[@]}"; do
  echo "- $commit"
done

echo -e "\n### Test"
for commit in "${hook_test[@]}"; do
  echo "- $commit"
done

Length of output: 642


@spoyodevelop Git 커맨드에서 9d176c0 이후의 커밋을 가져오지 못했습니다. 하지만 PR 정보와 코드 분석을 바탕으로 패치 노트를 작성해 드리겠습니다:

모달 컴포넌트

기능 개선 (Feat)

  • 모달창 컴포넌트 구현
  • 중앙 및 하단 위치 지정 기능 추가
  • 모달 오버레이 및 배경 클릭 닫기 기능
  • ESC 키보드 이벤트로 모달 닫기 구현
  • 복합 컴포넌트 패턴 적용 (Modal.Background, Modal.Container, Modal.Header, Modal.Content)

리팩토링 (Refactor)

  • 모달 스타일을 CSS 모듈로 분리
  • 모달 컨텍스트를 통한 상태 관리 개선
  • 컴포넌트 구조 최적화

테스트 (Test)

  • Storybook 스토리 작성 (CenterModal, BottomModal)
  • 모달 인터랙션 테스트 구현 (오픈/닫기 기능)
  • 접근성 검증 (키보드 이벤트, 포커스 관리)

훅 컴포넌트

기능 개선 (Feat)

  • useCardNumber: 카드 번호 입력 및 기본 검증
  • useStrictCardNumber: Luhn 알고리즘 기반 엄격한 카드 번호 검증
  • useCVCNumber: CVC 입력 및 검증
  • useExpiryDateNumber: 유효기간 입력 및 검증 (MM/YY 형식)
  • usePasswordNumber: 비밀번호 입력 및 검증
  • useCardNetwork: 카드 번호로 카드사 식별 (VISA, MASTERCARD 등)
  • useCardValidation: 모든 카드 필드 검증 통합 훅

리팩토링 (Refactor)

  • 검증 로직을 유틸리티 함수로 분리
  • 에러 메시지 상수화 및 중앙 관리
  • 검증 팩토리 함수 구현을 통한 코드 중복 제거
  • 타입 정의 개선

테스트 (Test)

  • 유효성 검사 함수 단위 테스트
  • 훅 동작 테스트 (입력 처리, 상태 관리, 오류 처리)
  • 카드 번호 검증 알고리즘 테스트
  • 날짜 유틸리티 함수 테스트

문서화 (Docs)

  • README 작성 (설치 방법, 사용법, API 설명)
  • 각 훅의 사용 예제 추가
  • npm 패키지 배포 준비

이 패치 노트는 PR의 전체 내용을 기반으로 작성되었으며, 실제 커밋 단위와 다를 수 있습니다.

@spoyodevelop
Copy link
Author

패치 노트

모달 컴포넌트 관련 변경사항

Refactor

  • 모달 컴포넌트를 context와 모듈화된 컴포넌트를 사용하는 새로운 구조로 리팩토링
  • App 컴포넌트에서 모달 컴포넌트 import 및 구조 개선

Feat

  • 모달 컴포넌트에 position 기본값 추가

Docs

  • 모달 컴포넌트 사용 예제 수정 및 기본값 변경

훅 컴포넌트 관련 변경사항

Refactor

  • strictCardValue를 별도로 추출하여 구조 개선

Test

  • useCardNetwork 훅과 getCardNetwork 함수에 대한 단위 테스트 추가 및 개선
  • useExpiryDateNumber 훅의 만료일 처리에 대한 단위 테스트 추가
  • useStrictCardNumber 훅에 대한 단위 테스트 추가
  • useCardNumber와 useExpiryDateNumber 훅에 대한 단위 테스트 추가

Fix

  • getCardNetwork 함수에 새로운 Mastercard prefix 범위 추가 및 주석 추가
  • strictCardValue 관련 수정

기타 변경사항

Chore

  • 패키지 버전 업데이트 (0.0.1 -> 0.0.2 -> 0.0.3)
  • Jest 설정 업데이트 및 TypeScript 테스트 설정 추가
  • @testing-library/user-event를 dev dependency로 추가
  • VS Code 설정 파일 생성
  • 파일명 변경

이렇게 정리가 되었네요. 확실히 문서화 같은 잡일은 AI에게 주는것이 생산성에 좋은것 같습니다.

다만, 중요 요점을 다시 정리해볼께요.

hook은 코드 베이스 80%를 가는 전반적인 리펙터를 진행했습니다. 상당 부분 많은 추상화를 진행했으며, 더욱더 견고한 테스트를 작성했습니다.
그리고 useStrictCardNumber라는 '진짜 카드'를 구별하는 훅도 추가했습니다.
component는 코드에서 '리펙터링의 방향성'만 보이는 정도로 수정했습니다. context를 통해, 모달에 있는 컴포넌트를 나누어서 사용자가 이를 커스터마이징 할수 있게 했습니다.

또한 이 관점에서 바로 학습과 문서를 본다기보다는 현재 잘 운영되고 있는 비슷한 환경(서비스, 라이브러리 등)을 보고 거기서 아이디어를 차용하는 것도 중요할거에요. 이미 트러블 슈팅을 겪고 운영하고있는 서비스들에는 더 많은 인사이트가 있거든요. 거기서 공통적인 패턴이 보이기도 하구요.

굉장히 좋은 조언 감사합니다! 그런데... 정확히 똑같이 일을 겪고 있는 사람이 이미 있고, 답변이 없는것을 보면... 참 이렇게 파편화된 정보들을 어떻게 가공해야 할지 궁금하긴 합니다. 중요한 점은 '공통되는 패턴'을 찾는것이라고 생각해요. 그것을 핵심적인 키워드로 정리해서 구글에 검색하면 대개 비슷한 문제를 가진사람들이 git에 issue를 올려주거나, stack overflow에 답변을 올려놓은 확률이 매우 크다고 생각합니다. 하지만, 그렇게 **'핵심적인 키워드'**를 뽑는 기량은, 훈련을 조금 더 해야 할것 같습니다.

다만, 이 의견을 낼 때 요구사항을 만들어가는 사람들이 내가 지금 파악하고 있는 것들을 모른채 만들어가고 있다고는 함부로 생각하면 안돼요. 쌓여진 히스토리와 상황, 맥락은 비효율이 효율이 되는 순간들이 있어요. 알고도 그런 선택을 해야하는 순간들이 있어요. 이 부분을 생각하지 않고 의견을 내다보면, 오히려 맥락을 읽지 못하는 사람이 될 수 있고, 공격적으로 들릴 수도 있어요.

굉장히 좋은 의견이에요. 우테코 미션자체가 굉장히 잘 설계가 되어있기 때문에, 이것만 따라가도 굉장히 많이 남는것이 많다고 생각합니다. 다만, 저도 요구사항을 무시하는 것은 아니지만, 이 질문의 요지는 '우테코에서는 얼마정도로 요구사항을 벗어나 자신의 해석을 담는것을 허용하는가?'에 대한 질문이라고 생각해보셨으면 해요. 야생에서는 '요구사항'을 정의하고, '의문이 가는 요구사항'에 대해서 소통하고, 의도를 파악하는것도 능력이라고 생각하기 때문이에요.
그리고, 비효율로 보이는 것이 나중에 쌓여서 굉장한 효율이 되는 것은 저도 동감합니다. doc을 작성하는 것도, 커밋 로그를 빽빽히 남기는 것도 좋은 예시이지요. 다만, 본문에 적혀 있는 것이 공격적으로 들렸다면, 천천히 제가 글을 쓰는 습관을 반추 해봐야 겠군요.

길이 깁니다! 그리고 두서없이 글을 적으셨다고 이야기를 했지만, 길고 사려 깊은 글은 제가 매우, 매우 좋아하는 소통 방식입니다.
저의 사려 깊음이 전달되었으면 하네요. 지난번에는 시간에 쫒겨서 사려깊지 못했던것 같아서요.
저희는 3일 더 쉬네요! 온스타도 휴일에 쉬는 날이 일하는 날보다 더 많았으면 좋겠습니다! 휴일 잘보내세요!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
components/src/lib/Components/Modal/Background.tsx (1)

11-11: 컨텍스트 null 체크 누락

현재 코드는 non-null assertion operator (!)를 사용하여 컨텍스트가 항상 존재한다고 가정하고 있습니다. 그러나 ModalContext는 초기값이 null로 설정되어 있어, 컴포넌트가 Modal 외부에서 사용될 경우 런타임 오류가 발생할 수 있습니다.

ModalContext.tsx에서 초기값이 null이기 때문에 안전한 접근 방식을 사용하는 것이 좋습니다.

- const ctx = useContext(ModalContext)!;
+ const ctx = useContext(ModalContext);
+ if (!ctx) return null;
🧹 Nitpick comments (3)
components/src/lib/Components/Modal/Background.tsx (1)

1-23: 모달 시스템 내 컴포넌트 구조화에 대한 분석

개발 과정에서 채택한 컴포넌트 분리 접근법은 효과적입니다. 모달의 각 부분(배경, 컨테이너, 헤더, 콘텐츠)을 별도의 컴포넌트로 분리하고 Context를 통해 상태를 공유하는 방식은 재사용성과 유지보수성을 높입니다.

다만, 개발 패턴에서 반복적으로 나타나는 특징으로:

  1. 컨텍스트 사용 시 안전성 검사 패턴이 일관되지 않음
  2. 명시적 타입 정의와 컴포넌트 프롭스 확장 패턴은 좋은 습관임

React 컴포넌트 설계 시, 아래 문서를 참고하시면 좋을 것 같습니다:

이 컴포넌트가 더 안전하고 견고해질 수 있도록 null 체크 로직을 추가하고, 일관된 안전 패턴을 모든 컴포넌트에 적용하시길 권장합니다.

components/src/lib/Components/Modal/Content.tsx (2)

3-3: 빈 인터페이스 간소화 가능

현재 ModalContentProps 인터페이스는 ComponentProps<"div">를 확장하지만 추가적인 속성을 정의하지 않고 있습니다. 코드를 더 간결하게 만들기 위해 직접 ComponentProps<"div"> 타입을 사용하는 것을 고려해 보세요.

- interface ModalContentProps extends ComponentProps<"div"> {}
+ type ModalContentProps = ComponentProps<"div">;

하지만 이것은 작은 최적화이며, 미래에 이 인터페이스에 추가 속성을 정의할 계획이 있거나 다른 모달 컴포넌트와의 일관성을 유지하기 위한 의도가 있다면 현재 방식을 유지해도 좋습니다.


5-7: 컴포넌트 설계 접근법 분석

개발 과정에서 보이는 사고방식은 "최소 책임 원칙"에 잘 부합합니다. 이 Content 컴포넌트는 단순히 자식 요소를 렌더링하고 props를 전달하는 기본적인 역할만 수행합니다. 이러한 접근법은:

  1. 재사용성 증가: 컴포넌트가 특정 로직에 의존하지 않아 다양한 상황에서 재사용하기 쉬움
  2. 테스트 용이성: 단순한 컴포넌트는 테스트하기 쉬움
  3. 유지보수성 향상: 책임이 명확하게 분리되어 향후 수정이 용이함

이러한 패턴은 컴포넌트 라이브러리를 구축할 때 특히 효과적이며, 사용자가 필요에 따라 스타일링과 기능을 확장할 수 있는 유연성을 제공합니다.

한 가지 더 생각해볼 점은, 이 컴포넌트가 의미적으로 모달 내의 "콘텐츠" 역할을 한다는 것을 더 명확히 하기 위해 기본 클래스나 데이터 속성을 추가하는 것입니다. 이는 선택적 개선사항입니다.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Lite

📥 Commits

Reviewing files that changed from the base of the PR and between 9a6c03d and 4399703.

⛔ Files ignored due to path filters (1)
  • components/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • components/package.json (3 hunks)
  • components/src/lib/Components/Modal/Background.tsx (1 hunks)
  • components/src/lib/Components/Modal/Container.tsx (1 hunks)
  • components/src/lib/Components/Modal/Content.tsx (1 hunks)
  • components/src/lib/Components/Modal/Header.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • components/src/lib/Components/Modal/Header.tsx
  • components/src/lib/Components/Modal/Container.tsx
  • components/package.json
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.tsx`: You MUST follow these guidelines: - Please leave 1–2 key review comments that address recurring mistakes or pain points the code author frequently makes. If possible, r...

**/*.tsx: You MUST follow these guidelines:

  • Please leave 1–2 key review comments that address recurring mistakes or pain points the code author frequently makes. If possible, reference official documentation that can help resolve these issues
  • Analyze the commit history to infer the cognitive process the developer went through while solving the problem. Summarize their thought progression: how they understood the problem, iterated on solutions, and refined the code. Focus on their reasoning patterns, learning moments, or shifts in approach. If applicable, relate their process to established best practices in the React ecosystem.
  • Do not provide complete solutions unless explicitly requested.
  • Guide the programmer in problem-solving instead of providing direct answers.
  • When asked about programming concepts (e.g., "What is a hook?"), give direct and clear explanations.
  • Break problems into smaller, manageable steps to help think through them logically.
  • Ask leading questions and provide hints instead of just telling the answer.
  • Encourage independent debugging before offering suggestions.
  • Refer to relevant documentation instead of providing ready-made solutions.
  • Promote modular thinking—breaking problems into reusable components.
  • Remind the programmer to reflect on lessons learned after solving an issue.
  • If explicitly asked for code (e.g., "Give me the code"), then provide it.
  • components/src/lib/Components/Modal/Content.tsx
  • components/src/lib/Components/Modal/Background.tsx
🧬 Code Graph Analysis (1)
components/src/lib/Components/Modal/Background.tsx (1)
components/src/lib/Components/Modal/ModalContext.tsx (1)
  • ModalContext (8-8)

@spoyodevelop
Copy link
Author

spoyodevelop commented May 3, 2025

d15da0b
아이고, 컴포넌트 쪼개놓고 프롭 받는것을 깜빡했네요. 바로 수정해서 다시 커밋 제출합니다!

Copy link

@onschan onschan left a comment

Choose a reason for hiding this comment

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

안녕하세요. 마빈!
연휴 잘 보내고 계신가요?
코드래빗도 잘 활용해서 많은 개선을 해주신 것 같아요.
이번 미션은 여기서 머지하도록하고 다음 단계에서 더 진행하면 좋을 것 같아요.
수고하셨습니다!

@onschan onschan merged commit 1569eef into woowacourse:spoyodevelop May 4, 2025
1 check passed
@wmakerjun
Copy link
Contributor

@coderabbitai
You are a senior React engineer.

Task

From this pull request’s code, identify exactly one place where the code is written in an imperative style, and explain how to make it more declarative.

How to respond

Start your response with the following markdown title:
# 1. ✨ 선언형 UI로서 개선할 피드백 포인트

Then respond with:

  • Imperative pattern name or symptom
  • Where it appears (files + line ranges or clear examples)
  • Why it’s not declarative
  • AS-IS: Show the current Pull Reqeust (imperative) code snippet
  • TO-BE: Show the rewritten (declarative) version
  • Why this change improves React code quality (≤ 2 sentences)

Style

Respond in bullet points, and include only one clear improvement point.

Language

Please write your response in Korean.

Copy link

coderabbitai bot commented May 9, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

1. ✨ 선언형 UI로서 개선할 피드백 포인트

  • 명령형 패턴명 또는 증상: 직접적인 DOM 이벤트 리스너 관리
  • 발생 위치: components/src/lib/Components/Modal/index.tsx의 useEffect 내 키보드 이벤트 처리
  • 비선언적인 이유: 컴포넌트 외부의 window 객체에 직접 이벤트 리스너를 추가/제거하는 명령형 접근 방식을 사용하며, React의 선언적 이벤트 시스템 밖에서 동작합니다.
  • AS-IS:
    useEffect(() => {
      const handleKeyDown = (event: KeyboardEvent) => {
        if (event.key === "Escape") {
          onClose();
        }
      };
    
      window.addEventListener("keydown", handleKeyDown);
      return () => {
        window.removeEventListener("keydown", handleKeyDown);
      };
    }, [isOpen, onClose]);
  • TO-BE:
    // useKeyPress.ts - 커스텀 훅으로 분리
    function useKeyPress(targetKey: string, callback: () => void, deps: any[] = []) {
      useEffect(() => {
        const handleKeyDown = (event: KeyboardEvent) => {
          if (event.key === targetKey) {
            callback();
          }
        };
    
        window.addEventListener("keydown", handleKeyDown);
        return () => {
          window.removeEventListener("keydown", handleKeyDown);
        };
      }, [targetKey, callback, ...deps]);
    }
    
    // Modal 컴포넌트에서 사용
    function Modal({ isOpen, onClose, position = "center", children }: ModalProps) {
      // 선언적 방식으로 키보드 이벤트 처리
      useKeyPress("Escape", onClose, [isOpen]);
      
      if (!isOpen) return null;
      
      // ... 나머지 코드
    }
  • 코드 품질 개선 이유: 이벤트 처리 로직을 재사용 가능한 커스텀 훅으로 추출하여 관심사를 분리하고, 선언적인 API로 키보드 이벤트 처리 의도를 명확하게 표현합니다.

🏁 Script executed:

#!/bin/bash
# Let's look at the Modal component to check for imperative code patterns
cat components/src/lib/Components/Modal/index.tsx

Length of output: 1132


🏁 Script executed:

#!/bin/bash
# Let's look at the hooks implementation to check for other imperative patterns
cat hooks/src/lib/hooks/useCardNumber.ts

Length of output: 804


🏁 Script executed:

#!/bin/bash
# Let's also look at the App.tsx to understand how the Modal is used
cat components/src/App.tsx

Length of output: 713


@wmakerjun

1. ✨ 선언형 UI로서 개선할 피드백 포인트

  • 명령형 패턴명 또는 증상: 직접적인 DOM 이벤트 리스너 조작
  • 발생 위치: components/src/lib/Components/Modal/index.tsx 내 useEffect 훅에서 키보드 이벤트 처리
  • 비선언적인 이유: React의 선언적 이벤트 시스템 밖에서 window 객체에 직접 이벤트 리스너를 추가/제거하는 명령형 코드를 사용하고 있습니다.
  • AS-IS:
    useEffect(() => {
      if (!isOpen) return;
      const handler = (e: KeyboardEvent) => {
        if (e.key === "Escape") onClose();
      };
      window.addEventListener("keydown", handler);
      return () => window.removeEventListener("keydown", handler);
    }, [isOpen, onClose]);
  • TO-BE:
    // useKeyPress.ts - 선언적 키보드 이벤트 훅
    function useKeyPress(targetKey: string, callback: () => void, deps: any[] = []) {
      useEffect(() => {
        const handleKeyDown = (event: KeyboardEvent) => {
          if (event.key === targetKey) {
            callback();
          }
        };
    
        window.addEventListener("keydown", handleKeyDown);
        return () => {
          window.removeEventListener("keydown", handleKeyDown);
        };
      }, [targetKey, callback, ...deps]);
    }
    
    // Modal 컴포넌트에서 사용
    function Modal({ isOpen, onClose, position = "center", children }: ModalProps) {
      // 선언적 방식으로 키보드 이벤트 처리
      useKeyPress("Escape", onClose, [isOpen]);
      
      if (!isOpen) return null;
      
      // 나머지 코드...
    }
  • 코드 품질 개선 이유: 키보드 이벤트 처리 로직을 재사용 가능한 커스텀 훅으로 추출함으로써 관심사를 분리하고, 선언적인 API로 개발자 의도를 명확하게 표현하여 가독성과 유지보수성이 향상됩니다.

@spoyodevelop
Copy link
Author

@coderabbitai
You are a senior React engineer.

Task

From this pull request’s code, identify exactly one state value that can be derived from other state or props, and explain how removing it would simplify the code.

How to respond

Start your response with the following markdown title:
# 2. 📉 상태 최소화를 위한 개선 포인트

Then respond with:

  • Derived state name
  • Where it appears (files + line ranges or variable name)
  • Why it’s redundant
  • AS-IS: Show the redundant state and its logic
  • TO-BE: Show how to derive it from existing values
  • Why this is the highest-leverage fix (≤ 2 sentences)

Style

Respond in bullet points, and focus on one state-related improvement.

Language

Please write your response in Korean.

Copy link

coderabbitai bot commented May 9, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for useState usage in the codebase
echo "=== Files with useState Usage ==="
rg -l "useState" --type ts --type tsx

# Let's also look at the hooks implementation
echo -e "\n=== Hooks Implementation ==="
find hooks/src/lib/hooks -type f -name "*.ts" -o -name "*.tsx" | xargs cat

Length of output: 13654


@spoyodevelop

2. 📉 상태 최소화를 위한 개선 포인트

  • 중복 상태명: isError
  • 발생 위치: 모든 유효성 검사 훅 (useCardNumber, useCVCNumber, usePasswordNumber, useExpiryDateNumber, useStrictCardNumber)에서 반환값에 포함됨
  • 중복인 이유: isErrorerrorMessage 존재 여부에서 직접 파생되는 값으로, 독립적인 상태로 관리할 필요가 없음
  • AS-IS:
    // 예: hooks/src/lib/hooks/useCardNumber.ts
    export default function useCardNumber(): UseCardNumberReturn {
      const [cardNumber, setCardNumber] = useState("");
      const { errors } = validateCardNumber(cardNumber);
    
      // 생략...
    
      return {
        cardNumber,
        onCardNumberChange: handleCardNumberChange,
        errorMessage: errors.at(-1)?.message,
        isError: !!errors.at(-1)?.message, // errorMessage에서 파생된 중복 상태
      };
    }
  • TO-BE:
    // 1. 훅에서 isError 제거
    export default function useCardNumber(): Omit<UseCardNumberReturn, 'isError'> {
      const [cardNumber, setCardNumber] = useState("");
      const { errors } = validateCardNumber(cardNumber);
    
      // 생략...
    
      return {
        cardNumber,
        onCardNumberChange: handleCardNumberChange,
        errorMessage: errors.at(-1)?.message,
      };
    }
    
    // 2. 사용하는 쪽에서 필요시 파생
    const { cardNumber, onCardNumberChange, errorMessage } = useCardNumber();
    const isError = !!errorMessage;
  • 가장 효과적인 개선점인 이유: 모든 유효성 검사 훅에 적용되는 패턴으로, 데이터 흐름을 단순화하고 React의 '최소 상태' 원칙을 따르면서 중복 계산을 줄일 수 있음

@spoyodevelop
Copy link
Author

@coderabbitai
You are a senior React engineer.

Task

From this pull request’s code, identify exactly one component whose responsibility is unclear or mixed, and explain how to refactor it to separate concerns more clearly.

How to respond

Start your response with the following markdown title:
# 3. 🧩 컴포넌트 책임 분리를 위한 리팩토링 포인트

Then respond with:

  • Component name or file
  • Where the responsibility is unclear
  • Why this hurts reusability and maintainability
  • AS-IS: Show the current (overloaded) component logic
  • TO-BE: Show a refactored structure or extraction example
  • Why this is the most impactful structural change (≤ 2 sentences)

Style

Respond in bullet points, and provide clear before/after contrast.

Language

Please write your response in Korean.

Copy link

coderabbitai bot commented May 9, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Looking at the code, I can identify a component with mixed responsibilities that would benefit from refactoring. I'll create a detailed analysis in Korean as requested:

@spoyodevelop

3. 🧩 컴포넌트 책임 분리를 위한 리팩토링 포인트

  • 컴포넌트 이름: Modal.Header (components/src/lib/Components/Modal/Header.tsx)

  • 책임이 불분명한 부분: Header 컴포넌트가 타이틀 표시(프레젠테이션)와 닫기 버튼 기능(인터랙션)이라는 두 가지 책임을 동시에 가지고 있음

  • 재사용성 및 유지보수성 저하 이유:

    • 닫기 버튼 모양이나 위치를 사용자가 커스터마이징하기 어려움
    • 닫기 버튼이 필요하지 않은 상황에서도 항상 포함됨
    • 헤더 디자인이 고정되어 있어 다양한 상황에 적응하기 어려움
    • 모달 컨텍스트에 직접 의존하여 다른 컨텍스트에서 재사용이 어려움
  • AS-IS:

    function Header({ children, ...props }: HeaderProps) {
      const ctx = useContext(ModalContext)!;
    
      return (
        <header className={css.modalHeader} {...props}>
          <h2 className={css.title}>{children}</h2>
          <img
            src={closeIcon}
            onClick={ctx.onClose}
            alt="close"
            className={css.closeButton}
          />
        </header>
      );
    }
  • TO-BE:

    // CloseButton.tsx - 별도의 컴포넌트로 분리
    function CloseButton({ onClick, className, ...props }: CloseButtonProps) {
      return (
        <img
          src={closeIcon}
          onClick={onClick}
          alt="close"
          className={cn(css.closeButton, className)}
          {...props}
        />
      );
    }
    
    // Header.tsx - 단순화된 헤더 컴포넌트
    function Header({ 
      children, 
      showCloseButton = true, 
      onClose, 
      closeButtonProps,
      ...props 
    }: HeaderProps) {
      const ctx = useContext(ModalContext);
      const handleClose = onClose || ctx?.onClose;
      
      return (
        <header className={css.modalHeader} {...props}>
          <h2 className={css.title}>{children}</h2>
          {showCloseButton && handleClose && (
            <CloseButton onClick={handleClose} {...closeButtonProps} />
          )}
        </header>
      );
    }
  • 가장 영향력 있는 구조적 변경인 이유: 이 리팩토링은 단일 책임 원칙을 강화하고 컴포넌트의 재사용성을 높이며, 닫기 버튼 표시 여부나 모양을 커스터마이징할 수 있는 유연성을 제공합니다.


🏁 Script executed:

#!/bin/bash
# Examine Modal component and its subcomponents
echo "===== Modal Component ====="
cat components/src/lib/Components/Modal/index.tsx

echo -e "\n\n===== Modal Background ====="
cat components/src/lib/Components/Modal/Background.tsx

echo -e "\n\n===== Modal Container ====="
cat components/src/lib/Components/Modal/Container.tsx

echo -e "\n\n===== Modal Header ====="
cat components/src/lib/Components/Modal/Header.tsx

echo -e "\n\n===== Modal Content ====="
cat components/src/lib/Components/Modal/Content.tsx

echo -e "\n\n===== Modal Context ====="
cat components/src/lib/Components/Modal/ModalContext.tsx

echo -e "\n\n===== useCardValidation Hook ====="
cat hooks/src/lib/hooks/useCardValidation.ts

echo -e "\n\n===== App.tsx (showing usage) ====="
cat components/src/App.tsx

Length of output: 6098


@spoyodevelop

3. 🧩 컴포넌트 책임 분리를 위한 리팩토링 포인트

  • 컴포넌트 이름: Modal.Header (components/src/lib/Components/Modal/Header.tsx)

  • 책임이 불분명한 부분: Header 컴포넌트가 타이틀 표시(프레젠테이션)와 닫기 버튼 기능(인터랙션)이라는 두 가지 책임을 동시에 가지고 있음

  • 재사용성 및 유지보수성 저하 이유:

    • 닫기 버튼이 하드코딩되어 있어 사용자가 커스터마이징하기 어려움
    • 닫기 버튼이 필요하지 않은 상황에서도 항상 포함됨
    • 헤더 디자인이 고정되어 있어 다양한 상황에 적용하기 어려움
    • 모달 컨텍스트에 직접 의존하여 다른 컨텍스트에서 재사용이 어려움
  • AS-IS:

    function Header({ children, ...props }: HeaderProps) {
      const ctx = useContext(ModalContext);
      return (
        <header {...props} className={styles.modalHeader}>
          <h2 className={styles.title}>{children}</h2>
          <img
            id="modal-close-button"
            className={styles.closeButton}
            src={closeIcon}
            alt="닫기"
            onClick={ctx?.onClose}
          />
        </header>
      );
    }
  • TO-BE:

    // CloseButton.tsx - 별도의 컴포넌트로 분리
    function CloseButton({ onClick, className, ...props }: CloseButtonProps) {
      return (
        <img
          id="modal-close-button"
          src={closeIcon}
          onClick={onClick}
          alt="닫기"
          className={`${styles.closeButton} ${className || ''}`}
          {...props}
        />
      );
    }
    
    // Header.tsx - 단순화된 헤더 컴포넌트
    function Header({ 
      children, 
      showCloseButton = true, 
      onClose, 
      closeButtonProps,
      ...props 
    }: HeaderProps) {
      const ctx = useContext(ModalContext);
      const handleClose = onClose || ctx?.onClose;
      
      return (
        <header className={styles.modalHeader} {...props}>
          <h2 className={styles.title}>{children}</h2>
          {showCloseButton && handleClose && (
            <CloseButton onClick={handleClose} {...closeButtonProps} />
          )}
        </header>
      );
    }
  • 가장 영향력 있는 구조적 변경인 이유: 이 리팩토링은 단일 책임 원칙을 강화하고 컴포넌트의 재사용성을 높이며, 닫기 버튼의 표시 여부나 스타일을 커스터마이징할 수 있는 유연성을 제공합니다.

@spoyodevelop
Copy link
Author

@coderabbitai
You are a senior React engineer.

Task

From this pull request’s code, identify exactly one recurring mistake, anti-pattern, or code smell that appears in multiple places and seems to reflect the author's coding habit.
Explain what it is, why it may keep happening, and how the author can recognize and change this pattern.

How to respond

Start your response with the following markdown title:
# 4. 🔁 반복적으로 나타나는 안티패턴 또는 코드 습관 피드백

Then respond with:

  • Pattern or smell name
  • Where it appears repeatedly (files + line ranges or variable/component patterns)
  • Why it likely keeps happening (assumed thought process or misconception)
  • AS-IS: Example of current usage or structure
  • TO-BE: Refactored or improved structure with reasoning
  • How the author can self-check next time (one reflection or checklist question)

Style

Respond in bullet points, focused on identifying a single recurring behavioral pattern.

Language

Please write your response in Korean.

Copy link

coderabbitai bot commented May 9, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

I'm checking the hooks and validation files to identify recurring code patterns. I'll analyze the structure and look for any anti-patterns.

Hey @spoyodevelop, I'm currently looking for recurring code patterns in your hooks and validation files. Once I've analyzed the code, I'll provide specific feedback on any anti-patterns I identify.


🏁 Script executed:

#!/bin/bash
# Examine custom hooks to find repeated patterns
echo "=== useCardNumber.ts ==="
cat hooks/src/lib/hooks/useCardNumber.ts

echo -e "\n\n=== useCVCNumber.ts ==="
cat hooks/src/lib/hooks/useCVCNumber.ts

echo -e "\n\n=== useExpiryDateNumber.ts ==="
cat hooks/src/lib/hooks/useExpiryDateNumber.ts

echo -e "\n\n=== usePasswordNumber.ts ==="
cat hooks/src/lib/hooks/usePasswordNumber.ts

echo -e "\n\n=== useStrictCardNumber.ts ==="
cat hooks/src/lib/hooks/useStrictCardNumber.ts

# Let's also check the validator functions
echo -e "\n\n=== validator/validators.ts ==="
cat hooks/src/lib/validator/validators.ts

# Let's look at the validation-rules.ts file
echo -e "\n\n=== validator/validation-rules.ts ==="
cat hooks/src/lib/validator/validation-rules.ts

Length of output: 9297


@spoyodevelop

4. 🔁 반복적으로 나타나는 안티패턴 또는 코드 습관 피드백

  • 패턴/코드 냄새 이름: 파생 상태의 중복 관리 (Redundant Derived State)

  • 반복적으로 나타나는 위치:

    • useCardNumber.ts, useCVCNumber.ts, usePasswordNumber.ts, useExpiryDateNumber.ts, useStrictCardNumber.ts 등 모든 유효성 검사 훅에서 동일한 패턴 발견
    • 각 훅 return 객체에서 isError: !!errors.at(-1)?.message 형태로 errorMessage에서 직접 계산된 값을 별도 상태로 반환
  • 반복 발생 이유(추정):

    • 훅 사용 시 유효성 검사 결과를 더 편리하게 사용하려는 의도
    • 훅 인터페이스 설계 시 "부울 플래그를 통한 빠른 조건부 렌더링" 패턴을 채택
    • 훅 구현 시 "모든 필요한 정보를 한 번에 제공" 원칙 적용
  • AS-IS:

    // 모든 유효성 검사 훅에서 반복되는 패턴
    export default function useCardNumber(): UseCardNumberReturn {
      const [cardNumber, setCardNumber] = useState("");
      const { errors } = validateCardNumber(cardNumber);
    
      // ...
    
      return {
        cardNumber,
        onCardNumberChange: handleCardNumberChange,
        errorMessage: errors.at(-1)?.message,
        isError: !!errors.at(-1)?.message, // errorMessage에서 직접 계산된 중복 상태
      };
    }
  • TO-BE:

    // 1. 훅에서 파생 상태 제거
    export default function useCardNumber(): Omit<UseCardNumberReturn, 'isError'> {
      const [cardNumber, setCardNumber] = useState("");
      const { errors } = validateCardNumber(cardNumber);
    
      // ...
    
      return {
        cardNumber,
        onCardNumberChange: handleCardNumberChange,
        errorMessage: errors.at(-1)?.message,
      };
    }
    
    // 2. 공통 패턴을 추상화한 고차 훅 생성
    function createValidationHook<T extends string>(
      useHook: () => { [K in T]: string, onChange: Function, errorMessage?: string }
    ) {
      return () => {
        const result = useHook();
        return {
          ...result,
          isError: !!result.errorMessage  // 필요한 경우에만 계산
        };
      };
    }
  • 다음에 자가 점검 방법:
    "이 값이 다른 상태나 props로부터 즉시 계산될 수 있는가? 만약 그렇다면, 이것은 상태가 아닌 파생 값으로 처리해야 하지 않을까?"라고 자문해보세요.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants