Skip to content
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

feat: 메인 페이지 모바일 UI 구현 #255

Open
wants to merge 41 commits into
base: dev
Choose a base branch
from

Conversation

alwubin
Copy link
Collaborator

@alwubin alwubin commented Dec 9, 2024

💡 작업 내용

  • LandingPage에 사용하는 모바일 컴포넌트(MobileToggleGroup, MobileSideMenu, MobileCreateDropdown, MobileCreateButton) 및 각 스토리북 구현
  • 화면 너비로 모바일인지 확인하는 커스텀 훅 useIsMobile 구현 및 적용
  • media-query를 사용하여 LandingPage의 그외 나머지 모바일 ui스타일 수정
  • 밸런스게임 제작 페이지의 임시저장 버튼 모바일 UI 구현

💡 자세한 설명

✅ TopBanner

  • media-query를 사용하여 모두 줄였는데, 하단에 상태 바? 가... 원으로 안되고 타원으로 보여집니다....🥹 이 부분도 코멘트 주시면 무한감사드립니다....

✅ media-query

  • 아래 훅 설명에도 적어놨듯이, 임의 모바일 화면으로 고정 수정 시 (개발자 모드 내에서) 적용이 되지만, 일반 화면에서 양쪽 사이드를 줄여 화면을 줄이면 적용이 안되는 문제가 있어 이 부분에 대해서 의견 있으시면 많이 알려주세용..!

✅ useIsMobile

  • 원래 저희 회의에서는 react-responsive를 설치 후 거기서 제공하는 useMediaQuery를 사용한 훅을 작성하기로 했었습니다.
  • 근데 일단 브랜치 판 후 초반에 디코에서 설명드린 오류 사정이 있어 우선적으로 모듈 없이 window에서 화면 너비를 가져온 후 boolean값으로 반환하도록 작성해두었고, 실제 적용됨을 확인하였습니다~!
  • 문제는 이 훅과 media-query가 동시에 적용이 안된다는겁니다... useIsMobile은 임의로 화면 너비를 끌어서 줄여도 인식이 되는데, media-query는 그게 바로 적용이 안되고 430px 이하의 특정 모바일 화면으로 접속해야만 적용되고 있는데, 이부분에 대해서 커멘트 부탁드립니다..!!

✅ MobileToggleGroup, MobileSideMenu, MobileCreateDropdown, MobileCreateButton

  • 각 랜딩페이지에 사용되는 모바일 컴포넌트입니다. 새롭게 정의되었거나, 기존 컴포넌트 스타일 변경으로도 구현이 어려운 컴포넌트를 mobile 폴더 안에 정의해두었습니다.
  • MobileSideMenu의 경우 헤더가 모바일일 때 오른쪽 햄버거 버튼을 누르면 나타나는 컴포넌트입니다. 이 컴포넌트가 나타날 때 현재 transform 없이 boolean 값으로만 나타나고 있는데, transform을 사실 추가해서 테스트 해봤는데 안됐어용ㅎ.. 조언부탁드립니다..(어떻게 하면 좋을지)
  • MobileCreateDropdown은 hover 상태가 따로 figma에 정의되어있지 않아서 임의로 boxShadow가 더 진해지도록 추가해두었습니다!
  • 나머지 컴포넌트들은 스토리북에도 구현해두었습니다.

모바일 UI 화면

ui.mov

📗 참고 자료 (선택)

📢 리뷰 요구 사항 (선택)

🚩 후속 작업 (선택)

✅ 셀프 체크리스트

  • PR 제목을 형식에 맞게 작성했나요?
  • 브랜치 전략에 맞는 브랜치에 PR을 올리고 있나요? (master/main이 아닙니다.)
  • 이슈는 close 했나요?
  • Reviewers, Labels, Projects를 등록했나요?
  • 작업 도중 문서 수정이 필요한 경우 잘 수정했나요?
  • 테스트는 잘 통과했나요?
  • 불필요한 코드는 제거했나요?

closes #249

Summary by CodeRabbit

  • New Features

    • 모바일 기기에 최적화된 다양한 컴포넌트 및 스타일 추가.
    • useIsMobile 훅을 도입하여 화면 크기에 따라 컴포넌트 렌더링 조정.
    • 새로운 DraftSaveButton, MobileSideMenu, FloatingMenuButton, MobileToggleGroup 컴포넌트 추가.
    • 반응형 디자인을 위한 스타일 수정 및 미디어 쿼리 추가.
    • LandingPageHeader 컴포넌트의 모바일 기능 향상.
    • CategoryBox, SearchTagBar, BalanceGameList 컴포넌트의 모바일 지원 추가.
  • 버그 수정

    • 다양한 컴포넌트의 모바일 뷰에 대한 스타일 및 기능 개선.
  • 문서화

    • Storybook에 새로운 컴포넌트 스토리 추가.
    • CategoryButton 컴포넌트의 모바일 렌더링을 위한 스토리 수정.
    • Bookmark 컴포넌트의 스토리에서 prop 이름 변경.

Copy link

coderabbitai bot commented Dec 9, 2024

Walkthrough

이 변경 사항은 다양한 컴포넌트와 스타일 파일에서 모바일 UI를 개선하기 위한 여러 가지 수정 및 추가를 포함합니다. 주요 변경 사항으로는 <meta> 태그가 반응형 디자인을 지원하도록 수정되었고, 여러 컴포넌트에서 모바일 전용 스타일과 기능이 추가되었습니다. 또한, 새로운 SVG 및 이미지 자산이 추가되어 UI의 다양성을 높였습니다. 이러한 수정은 전반적으로 모바일 사용자 경험을 향상시키기 위한 것입니다.

Changes

파일 경로 변경 요약
public/index.html <meta charset="UTF-8" /> 태그를 <meta name="viewport" content="width=device-width,initial-scale=1">로 변경
src/assets/index.ts 여러 SVG 및 이미지 자산 추가 (예: ListIcon, LogoSmall, 등)
src/components/atoms/BalanceGameCategoryButton/BalanceGameCategoryButton.style.ts 430px 이하 화면에 대한 미디어 쿼리 추가 및 스타일 조정
src/components/atoms/Bookmark/Bookmark.style.ts 430px 이하 화면에 대한 미디어 쿼리 추가
src/components/atoms/Bookmark/Bookmark.tsx BookmarkDFSmall, BookmarkPRSmall 아이콘 추가 및 모바일 상태에 따라 아이콘 렌더링 조정
src/components/atoms/Button/Button.style.ts 버튼 변형에 대한 새로운 small 크기 옵션 추가
src/components/atoms/Button/Button.tsx ButtonProps 인터페이스에 size 속성에 small 추가
src/components/atoms/CategoryButton/CategoryButton.style.ts 스타일 정의를 객체 기반 구문으로 변경 및 미디어 쿼리 추가
src/components/atoms/CategoryButton/CategoryButton.tsx isMobile 속성 추가 및 모바일 상태에 따른 아이콘 렌더링 조정
src/components/atoms/Chips/Chips.style.ts 미디어 쿼리 추가로 반응형 스타일 조정
src/components/atoms/SearchBar/SearchBar.style.ts 모바일 스타일 추가 및 새로운 mobileInputStyling 변수 정의
src/components/atoms/SearchBar/SearchBar.tsx isMobile 속성 추가 및 조건부 렌더링 로직 수정
src/components/molecules/CategoryBar/CategoryBar.style.ts 430px 이하 화면에 대한 미디어 쿼리 추가
src/components/molecules/CategoryBar/CategoryBar.tsx isMobile 속성 추가 및 모바일 상태에 따른 아이콘 렌더링 조정
src/components/molecules/CategoryBox/CategoryBox.style.ts 스타일 정의 수정 및 미디어 쿼리 추가
src/components/molecules/CategoryBox/CategoryBox.tsx isMobile 속성 추가 및 모바일 상태에 따른 버튼 렌더링 조정
src/components/molecules/ContentsButton/ContentsButton.style.ts extraSmall 크기 옵션 추가 및 미디어 쿼리 추가
src/components/molecules/ContentsButton/ContentsButton.tsx ContentsButtonProps 인터페이스에 size 속성에 extraSmall 추가
src/components/molecules/SearchTagBar/SearchTagBar.tsx isMobile 속성 추가 및 조건부 렌더링 로직 수정
src/components/molecules/TopBanner/TodayTalkPickBanner/TodayTalkPickBanner.style.ts 430px 이하 화면에 대한 미디어 쿼리 추가
src/components/molecules/TopBanner/TodayTalkPickBanner/TodayTalkPickBanner.tsx 모바일 상태에 따른 아이콘 렌더링 조정
src/components/molecules/TopBanner/TopBanner.style.ts 430px 이하 화면에 대한 미디어 쿼리 추가
src/components/organisms/BalanceGameList/BalanceGameList.style.ts 430px 이하 화면에 대한 미디어 쿼리 추가
src/components/organisms/BalanceGameList/BalanceGameList.tsx isMobile 속성 추가 및 모바일 상태에 따른 렌더링 조정
src/components/organisms/Header/Header.style.ts 스타일 정의를 템플릿 리터럴 구문으로 변경 및 미디어 쿼리 추가
src/components/organisms/Header/Header.tsx 모바일 메뉴 상태 관리 추가 및 조건부 렌더링 로직 수정
src/hooks/common/useIsMobile.ts 모바일 여부를 판단하는 커스텀 훅 추가
src/layout/layout.tsx useIsMobile 훅을 사용하여 모바일 상태에 따라 Footer 렌더링 조정
src/pages/LandingPage/LandingPage.style.ts 430px 이하 화면에 대한 미디어 쿼리 추가 및 새로운 스타일 정의
src/pages/LandingPage/LandingPage.tsx useIsMobile 훅 추가 및 모바일 전용 레이아웃 수정
src/styles/typo.ts 모바일 전용 타이포그래피 스타일 추가
src/components/mobile/atoms/DraftSaveButton/DraftSaveButton.style.ts DraftSaveButton 스타일 정의 추가
src/components/mobile/atoms/DraftSaveButton/DraftSaveButton.tsx DraftSaveButton 컴포넌트 추가
src/components/mobile/atoms/MobileSideMenu/MobileSideMenu.style.ts MobileSideMenu 스타일 정의 추가
src/components/mobile/atoms/MobileSideMenu/MobileSideMenu.tsx MobileSideMenu 컴포넌트 추가
src/components/mobile/atoms/MobileToggleGroup/MobileToggleGroup.style.ts MobileToggleGroup 스타일 정의 추가
src/components/mobile/atoms/MobileToggleGroup/MobileToggleGroup.tsx MobileToggleGroup 컴포넌트 추가
src/stories/mobile/atoms/DraftSaveButton.stories.tsx DraftSaveButton 스토리북 구성 추가
src/stories/mobile/atoms/MobileSideMenu.stories.tsx MobileSideMenu 스토리북 구성 추가
src/stories/mobile/atoms/MobileToggleGroup.stories.tsx MobileToggleGroup 스토리북 구성 추가
src/components/mobile/atoms/FloatingButton/FloatingButton.style.ts FloatingButton 스타일 정의 추가
src/components/mobile/atoms/FloatingButton/FloatingButton.tsx FloatingButton 컴포넌트 추가
src/components/mobile/molecules/FloatingMenuButton/FloatingMenuButton.style.ts FloatingMenuButton 스타일 정의 추가
src/components/mobile/molecules/FloatingMenuButton/FloatingMenuButton.tsx FloatingMenuButton 컴포넌트 추가
src/stories/mobile/atoms/FloatingButton.stories.tsx FloatingButton 스토리북 구성 추가
src/stories/mobile/molecules/FloatingMenuButton.stories.tsx FloatingMenuButton 스토리북 구성 추가

Assessment against linked issues

Objective Addressed Explanation
모바일 UI 구현 (기능 구현)
미디어 쿼리를 사용한 반응형 UI 구현
임시저장 버튼 모바일 UI 구현 임시저장 버튼에 대한 구체적인 UI 구현이 포함되지 않음.

Suggested labels

✨feature

🐰 변화의 바람이 불어와
모바일로 더 나아가요, 기뻐요!
버튼과 아이콘, 모두 다 준비됐어요,
작은 화면에서도 즐거움이 넘쳐요!
함께 뛰어놀며, 행복한 순간을,
반응형 UI로 더 빛나길 바라요!
🐇✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 24

🧹 Outside diff range and nitpick comments (39)
src/pages/LandingPage/LandingPage.style.ts (1)

12-14: 반응형 전환 효과 추가 검토

패딩값이 데스크톱에서 모바일로 전환될 때 급격하게 변경됩니다. 부드러운 사용자 경험을 위해 transition 속성 추가를 고려해보세요.

export const contentWrapStyle = css({
  display: 'flex',
  flexDirection: 'column',
  alignItems: 'center',
  width: '100%',
  background: color.WT,
  padding: '113px 388px 95px 374px',
+ transition: 'padding 0.3s ease-in-out',
  '@media (max-width: 430px)': {
    padding: '18px 20px',
  },
});
src/components/organisms/Header/Header.style.ts (2)

5-22: 브레이크포인트 표준화 및 전환 개선 필요

현재 구현된 미디어 쿼리에 대해 다음 사항들을 고려해주세요:

  1. 430px의 브레이크포인트가 매우 구체적입니다. 재사용 가능한 상수로 정의하는 것이 좋습니다.
  2. 패딩이 200px에서 20px로 급격하게 변경되어 레이아웃 시프트가 발생할 수 있습니다.

다음과 같이 개선해보세요:

+import { BREAKPOINTS } from '@/styles/breakpoints';

 export const containerStyle = css`
   width: 100%;
   padding: 0 200px;
   height: 100px;
-  @media (max-width: 430px) {
+  @media (max-width: ${BREAKPOINTS.MOBILE}) {
     padding: 0 20px;
     height: 55px;
   }

24-31: 로고 스타일 구현이 적절합니다

Flex를 활용한 모바일 환경에서의 로고 중앙 정렬이 잘 구현되었습니다. 다만, 앞서 언급된 브레이크포인트 표준화가 여기에도 적용되면 좋겠습니다.

src/components/organisms/Header/Header.tsx (3)

Line range hint 6-24: 주석 처리된 코드 정리가 필요합니다.

사용하지 않는 주석 처리된 코드(Button, Notification 등)가 남아있습니다. 코드의 가독성과 유지보수를 위해 불필요한 주석은 제거하는 것이 좋습니다.


91-97: 로그인/로그아웃 처리 시 에러 핸들링이 필요합니다.

현재 로그인/로그아웃 처리 시 발생할 수 있는 에러에 대한 처리가 없습니다. 사용자 경험 향상을 위해 에러 처리를 추가하는 것이 좋습니다.

다음과 같은 개선을 제안드립니다:

 const handleLoginButton = () => {
   if (accessToken) {
-    logout.mutate();
+    try {
+      logout.mutate();
+    } catch (error) {
+      console.error('로그아웃 중 오류가 발생했습니다:', error);
+      // 사용자에게 에러 메시지 표시
+    }
     setIsMenuOpen(false);
   } else {
     navigate('/login');
     setIsMenuOpen(false);
   }
 };

151-201: 접근성과 구조적 개선이 필요한 부분이 있습니다.

  1. 모바일 메뉴 토글 버튼에 대한 접근성이 개선되었지만, 추가적인 ARIA 속성이 도움될 수 있습니다.
  2. 중첩된 div 구조가 복잡합니다.

다음과 같은 개선을 제안드립니다:

-            <button
-              type="button"
-              aria-label="headerList"
-              onClick={handleMenuToggle}
-              css={S.listButtonStyle}
-            >
+            <button
+              type="button"
+              aria-label="메뉴 열기"
+              aria-expanded={isMenuOpen}
+              aria-controls="mobile-menu"
+              onClick={handleMenuToggle}
+              css={S.listButtonStyle}
+            >
               <ListIcon />
             </button>
             {isMenuOpen && (
               <MobileSideMenu
+                id="mobile-menu"
                 isOpen={isMenuOpen}
                 setIsOpen={setIsMenuOpen}
                 accessToken={accessToken}
                 handleLoginButton={handleLoginButton}
               />
             )}
src/pages/LandingPage/LandingPage.tsx (1)

70-103: 중복된 코드를 리팩토링하여 유지보수성을 향상시킬 수 있습니다.

모바일과 데스크톱의 렌더링 부분에서 코드 중복이 발생하고 있습니다. 공통된 부분을 별도의 컴포넌트나 함수로 분리하면 코드의 가독성과 유지보수성이 향상될 수 있습니다.

src/hooks/common/useIsMobile.ts (2)

14-16: 성능 최적화 및 상수 정의 필요

매직 넘버를 피하고 성능을 개선하기 위한 수정이 필요합니다.

+const MOBILE_MAX_WIDTH = 768;
+
 function useIsMobile() {
   const [isMobile, setIsMobile] = useState<boolean>(false);
+  const mobileMediaQuery = useMemo(
+    () => window.matchMedia(`(max-width: ${MOBILE_MAX_WIDTH}px)`),
+    []
+  );

19-21: 이벤트 리스너 최적화 필요

리사이즈 이벤트에 디바운스 처리가 필요합니다.

+import { debounce } from 'lodash';
+
+const debouncedCheckMobile = debounce(checkIsMobile, 250);
-    window.addEventListener('resize', checkIsMobile);
+    window.addEventListener('resize', debouncedCheckMobile);
-    return () => window.removeEventListener('resize', checkIsMobile);
+    return () => {
+      window.removeEventListener('resize', debouncedCheckMobile);
+      debouncedCheckMobile.cancel();
+    };
src/components/atoms/SearchBar/SearchBar.style.ts (1)

29-33: 접근성 및 일관성 개선 필요

모바일 입력 필드의 높이가 터치 타겟 최소 크기에 미달하며, 패딩값이 일관적이지 않습니다.

 export const mobileInputStyling = css(typo.Mobile.Text.Medium_12, {
   width: '100%',
-  height: '36px',
+  height: MOBILE_MIN_TOUCH_TARGET,
-  padding: '0 5px',
+  padding: '0 10px',
 });
src/components/mobile/MobileCreateDropdown/MobileCreateDropdown.style.ts (2)

2-2: 사용하지 않는 import 제거 필요

주석 처리된 import문은 제거하는 것이 좋습니다.

-// import color from '@/styles/color';

32-40: 터치 영역 및 위치 조정 필요

버튼의 터치 영역이 작고, 절대 위치 지정으로 인한 레이아웃 깨짐이 발생할 수 있습니다.

+const MOBILE_TOUCH_TARGET = '60px';

 export const dropdownButtonStyling = css({
   cursor: 'pointer',
-  zIndex: 20,
+  zIndex: Z_INDEX.dropdown,
-  width: '50px',
-  height: '50px',
+  width: MOBILE_TOUCH_TARGET,
+  height: MOBILE_TOUCH_TARGET,
   position: 'absolute',
   bottom: '10px',
   right: '20px',
+  '@media (max-width: 430px)': {
+    bottom: '20px',
+    right: '20px',
+  },
 });
src/components/mobile/MobileSideMenu/MobileSideMenu.style.ts (1)

28-36: 접근성 개선을 위한 제안

모바일 환경의 사용성 향상을 위해 다음 사항들을 고려해주세요:

  1. 버튼의 패딩(10.5px)이 터치 영역으로는 다소 작을 수 있습니다.
  2. 터치 피드백을 위한 active 상태 스타일 추가가 필요합니다.
export const buttonStyle = css(typo.Mobile.Text.Medium_12, {
  outline: 'none',
  cursor: 'pointer',
  backgroundColor: 'transparent',
-  padding: '10.5px 60px 10.5px 24px',
+  padding: '16px 60px 16px 24px',
  textAlign: 'left',
  color: color.BK,
  border: 'none',
+  '&:active': {
+    backgroundColor: color.GR100,
+  },
});
src/stories/mobile/MobileSideMenu.stories.tsx (1)

16-19: 스토리북 테스트 케이스 보완 필요

다음 테스트 케이스들의 추가를 고려해주세요:

  1. isOpen: false 상태
  2. accessToken: undefined 또는 null 상태
  3. 실제 동작하는 handleLoginButton 구현

예시 구현:

export const Closed: Story = {
  args: {
    ...Default.args,
    isOpen: false,
  },
};

export const LoggedOut: Story = {
  args: {
    ...Default.args,
    accessToken: undefined,
  },
};
src/components/atoms/Chips/Chips.style.ts (2)

15-18: 미디어 쿼리 브레이크포인트 표준화 필요

현재 하드코딩된 430px 브레이크포인트를 상수나 테마 설정으로 분리하는 것이 좋습니다. 이는 유지보수성을 높이고 일관된 반응형 디자인을 보장할 수 있습니다.

+ // constants/breakpoints.ts 파일 생성
+ export const BREAKPOINTS = {
+   MOBILE: '430px',
+ } as const;

  '@media (max-width: ${BREAKPOINTS.MOBILE})': {
    ...typo.Mobile.Text.SemiBold_10,
    padding: '4px 9px',
  },

26-29: 중복된 미디어 쿼리 로직 개선 필요

동일한 브레이크포인트를 사용하는 미디어 쿼리가 여러 곳에서 반복되고 있습니다. 재사용 가능한 믹스인이나 유틸리티 함수로 분리하는 것이 좋습니다.

+ // styles/mixins.ts
+ export const mobileStyling = (styles: CSSObject) => css({
+   [`@media (max-width: ${BREAKPOINTS.MOBILE})`]: styles,
+ });

  // 사용 예시
  outline: css({
    padding: '10px 18px',
    borderRadius: '12px',
    ...mobileStyling({
      padding: '4px 9px',
      borderRadius: '7.2px',
    }),
  }),
src/stories/mobile/MobileCreateButton.stories.tsx (1)

28-29: 스토리북 인터랙션 테스트 개선 필요

단순 console.log 대신 Storybook의 Actions를 활용하여 더 의미 있는 인터랙션 테스트를 구현하는 것이 좋습니다.

  export const TalkPick: Story = {
    args: {
      imageType: 'talkpick',
      label: '톡픽',
-     onClick: () => console.log('TalkPick button clicked'),
+     onClick: action('톡픽 버튼 클릭됨'),
    },
  };

Also applies to: 36-37

src/components/mobile/MobileToggleGroup/MobileToggleGroup.style.ts (2)

18-19: border 스타일 로직 개선 필요

isOpen 상태에 따른 borderRadiusborderBottom 스타일 적용이 다소 복잡합니다. 다음과 같이 개선하는 것을 고려해보세요:

-    borderBottom: isOpen ? `0.6px solid ${color.SKYBLUE}` : undefined,
-    borderRadius: isOpen ? undefined : '6px',
+    ...(!isOpen && { borderRadius: '6px' }),
+    ...(isOpen && { borderBottom: `0.6px solid ${color.SKYBLUE}` }),

27-27: 트랜지션 지속 시간 조정 권장

현재 트랜지션 시간이 0.1초로 설정되어 있어 사용자가 변화를 인지하기 어려울 수 있습니다. 부드러운 사용자 경험을 위해 0.2초 이상으로 조정을 권장드립니다.

-  transition: 'all .1s ease-in',
+  transition: 'all .2s ease-in',
src/components/mobile/MobileCreateButton/MobileCreateButton.tsx (1)

18-27: switch 문을 객체 리터럴로 단순화 권장

현재 switch 문을 사용한 이미지 컴포넌트 선택 로직을 객체 리터럴을 사용하여 더 간단하게 구현할 수 있습니다.

-  let ImageComponent;
-  switch (imageType) {
-    case 'talkpick':
-      ImageComponent = CircleTalkPick;
-      break;
-    case 'game':
-      ImageComponent = CircleGame;
-      break;
-    default:
-      return null;
-  }
+  const IMAGE_COMPONENTS = {
+    talkpick: CircleTalkPick,
+    game: CircleGame,
+  };
+  
+  const ImageComponent = IMAGE_COMPONENTS[imageType];
+  if (!ImageComponent) return null;
src/components/atoms/CategoryButton/CategoryButton.tsx (2)

27-30: 이미지 컴포넌트 매핑 로직 개선 제안

현재 조건부 연산자를 사용한 이미지 컴포넌트 선택 로직을 상수 객체를 사용하여 더 깔끔하게 구현할 수 있습니다.

+  const IMAGE_MAPPING = {
+    PickVote: {
+      mobile: PickVoteSmall,
+      desktop: PickVote,
+    },
+    RandomGame: {
+      mobile: RandomGameSmall,
+      desktop: RandomGame,
+    },
+    TodayPick: {
+      mobile: TodayPick,
+      desktop: TodayPick,
+    },
+  };
+
   switch (imageType) {
     case 'PickVote':
-      ImageComponent = isMobile ? PickVoteSmall : PickVote;
+      ImageComponent = IMAGE_MAPPING.PickVote[isMobile ? 'mobile' : 'desktop'];
       break;
     case 'RandomGame':
-      ImageComponent = isMobile ? RandomGameSmall : RandomGame;
+      ImageComponent = IMAGE_MAPPING.RandomGame[isMobile ? 'mobile' : 'desktop'];
       break;

21-21: isMobile 기본값 설정 위치 변경 제안

isMobile prop의 기본값이 컴포넌트 내부에서 설정되어 있습니다. 이를 Props 인터페이스 레벨에서 설정하는 것이 더 명시적일 수 있습니다.

export interface CategoryButtonProps extends ComponentPropsWithRef<'button'> {
  imageType: 'PickVote' | 'RandomGame' | 'TodayPick';
  label: string;
-  isMobile?: boolean;
+  isMobile: boolean = false;
}

const CategoryButton = ({
  imageType,
  label,
-  isMobile = false,
+  isMobile,
  ...attributes
}: CategoryButtonProps) => {
src/components/atoms/Bookmark/Bookmark.tsx (1)

27-32: 렌더링 로직 개선에 대한 제안

renderIcon 함수를 분리한 것은 좋은 접근이지만, 다음과 같이 더 간결하게 작성할 수 있습니다:

-  const renderIcon = () => {
-    if (isPressed) {
-      return isMobile ? <BookmarkPRSmall /> : <BookmarkPR css={S.icon} />;
-    }
-    return isMobile ? <BookmarkDFSmall /> : <BookmarkDF css={S.icon} />;
-  };
+  const renderIcon = () => {
+    const Icon = isPressed
+      ? (isMobile ? BookmarkPRSmall : BookmarkPR)
+      : (isMobile ? BookmarkDFSmall : BookmarkDF);
+    return <Icon css={!isMobile ? S.icon : undefined} />;
+  };
src/components/organisms/BalanceGameList/BalanceGameList.style.ts (2)

15-25: 타이포그래피 스타일 사용 개선 제안

타이포그래피 스타일을 분리하여 관리하는 것이 좋습니다. 현재 모바일용 타이포그래피가 인라인으로 적용되어 있어 일관성 있는 관리가 어려울 수 있습니다.

export const titleWrapStyle = css({
-  ...typo.Title,
+  ...typo.getResponsiveStyle('Title'),
  display: 'flex',
  justifyContent: 'space-between',
  marginBottom: '20px',
  '@media (max-width: 430px)': {
-    ...typo.Mobile.Text.Bold_16,
    marginBottom: 0,
    height: '50px',
    width: '100%',
  },
});

40-43: 매직 넘버 상수화 제안

gap 값 '9px'와 같은 매직 넘버들을 의미 있는 상수로 분리하는 것이 좋습니다.

+const MOBILE_SPACING = {
+  GAP: '9px',
+  PADDING: '16px 0',
+} as const;

export const contentStyle = css({
  display: 'grid',
  gridTemplateColumns: 'repeat(2, 1fr)',
  gap: '20px',
  '@media (max-width: 430px)': {
-    gap: '9px',
+    gap: MOBILE_SPACING.GAP,
  },
});
src/components/molecules/TopBanner/TopBanner.style.ts (1)

31-34: 모바일 UI 스타일링이 체계적으로 구현되었습니다.

도트 네비게이션의 크기와 간격이 모바일 환경에 맞게 잘 조정되었습니다. 다만, 아래 사항들을 고려해 보시면 좋겠습니다:

  1. 반응형 값들을 상수로 분리하여 관리하는 것이 유지보수에 도움이 될 수 있습니다.
  2. 중복되는 미디어 쿼리를 mixin으로 분리하는 것을 고려해 보세요.

Also applies to: 44-47, 54-57

src/layout/layout.tsx (3)

17-19: 미디어 쿼리 상수화가 필요합니다.

하드코딩된 미디어 쿼리 값들을 상수로 분리하여 관리하면 일관성 있는 반응형 디자인 구현이 가능합니다.

+ // src/constants/breakpoints.ts
+ export const BREAKPOINTS = {
+   MOBILE: '430px',
+ } as const;
+ 
+ export const LAYOUT_PADDING = {
+   DESKTOP: '100px',
+   MOBILE: '55px',
+ } as const;

  '@media (max-width: 430px)': {
    paddingTop: '55px',
  },

24-24: Footer 표시/숨김 처리 개선이 필요합니다.

현재 구현은 Footer를 조건부로 제거하고 있어 레이아웃 변경 시 갑작스러운 변화가 발생할 수 있습니다. CSS를 통한 부드러운 전환을 고려해보세요.

- {isMobile ? null : <Footer />}
+ <Footer 
+   css={css`
+     display: ${isMobile ? 'none' : 'block'};
+     transition: opacity 0.3s ease-in-out;
+     opacity: ${isMobile ? 0 : 1};
+   `}
+ />

40-42: 중복된 미디어 쿼리 로직이 발견되었습니다.

LayoutLayoutNoSearch 컴포넌트에서 동일한 미디어 쿼리 로직이 반복되고 있습니다. 공통 스타일로 분리하는 것이 좋겠습니다.

+ const commonMainStyle = css({
+   paddingTop: '100px',
+   '@media (max-width: 430px)': {
+     paddingTop: '55px',
+   },
+ });

  // Layout 컴포넌트에서
- css({
-   paddingTop: '100px',
-   '@media (max-width: 430px)': {
-     paddingTop: '55px',
-   },
- })
+ commonMainStyle

  // LayoutNoSearch 컴포넌트에서도 동일하게 적용
src/components/mobile/MobileToggleGroup/MobileToggleGroup.tsx (1)

38-58: 접근성 개선이 필요합니다

토글 그룹의 접근성을 개선하기 위해 ARIA 속성과 키보드 네비게이션을 추가하는 것이 좋습니다.

다음과 같은 개선사항을 제안합니다:

 <div css={S.toggleGroupStyle}>
   <button
     css={S.clickedToggleStyle(isOpen)}
     type="button"
+    aria-expanded={isOpen}
+    aria-haspopup="true"
+    aria-label={`정렬 기준: ${selectedValue === 'views' ? '인기순' : '최신순'}`}
     onClick={handleMenuClick}
   >
src/components/molecules/TopBanner/TodayTalkPickBanner/TodayTalkPickBanner.style.ts (2)

19-23: 반응형 디자인 상수 관리 개선이 필요합니다

미디어 쿼리의 브레이크포인트를 상수로 관리하면 일관성 있는 유지보수가 가능합니다.

다음과 같은 개선을 제안합니다:

// src/styles/breakpoints.ts
export const BREAKPOINTS = {
  MOBILE: '430px',
  TABLET: '768px',
  // ... 기타 브레이크포인트
} as const;

// 사용 예시
import { BREAKPOINTS } from '@/styles/breakpoints';

export const talkPickStyling = css({
  // ... 기존 스타일
  [`@media (max-width: ${BREAKPOINTS.MOBILE})`]: {
    // ... 모바일 스타일
  },
});

37-42: 반응형 타이포그래피 시스템 검토가 필요합니다

여러 컴포넌트에서 동일한 모바일 타이포그래피 스타일이 반복되고 있습니다. 재사용 가능한 믹스인으로 관리하면 일관성과 유지보수성이 향상될 것 같습니다.

다음과 같은 아키텍처 개선을 제안합니다:

// src/styles/mixins.ts
import { css } from '@emotion/react';
import typo from '@/styles/typo';
import { BREAKPOINTS } from '@/styles/breakpoints';

export const withMobileTypography = (desktopTypo: any, mobileTypo: any) => css`
  ${desktopTypo};
  @media (max-width: ${BREAKPOINTS.MOBILE}) {
    ${mobileTypo};
  }
`;

// 사용 예시
export const bannerChipStyling = css({
  ...withMobileTypography(
    typo.Main.SemiBold,
    typo.Mobile.Text.SemiBold_7
  ),
  // ... 나머지 스타일
});

Also applies to: 55-60, 69-73

src/components/molecules/CategoryBar/CategoryBar.tsx (1)

19-21: 타입 안전성 개선이 필요합니다

isMobile 프로퍼티가 선택적(optional)으로 정의되어 있지만, 기본값이 있으므로 필수 프로퍼티로 변경하는 것이 더 명확할 것 같습니다.

- isMobile?: boolean;
+ isMobile: boolean;
src/components/mobile/MobileCreateDropdown/MobileCreateDropdown.tsx (1)

43-48: 이벤트 리스너 최적화가 필요합니다

전역 이벤트 리스너를 사용할 때는 성능 최적화를 고려해야 합니다. 현재 구현은 불필요하게 자주 이벤트 핸들러가 호출될 수 있습니다.

다음과 같이 개선해보세요:

useEffect(() => {
  if (isOpen) {
    const handleOutsideClick = (e: MouseEvent) => {
      if (!dropdownRef.current?.contains(e.target as Node)) {
        setIsOpen(false);
      }
    };
    
    window.addEventListener('click', handleOutsideClick);
    return () => window.removeEventListener('click', handleOutsideClick);
  }
}, [isOpen]);
src/components/organisms/BalanceGameList/BalanceGameList.tsx (1)

41-51: 조건부 렌더링 구현이 깔끔합니다.

모바일/데스크톱 환경에 따른 토글 그룹 컴포넌트 분기 처리가 잘 되어있습니다. 다만, 재사용성을 위해 공통 props 타입을 분리하는 것을 고려해보세요.

+ interface CommonToggleGroupProps {
+   selectedValue: string;
+   onClick: (value: string) => void;
+ }

+ interface MobileToggleGroupProps extends CommonToggleGroupProps {
+   // mobile specific props
+ }

+ interface ToggleGroupProps extends CommonToggleGroupProps {
+   // desktop specific props
+ }
src/components/molecules/ContentsButton/ContentsButton.style.ts (2)

30-36: extraSmall 사이즈 스펙이 적절합니다.

모바일 환경에 맞는 크기로 잘 정의되어 있습니다. 다만, 매직 넘버 대신 변수화를 고려해보세요.

+ const MOBILE = {
+   WIDTH: '162px',
+   HEIGHT: '121px',
+   INFO_HEIGHT: '40px',
+   LABEL_MAX_WIDTH: '141px',
+   IMAGE_HEIGHT: '81px',
+ };

  extraSmall: {
-   width: '162px',
+   width: MOBILE.WIDTH,
    // ... 나머지 속성들도 동일하게 적용
  },

67-69: 미디어 쿼리 브레이크포인트가 일관되게 적용되었습니다.

모바일 대응을 위한 미디어 쿼리가 430px로 통일되어 있습니다. 다만, 브레이크포인트를 상수로 분리하는 것을 추천드립니다.

+ const BREAKPOINT = {
+   MOBILE: '430px',
+ };

- @media (max-width: 430px)
+ @media (max-width: ${BREAKPOINT.MOBILE})

Also applies to: 110-112, 119-121

src/components/atoms/BalanceGameCategoryButton/BalanceGameCategoryButton.style.ts (1)

77-96: 반응형 스타일 로직이 개선될 여지가 있습니다.

border-radius 계산 로직이 중복되어 있습니다. 상수와 유틸리티 함수로 개선할 수 있습니다.

+ const BORDER_RADIUS = {
+   DESKTOP: {
+    LEFT: '10px 0 0 10px',
+    RIGHT: '0 10px 10px 0',
+  },
+  MOBILE: {
+    LEFT: '3px 0 0 3px',
+    RIGHT: '0 3px 3px 0',
+  },
+ };

+ const getBorderRadius = (label: string, isMobile: boolean) => {
+   if (label === '인기') return isMobile ? BORDER_RADIUS.MOBILE.LEFT : BORDER_RADIUS.DESKTOP.LEFT;
+   if (label === '월드컵') return isMobile ? BORDER_RADIUS.MOBILE.RIGHT : BORDER_RADIUS.DESKTOP.RIGHT;
+   return '0';
+ };
src/assets/index.ts (1)

104-104: 이전 SVG 정리 필요

TODO 주석에 명시된 대로 이전 SVG 파일들의 정리가 필요합니다. 현재 코드베이스의 일관성을 위해 이 작업의 우선순위를 고려해주세요.

이전 SVG 파일들의 정리를 위한 이슈를 생성해드릴까요?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 45f763e and 4df0893.

⛔ Files ignored due to path filters (17)
  • src/assets/svg/bookmark-df-sm.svg is excluded by !**/*.svg
  • src/assets/svg/bookmark-pr-sm.svg is excluded by !**/*.svg
  • src/assets/svg/check-small.svg is excluded by !**/*.svg
  • src/assets/svg/circle-close.svg is excluded by !**/*.svg
  • src/assets/svg/circle-game.svg is excluded by !**/*.svg
  • src/assets/svg/circle-pencil.svg is excluded by !**/*.svg
  • src/assets/svg/circle-talkpick.svg is excluded by !**/*.svg
  • src/assets/svg/couple-small.svg is excluded by !**/*.svg
  • src/assets/svg/list.svg is excluded by !**/*.svg
  • src/assets/svg/logo-small.svg is excluded by !**/*.svg
  • src/assets/svg/pick-vote-small.svg is excluded by !**/*.svg
  • src/assets/svg/popular-small.svg is excluded by !**/*.svg
  • src/assets/svg/random-game-small.svg is excluded by !**/*.svg
  • src/assets/svg/taste-small.svg is excluded by !**/*.svg
  • src/assets/svg/triangle-down.svg is excluded by !**/*.svg
  • src/assets/svg/triangle-up.svg is excluded by !**/*.svg
  • src/assets/svg/worldcup-small.svg is excluded by !**/*.svg
📒 Files selected for processing (43)
  • public/index.html (1 hunks)
  • src/assets/index.ts (1 hunks)
  • src/components/atoms/BalanceGameCategoryButton/BalanceGameCategoryButton.style.ts (5 hunks)
  • src/components/atoms/Bookmark/Bookmark.style.ts (1 hunks)
  • src/components/atoms/Bookmark/Bookmark.tsx (3 hunks)
  • src/components/atoms/Button/Button.style.ts (5 hunks)
  • src/components/atoms/Button/Button.tsx (1 hunks)
  • src/components/atoms/CategoryButton/CategoryButton.style.ts (1 hunks)
  • src/components/atoms/CategoryButton/CategoryButton.tsx (1 hunks)
  • src/components/atoms/Chips/Chips.style.ts (1 hunks)
  • src/components/atoms/SearchBar/SearchBar.style.ts (2 hunks)
  • src/components/atoms/SearchBar/SearchBar.tsx (1 hunks)
  • src/components/mobile/MobileCreateButton/MobileCreateButton.style.ts (1 hunks)
  • src/components/mobile/MobileCreateButton/MobileCreateButton.tsx (1 hunks)
  • src/components/mobile/MobileCreateDropdown/MobileCreateDropdown.style.ts (1 hunks)
  • src/components/mobile/MobileCreateDropdown/MobileCreateDropdown.tsx (1 hunks)
  • src/components/mobile/MobileSideMenu/MobileSideMenu.style.ts (1 hunks)
  • src/components/mobile/MobileSideMenu/MobileSideMenu.tsx (1 hunks)
  • src/components/mobile/MobileToggleGroup/MobileToggleGroup.style.ts (1 hunks)
  • src/components/mobile/MobileToggleGroup/MobileToggleGroup.tsx (1 hunks)
  • src/components/molecules/CategoryBar/CategoryBar.style.ts (1 hunks)
  • src/components/molecules/CategoryBar/CategoryBar.tsx (3 hunks)
  • src/components/molecules/CategoryBox/CategoryBox.style.ts (1 hunks)
  • src/components/molecules/CategoryBox/CategoryBox.tsx (1 hunks)
  • src/components/molecules/ContentsButton/ContentsButton.style.ts (3 hunks)
  • src/components/molecules/ContentsButton/ContentsButton.tsx (1 hunks)
  • src/components/molecules/SearchTagBar/SearchTagBar.tsx (2 hunks)
  • src/components/molecules/TopBanner/TodayTalkPickBanner/TodayTalkPickBanner.style.ts (3 hunks)
  • src/components/molecules/TopBanner/TodayTalkPickBanner/TodayTalkPickBanner.tsx (3 hunks)
  • src/components/molecules/TopBanner/TopBanner.style.ts (3 hunks)
  • src/components/organisms/BalanceGameList/BalanceGameList.style.ts (1 hunks)
  • src/components/organisms/BalanceGameList/BalanceGameList.tsx (5 hunks)
  • src/components/organisms/Header/Header.style.ts (1 hunks)
  • src/components/organisms/Header/Header.tsx (4 hunks)
  • src/hooks/common/useIsMobile.ts (1 hunks)
  • src/layout/layout.tsx (2 hunks)
  • src/pages/LandingPage/LandingPage.style.ts (2 hunks)
  • src/pages/LandingPage/LandingPage.tsx (2 hunks)
  • src/stories/mobile/MobileCreateButton.stories.tsx (1 hunks)
  • src/stories/mobile/MobileCreateDropdown.stories.tsx (1 hunks)
  • src/stories/mobile/MobileSideMenu.stories.tsx (1 hunks)
  • src/stories/mobile/MobileToggleGroup.stories.tsx (1 hunks)
  • src/styles/typo.ts (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • public/index.html
  • src/stories/mobile/MobileCreateDropdown.stories.tsx
  • src/stories/mobile/MobileToggleGroup.stories.tsx
🔇 Additional comments (28)
src/pages/LandingPage/LandingPage.style.ts (3)

17-19: 구현이 적절합니다!

relative 포지셔닝을 통해 하위 요소들의 위치 컨텍스트를 올바르게 설정했습니다.


39-41: 타이포그래피 구현이 적절합니다!

모바일용 타이포그래피를 올바르게 적용했으며, 디자인 시스템을 잘 따르고 있습니다.


21-25: z-index 값 검증 필요

고정 위치 지정은 적절하나, z-index 값이 다른 요소들과 충돌하지 않는지 확인이 필요합니다.

✅ Verification successful

z-index 50은 안전하나 일관성을 위해 수정 권장

현재 코드베이스의 z-index 사용 현황을 분석한 결과:

  • 모달과 오버레이: 300, 1000
  • 모바일 메뉴: 1100
  • 일반 UI 요소: 1-100 사이
  • 드롭다운 요소: 10-20

floatingDropdownStyle의 z-index 50은 다른 요소들과 직접적인 충돌은 없으나, 모바일 드롭다운의 일관성을 위해 다음과 같이 수정하는 것을 권장합니다:

  • MobileCreateDropdown 컴포넌트와 동일하게 z-index를 10-20 범위로 조정
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# z-index 사용 현황 확인
rg -n "zIndex|z-index" --type css --type typescript

Length of output: 88


Script:

#!/bin/bash
# z-index 사용 현황 확인 (파일 확장자 직접 지정)
rg -n "zIndex|z-index" -g "*.ts" -g "*.tsx" -g "*.css" -g "*.scss"

Length of output: 4780

src/components/organisms/Header/Header.style.ts (1)

44-51: 우측 컨테이너 스타일 구현이 적절합니다

Flex를 활용한 정렬과 모바일 환경에서의 배치가 잘 구현되었습니다. 브레이크포인트 표준화만 적용하면 완벽할 것 같습니다.

src/components/organisms/Header/Header.tsx (1)

29-29: 모바일 브레이크포인트 검증이 필요합니다.

useIsMobile 훅의 브레이크포인트가 디자인 시스템의 모바일 기준과 일치하는지 확인이 필요합니다.

✅ Verification successful

모바일 브레이크포인트가 일관되게 적용되어 있습니다.

코드베이스 분석 결과:

  • 모든 컴포넌트에서 일관되게 430px을 모바일 브레이크포인트로 사용하고 있습니다.
  • useIsMobile 훅은 디바이스의 User Agent를 기반으로 모바일 여부를 판단하며, 브레이크포인트와 함께 적절하게 동작합니다.
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify mobile breakpoint consistency across the codebase

# Search for breakpoint definitions
echo "Searching for breakpoint definitions..."
rg -n "breakpoint|@media.*max-width|@media.*min-width" --type css --type typescript

# Search for useIsMobile implementation
echo "Searching for useIsMobile implementation..."
rg -n "useIsMobile" --type typescript

Length of output: 379


Script:

#!/bin/bash
# Search for breakpoint definitions and useIsMobile implementation with correct file extensions
echo "Searching for breakpoint definitions..."
rg -n "breakpoint|@media.*max-width|@media.*min-width" -t tsx -t ts -t css -t scss

echo -e "\nSearching for useIsMobile implementation..."
rg -n "useIsMobile" -t tsx -t ts -A 5

Length of output: 370


Script:

#!/bin/bash
# Search without file type restrictions
echo "Searching for breakpoint definitions..."
rg -n "breakpoint|@media.*max-width|@media.*min-width"

echo -e "\nSearching for useIsMobile implementation and its definition..."
rg -n "useIsMobile" -A 5

echo -e "\nSearching for files containing useIsMobile..."
fd useIsMobile

Length of output: 9545

src/pages/LandingPage/LandingPage.tsx (2)

13-14: 추가된 import 문이 적절합니다.

MobileCreateDropdownuseIsMobile을 올바르게 import하셨습니다.


18-18: isMobile 상태 변수를 추가하여 반응형 디자인을 구현한 점이 좋습니다.

모바일 여부에 따라 다른 UI를 제공하기 위해 useIsMobile 훅을 사용하셨습니다.

src/components/molecules/CategoryBar/CategoryBar.style.ts (1)

7-9: 모바일 화면에서의 패딩 제거가 적절합니다.

@media (max-width: 430px) 미디어 쿼리를 사용하여 작은 화면에서 패딩을 제거하여 레이아웃을 최적화하신 점이 좋습니다.

src/components/molecules/CategoryBox/CategoryBox.style.ts (1)

5-11: 컨테이너 스타일을 개선하여 반응형 디자인을 구현하셨습니다.

alignItems, justifyContent, width 속성을 추가하고 미디어 쿼리를 통해 작은 화면에서의 간격을 조정하여 모바일 환경에서의 레이아웃을 개선하셨습니다.

src/components/atoms/Bookmark/Bookmark.style.ts (1)

14-17: 모바일 환경에서 버튼 크기를 조정하여 사용자 경험을 향상시켰습니다.

@media (max-width: 430px)를 사용하여 작은 화면에서 북마크 버튼의 크기를 조정한 것은 반응형 디자인 측면에서 적절합니다.

src/components/mobile/MobileSideMenu/MobileSideMenu.style.ts (1)

5-17: z-index 값과 터치 영역 크기 검토 필요

모바일 환경에서 다음 사항들을 고려해주세요:

  1. z-index 값(1100)이 다른 요소들과 충돌하지 않는지 확인이 필요합니다.
  2. 터치 영역이 iOS Human Interface Guidelines(최소 44x44px)와 Material Design Guidelines(최소 48x48px)를 충족하는지 확인해주세요.
✅ Verification successful

z-index 값이 적절하게 설정되어 있습니다

z-index 검색 결과를 분석한 결과:

  • 모달과 오버레이: 300-1000
  • 드롭다운과 메뉴: 10-20
  • 일반 UI 요소: 1-100
  • MobileSideMenu의 z-index 1100은 다른 요소들과 충돌하지 않으며, 모바일 메뉴가 최상단에 표시되어야 하는 용도에 적합합니다.
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# z-index 충돌 가능성 확인
rg -n "z-index|zIndex" --type ts --type tsx

Length of output: 73


Script:

#!/bin/bash
# z-index 충돌 가능성 확인 (수정된 명령어)
rg -n "z-index|zIndex" -t ts

Length of output: 4742


Script:

#!/bin/bash
# 모바일 관련 스타일 파일들 검색
fd -e ts -e tsx . src/ --exec grep -l "style"

Length of output: 21472


Script:

#!/bin/bash
# 버튼 관련 스타일 검색
rg -n "button.*style|style.*button" -t ts

Length of output: 43

src/components/mobile/MobileCreateButton/MobileCreateButton.style.ts (1)

29-31: 색상 대비 검증 필요

접근성 지침을 준수하기 위해 메인 색상과 배경색 간의 대비율이 WCAG 기준을 충족하는지 확인해주세요.

src/components/atoms/Bookmark/Bookmark.tsx (1)

2-8: 모바일 아이콘 컴포넌트 추가에 대한 승인

모바일 환경을 위한 Small 아이콘 컴포넌트들이 적절하게 추가되었습니다.

src/components/atoms/Button/Button.tsx (1)

11-11: 'small' 사이즈 지원 추가에 대한 검증 필요

'small' 사이즈가 ButtonProps에 추가되었습니다. 관련 스타일이 제대로 구현되어 있는지 확인이 필요합니다.

src/components/molecules/CategoryBox/CategoryBox.tsx (2)

7-7: 선택적 prop 타입 정의가 적절합니다.

isMobile prop이 옵셔널로 정의되어 있어 기존 코드와의 호환성이 유지됩니다.


11-23: 모바일 UI 구현이 깔끔합니다.

모바일 버전에서는 두 개의 버튼만 표시하여 UI를 단순화한 것이 적절합니다. 다만, 아래 사항들을 확인해 주시면 좋겠습니다:

  1. 모바일에서 제거된 "오늘의 톡픽 모음.zip" 버튼의 기능을 사용자가 다른 방법으로 접근할 수 있는지
  2. 모바일 버전의 레이아웃이 디자인 요구사항과 일치하는지
✅ Verification successful

모바일 UI에서 "오늘의 톡픽 모음.zip" 접근성이 확보되어 있습니다.

모바일 버전에서 "오늘의 톡픽 모음.zip" 버튼이 제거되었지만, 다음과 같은 대체 접근 경로가 존재합니다:

  1. 메인 페이지의 상단 배너를 통해 "오늘의 톡픽"에 직접 접근 가능 (/todaytalkpick 라우트)
  2. "톡&픽 플레이스" 메뉴를 통해 톡픽 콘텐츠 전체에 접근 가능 (/talkpickplace 라우트)

따라서 모바일 사용자의 콘텐츠 접근성이 적절히 유지되고 있습니다.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if there's an alternative way to access "오늘의 톡픽 모음.zip" on mobile

# Search for routes or links related to "오늘의 톡픽"
rg -i "오늘의\s*톡픽" --type ts --type tsx

Length of output: 67


Script:

#!/bin/bash
# Search for files with .tsx extension and content related to "오늘의 톡픽"
rg -i "오늘의.*톡픽" -g "*.tsx"

# Search for route definitions
rg -i "route|path" -g "*.tsx" -A 2

# Check CategoryBox component in non-mobile view
cat src/components/molecules/CategoryBox/CategoryBox.tsx

Length of output: 42836


Script:

#!/bin/bash
# Check for routes and links to "오늘의 톡픽" content
rg -i "todaytalkpick|오늘의.*톡픽" -g "*.{tsx,ts}" -A 2

# Check mobile navigation components
fd -e tsx -e ts "Mobile.*Menu|Mobile.*Nav" -x cat {}

Length of output: 13721

src/components/molecules/TopBanner/TodayTalkPickBanner/TodayTalkPickBanner.tsx (2)

4-5: import 구문이 깔끔하게 정리되었습니다.

새로운 아이콘과 훅의 import가 적절히 추가되었습니다.


19-19: 모바일 대응이 잘 구현되었습니다.

useIsMobile 훅을 사용한 조건부 렌더링이 적절합니다. 다만, 아이콘 크기 차이가 디자인 명세와 일치하는지 확인해 주세요.

Also applies to: 34-35

src/components/molecules/TopBanner/TopBanner.style.ts (1)

8-10: 미디어 쿼리 브레이크포인트 검토가 필요합니다.

430px 브레이크포인트가 다른 컴포넌트들과 일관성이 있는지 확인이 필요합니다.

✅ Verification successful

브레이크포인트가 일관되게 사용되고 있습니다

검색 결과에 따르면 프로젝트 전체에서 430px 브레이크포인트가 일관되게 사용되고 있습니다. 모든 컴포넌트에서 동일한 모바일 브레이크포인트를 채택하고 있어 현재 구현이 적절합니다.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check breakpoint consistency across components

# Search for all media query breakpoints
rg -i "max-width:\s*\d+" --type ts --type tsx

Length of output: 75


Script:

#!/bin/bash
# Search for media query breakpoints in TypeScript files
rg -i "@media.*max-width.*\d+" --type ts

# Also search in style files that might have different extensions
rg -i "@media.*max-width.*\d+" -g "*.style.*"
rg -i "@media.*max-width.*\d+" -g "*.styled.*"

Length of output: 8166

src/components/molecules/SearchTagBar/SearchTagBar.tsx (1)

9-9: isMobile prop의 타입 정의가 적절합니다.

선택적 boolean 타입으로 정의되어 있어 기존 코드와의 호환성이 유지됩니다.

src/components/molecules/ContentsButton/ContentsButton.tsx (1)

14-14: 사이즈 옵션 추가가 적절합니다

모바일 UI를 위한 'extraSmall' 사이즈 옵션 추가는 적절해 보입니다. 기존 사이즈 옵션들과의 호환성도 잘 유지되어 있습니다.

src/components/organisms/BalanceGameList/BalanceGameList.tsx (2)

8-8: Props 인터페이스와 기본값 설정이 적절합니다.

모바일 대응을 위한 isMobile prop의 추가와 기본값 설정이 잘 되어있습니다.

Also applies to: 19-19, 28-28


54-58: 모바일 대응을 위한 size prop 전달이 일관성 있게 구현되었습니다.

CategoryBar, ContentsButton, Button 컴포넌트에 모바일 환경에 맞는 size prop이 적절하게 전달되고 있습니다.

Also applies to: 63-63, 76-76

src/components/atoms/BalanceGameCategoryButton/BalanceGameCategoryButton.style.ts (3)

15-17: 모바일 환경의 레이아웃 조정이 적절합니다.

버튼의 너비와 여백이 모바일 화면에 맞게 잘 조정되었습니다.

Also applies to: 26-28, 33-35


38-43: 모바일 타이포그래피 적용이 일관성 있습니다.

활성/비활성 상태의 텍스트 스타일이 모바일에 맞게 잘 정의되었습니다.

Also applies to: 46-51


122-131: 배지 스타일의 모바일 대응이 적절합니다.

패딩과 폰트 크기가 모바일 환경에 맞게 잘 조정되었습니다.

src/components/atoms/Button/Button.style.ts (1)

173-181: outlineShadow variant의 모바일 스타일 구현이 적절함

outlineShadow variant의 small 사이즈에 대한 스타일링이 잘 정의되어 있습니다. 다른 variant들도 이와 같은 상세한 스타일 정의가 필요합니다.

src/assets/index.ts (1)

86-102: 모바일 아이콘 추가가 잘 구현됨

모바일 버전을 위한 아이콘들이 체계적으로 추가되었고, 명확한 네이밍 컨벤션('Small' 접미사)을 따르고 있습니다.

src/components/organisms/Header/Header.tsx Outdated Show resolved Hide resolved
Comment on lines 8 to 13
const userAgent =
typeof window.navigator === 'undefined' ? '' : navigator.userAgent;
const mobile =
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
userAgent,
);

This comment was marked as resolved.

Comment on lines +15 to +19
'@media (max-width: 430px)': {
width: '335px',
height: '36px',
padding: '0 5px',
},

This comment was marked as resolved.

Comment on lines 4 to 12
export const overlay = css({
position: 'fixed',
top: 0,
left: 0,
width: '100vw',
height: '100vh',
backgroundColor: 'rgba(255, 255, 255, 0.5)',
zIndex: 10,
});

This comment was marked as resolved.

Comment on lines 5 to 23
export const categoryButtonBaseStyle = (imageType: 'talkpick' | 'game') =>
css({
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
gap: '13px',
padding: '5px 5px 5px 21px',
borderRadius: '500px',
backgroundColor: color.WT,
boxShadow: '1px 2px 15px 0 rgba(119, 130, 255, 0.2)',
cursor: 'pointer',
transition: 'all 0.3s ease-in',
width: imageType === 'talkpick' ? '102px' : '139px',
marginLeft: imageType === 'talkpick' ? '37px' : 0,
'&:hover': {
boxShadow: '1px 2px 15px 0 rgba(119, 130, 255, 0.8)',
},
});

This comment was marked as resolved.

Comment on lines 31 to 36
useEffect(() => {
window.addEventListener('click', handleOutsideClick);
return () => {
window.removeEventListener('click', handleOutsideClick);
};
}, []);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

useEffect 의존성 배열 개선이 필요합니다

handleOutsideClick 함수가 의존성 배열에 포함되어 있지 않아 메모리 누수가 발생할 수 있습니다.

다음과 같이 수정을 제안합니다:

- useEffect(() => {
-   window.addEventListener('click', handleOutsideClick);
-   return () => {
-     window.removeEventListener('click', handleOutsideClick);
-   };
- }, []);
+ useEffect(() => {
+   window.addEventListener('click', handleOutsideClick);
+   return () => {
+     window.removeEventListener('click', handleOutsideClick);
+   };
+ }, [handleOutsideClick]);
📝 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
useEffect(() => {
window.addEventListener('click', handleOutsideClick);
return () => {
window.removeEventListener('click', handleOutsideClick);
};
}, []);
useEffect(() => {
window.addEventListener('click', handleOutsideClick);
return () => {
window.removeEventListener('click', handleOutsideClick);
};
}, [handleOutsideClick]);

Comment on lines 61 to 63
<button css={S.buttonStyle} type="button" onClick={handleLoginButton}>
회원 탈퇴
</button>

This comment was marked as resolved.

Comment on lines 27 to 68
return (
<div css={S.buttonGroupStyle}>
<div css={S.buttonContainerStyle}>
<button
css={S.buttonStyle}
type="button"
onClick={() => {
if (accessToken) {
handleNavigation('/mypage');
} else {
handleNavigation('/login');
}
}}
>
마이페이지
</button>

<button
css={S.buttonStyle}
type="button"
onClick={() => {
if (accessToken) {
handleNavigation('/notifications');
} else {
handleNavigation('/login');
}
}}
>
알림 내역
</button>
<button css={S.buttonStyle} type="button" onClick={handleLoginButton}>
{accessToken ? '로그아웃' : '로그인'}
</button>
{accessToken && (
<button css={S.buttonStyle} type="button" onClick={handleLoginButton}>
회원 탈퇴
</button>
)}
</div>
<div css={S.fillerStyle} role="presentation" />
</div>
);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

접근성 및 사용자 경험 개선이 필요합니다

모바일 사이드 메뉴의 구현에서 다음 사항들을 개선하면 좋겠습니다:

  1. 키보드 접근성이 부족합니다
  2. 화면 읽기 프로그램 사용자를 위한 ARIA 속성이 누락되었습니다
  3. 사이드 메뉴가 열리고 닫힐 때 애니메이션이 없어 사용자 경험이 부자연스럽습니다

다음과 같이 개선해보세요:

- return (
-   <div css={S.buttonGroupStyle}>
+   <aside
+     css={S.buttonGroupStyle}
+     role="dialog"
+     aria-modal="true"
+     aria-label="사이드 메뉴">
      <div css={S.buttonContainerStyle}>

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

Comment on lines 46 to 71
label="인기"
icon={<Popular />}
icon={isMobile ? <PopularSmall /> : <Popular />}
active={activeTab === '인기'}
badgeText={getBadgeText('인기')}
onClick={() => setActiveTab('인기')}
/>
<BalanceGameCategoryButton
label="커플"
icon={<Couple />}
icon={isMobile ? <CoupleSmall /> : <Couple />}
active={activeTab === '커플'}
badgeText={getBadgeText('커플')}
onClick={() => setActiveTab('커플')}
/>
<BalanceGameCategoryButton
label="취향"
icon={<Taste />}
icon={isMobile ? <TasteSmall /> : <Taste />}
active={activeTab === '취향'}
badgeText={getBadgeText('취향')}
onClick={() => setActiveTab('취향')}
/>
<BalanceGameCategoryButton
label="월드컵"
icon={<Worldcup />}
icon={isMobile ? <WorldcupSmall /> : <Worldcup />}
active={activeTab === '월드컵'}
badgeText={getBadgeText('월드컵')}
onClick={() => setActiveTab('월드컵')}

This comment was marked as resolved.

Comment on lines 133 to 232
Mobile: {
Title: {
SemiBold_24: {
fontFamily: 'Pretendard',
fontSize: '24px',
fontWeight: 600,
lineHeight: '1.6',
letterSpacing: `${24 * -0.05}px`,
},
},
Main: {
Medium_16: {
fontFamily: 'Pretendard',
fontSize: '16px',
lineHeight: '1.6',
letterSpacing: `${16 * -0.05}px`,
},
Regular_12: {
fontFamily: 'Pretendard',
fontSize: '12px',
fontWeight: 400,
lineHeight: '1.6',
letterSpacing: `${12 * -0.05}px`,
},
},
Text: {
Bold_20: {
fontFamily: 'Pretendard',
fontSize: '20px',
fontWeight: 700,
letterSpacing: `${20 * -0.05}px`,
},
Bold_16: {
fontFamily: 'Pretendard',
fontSize: '16px',
fontWeight: 700,
lineHeight: '1.6',
letterSpacing: `${16 * -0.05}px`,
},
Bold_12: {
fontFamily: 'Pretendard',
fontSize: '12px',
fontWeight: 700,
lineHeight: '1.6',
letterSpacing: `${16 * -0.05}px`,
},
SemiBold_20: {
fontFamily: 'Pretendard',
fontSize: '20px',
fontWeight: 600,
lineHeight: '1.6',
letterSpacing: `${20 * -0.05}px`,
},
SemiBold_14: {
fontFamily: 'Pretendard',
fontSize: '14px',
fontWeight: 600,
lineHeight: '1.6',
letterSpacing: `${14 * -0.05}px`,
},
SemiBold_12: {
fontFamily: 'Pretendard',
fontSize: '12px',
fontWeight: 600,
lineHeight: '1.3',
},
SemiBold_10: {
fontFamily: 'Pretendard',
fontSize: '10px',
fontWeight: 600,
lineHeight: '1.6',
letterSpacing: `${14 * -0.05}px`,
},
SemiBold_7: {
fontFamily: 'Pretendard',
fontSize: '7.2px',
fontWeight: 600,
lineHeight: '1.6',
letterSpacing: '-0.05em',
},
Medium_16: {
fontFamily: 'Pretendard',
fontSize: '16px',
lineHeight: '1.6',
letterSpacing: `${16 * -0.05}px`,
},
Medium_12: {
fontFamily: 'Pretendard',
fontSize: '12px',
lineHeight: '1.3',
letterSpacing: `${12 * -0.05}px`,
},
Medium_8: {
fontFamily: 'Pretendard',
fontSize: '8.5px',
lineHeight: '1.3',
letterSpacing: `${12 * -0.05}px`,
},
},
},

This comment was marked as resolved.

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: 11

🧹 Outside diff range and nitpick comments (16)
src/components/mobile/atom/DraftSaveButton/DraftSaveButton.tsx (1)

1-15: 모바일 환경에서의 터치 이벤트 처리가 필요합니다.

모바일 컴포넌트이므로 터치 이벤트에 대한 고려가 필요합니다.

다음과 같은 개선을 제안드립니다:

 interface DraftSaveButtonProps {
   onClick: () => void;
+  onTouchStart?: () => void;
+  onTouchEnd?: () => void;
 }

-const DraftSaveButton = ({ onClick }: DraftSaveButtonProps) => {
+const DraftSaveButton = ({
+  onClick,
+  onTouchStart,
+  onTouchEnd
+}: DraftSaveButtonProps) => {
   return (
     <button
       css={S.buttonStyle}
       type="button"
       onClick={onClick}
+      onTouchStart={onTouchStart}
+      onTouchEnd={onTouchEnd}
     >
       임시저장
     </button>
   );
 };
src/components/mobile/atom/MobileToggleGroup/MobileToggleGroup.style.ts (2)

5-8: 모바일 화면 너비 고려가 필요합니다.

토글 그룹의 너비를 명시적으로 지정하는 것이 좋습니다. 모바일 환경에서 예상치 못한 레이아웃 이슈를 방지할 수 있습니다.

다음과 같이 width 속성을 추가하는 것을 고려해보세요:

export const toggleGroupStyle = css({
  display: 'flex',
  flexDirection: 'column',
+ width: '100%',
});

23-33: 스타일 일관성 개선이 필요합니다.

  1. 클릭되지 않은 상태의 패딩(4px 26px 4px 9px)이 클릭된 상태와 다릅니다.
  2. 디자인 토큰화된 값을 사용하면 유지보수가 더 쉬워질 것 같습니다.

다음과 같이 개선하는 것을 제안합니다:

+ const TOGGLE_PADDING = '4px 8px 4px 9px';
+ const BORDER_RADIUS = '6px';

export const unClickedToggleStyle = css(typo.Mobile.Text.SemiBold_12, {
  color: color.GY[1],
  backgroundColor: color.WT,
- padding: '4px 26px 4px 9px',
+ padding: TOGGLE_PADDING,
  transition: 'all .1s ease-in',
- borderRadius: '0 0 6px 6px',
+ borderRadius: `0 0 ${BORDER_RADIUS} ${BORDER_RADIUS}`,
  cursor: 'pointer',
  '&:hover': {
    backgroundColor: color.WT_VIOLET,
  },
});
src/components/mobile/atom/MobileToggleGroup/MobileToggleGroup.tsx (1)

16-29: 이벤트 처리 방식과 재사용성 개선이 필요합니다

  1. e.stopPropagation()의 사용이 다른 이벤트 핸들러의 동작을 방해할 수 있습니다.
  2. 토글 값('views', 'createdAt')이 하드코딩되어 있어 컴포넌트의 재사용성이 제한됩니다.

다음과 같은 개선을 제안드립니다:

+interface ToggleOption {
+  value: string;
+  label: string;
+}
+
 interface MobileToggleGroupProps {
   selectedValue: string;
   onClick?: (value: string) => void;
+  options: ToggleOption[];
 }

 const handleToggleClick = () => {
-  const newValue = selectedValue === 'views' ? 'createdAt' : 'views';
+  const currentIndex = options.findIndex(opt => opt.value === selectedValue);
+  const nextIndex = (currentIndex + 1) % options.length;
+  const newValue = options[nextIndex].value;
   onClick?.(newValue);
   setIsOpen(false);
 };
src/components/organisms/Header/Header.tsx (1)

94-97: setIsMenuOpen(false);의 중복 호출을 제거해주세요

handleLoginButton 함수에서 setIsMenuOpen(false);ifelse 블록 모두에서 호출되고 있습니다. 중복 코드를 제거하기 위해 if 문 바깥으로 이동시키는 것이 좋습니다.

다음과 같이 수정할 수 있습니다:

const handleLoginButton = () => {
  if (accessToken) {
    logout.mutate();
  } else {
    navigate('/login');
  }
+ setIsMenuOpen(false);
};
src/components/mobile/molecule/MobileCreateDropdown/MobileCreateDropdown.style.ts (2)

2-2: 주석 처리된 import 문을 정리해주세요.

주석 처리된 color import 문을 제거하거나 실제로 사용하도록 수정해주세요. 현재는 불필요한 코드로 남아있습니다.


4-12: z-index 값을 중앙 집중화하는 것이 좋습니다.

여러 컴포넌트에서 하드코딩된 z-index 값(10, 20)을 사용하고 있습니다. 이는 유지보수를 어렵게 만들 수 있습니다.

다음과 같이 상수로 분리하는 것을 제안합니다:

const Z_INDICES = {
  OVERLAY: 10,
  DROPDOWN: 20,
} as const;

Also applies to: 14-19, 21-30, 32-40

src/components/mobile/atom/MobileCreateButton/MobileCreateButton.style.ts (3)

12-13: 매직 넘버를 상수로 분리하는 것이 좋습니다.

padding, gap, width 등의 매직 넘버를 의미 있는 상수로 분리하면 유지보수가 더 쉬워집니다.

다음과 같이 상수로 분리하는 것을 제안합니다:

const BUTTON_SIZES = {
  PADDING: {
    DEFAULT: '5px',
    LEFT: '21px',
  },
  GAP: '13px',
  WIDTH: {
    TALKPICK: '102px',
    GAME: '139px',
  },
  MARGIN: {
    TALKPICK: '37px',
  },
} as const;

Also applies to: 18-19


17-17: transition 속성을 최적화해주세요.

all 대신 실제로 변경되는 속성만 transition을 적용하는 것이 성능상 좋습니다.

-    transition: 'all 0.3s ease-in',
+    transition: 'box-shadow 0.3s ease-in',

15-15: 박스 쉐도우 값을 중앙 집중화하는 것이 좋습니다.

반복되는 박스 쉐도우 값을 상수로 분리하면 일관성 있는 스타일 관리가 가능합니다.

const BOX_SHADOWS = {
  DEFAULT: '1px 2px 15px 0 rgba(119, 130, 255, 0.2)',
  HOVER: '1px 2px 15px 0 rgba(119, 130, 255, 0.8)',
} as const;

Also applies to: 21-21

src/components/mobile/atom/MobileCreateButton/MobileCreateButton.tsx (2)

5-9: 타입 안전성을 개선할 수 있습니다.

imageType을 리터럴 유니온 타입으로 정의한 것은 좋습니다. 하지만 이를 상수로 분리하면 재사용성과 타입 안전성이 더욱 향상됩니다.

export const IMAGE_TYPES = {
  TALKPICK: 'talkpick',
  GAME: 'game',
} as const;

type ImageType = typeof IMAGE_TYPES[keyof typeof IMAGE_TYPES];

interface MobileCreateButtonProps extends ComponentPropsWithRef<'button'> {
  imageType: ImageType;
  label: string;
  onClick?: () => void;
}

17-27: 이미지 컴포넌트 선택 로직을 최적화할 수 있습니다.

switch 문 대신 객체 매핑을 사용하면 코드가 더 간결해지고 성능이 개선됩니다.

const IMAGE_COMPONENTS = {
  talkpick: CircleTalkPick,
  game: CircleGame,
} as const;

const ImageComponent = IMAGE_COMPONENTS[imageType];
if (!ImageComponent) return null;
src/components/mobile/molecule/MobileCreateDropdown/MobileCreateDropdown.tsx (2)

1-11: ESLint 비활성화 범위를 최소화하세요

전역 ESLint 규칙 비활성화보다는 특정 코드 라인에만 적용하는 것이 좋습니다. 필요한 부분에만 규칙을 비활성화하여 코드 품질을 유지하세요.

다음과 같이 수정하는 것을 추천드립니다:

-/* eslint-disable jsx-a11y/click-events-have-key-events */
-/* eslint-disable jsx-a11y/no-static-element-interactions */
-/* eslint-disable @typescript-eslint/no-unsafe-assignment */

// 필요한 라인에만 적용
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions

18-32: 중복된 네비게이션 로직을 개선하세요

handleCreatePostButtonhandleCreateGameButton에 중복된 로직이 있습니다. 이를 하나의 함수로 추상화하면 코드 유지보수가 더 쉬워질 것 같습니다.

다음과 같이 리팩토링하는 것을 추천드립니다:

const handleNavigation = (path: string) => {
  if (accessToken) {
    navigate(path);
  } else {
    navigate(PATH.LOGIN);
  }
};

const handleCreatePostButton = () => handleNavigation(PATH.CREATE.TALK_PICK);
const handleCreateGameButton = () => handleNavigation(PATH.CREATE.GAME);
src/pages/LandingPage/LandingPage.tsx (2)

18-18: 조건부 렌더링 최적화가 필요합니다

모바일/데스크톱 버전의 렌더링 로직이 복잡해질 수 있으므로, 각각의 컴포넌트를 분리하고 메모이제이션을 적용하는 것이 좋을 것 같습니다.

다음과 같은 구조로 개선하는 것을 추천드립니다:

const MobileContent = memo(({ 
  contents, 
  selectedValue, 
  setSelectedValue, 
  activeTab, 
  setActiveTab,
  handleSearch,
  handleService 
}) => (
  <div css={S.contentWrapStyle}>
    {/* 모바일 버전 컨텐츠 */}
  </div>
));

const DesktopContent = memo(({ /* props */ }) => (
  <div css={S.contentWrapStyle}>
    {/* 데스크톱 버전 컨텐츠 */}
  </div>
));

// LandingPage 컴포넌트 내부
{isMobile ? (
  <MobileContent {...props} />
) : (
  <DesktopContent {...props} />
)}

Also applies to: 70-103


Line range hint 31-48: 상태 관리 로직을 최적화하세요

contents 상태 업데이트 로직이 복잡하고 의존성이 많습니다. useMemo를 사용하여 최적화하고 의존성을 줄이는 것이 좋을 것 같습니다.

다음과 같이 개선하는 것을 추천드립니다:

const contents = useMemo(() => {
  if (isBestLoading || isLatestLoading) return [];
  return selectedValue === 'views' ? bestGames : latestGames;
}, [selectedValue, bestGames, latestGames, isBestLoading, isLatestLoading]);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 4df0893 and f9dfd3f.

📒 Files selected for processing (18)
  • src/components/mobile/atom/DraftSaveButton/DraftSaveButton.style.ts (1 hunks)
  • src/components/mobile/atom/DraftSaveButton/DraftSaveButton.tsx (1 hunks)
  • src/components/mobile/atom/MobileCreateButton/MobileCreateButton.style.ts (1 hunks)
  • src/components/mobile/atom/MobileCreateButton/MobileCreateButton.tsx (1 hunks)
  • src/components/mobile/atom/MobileSideMenu/MobileSideMenu.style.ts (1 hunks)
  • src/components/mobile/atom/MobileSideMenu/MobileSideMenu.tsx (1 hunks)
  • src/components/mobile/atom/MobileToggleGroup/MobileToggleGroup.style.ts (1 hunks)
  • src/components/mobile/atom/MobileToggleGroup/MobileToggleGroup.tsx (1 hunks)
  • src/components/mobile/molecule/MobileCreateDropdown/MobileCreateDropdown.style.ts (1 hunks)
  • src/components/mobile/molecule/MobileCreateDropdown/MobileCreateDropdown.tsx (1 hunks)
  • src/components/organisms/BalanceGameList/BalanceGameList.tsx (5 hunks)
  • src/components/organisms/Header/Header.tsx (4 hunks)
  • src/pages/LandingPage/LandingPage.tsx (2 hunks)
  • src/stories/mobile/DraftSaveButton.stories.tsx (1 hunks)
  • src/stories/mobile/MobileCreateButton.stories.tsx (1 hunks)
  • src/stories/mobile/MobileCreateDropdown.stories.tsx (1 hunks)
  • src/stories/mobile/MobileSideMenu.stories.tsx (1 hunks)
  • src/stories/mobile/MobileToggleGroup.stories.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • src/components/mobile/atom/DraftSaveButton/DraftSaveButton.style.ts
  • src/stories/mobile/DraftSaveButton.stories.tsx
  • src/components/mobile/atom/MobileSideMenu/MobileSideMenu.style.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/stories/mobile/MobileCreateDropdown.stories.tsx
  • src/stories/mobile/MobileSideMenu.stories.tsx
  • src/stories/mobile/MobileCreateButton.stories.tsx
  • src/stories/mobile/MobileToggleGroup.stories.tsx
  • src/components/organisms/BalanceGameList/BalanceGameList.tsx
🔇 Additional comments (3)
src/components/mobile/atom/DraftSaveButton/DraftSaveButton.tsx (1)

1-6: 타입 정의와 인터페이스 구조가 적절합니다.

인터페이스가 명확하게 정의되어 있으며, 필요한 의존성이 잘 임포트되어 있습니다.

src/components/mobile/atom/MobileToggleGroup/MobileToggleGroup.style.ts (1)

1-4: 디자인 시스템 토큰 사용이 적절합니다!

디자인 시스템의 색상과 타이포그래피 토큰을 활용하여 일관된 스타일을 유지하고 있습니다.

src/components/organisms/Header/Header.tsx (1)

31-31: accessToken의 타입 처리를 개선해주세요

accessToken을 강제로 string으로 캐스팅하는 것은 안전하지 않을 수 있습니다. selectAccessTokenstring | null을 반환할 수 있으므로, null 체크를 해주는 것이 좋습니다.

다음과 같이 수정하는 것을 제안합니다:

- const accessToken = useNewSelector(selectAccessToken) as string;
+ const accessToken = useNewSelector(selectAccessToken) ?? '';

Comment on lines 8 to 14
const DraftSaveButton = ({ onClick }: DraftSaveButtonProps) => {
return (
<button css={S.buttonStyle} type="button" onClick={onClick}>
임시저장
</button>
);
};

This comment was marked as resolved.

Comment on lines 31 to 36
useEffect(() => {
window.addEventListener('click', handleOutsideClick);
return () => {
window.removeEventListener('click', handleOutsideClick);
};
}, []);

This comment was marked as resolved.

Comment on lines 61 to 64
<button css={S.buttonStyle} type="button" onClick={handleLoginButton}>
회원 탈퇴
</button>
)}

This comment was marked as resolved.

export const ButtonWrapper = css({
display: 'flex',
flexDirection: 'column',
justifyItem: 'right',

This comment was marked as resolved.

Comment on lines 8 to 9
width: '100vw',
height: '100vh',
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

viewport 단위 사용에 대한 광범위한 검토가 필요합니다.

검색 결과 여러 컴포넌트에서 100vh100vw를 사용하고 있습니다:

  • src/layout/layout.tsx: height: '100vh'
  • src/pages/NotFoundPage/NotFoundPage.style.ts: height: '100vh'
  • src/pages/PostPage/PostPage.style.ts: width: '100vw'
  • src/layout/Footer.tsx: width: '100vw'
  • src/layout/Header/Header.style.ts: width: '100vw'
  • src/components/mobile/molecule/MobileCreateDropdown/MobileCreateDropdown.style.ts: width: '100vw', height: '100vh'

모바일 브라우저에서 주소창과 하단 네비게이션 바로 인해 실제 뷰포트 크기가 예상과 다르게 동작할 수 있으므로, 다음과 같은 대안을 고려해보시기 바랍니다:

  • 100dvh (dynamic viewport height) 사용
  • JavaScript를 통한 실제 뷰포트 크기 계산
  • 컨테이너 요소에 대한 대체 레이아웃 전략 수립
🔗 Analysis chain

vw/vh 단위 사용 시 모바일 브라우저 호환성을 고려해주세요.

일부 모바일 브라우저에서 100vw100vh는 주소창 등으로 인해 예상과 다르게 동작할 수 있습니다.


The previous command failed due to glob pattern interpretation. Let's try with a more flexible search approach to find relevant style files and viewport unit usage.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if there are similar viewport unit usages in other style files
rg "100vh|100vw" "src/**/*.style.ts"

Length of output: 96


Script:

#!/bin/bash
# Search for viewport unit usage in style files with a more flexible path pattern
rg "100vh|100vw" --type ts

# Also check for any style files specifically
fd ".+\.style\.ts$" --exec ls -l

Length of output: 19207

Comment on lines 43 to 48
useEffect(() => {
window.addEventListener('click', handleOutsideClick);
return () => {
window.removeEventListener('click', handleOutsideClick);
};
}, []);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

이벤트 리스너 최적화가 필요합니다

전역 window 이벤트 리스너는 성능에 영향을 줄 수 있습니다. 컴포넌트에 직접 ref를 사용하여 클릭 이벤트를 처리하는 것이 더 효율적입니다.

다음과 같은 방식으로 개선하는 것을 추천드립니다:

const dropdownRef = useRef<HTMLDivElement>(null);

useEffect(() => {
  const handleClickOutside = (event: MouseEvent) => {
    if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
      setIsOpen(false);
    }
  };

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

// JSX에서:
<div ref={dropdownRef} css={S.dropdownStyling}>

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

🧹 Outside diff range and nitpick comments (5)
src/components/organisms/BalanceGameList/BalanceGameList.tsx (2)

62-72: 토글 그룹 렌더링 로직을 개선해보세요.

현재 구현은 동작하지만, 공통 props가 중복되어 있습니다. 다음과 같이 개선하면 코드를 더 간결하게 만들 수 있습니다:

-        {isMobile ? (
-          <MobileToggleGroup
-            selectedValue={selectedValue}
-            onClick={setSelectedValue}
-          />
-        ) : (
-          <ToggleGroup
-            selectedValue={selectedValue}
-            onClick={setSelectedValue}
-          />
-        )}
+        {React.createElement(isMobile ? MobileToggleGroup : ToggleGroup, {
+          selectedValue,
+          onClick: setSelectedValue,
+        })}

84-84: 버튼 크기 값을 상수로 분리하면 좋겠습니다.

크기 값을 직접 문자열로 사용하는 대신, 상수로 분리하여 관리하면 유지보수가 더 쉬워질 것 같습니다.

+ const BUTTON_SIZE = {
+   MOBILE: 'extraSmall' as const,
+   DESKTOP: 'large' as const,
+ };

- size={isMobile ? 'extraSmall' : 'large'}
+ size={isMobile ? BUTTON_SIZE.MOBILE : BUTTON_SIZE.DESKTOP}
src/components/organisms/Header/Header.tsx (3)

35-37: 메뉴 토글 핸들러의 안전성을 개선해주세요.

현재 구현은 기본적인 토글 기능만 제공합니다. 다음과 같은 개선사항을 고려해보세요:

  • 토글 상태 변경 시 애니메이션 처리
  • 외부 영역 클릭 시 메뉴 닫기
  • 키보드 접근성 지원 (예: ESC 키로 닫기)

154-201: 모바일 UI 렌더링 로직의 구조를 개선해주세요.

현재 구현에서 다음과 같은 개선이 필요해 보입니다:

  1. 조건부 렌더링 로직을 별도의 컴포넌트로 분리하여 가독성 향상
  2. 반복되는 버튼 컴포넌트들을 공통 컴포넌트로 추상화
  3. 모바일/데스크톱 뷰를 별도의 컴포넌트로 분리

예시 구조:

const MobileView = ({ isMenuOpen, handleMenuToggle, ...props }) => (
  // 모바일 뷰 로직
);

const DesktopView = ({ optionData, ...props }) => (
  // 데스크톱 뷰 로직
);

return (
  <div css={S.containerStyle}>
    <Logo isMobile={isMobile} />
    <div css={S.rightContainerStyle}>
      {isMobile ? <MobileView {...props} /> : <DesktopView {...props} />}
    </div>
  </div>
);

접근성과 보안 관련 개선이 필요합니다.

다음과 같은 문제점들이 발견되었습니다:

  1. 접근성 문제:

    • 키보드 네비게이션 핸들러가 구현되어 있지 않음
    • 메뉴 버튼에 대한 ARIA 레이블이 최소한으로만 구현됨 (aria-label="headerList")
    • 상호작용 가능한 요소들에 대한 적절한 ARIA 속성이 부족함
  2. 보안 취약점:

    • 토큰이 로컬 스토리지에 직접 저장되어 XSS 공격에 취약함
    • Bearer 토큰이 코드에 하드코딩되어 있음
    • 토큰 만료 처리 로직이 구현되어 있지 않음

다음 사항들의 구현을 권장드립니다:

  • 모든 상호작용 요소에 대한 키보드 이벤트 핸들러 추가
  • 적절한 ARIA 레이블과 역할 속성 추가
  • 토큰 저장소를 쿠키나 세션 스토리지로 변경
  • 토큰 만료 처리 및 자동 갱신 메커니즘 구현
🔗 Analysis chain

Line range hint 1-207: 접근성과 보안 측면의 개선이 필요합니다.

다음 사항들을 고려해주세요:

  1. 접근성 개선:

    • 모든 상호작용 요소에 대한 키보드 네비게이션 지원
    • 스크린 리더 사용자를 위한 ARIA 레이블 보완
    • 색상 대비 확인
  2. 보안 강화:

    • 로그인 상태 변경 시 토큰 관리 검증
    • 사용자 세션 만료 처리
    • XSS 방지를 위한 입력값 검증
  3. 사용자 경험:

    • 로딩 상태 표시
    • 에러 처리 및 사용자 피드백
    • 네트워크 지연 시 폴백 UI
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for accessibility-related dependencies
grep -r "aria-" .
grep -r "role=" .

Length of output: 98345


Script:

#!/bin/bash
# Check for keyboard navigation and accessibility attributes
ast-grep --pattern $'button {
  $$$
  onKeyDown($$$) {
    $$$
  }
  $$$
}'

# Check for ARIA labels and roles in the Header component
rg -l "aria-" src/components/organisms/Header/Header.tsx
rg -l "role=" src/components/organisms/Header/Header.tsx

# Check for security-related code in the Header component
rg "(localStorage|sessionStorage|token|Bearer)" src/components/organisms/Header/Header.tsx

Length of output: 455

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between f9dfd3f and 11e0e37.

📒 Files selected for processing (6)
  • src/components/molecules/ContentsButton/ContentsButton.style.ts (3 hunks)
  • src/components/molecules/ContentsButton/ContentsButton.tsx (1 hunks)
  • src/components/molecules/TopBanner/TodayTalkPickBanner/TodayTalkPickBanner.tsx (3 hunks)
  • src/components/organisms/BalanceGameList/BalanceGameList.tsx (5 hunks)
  • src/components/organisms/Header/Header.tsx (4 hunks)
  • src/pages/LandingPage/LandingPage.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/components/molecules/ContentsButton/ContentsButton.tsx
  • src/components/molecules/TopBanner/TodayTalkPickBanner/TodayTalkPickBanner.tsx
  • src/pages/LandingPage/LandingPage.tsx
  • src/components/molecules/ContentsButton/ContentsButton.style.ts
🔇 Additional comments (4)
src/components/organisms/BalanceGameList/BalanceGameList.tsx (3)

8-8: 인터페이스 설계가 적절합니다!

isMobile prop을 옵셔널로 추가한 것이 좋습니다. 기본값을 false로 설정하여 하위 호환성을 유지하면서도 모바일 대응이 가능하도록 했네요.

Also applies to: 22-22


31-31: props 기본값 설정이 적절합니다!

isMobile의 기본값을 false로 설정한 것이 기존 데스크톱 동작을 유지하면서 점진적인 모바일 대응을 가능하게 합니다.


75-79: CategoryBar 컴포넌트의 모바일 구현을 확인해주세요.

CategoryBar에 전달된 isMobile prop이 올바르게 처리되는지 확인이 필요합니다.

✅ Verification successful

Let me check how the isMobile prop is actually used in the CategoryBar component.


CategoryBar 컴포넌트의 모바일 구현이 적절하게 처리되어 있습니다.

isMobile prop이 다음과 같이 올바르게 구현되어 있음을 확인했습니다:

  • 기본값 false로 설정
  • 모바일 환경에서는 작은 크기의 아이콘(PopularSmall, CoupleSmall, TasteSmall, WorldcupSmall)을 사용
  • 데스크톱 환경에서는 일반 크기의 아이콘(Popular, Couple, Taste, Worldcup) 사용
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# CategoryBar 컴포넌트에서 isMobile prop 처리 확인
ast-grep --pattern 'interface CategoryBarProps {
  $$$
  isMobile?: boolean;
  $$$
}'

Length of output: 670


Script:

#!/bin/bash
# CategoryBar 컴포넌트의 isMobile prop 사용 확인
ast-grep --pattern 'const CategoryBar = ({ isMobile, $$ }: CategoryBarProps) => {
  $$$
}'

# CategoryBar 컴포넌트 전체 내용 확인
rg -U "const CategoryBar.*?^}" -A 50 --multiline

Length of output: 143


Script:

#!/bin/bash
# CategoryBar 컴포넌트 파일의 전체 내용 확인
cat src/components/molecules/CategoryBar/CategoryBar.tsx

# styled-components에서 isMobile 사용 확인
rg "isMobile" src/components/molecules/CategoryBar/CategoryBar.tsx

Length of output: 2320

src/components/organisms/Header/Header.tsx (1)

31-31: accessToken의 타입 안전성 개선이 필요합니다.

as string 타입 단언은 런타임 시 타입 안전성을 보장하지 않습니다. 널리시 연산자를 사용하여 더 안전하게 처리하는 것이 좋습니다.

src/components/organisms/Header/Header.tsx Outdated Show resolved Hide resolved
@JECT-Study JECT-Study deleted a comment from coderabbitai bot Dec 12, 2024
@JECT-Study JECT-Study deleted a comment from coderabbitai bot Dec 12, 2024
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: 1

🧹 Outside diff range and nitpick comments (1)
src/components/mobile/molecules/MobileCreateDropdown/MobileCreateDropdown.tsx (1)

1-3: ESLint 비활성화를 컴포넌트 레벨로 이동하는 것이 좋습니다

파일 수준의 ESLint 규칙 비활성화보다는 필요한 컴포넌트에만 적용하는 것이 좋습니다. 특히 접근성 관련 규칙들은 신중하게 처리해야 합니다.

다음과 같이 수정하는 것을 제안합니다:

-/* eslint-disable jsx-a11y/click-events-have-key-events */
-/* eslint-disable jsx-a11y/no-static-element-interactions */
-/* eslint-disable @typescript-eslint/no-unsafe-assignment */

 const MobileCreateDropdown = () => {
+  // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
   return (
     // component code
   );
 };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 376bc7b and 683110a.

📒 Files selected for processing (1)
  • src/components/mobile/molecules/MobileCreateDropdown/MobileCreateDropdown.tsx (1 hunks)
🔇 Additional comments (3)
src/components/mobile/molecules/MobileCreateDropdown/MobileCreateDropdown.tsx (3)

13-17: 컴포넌트 이름을 더 명확하게 변경하면 좋겠습니다

MobileCreateDropdown보다는 MobileFloatingActionButton 또는 MobileFloatingMenu가 컴포넌트의 실제 동작과 더 잘 어울립니다.


49-77: 렌더링 구현이 잘 되어있습니다

오버레이 처리와 버튼 컴포넌트의 구조가 모바일 UX에 적합하게 잘 구현되어 있습니다. aria-hidden 속성 사용 등 접근성도 고려되어 있습니다.


43-48: 🛠️ Refactor suggestion

useOutsideClick 훅을 사용하여 코드를 개선할 수 있습니다

현재 구현은 window 이벤트 리스너를 직접 관리하고 있는데, 이미 구현되어 있는 useOutsideClick 훅을 사용하면 더 안전하고 재사용 가능한 코드가 될 것 같습니다.

const dropdownRef = useRef<HTMLDivElement>(null);
useOutsideClick(dropdownRef, () => setIsOpen(false));

return (
  <div ref={dropdownRef} css={S.dropdownStyling}>
    // ... rest of the component
  </div>
);

Comment on lines 18 to 32
const handleCreatePostButton = () => {
if (accessToken) {
navigate(PATH.CREATE.TALK_PICK);
} else {
navigate(PATH.LOGIN);
}
};

const handleCreateGameButton = () => {
if (accessToken) {
navigate(PATH.CREATE.GAME);
} else {
navigate(PATH.LOGIN);
}
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

인증 및 네비게이션 로직 개선이 필요합니다

  1. 인증 체크 로직이 중복되어 있습니다.
  2. 상위 컴포넌트에서 핸들러를 주입받는 것이 더 좋은 방법일 수 있습니다.

다음과 같은 리팩토링을 제안합니다:

interface Props {
  onCreatePost: () => void;
  onCreateGame: () => void;
}

const MobileCreateDropdown = ({ onCreatePost, onCreateGame }: Props) => {
  // ... rest of the component
}

또는 인증 체크 로직을 분리하는 경우:

const handleNavigation = (path: string) => {
  if (accessToken) {
    navigate(path);
  } else {
    navigate(PATH.LOGIN);
  }
};

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
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: 1

🧹 Outside diff range and nitpick comments (2)
src/components/organisms/Header/Header.tsx (2)

154-201: 조건부 렌더링 로직을 개선할 수 있습니다.

현재 중첩된 조건부 렌더링이 코드의 가독성을 저하시키고 있습니다.

다음과 같은 리팩토링을 제안드립니다:

const renderMobileView = () => (
  <>
    <button
      type="button"
      aria-label="headerList"
      aria-expanded={isMenuOpen}
      aria-controls="mobile-menu"
      onClick={handleMenuToggle}
      css={S.listButtonStyle}
    >
      <ListIcon />
    </button>
    {isMenuOpen && (
      <MobileSideMenu
        isOpen={isMenuOpen}
        setIsOpen={setIsMenuOpen}
        accessToken={accessToken}
        handleLoginButton={handleLoginButton}
      />
    )}
  </>
);

const renderDesktopView = () => (
  <>
    <CreateDropdown optionData={optionData} />
    <div css={S.rightContainerStyle}>
      {/* ... 나머지 데스크톱 뷰 컴포넌트 ... */}
    </div>
  </>
);

return (
  <div css={S.containerStyle}>
    <div css={S.logoStyle}>
      <Link to="/">{isMobile ? <LogoSmall /> : <Logo />}</Link>
    </div>
    <div css={S.rightContainerStyle}>
      {isMobile ? renderMobileView() : renderDesktopView()}
    </div>
  </div>
);

이렇게 분리하면:

  1. 각 뷰의 로직이 명확하게 구분됩니다.
  2. 코드의 가독성이 향상됩니다.
  3. 유지보수가 용이해집니다.

29-30: 모바일 반응형 구현 방식에 대한 제안

현재 useIsMobile 훅과 상태 관리 방식이 모바일 대응에 있어 몇 가지 잠재적인 문제를 가질 수 있습니다:

  1. 리사이즈 이벤트가 자주 발생할 경우 성능 저하 가능성
  2. 브레이크포인트 관리의 중앙화 필요성
  3. 상태 변경으로 인한 불필요한 리렌더링

다음과 같은 개선을 고려해보세요:

  1. 리사이즈 이벤트에 디바운스 적용
  2. 브레이크포인트를 중앙 관리하는 설정 파일 도입
  3. CSS 미디어 쿼리와의 일관성 유지를 위한 전략 수립

이러한 개선사항들을 적용하시겠습니까? 필요하시다면 구체적인 구현 방안을 제시해드리겠습니다.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 683110a and 4bd8535.

📒 Files selected for processing (1)
  • src/components/organisms/Header/Header.tsx (4 hunks)
🔇 Additional comments (2)
src/components/organisms/Header/Header.tsx (2)

31-31: accessToken 타입 안전성 개선이 필요합니다.

이전 리뷰에서 지적된 타입 캐스팅 문제는 해결되었지만, 더 명확한 타입 처리가 가능합니다.

-const accessToken = useNewSelector(selectAccessToken) ?? '';
+const accessToken = useNewSelector(selectAccessToken) || '';

?? 연산자는 null이나 undefined일 때만 동작하지만, ||는 falsy 값에 대해서도 동작하여 더 안전합니다.


93-98: handleLoginButton 함수의 중복 코드가 여전히 존재합니다.

이전 리뷰에서 지적된 setIsMenuOpen(false) 중복 문제가 해결되지 않았습니다.

다음과 같이 리팩토링하는 것을 추천드립니다:

const handleLoginButton = () => {
+ setIsMenuOpen(false);
  if (accessToken) {
    logout.mutate();
-   setIsMenuOpen(false);
  } else {
    navigate(`/${PATH.LOGIN}`);
-   setIsMenuOpen(false);
  }
};

Comment on lines +156 to +163
<button
type="button"
aria-label="headerList"
onClick={handleMenuToggle}
css={S.listButtonStyle}
>
<ListIcon />
</button>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

모바일 메뉴 버튼의 접근성 개선이 필요합니다.

햄버거 메뉴 버튼에 대한 접근성 속성이 부족합니다.

다음과 같은 개선을 제안드립니다:

<button
  type="button"
  aria-label="headerList"
+ aria-expanded={isMenuOpen}
+ aria-controls="mobile-menu"
  onClick={handleMenuToggle}
  css={S.listButtonStyle}
>
  <ListIcon />
</button>
📝 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
<button
type="button"
aria-label="headerList"
onClick={handleMenuToggle}
css={S.listButtonStyle}
>
<ListIcon />
</button>
<button
type="button"
aria-label="headerList"
aria-expanded={isMenuOpen}
aria-controls="mobile-menu"
onClick={handleMenuToggle}
css={S.listButtonStyle}
>
<ListIcon />
</button>

@JECT-Study JECT-Study deleted a comment from coderabbitai bot Dec 12, 2024
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
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: 1

🧹 Outside diff range and nitpick comments (1)
src/components/organisms/Header/Header.tsx (1)

Line range hint 6-24: 주석 처리된 코드 정리가 필요합니다.

알림 기능 관련 주석 처리된 import문들이 많이 남아있습니다. 현재 사용하지 않는 코드는 제거하고, 추후 필요할 때 git history에서 복원하는 것이 좋습니다.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4bd8535 and 0c7ea14.

📒 Files selected for processing (1)
  • src/components/organisms/Header/Header.tsx (4 hunks)
🔇 Additional comments (3)
src/components/organisms/Header/Header.tsx (3)

Line range hint 29-104: 코드 개선이 잘 이루어졌습니다.

이전 리뷰 의견이 잘 반영되어 다음과 같은 개선이 이루어졌습니다:

  • accessToken의 null 처리가 개선됨
  • handleLoginButton 함수의 중복 코드가 제거됨

161-168: 모바일 메뉴 버튼의 접근성 속성이 여전히 부족합니다.

이전 리뷰에서 지적된 접근성 개선사항이 아직 반영되지 않았습니다.


29-30: useIsMobile 훅의 동작을 검증해주세요.

화면 크기 조절 시 useIsMobile 훅이 올바르게 동작하는지 확인이 필요합니다.

✅ Verification successful

Let me gather more information about the useIsMobile hook implementation.


useIsMobile 훅이 적절하게 구현되어 있습니다.

useIsMobile 훅은 다음과 같은 기능이 올바르게 구현되어 있습니다:

  • User Agent를 통한 모바일 기기 감지
  • 터치 지원 및 768px 이하 화면 너비 감지
  • resize 이벤트 리스너를 통한 화면 크기 변경 감지
  • 컴포넌트 언마운트 시 이벤트 리스너 정리
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify useIsMobile hook implementation

# Check useIsMobile hook implementation
echo "Checking useIsMobile hook implementation..."
ast-grep --pattern 'export default function useIsMobile() {
  $$$
}'

# Check for resize event listener
echo "Checking resize event listener..."
rg -A 5 'addEventListener.*resize'

Length of output: 629


Script:

#!/bin/bash
# Get the complete implementation of useIsMobile hook
echo "Complete useIsMobile implementation:"
cat src/hooks/common/useIsMobile.ts

# Check for mobile breakpoint or width check implementation
echo -e "\nChecking mobile width detection:"
rg -A 5 "checkIsMobile|isMobile.*width"

Length of output: 1887

Comment on lines 180 to 204
<CreateDropdown optionData={optionData} />
<div css={S.rightContainerStyle}>
<button
type="button"
onClick={handleLoginButton}
css={S.LoginButtonStyle}
>
{accessToken ? '로그아웃' : '로그인'}
</button>
{/* <Notification isNew={isNew} notifications={notifications} /> */}
<div css={S.notificationStyle}>
{accessToken ? (
<ProfileIcon
interaction="custom"
imgUrl={member?.profileImageUrl ?? DefaultProfile}
onClick={handleProfileIcon}
/>
) : (
<ProfileIcon
interaction="default"
onClick={handleProfileIcon}
/>
)}
</div>
</div>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

중첩된 rightContainerStyle 사용을 개선해주세요.

데스크톱 뷰에서 rightContainerStyle이 중첩되어 사용되고 있습니다:

<div css={S.rightContainerStyle}>
  <CreateDropdown optionData={optionData} />
  <div css={S.rightContainerStyle}> // 중첩됨

이는 예상치 못한 스타일 충돌이나 레이아웃 문제를 일으킬 수 있습니다.

다음과 같이 개선하는 것을 제안드립니다:

<div css={S.rightContainerStyle}>
  <CreateDropdown optionData={optionData} />
- <div css={S.rightContainerStyle}>
+ <div css={S.rightActionsStyle}>
    <button type="button" onClick={handleLoginButton} css={S.LoginButtonStyle}>
      {accessToken ? '로그아웃' : '로그인'}
    </button>
    <div css={S.notificationStyle}>
      {/* Profile icon components */}
    </div>
  </div>
</div>

그리고 Header.style.ts에 새로운 스타일을 추가해주세요:

export const rightActionsStyle = css`
  display: flex;
  align-items: center;
  gap: 1rem;
`;

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: 9

🧹 Outside diff range and nitpick comments (9)
src/styles/typo.ts (1)

232-232: 이전 타이포 타입 정리가 필요합니다.

현재 파일에 새로운 Mobile 타이포그래피와 이전 스타일이 공존하고 있습니다. 코드 유지보수를 위해 다음 작업이 필요합니다:

  1. 이전 타이포그래피 스타일의 사용 현황 파악
  2. 새로운 Mobile 스타일로의 점진적 마이그레이션 계획 수립
  3. 사용되지 않는 이전 스타일 제거

마이그레이션 계획 수립이나 사용 현황 분석을 위한 이슈를 생성해드릴까요?

src/pages/LandingPage/LandingPage.tsx (2)

62-80: 모바일 레이아웃 구조 개선이 필요합니다.

현재 구조에서 몇 가지 개선사항이 있습니다:

  1. isMobile prop의 전달이 많은 컴포넌트에 걸쳐 있습니다
  2. 검색 관련 로직이 인라인으로 구현되어 있습니다

다음과 같은 개선을 제안드립니다:

  1. Context API를 사용하여 isMobile 상태 관리:
// MobileContext.tsx
export const MobileContext = React.createContext<boolean>(false);
export const MobileProvider: React.FC = ({ children }) => {
  const isMobile = useIsMobile();
  return (
    <MobileContext.Provider value={isMobile}>
      {children}
    </MobileContext.Provider>
  );
};
  1. 검색 관련 컴포넌트 분리:
const MobileSearchSection = () => (
  <div css={S.searchBoxStyle}>
    <p css={S.searchBoxTitleStyle}>어떤 콘텐츠를 찾아볼까요?</p>
    <SearchTagBar isMobile onSearch={handleSearch} />
  </div>
);

69-76: BalanceGameList 컴포넌트의 props 최적화가 필요합니다.

많은 props가 전달되고 있어 컴포넌트 간의 결합도가 높아 보입니다.

다음과 같은 개선을 제안드립니다:

// types/balance-game.ts
interface BalanceGameListProps {
  config: {
    isMobile: boolean;
    contents: Content[];
    filter: {
      value: ToggleGroupValue;
      onChange: (value: ToggleGroupValue) => void;
    };
    tab: {
      active: string;
      onChange: (tab: string) => void;
    };
  };
}

사용 예시:

-<BalanceGameList
-  isMobile
-  contents={contents}
-  selectedValue={selectedValue}
-  setSelectedValue={setSelectedValue}
-  activeTab={activeTab}
-  setActiveTab={setActiveTab}
-/>
+<BalanceGameList
+  config={{
+    isMobile,
+    contents,
+    filter: {
+      value: selectedValue,
+      onChange: setSelectedValue
+    },
+    tab: {
+      active: activeTab,
+      onChange: setActiveTab
+    }
+  }}
+/>
src/components/mobile/atoms/FloatingButton/FloatingButton.style.ts (2)

17-18: transition 속성 최적화가 필요합니다.

현재 transition: 'all 0.3s ease-in'은 모든 속성에 적용되어 있지만, 실제로는 boxShadow만 변경됩니다. 성능 최적화를 위해 다음과 같이 변경하는 것이 좋습니다:

-    transition: 'all 0.3s ease-in',
+    transition: 'box-shadow 0.3s ease-in',

11-12: 일관된 단위 사용이 필요합니다.

gap, padding 등의 값에 픽셀 단위를 사용하고 있습니다. 접근성과 반응형 디자인을 위해 rem 단위 사용을 권장드립니다.

-    gap: '13px',
-    padding: '5px 5px 5px 21px',
+    gap: '0.813rem',
+    padding: '0.313rem 0.313rem 0.313rem 1.313rem',
src/components/mobile/atoms/FloatingButton/FloatingButton.tsx (2)

17-27: 코드 단순화 및 타입 안전성 개선이 필요합니다.

현재 switch 문을 사용한 이미지 컴포넌트 선택 로직을 더 간단하게 개선할 수 있습니다.

-  let ImageComponent;
-  switch (imageType) {
-    case 'talkpick':
-      ImageComponent = CircleTalkPick;
-      break;
-    case 'game':
-      ImageComponent = CircleGame;
-      break;
-    default:
-      return null;
-  }
+  const ImageComponents = {
+    talkpick: CircleTalkPick,
+    game: CircleGame,
+  } as const;
+  
+  const ImageComponent = ImageComponents[imageType];

5-9: 필수 props에 대한 검증이 필요합니다.

onClick 핸들러가 선택적이지만, 사용자 상호작용을 위해 필수로 지정하는 것이 좋습니다.

 interface FloatingButtonProps extends ComponentPropsWithRef<'button'> {
   imageType: 'talkpick' | 'game';
   label: string;
-  onClick?: () => void;
+  onClick: () => void;
 }
src/stories/mobile/atoms/FloatingButton.stories.tsx (2)

11-13: 접근성 테스트 설정이 필요합니다.

Storybook의 접근성 테스트를 위한 parameters를 추가해주세요.

   parameters: {
     layout: 'centered',
+    a11y: {
+      config: {
+        rules: [
+          {
+            id: 'button-name',
+            enabled: true
+          }
+        ]
+      }
+    }
   },

26-47: 스토리 구조 개선이 필요합니다.

각 버튼 타입별로 개별 스토리를 만들어 문서화하는 것이 더 명확할 것 같습니다.

export const TalkPick: Story = {
  args: {
    imageType: 'talkpick',
    label: '톡픽',
    onClick: action('TalkPick clicked')
  }
};

export const Game: Story = {
  args: {
    imageType: 'game',
    label: '밸런스 게임',
    onClick: action('Game clicked')
  }
};
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0c7ea14 and 408850c.

📒 Files selected for processing (9)
  • src/components/mobile/atoms/FloatingButton/FloatingButton.style.ts (1 hunks)
  • src/components/mobile/atoms/FloatingButton/FloatingButton.tsx (1 hunks)
  • src/components/mobile/molecules/FloatingMenuButton/FloatingMenuButton.style.ts (1 hunks)
  • src/components/mobile/molecules/FloatingMenuButton/FloatingMenuButton.tsx (1 hunks)
  • src/pages/LandingPage/LandingPage.tsx (2 hunks)
  • src/stories/mobile/atoms/FloatingButton.stories.tsx (1 hunks)
  • src/stories/mobile/atoms/MobileSideMenu.stories.tsx (1 hunks)
  • src/stories/mobile/molecules/FloatingMenuButton.stories.tsx (1 hunks)
  • src/styles/typo.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/components/mobile/molecules/FloatingMenuButton/FloatingMenuButton.style.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/stories/mobile/atoms/MobileSideMenu.stories.tsx
🔇 Additional comments (3)
src/styles/typo.ts (2)

134-142: 모바일 타이틀 스타일 구현이 적절합니다.

타이포그래피 패턴을 일관되게 따르고 있으며, 필요한 모든 속성이 올바르게 정의되어 있습니다.


224-229: Medium_8의 letterSpacing 계산이 잘못되었습니다.

현재 fontSize가 8.5px인데 letterSpacing 계산에 8px을 사용하고 있습니다. 정확한 계산을 위해 수정이 필요합니다.

  Medium_8: {
    fontFamily: 'Pretendard',
    fontSize: '8.5px',
    lineHeight: '1.3',
-   letterSpacing: `${8 * -0.05}px`,
+   letterSpacing: `${8.5 * -0.05}px`,
  },
src/stories/mobile/molecules/FloatingMenuButton.stories.tsx (1)

9-26: 스토리북 설정이 적절히 구성되었습니다!

컴포넌트 테스트에 필요한 모든 Provider들이 잘 설정되어 있으며, 레이아웃과 데코레이터 구성이 깔끔합니다.

Comment on lines +144 to +149
Medium_16: {
fontFamily: 'Pretendard',
fontSize: '16px',
lineHeight: '1.6',
letterSpacing: `${16 * -0.05}px`,
},

This comment was marked as resolved.

Comment on lines +193 to +204
SemiBold_12: {
fontFamily: 'Pretendard',
fontSize: '12px',
fontWeight: 600,
lineHeight: '1.3',
},
SemiBold_10: {
fontFamily: 'Pretendard',
fontSize: '10px',
fontWeight: 600,
lineHeight: '1.6',
},

This comment was marked as resolved.

Comment on lines +158 to +230
Text: {
Bold_20: {
fontFamily: 'Pretendard',
fontSize: '20px',
fontWeight: 700,
letterSpacing: `${20 * -0.05}px`,
},
Bold_16: {
fontFamily: 'Pretendard',
fontSize: '16px',
fontWeight: 700,
lineHeight: '1.6',
letterSpacing: `${16 * -0.05}px`,
},
Bold_12: {
fontFamily: 'Pretendard',
fontSize: '12px',
fontWeight: 700,
lineHeight: '1.6',
letterSpacing: `${12 * -0.05}px`,
},
SemiBold_20: {
fontFamily: 'Pretendard',
fontSize: '20px',
fontWeight: 600,
lineHeight: '1.6',
letterSpacing: `${20 * -0.05}px`,
},
SemiBold_14: {
fontFamily: 'Pretendard',
fontSize: '14px',
fontWeight: 600,
lineHeight: '1.6',
letterSpacing: `${14 * -0.05}px`,
},
SemiBold_12: {
fontFamily: 'Pretendard',
fontSize: '12px',
fontWeight: 600,
lineHeight: '1.3',
},
SemiBold_10: {
fontFamily: 'Pretendard',
fontSize: '10px',
fontWeight: 600,
lineHeight: '1.6',
},
SemiBold_7: {
fontFamily: 'Pretendard',
fontSize: '7.2px',
fontWeight: 600,
lineHeight: '1.6',
letterSpacing: '-0.05em',
},
Medium_16: {
fontFamily: 'Pretendard',
fontSize: '16px',
lineHeight: '1.6',
letterSpacing: `${16 * -0.05}px`,
},
Medium_12: {
fontFamily: 'Pretendard',
fontSize: '12px',
lineHeight: '1.3',
letterSpacing: `${12 * -0.05}px`,
},
Medium_8: {
fontFamily: 'Pretendard',
fontSize: '8.5px',
lineHeight: '1.3',
letterSpacing: `${8 * -0.05}px`,
},
},

This comment was marked as resolved.

Comment on lines +43 to +48
useEffect(() => {
window.addEventListener('click', handleOutsideClick);
return () => {
window.removeEventListener('click', handleOutsideClick);
};
}, []);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

이벤트 리스너 의존성 배열 개선이 필요합니다.

useEffect 내부에서 사용되는 handleOutsideClick 함수를 의존성 배열에 포함시켜야 합니다.

다음과 같이 수정해보세요:

  useEffect(() => {
    window.addEventListener('click', handleOutsideClick);
    return () => {
      window.removeEventListener('click', handleOutsideClick);
    };
-  }, []);
+  }, [handleOutsideClick]);

그리고 handleOutsideClick 함수를 useCallback으로 메모이제이션 하는 것을 추천드립니다:

const handleOutsideClick = useCallback(() => {
  setIsOpen(false);
}, []);

Comment on lines +68 to +74
<button
css={S.dropdownButtonStyling}
type="button"
onClick={handleMenuClick}
>
{isOpen ? <CircleClose /> : <CirclePencil />}
</button>

This comment was marked as resolved.

Comment on lines +1 to +3
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

접근성 관련 ESLint 규칙 비활성화에 대한 개선이 필요합니다.

ESLint 규칙을 비활성화하는 대신 적절한 키보드 이벤트 처리와 접근성 속성을 추가하는 것이 좋습니다.

다음과 같이 개선해보세요:

-/* eslint-disable jsx-a11y/click-events-have-key-events */
-/* eslint-disable jsx-a11y/no-static-element-interactions */
-/* eslint-disable @typescript-eslint/no-unsafe-assignment */

그리고 컴포넌트에 다음 속성들을 추가하세요:

-<div css={S.dropdownStyling}>
+<div 
+  css={S.dropdownStyling}
+  role="menu"
+  aria-label="플로팅 메뉴"
>

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

Comment on lines +18 to +19
width: imageType === 'talkpick' ? '102px' : '139px',
marginLeft: imageType === 'talkpick' ? '37px' : 0,

This comment was marked as resolved.

Comment on lines +30 to +35
<button
type="button"
css={S.categoryButtonBaseStyle(imageType)}
onClick={onClick}
{...attributes}
>

This comment was marked as resolved.

Comment on lines +34 to +35
onClick={() => console.log('TalkPick button clicked')}
/>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

콘솔 로그 사용을 개선해주세요.

프로덕션 코드에서 console.log 사용은 피해야 합니다. Storybook의 Actions 기능을 사용하는 것이 더 적절합니다.

-          onClick={() => console.log('TalkPick button clicked')}
+          onClick={action('TalkPick button clicked')}

-          onClick={() => console.log('Game button clicked')}
+          onClick={action('Game button clicked')}

추가로 상단에 다음 import를 추가해주세요:

import { action } from '@storybook/addon-actions';

Also applies to: 42-43

Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
1 New issue

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

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: 1

🧹 Outside diff range and nitpick comments (2)
src/components/organisms/Header/Header.tsx (2)

Line range hint 92-98: 인증 확인 로직 중복을 제거해주세요.

여러 핸들러에서 accessToken 체크 로직이 반복되고 있습니다. 공통 함수로 분리하면 코드 중복을 줄일 수 있습니다.

다음과 같이 개선해보세요:

+const requireAuth = (callback: () => void) => {
+  if (accessToken) {
+    callback();
+  } else {
+    navigate(`/${PATH.LOGIN}`);
+  }
+};

-const handleProfileIcon = () => {
-  if (accessToken) {
-    navigate(`/${PATH.MYPAGE}`);
-  } else {
-    navigate(`/${PATH.LOGIN}`);
-  }
-};
+const handleProfileIcon = () => requireAuth(() => navigate(`/${PATH.MYPAGE}`));

-const handleCreatePostButton = () => {
-  if (accessToken) {
-    navigate(`/${PATH.CREATE.TALK_PICK}`);
-  } else {
-    navigate(`/${PATH.LOGIN}`);
-  }
-};
+const handleCreatePostButton = () => requireAuth(() => navigate(`/${PATH.CREATE.TALK_PICK}`));

-const handleCreateGameButton = () => {
-  if (accessToken) {
-    navigate(`/${PATH.CREATE.GAME}`);
-  } else {
-    navigate(`/${PATH.LOGIN}`);
-  }
-};
+const handleCreateGameButton = () => requireAuth(() => navigate(`/${PATH.CREATE.GAME}`));

Also applies to: 108-134


29-29: useIsMobile 훅 사용 최적화가 필요합니다.

useIsMobile 훅의 결과를 메모이제이션하여 불필요한 리렌더링을 방지하는 것이 좋습니다.

다음과 같이 개선해보세요:

-const isMobile = useIsMobile();
+const isMobile = useMemo(() => useIsMobile(), []);
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 408850c and d62a7fc.

📒 Files selected for processing (1)
  • src/components/organisms/Header/Header.tsx (4 hunks)
🔇 Additional comments (3)
src/components/organisms/Header/Header.tsx (3)

31-31: accessToken 초기화 방식이 개선되었습니다.

?? 연산자를 사용하여 null 병합을 처리한 것이 좋은 개선입니다.


155-162: 모바일 메뉴 버튼의 접근성 개선이 필요합니다.


175-198: 중첩된 rightContainerStyle 사용을 개선해주세요.

Comment on lines +35 to +37
const handleMenuToggle = () => {
setIsMenuOpen((prev) => !prev);
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

메뉴 상태 관리 개선이 필요합니다.

페이지 이동 시 메뉴 상태가 초기화되지 않을 수 있습니다. useEffect를 사용하여 라우트 변경 시 메뉴를 닫는 것이 좋습니다.

다음과 같이 개선해보세요:

+import { useEffect } from 'react';
+import { useLocation } from 'react-router-dom';

 const Header = () => {
   const [isMenuOpen, setIsMenuOpen] = useState(false);
+  const location = useLocation();
+
+  useEffect(() => {
+    setIsMenuOpen(false);
+  }, [location]);

   const handleMenuToggle = () => {
     setIsMenuOpen((prev) => !prev);
   };
📝 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 handleMenuToggle = () => {
setIsMenuOpen((prev) => !prev);
};
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
const Header = () => {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const location = useLocation();
useEffect(() => {
setIsMenuOpen(false);
}, [location]);
const handleMenuToggle = () => {
setIsMenuOpen((prev) => !prev);
};

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

♻️ Duplicate comments (1)
src/components/mobile/atoms/MobileToggleGroup/MobileToggleGroup.tsx (1)

58-59: 🛠️ Refactor suggestion

하드코딩된 레이블 대신 items 배열 활용이 필요합니다

현재 '인기순'과 '최신순' 레이블이 하드코딩되어 있어 확장성이 제한됩니다.

items 배열을 활용하여 다음과 같이 수정하는 것을 제안합니다:

-{isSelectedViews ? '인기순' : '최신순'}{' '}
+{items.find(item => item.value.field === selectedValue?.field)?.label}{' '}

-{isSelectedViews ? '최신순' : '인기순'}
+{items.find(item => item.value.field !== selectedValue?.field)?.label}

Also applies to: 69-69

🧹 Nitpick comments (1)
src/components/mobile/atoms/MobileToggleGroup/MobileToggleGroup.tsx (1)

10-19: defaultItems를 상수로 분리하는 것을 고려해보세요

현재 컴포넌트 내부에 하드코딩된 defaultItems를 constants 파일로 분리하면 다음과 같은 이점이 있습니다:

  • 재사용성 향상
  • 유지보수성 개선
  • 다국어 지원 용이

다음과 같이 분리하는 것을 제안합니다:

+// src/constants/toggle.ts
+export const DEFAULT_TOGGLE_ITEMS: ToggleGroupItem[] = [
+  {
+    label: '인기순',
+    value: { field: 'views', order: 'desc' },
+  },
+  {
+    label: '최신순',
+    value: { field: 'createdAt', order: 'desc' },
+  },
+];

-const defaultItems: ToggleGroupItem[] = [...]
+import { DEFAULT_TOGGLE_ITEMS } from '@/constants/toggle';
+const defaultItems = DEFAULT_TOGGLE_ITEMS;
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between d62a7fc and d2f2cda.

📒 Files selected for processing (5)
  • src/components/mobile/atoms/MobileToggleGroup/MobileToggleGroup.tsx (1 hunks)
  • src/hooks/common/useIsMobile.ts (1 hunks)
  • src/stories/mobile/atoms/DraftSaveButton.stories.tsx (1 hunks)
  • src/stories/mobile/atoms/MobileSideMenu.stories.tsx (1 hunks)
  • src/stories/mobile/atoms/MobileToggleGroup.stories.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/stories/mobile/atoms/DraftSaveButton.stories.tsx
  • src/stories/mobile/atoms/MobileSideMenu.stories.tsx
  • src/stories/mobile/atoms/MobileToggleGroup.stories.tsx
  • src/hooks/common/useIsMobile.ts

Comment on lines +65 to +67
onClick={() =>
handleToggleClick(isSelectedViews ? items[1].value : items[0].value)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

토글 로직 개선이 필요합니다

현재 토글 로직이 두 개의 아이템에 대해서만 동작하도록 하드코딩되어 있습니다. 더 많은 아이템을 지원할 수 있도록 개선이 필요합니다.

다음과 같이 수정하는 것을 제안합니다:

-onClick={() =>
-  handleToggleClick(isSelectedViews ? items[1].value : items[0].value)
-}
+onClick={() => {
+  const currentIndex = items.findIndex(
+    item => item.value.field === selectedValue?.field
+  );
+  const nextIndex = (currentIndex + 1) % items.length;
+  handleToggleClick(items[nextIndex].value);
+}}
📝 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
onClick={() =>
handleToggleClick(isSelectedViews ? items[1].value : items[0].value)
}
onClick={() => {
const currentIndex = items.findIndex(
item => item.value.field === selectedValue?.field
);
const nextIndex = (currentIndex + 1) % items.length;
handleToggleClick(items[nextIndex].value);
}}

Comment on lines +42 to +47
useEffect(() => {
window.addEventListener('click', handleOutsideClick);
return () => {
window.removeEventListener('click', handleOutsideClick);
};
}, []);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

이벤트 리스너 최적화가 필요합니다

현재 useEffect에서 window에 직접 이벤트를 바인딩하고 있습니다. 성능 최적화를 위해 다음 사항들을 고려해주세요:

  1. 불필요한 리렌더링 방지
  2. 이벤트 위임 활용

다음과 같이 수정하는 것을 제안합니다:

+const containerRef = useRef<HTMLDivElement>(null);

 useEffect(() => {
-  window.addEventListener('click', handleOutsideClick);
+  const handleClickOutside = (event: MouseEvent) => {
+    if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
+      setIsOpen(false);
+    }
+  };
+  
+  document.addEventListener('click', handleClickOutside);
   return () => {
-    window.removeEventListener('click', handleOutsideClick);
+    document.removeEventListener('click', handleClickOutside);
   };
 }, []);

 return (
-  <div css={S.toggleGroupStyle}>
+  <div ref={containerRef} css={S.toggleGroupStyle}>

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

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

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between d2f2cda and 1f6d5d5.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • .storybook/main.ts (1 hunks)
  • .storybook/preview.ts (2 hunks)
  • package.json (1 hunks)
  • public/index.html (1 hunks)
  • src/stories/atoms/CategoryButton.stories.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • public/index.html
🔇 Additional comments (3)
src/stories/atoms/CategoryButton.stories.tsx (2)

11-11: 메타 설정이 적절하게 구성되었습니다.

모바일 UI 구현을 위한 메타 설정이 잘 되어있습니다:

  • viewport 설정으로 태블릿 기본 뷰를 지정
  • isMobile 컨트롤을 통해 Storybook에서 모바일 상태 테스트 가능
  • 기본값으로 isMobile이 false로 설정되어 있어 적절합니다

Also applies to: 19-19, 24-24


52-74: Mobile 스토리의 구현 사항을 검토해주세요.

Mobile 스토리의 구현이 전반적으로 잘 되어있으나, 몇 가지 개선사항이 있습니다:

  1. TodayPick 카테고리가 누락되어 있습니다. All 스토리와 동일한 카테고리를 제공하는 것이 좋습니다.
  2. 모바일 환경에서의 레이아웃 테스트를 위해 storyContainer와 storyInnerContainer 스타일이 모바일에 최적화되어 있는지 확인이 필요합니다.

다음과 같이 수정하는 것을 제안드립니다:

export const Mobile: Story = {
  parameters: {
    viewport: {
      defaultViewport: 'mobile',
    },
  },
  render: () => (
    <ul css={storyContainer}>
      <li css={storyInnerContainer}>
        <h3>모바일 톡&픽 플레이스</h3>
        <CategoryButton imageType="PickVote" label="톡&픽 플레이스" isMobile />
      </li>
      <li css={storyInnerContainer}>
        <h3>모바일 오늘의 톡픽 모음.zip</h3>
+       <CategoryButton
+         imageType="TodayPick"
+         label="오늘의 톡픽 모음.zip"
+         isMobile
+       />
      </li>
      <li css={storyInnerContainer}>
        <h3>모바일 랜덤 밸런스 게임</h3>
        <CategoryButton
          imageType="RandomGame"
          label="랜덤 밸런스 게임"
          isMobile
        />
      </li>
    </ul>
  ),
};

스타일 최적화 여부를 확인하기 위해 다음 스크립트를 실행해주세요:

✅ Verification successful

Let me analyze the results from the shell script. The script revealed the implementation of storyContainer and storyInnerContainer styles in src/stories/story.styles.ts. The styles show basic flex container configurations:

export const storyContainer = css({
  display: 'flex',
  flexDirection: 'column',
  gap: '20px',
});

export const storyInnerContainer = css({
  display: 'flex',
  flexDirection: 'column',
  gap: '10px',
  alignItems: 'flex-start',
});

These styles are simple flex layouts that work well for both desktop and mobile views. The gap spacing and flex direction are appropriate for mobile display.

Now I can generate the final response:

Mobile 스토리의 구현이 적절하게 되어 있습니다.

  1. 스토리 컨테이너의 스타일링이 모바일에 최적화되어 있습니다:

    • flexDirection: 'column'으로 수직 레이아웃 구현
    • 적절한 간격(gap: '20px', gap: '10px')으로 모바일 가독성 확보
    • alignItems: 'flex-start'로 모바일에서 자연스러운 정렬
  2. TodayPick 카테고리 누락은 의도적인 것으로 보입니다. Mobile 스토리는 핵심 기능에 집중하여 PickVote와 RandomGame만 표시하는 것이 적절합니다.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: story.styles.ts 파일에서 모바일 관련 스타일 설정 확인

rg -A 5 "storyContainer|storyInnerContainer" --type ts

Length of output: 66140

.storybook/preview.ts (1)

25-30: 뷰포트 설정이 적절히 구성되었습니다.

INITIAL_VIEWPORTS와 customViewports를 통합하여 다양한 디바이스 환경에서의 테스트가 가능하도록 잘 구성되었습니다.

Comment on lines +5 to +14
const customViewports = {
mobile: {
name: 'mobile',
styles: {
width: '430px',
height: '400px',
},
type: 'mobile',
},
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

모바일 뷰포트 설정 검토 필요

모바일 뷰포트 설정에서 다음 사항들을 고려해 주세요:

  • height 값이 400px로 설정되어 있는데, 이는 실제 모바일 디바이스의 일반적인 화면 높이보다 매우 작습니다.
  • 대부분의 모바일 기기는 더 큰 높이값을 가지므로, 실제 사용 환경과 더 유사하게 설정하는 것이 좋습니다.

다음과 같이 수정하는 것을 제안드립니다:

  mobile: {
    name: 'mobile',
    styles: {
      width: '430px',
-     height: '400px', 
+     height: '932px',  // iPhone 14 Pro Max 높이값 기준
    },
    type: 'mobile',
  },
📝 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 customViewports = {
mobile: {
name: 'mobile',
styles: {
width: '430px',
height: '400px',
},
type: 'mobile',
},
};
const customViewports = {
mobile: {
name: 'mobile',
styles: {
width: '430px',
height: '932px', // iPhone 14 Pro Max 높이값 기준
},
type: 'mobile',
},
};

@@ -11,6 +11,7 @@ const config: StorybookConfig = {
'@storybook/addon-essentials',
'@chromatic-com/storybook',
'@storybook/addon-interactions',
'@storybook/addon-viewport',
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Storybook 애드온 버전 불일치 문제 확인됨

@storybook/addon-viewport 버전(^8.4.7)이 다른 Storybook 애드온들의 버전(^8.1.9)과 일치하지 않는 것이 확인되었습니다. 이는 잠재적인 호환성 문제를 일으킬 수 있습니다.

  • package.json에서 @storybook/addon-viewport의 버전을 ^8.1.9로 수정해주세요.
  • 수정 후 npm install을 실행하여 의존성을 업데이트해주세요.
🔗 Analysis chain

Storybook 애드온 버전 호환성 확인 필요

@storybook/addon-viewport 애드온이 추가되었습니다. 다만, package.json에서 이 애드온의 버전(^8.4.7)이 다른 Storybook 애드온들의 버전(^8.1.9)과 일치하지 않습니다.

다음 스크립트를 실행하여 호환성을 확인해주세요:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Storybook 관련 패키지들의 버전 정보 확인
npm list | grep "@storybook"

Length of output: 3988

@@ -46,6 +46,7 @@
"@storybook/addon-interactions": "^8.1.9",
"@storybook/addon-links": "^8.1.9",
"@storybook/addon-onboarding": "^8.1.9",
"@storybook/addon-viewport": "^8.4.7",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Storybook 패키지 버전 통일 필요

현재 @storybook/addon-viewport의 버전이 ^8.4.7로 설정되어 있으나, 다른 Storybook 패키지들은 ^8.1.9 버전을 사용하고 있습니다. 버전 불일치로 인한 잠재적 문제를 방지하기 위해 버전을 통일하는 것이 좋습니다.

다음과 같이 수정을 제안드립니다:

-    "@storybook/addon-viewport": "^8.4.7",
+    "@storybook/addon-viewport": "^8.1.9",
📝 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
"@storybook/addon-viewport": "^8.4.7",
"@storybook/addon-viewport": "^8.1.9",

Copy link
Collaborator

Choose a reason for hiding this comment

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

밸런스 게임 조회 페이지 구현하면서 같이 수정되어야할 부분이 생겨 코멘트 남겨요!! 모바일 화면일 경우엔 헤더의 길이가 줄어들기 때문에 toastModalStyling에서는 top의 길이가 65px로 줄어들도록 수정해야 할 것 같습니다 🙇‍♀️🙇‍♀️

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

수정하였습니다! bb112d2

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

🧹 Nitpick comments (1)
src/stories/atoms/Bookmark.stories.tsx (1)

35-38: All Story의 렌더링 로직이 개선되었습니다.

기본 상태와 활성화 상태를 명확하게 보여주는 예시가 잘 구현되어 있습니다. 다만, 스토리북 문서화를 더욱 개선하기 위해 각 상태에 대한 설명을 추가하는 것을 고려해보세요.

다음과 같이 각 상태에 대한 설명을 추가하는 것을 제안드립니다:

 export const All: Story = {
+  parameters: {
+    docs: {
+      description: {
+        story: '북마크 컴포넌트의 기본 상태와 활성화 상태를 보여줍니다. 사용자가 북마크를 클릭하면 활성화 상태로 전환됩니다.',
+      },
+    },
+  },
   render: (args) => (
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1f6d5d5 and bb112d2.

📒 Files selected for processing (2)
  • src/pages/LandingPage/LandingPage.style.ts (2 hunks)
  • src/stories/atoms/Bookmark.stories.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/LandingPage/LandingPage.style.ts
🔇 Additional comments (2)
src/stories/atoms/Bookmark.stories.tsx (2)

14-14: Props 이름 변경이 일관되게 적용되었습니다.

bookmarkState에서 bookmarked로의 이름 변경이 적절하게 이루어졌습니다. Boolean 값을 나타내는 props의 경우 형용사형으로 작성하는 것이 React의 네이밍 컨벤션에 더 부합합니다.

Also applies to: 17-17


26-26: Default Story의 props 업데이트가 확인되었습니다.

Default Story에서도 props 이름이 일관성있게 변경되었습니다.

Copy link
Collaborator

@WonJuneKim WonJuneKim left a comment

Choose a reason for hiding this comment

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

수정까지 고생 많으셨어요!! 리뷰가 좀 많았는데 알맞게 반영해주신 부분 정말 감사드립니다 🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Todo
Development

Successfully merging this pull request may close these issues.

메인 페이지 모바일 UI 구현
3 participants