Skip to content

feat: header, option 공통 컴포넌트#11

Merged
cindy-chaewon merged 14 commits into
developfrom
feat/#8-header-option
Apr 25, 2026
Merged

feat: header, option 공통 컴포넌트#11
cindy-chaewon merged 14 commits into
developfrom
feat/#8-header-option

Conversation

@cindy-chaewon

@cindy-chaewon cindy-chaewon commented Apr 25, 2026

Copy link
Copy Markdown
Collaborator

🔗 연결된 이슈

📝 작업 요약

공통 헤더 위젯과 OptionMenu 컴포넌트를 구현하고, 관련 아이콘을 추가했습니다. OptionMenu는 items 배열 prop 기반으로 설계해 어떤 아이템 조합도 자유롭게 사용할 수 있습니다.

🔍 주요 변경사항

  • Header 위젯 구현 — default / detail / write 3가지 variant 지원
  • default: 뒤로가기 + 공유 + 설정 버튼
  • detail: 뒤로가기 + OptionMenu (수정/공유/삭제 or 신고/공유)
  • write: 뒤로가기 + CheckButton (체크 토글)
  • OptionMenu 구현 — shadcn DropdownMenu 기반, items 배열로 아이콘·라벨·onClick·isDestructive 자유롭게 구성, 열릴 때 dim 오버레이 노출
  • 아이콘 추가: IcBack, IcCheck, IcOption, IcSetting
  • 전역 cursor: pointer 스타일 추가
  • --color-destructive 값 #ff3b30으로 수정
  • shared/ui/ 내 커스텀 파일도 kebab-case 네이밍 규칙 적용 및 docs 업데이트

📸 스크린샷 (선택사항)

image

📌 PR 중요도

  • 🔴 High: 중요한 기능 추가/버그 수정 (반드시 리뷰 필요)
  • 🟡 Medium: 일반적인 기능 개선
  • 🟢 Low: 사소한 수정/문서 업데이트

💬 기타 사항

cindy-chaewon and others added 10 commits April 25, 2026 12:35
* chore: VSCode 팀 공통 개발환경 설정 추가 (#1)

* docs: Biome 활용법 추가 (#1)

* chore: jsonc 언어 ID 추가 (#1)
* feat: 아이콘 공통 컴포넌트 초기 세팅 (#3)

* feat: 모바일 레이아웃 세팅 (#3)

* feat: 폰트 추가 (#3)

* feat: 디자인 시스템 컬러 및 타이포그래피 토큰 추가 (#3)

- 그레이 스케일 컬러 토큰 추가 (gray-0 ~ gray-700)
- shadcn/ui 호환 시맨틱 컬러 토큰 추가
- 타이포그래피 유틸리티 클래스 추가 (head, subhead, body, caption, title)
- 앱 기본 배경색 설정 및 base 스타일 보완

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: UI 마크업 가이드 문서 추가 (#3)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: 아이콘 가이드 추가 및 문서 정리 (#3)

- icon-guide.md 분리 신규 추가
- ui-markup-guide.md 아이콘 섹션 제거 및 링크로 대체
- CLAUDE.md 참고 문서 목록 업데이트
- tsconfig.json baseUrl 제거

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* style: 앱 배경색 gray-50 토큰으로 변경 (#3)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: shadcn/ui 호환 토큰 누락 추가 (#3)

- destructive-foreground, accent, accent-foreground 토큰 추가

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel

vercel Bot commented Apr 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
first-penguins-fe Ready Ready Preview, Comment Apr 25, 2026 11:27am

@coderabbitai

coderabbitai Bot commented Apr 25, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Radix UI 의존성을 추가하고 이를 기반으로 드롭다운 메뉴, 옵션 메뉴, 헤더 공통 컴포넌트를 새롭게 구현합니다. 아이콘 컴포넌트 8개를 추가하고, 설정 및 문서를 업데이트합니다.

Changes

Cohort / File(s) Summary
Configuration & Dependencies
biome.json, package.json
Biome 린터 규칙 비활성화 및 radix-ui 의존성 추가
Documentation
docs/naming-convention.md, docs/ui-markup-guide.md
shared/ui/ 컴포넌트 네이밍 규칙(kebab-case.tsx) 및 예제 업데이트
Global Styles
src/app/globals.css
파괴적 색상 토큰 값 변경 및 버튼 커서 동작 추가
Dropdown Menu Component
src/shared/ui/dropdown-menu.tsx
Radix UI DropdownMenu 원시 요소를 감싼 스타일링된 래퍼 컴포넌트 세트 구현
Icon Components
src/shared/ui/icons/IcBack.tsx, IcCheck.tsx, IcEdit.tsx, IcOption.tsx, IcReport.tsx, IcSetting.tsx, IcShare.tsx, IcTrash.tsx, index.ts
8개의 새로운 아이콘 컴포넌트 추가 및 인덱스 파일 업데이트
Option Menu Component
src/shared/ui/option-menu.tsx
드롭다운 메뉴 기반 옵션 메뉴 컴포넌트 구현(배경 오버레이, 커스텀 트리거 지원)
Header Widget
src/widgets/header/ui/Header.tsx, CheckButton.tsx, index.ts
헤더 및 체크 버튼 공통 컴포넌트 구현 및 배럴 모듈 추가

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: implementing shared Header and OptionMenu components, which matches the primary objective of this PR.
Description check ✅ Passed The description provides relevant details about the implemented Header and OptionMenu components, added icons, styling changes, and documentation updates, all related to the changeset.
Linked Issues check ✅ Passed The PR fully implements both objectives from issue #8: Header 공통 컴포넌트 (with default/detail/write variants) and OptionMenu 공통 컴포넌트 (with flexible items array API).
Out of Scope Changes check ✅ Passed All changes are aligned with issue #8 objectives. Icon additions (IcBack, IcCheck, IcOption, IcSetting, IcEdit, IcReport, IcShare, IcTrash), styling updates (cursor pointer, color-destructive), DropdownMenu implementation, documentation, and configuration changes all support the Header and OptionMenu component implementation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#8-header-option

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

<Header 
 title="캘린더" 
 onBack={~} 
 headerOptions={
   <OptionMenu
      items={
        optionMenuVariant === "default"
          ? [
              { icon: <IcEdit />, label: "수정하기", onClick: onEdit },
              { icon: <IcShare />, label: "공유하기", onClick: onShare },
              { icon: <IcTrash />, label: "삭제", onClick: onDelete, isDestructive: true },
            ]
          : [
              { icon: <IcReport />, label: "신고하기", onClick: onReport },
              { icon: <IcShare />, label: "공유하기", onClick: onShare },
            ]
      }
    />
}

요런식으로 헤더 옵션 메뉴를 props로 전달할 수 있도록 하고, Header는 레이아웃을 그리는 역할만 하는 컴포넌트면 더 확장성 높은 컴포넌트가 될 것 같아요!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

반영했습니닷!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (7)
src/shared/ui/icons/IcBack.tsx (1)

9-9: strokeWidth 정밀도 정리(선택).

"1.75008"은 Figma export의 잔여 부동소수점 값으로 보입니다. 시각적 차이가 없으므로 "1.75"로 정리하면 가독성이 좋아집니다.

♻️ 제안 수정
-      strokeWidth="1.75008"
+      strokeWidth="1.75"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/icons/IcBack.tsx` at line 9, IcBack 컴포넌트의 SVG 속성에 남아있는 과도한
부동소수점 값은 가독성을 해치므로, 아이콘 JSX에서 strokeWidth="1.75008"을 찾아 간단히 strokeWidth="1.75"로
정리해 주세요; 관련 식별자는 IcBack (파일 내 SVG element의 strokeWidth 속성)입니다.
docs/naming-convention.md (1)

14-17: 아이콘 파일 예외 규칙을 명시해 주세요.

shared/ui/ 컴포넌트는 kebab-case.tsx로 적었지만, 실제 본 PR의 shared/ui/icons/ 아래 파일들(IcBack.tsx, IcOption.tsx, IcReport.tsx 등)은 PascalCase.tsx입니다. ui-markup-guide.md의 예시와 일관되게 "단, shared/ui/icons/ 아이콘 컴포넌트는 PascalCase.tsx (IcHome.tsx 등)"라는 예외를 한 줄 추가해 두면 신규 기여자가 헷갈리지 않습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/naming-convention.md` around lines 14 - 17, The naming guideline lacks
an explicit exception for icon components under shared/ui/icons; update
docs/naming-convention.md to add one sentence clarifying that while shared/ui
components use kebab-case (e.g., button.tsx, option-menu.tsx), icon components
in shared/ui/icons should use PascalCase like IcBack.tsx, IcOption.tsx,
IcReport.tsx (examples: IcHome.tsx), so new contributors know to treat icons as
PascalCase.tsx.
package.json (1)

18-25: radix-ui 통합 패키지와 @radix-ui/react-slot이 공존합니다.

shadcn 최신 가이드에 맞춰 통합 패키지(radix-ui)를 추가한 것은 적절합니다. 다만 기존 @radix-ui/react-slot이 그대로 남아 있어, 이후 컴포넌트마다 from "radix-ui" / from "@radix-ui/react-*" 두 가지 임포트 스타일이 섞일 수 있습니다. 팀 컨벤션으로 한 가지 스타일을 정해두는 것을 권장합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` around lines 18 - 25, The package list contains both the
unified radix-ui package and the legacy scoped package `@radix-ui/react-slot`
which will lead to mixed import styles; decide on one convention and update
package.json accordingly by removing the redundant package (e.g., delete
"@radix-ui/react-slot": "^1.2.4" if you adopt "radix-ui") and then refactor
imports across the codebase to use the chosen symbol (either imports from
"radix-ui" or from "@radix-ui/react-*" but not both), ensuring modules that
previously imported from "@radix-ui/react-slot" are changed to the corresponding
"radix-ui" import.
biome.json (1)

100-103: src/shared/ui/*.tsx 글롭이 비재귀적입니다.

현재 글롭은 src/shared/ui/ 직속 파일만 매칭하며, src/shared/ui/icons/ 하위 컴포넌트는 포함하지 않습니다. 다행히 현재 모든 컴포넌트(button.tsx, dropdown-menu.tsx, option-menu.tsx, BaseIcon.tsx, IcBack.tsx 등)는 이미 : React.ReactElement 명시적 반환 타입을 선언하고 있어 useExplicitType 규칙 위반이 없습니다.

다만 향후 shadcn 기반 컴포넌트가 하위 폴더에 추가될 때 의도치 않게 린트 경고가 발생할 수 있으므로, 글롭을 재귀 패턴으로 변경하면 더욱 안정적입니다.

♻️ 선택적 개선: 재귀 글롭으로 통일
     {
-      "includes": ["src/shared/ui/*.tsx"],
+      "includes": ["src/shared/ui/**/*.tsx"],
       "linter": { "rules": { "nursery": { "useExplicitType": "off" } } }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@biome.json` around lines 100 - 103, The include glob "src/shared/ui/*.tsx" is
non-recursive and will miss files in subfolders like src/shared/ui/icons, so
update the includes entry in biome.json to use a recursive glob (e.g., change
"src/shared/ui/*.tsx" to "src/shared/ui/**/*.tsx") so all nested TSX components
are linted consistently and avoid future unexpected useExplicitType warnings;
keep the existing linter rule override ("nursery": { "useExplicitType": "off" })
intact.
src/shared/ui/dropdown-menu.tsx (1)

9-210: Arrow Function + 명시적 반환 타입 컨벤션 권장

코딩 가이드라인은 모든 TypeScript 함수에 대해 화살표 함수와 명시적 반환 타입을 사용하도록 규정하고 있습니다. 본 파일은 shadcn/ui 보일러플레이트라 일괄 적용이 부담스러울 수 있지만, 프로젝트 일관성을 위해 점진적 마이그레이션을 고려해보세요.

예시:

-function DropdownMenu({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
-  return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
-}
+const DropdownMenu = (
+  props: React.ComponentProps<typeof DropdownMenuPrimitive.Root>,
+): React.ReactElement => <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;

As per coding guidelines: "Use Arrow Functions with explicit return type annotations for all functions in TypeScript".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/dropdown-menu.tsx` around lines 9 - 210, Convert the listed
named functions (e.g., DropdownMenu, DropdownMenuPortal, DropdownMenuTrigger,
DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem,
DropdownMenuCheckboxItem, DropdownMenuRadioGroup, DropdownMenuRadioItem,
DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub,
DropdownMenuSubTrigger, DropdownMenuSubContent) to arrow functions with explicit
return type annotations (e.g., : React.ReactElement or JSX.Element) while
preserving their prop types (React.ComponentProps<typeof
DropdownMenuPrimitive.*> and the custom prop intersections like in
DropdownMenuItem and DropdownMenuLabel) and all internal behavior/JSX (including
data-slot attributes, className handling via cn, default props such as
sideOffset or variant, and children forwarding); update each function signature
only (no logic changes) so the file conforms to the "arrow functions with
explicit return type" convention.
src/shared/ui/option-menu.tsx (1)

30-33: 백드롭 onClick 부재 — UX 의도 확인 요청

isOpen일 때 dim 오버레이가 깔리지만 클릭/터치에 대한 핸들러가 없어 닫기는 Radix의 outside-click 처리에 의존합니다. 일반적으로 dim을 탭하면 즉시 닫히는 UX를 기대하는 사용자가 많으므로, onClick={() => setIsOpen(false)}을 부여하거나 pointer-events-none을 명시해 의도를 분명히 하는 편이 좋습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/option-menu.tsx` around lines 30 - 33, The backdrop div
rendered when isOpen is true should explicitly handle taps/clicks instead of
silently relying on Radix: add an onClick handler to the overlay div that calls
setIsOpen(false) so tapping the dim closes the menu (reference: the isOpen
state, setIsOpen setter and the surrounding DropdownMenu component), or if the
intent is to make the backdrop non-interactive, mark it with pointer-events-none
to make that behavior explicit; update the overlay div accordingly to reflect
the chosen UX.
src/widgets/header/ui/CheckButton.tsx (1)

4-14: 컴포넌트 위치 검토 (선택)

CheckButton은 헤더 write 변형 외에서도 충분히 재사용 가능한 일반 토글 버튼처럼 보입니다. 코딩 가이드라인상 "재사용 UI는 shared/ui/, 멀티 기능 UI 조합은 widgets/{widgetName}/"이므로, 추후 다른 페이지/위젯에서도 쓰인다면 src/shared/ui/check-button/로 옮기는 것을 고려해보세요. 헤더 전용으로만 쓰일 계획이라면 현재 위치도 무방합니다.

As per coding guidelines, Place reusable UI components in shared/ui/ / Place multi-feature UI combinations in widgets/{widgetName}/.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/widgets/header/ui/CheckButton.tsx` around lines 4 - 14, CheckButton
appears to be a generic reusable toggle rather than header-specific; move the
CheckButton component (interface CheckButtonProps and the exported CheckButton)
from its current widgets/header/ui location into a new shared UI path (e.g.,
src/shared/ui/check-button/) and update imports where it's used, or if it truly
will remain header-only, document that decision in a comment near the
CheckButton export; ensure the exported name CheckButton and its props interface
remain unchanged so callers need only update import paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/globals.css`:
- Line 28: The destructive token `--color-destructive: `#ff3b30`` fails WCAG AA
contrast on white; add a darker foreground token (e.g.
`--color-destructive-foreground` or `--color-destructive-text` with a hex like
`#d70015` or `#c91a09`) alongside the existing `--color-destructive` in
src/app/globals.css, leaving `--color-destructive` for background use and using
the new token wherever destructive is rendered as text or small icons; update
the components that render destructive text (search for `isDestructive` in
option-menu.tsx and the destructive variant in dropdown-menu.tsx) to reference
the new foreground token.
- Around line 59-65: The CSS currently applies cursor: pointer to
[role="button"] regardless of aria-disabled, causing disabled custom buttons to
show a pointer; update the selectors so aria-disabled="true" is treated like
:disabled — either narrow the pointer rule to exclude [aria-disabled="true"]
(e.g. button:not(:disabled), [role="button"]:not([aria-disabled="true"])) or add
a rule that sets cursor: not-allowed for [role="button"][aria-disabled="true"],
adjusting the existing selectors (button:not(:disabled), [role="button"],
button:disabled) and the disabled cursor rule to include the aria-disabled
variant.

In `@src/shared/ui/option-menu.tsx`:
- Around line 48-68: The current items.map uses key={item.label} which can
produce duplicate React keys; switch to a stable unique identifier (e.g.,
item.id) and fall back to index only if no stable id exists when rendering in
DropdownMenuItem, and update any tests/consumers accordingly; also preserve
caller-provided classes when calling cloneElement(item.icon) by merging the
original icon's className (item.icon.props?.className) into the cn(...) call so
the caller's styling isn't overwritten.
- Around line 35-42: Replace the wrapper button's class "outline-none" with
"outline-hidden" and add focus-visible outline utility classes (e.g.,
focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary) on
the trigger so keyboard focus is visible and consistent with other dropdown
items (refer to DropdownMenuTrigger and IcOption). Also change rendering so that
when a custom trigger prop is provided you do not wrap it in an extra <button>
or unconditionally apply aria-label="더보기" — instead branch: if trigger exists
render the trigger as-is (letting callers supply accessible labeling) else
render the internal button with the aria-label and the new outline/focus-visible
classes to avoid nested buttons and duplicate labels.

In `@src/widgets/header/ui/CheckButton.tsx`:
- Around line 19-27: The check icon currently renders with a fixed class
"text-gray-0" inside the CheckButton component regardless of isChecked, causing
poor contrast when isChecked is false; update the rendering logic in
CheckButton.tsx so that IcCheck uses a conditional class (e.g., "text-gray-0"
when isChecked is true, and "text-gray-400" when false) or hide the IcCheck
entirely when unchecked; modify the JSX around the IcCheck usage and the
isChecked prop handling to apply the conditional class or conditional render.
- Around line 1-2: CheckButton is an interactive component that accepts an
onClick prop and can be imported into Server Components via
src/widgets/header/index.ts, which causes runtime serialization errors; fix it
by adding the "use client" directive as the very first line of
src/widgets/header/ui/CheckButton.tsx so the component (CheckButton) is treated
as a Client Component and can accept function props like onClick safely, keeping
behavior consistent with Header, NavBar, and dropdown-menu.

In `@src/widgets/header/ui/Header.tsx`:
- Around line 30-36: The centered title span (the JSX block rendering {title} in
Header.tsx) is absolutely positioned and can overlap the left back button or the
{right} slot when the title is long; update the title span styling to constrain
its width and apply truncation (e.g., set a max-width based on available header
space or use calc(100% - [leftAreaWidth] - [rightAreaWidth]), and add CSS for
overflow-hidden, white-space-nowrap and text-overflow:ellipsis) so long Korean
titles are clipped with an ellipsis and cannot visually collide with the left
button or the right slot.

---

Nitpick comments:
In `@biome.json`:
- Around line 100-103: The include glob "src/shared/ui/*.tsx" is non-recursive
and will miss files in subfolders like src/shared/ui/icons, so update the
includes entry in biome.json to use a recursive glob (e.g., change
"src/shared/ui/*.tsx" to "src/shared/ui/**/*.tsx") so all nested TSX components
are linted consistently and avoid future unexpected useExplicitType warnings;
keep the existing linter rule override ("nursery": { "useExplicitType": "off" })
intact.

In `@docs/naming-convention.md`:
- Around line 14-17: The naming guideline lacks an explicit exception for icon
components under shared/ui/icons; update docs/naming-convention.md to add one
sentence clarifying that while shared/ui components use kebab-case (e.g.,
button.tsx, option-menu.tsx), icon components in shared/ui/icons should use
PascalCase like IcBack.tsx, IcOption.tsx, IcReport.tsx (examples: IcHome.tsx),
so new contributors know to treat icons as PascalCase.tsx.

In `@package.json`:
- Around line 18-25: The package list contains both the unified radix-ui package
and the legacy scoped package `@radix-ui/react-slot` which will lead to mixed
import styles; decide on one convention and update package.json accordingly by
removing the redundant package (e.g., delete "@radix-ui/react-slot": "^1.2.4" if
you adopt "radix-ui") and then refactor imports across the codebase to use the
chosen symbol (either imports from "radix-ui" or from "@radix-ui/react-*" but
not both), ensuring modules that previously imported from "@radix-ui/react-slot"
are changed to the corresponding "radix-ui" import.

In `@src/shared/ui/dropdown-menu.tsx`:
- Around line 9-210: Convert the listed named functions (e.g., DropdownMenu,
DropdownMenuPortal, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuGroup,
DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioGroup,
DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator,
DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubTrigger,
DropdownMenuSubContent) to arrow functions with explicit return type annotations
(e.g., : React.ReactElement or JSX.Element) while preserving their prop types
(React.ComponentProps<typeof DropdownMenuPrimitive.*> and the custom prop
intersections like in DropdownMenuItem and DropdownMenuLabel) and all internal
behavior/JSX (including data-slot attributes, className handling via cn, default
props such as sideOffset or variant, and children forwarding); update each
function signature only (no logic changes) so the file conforms to the "arrow
functions with explicit return type" convention.

In `@src/shared/ui/icons/IcBack.tsx`:
- Line 9: IcBack 컴포넌트의 SVG 속성에 남아있는 과도한 부동소수점 값은 가독성을 해치므로, 아이콘 JSX에서
strokeWidth="1.75008"을 찾아 간단히 strokeWidth="1.75"로 정리해 주세요; 관련 식별자는 IcBack (파일 내
SVG element의 strokeWidth 속성)입니다.

In `@src/shared/ui/option-menu.tsx`:
- Around line 30-33: The backdrop div rendered when isOpen is true should
explicitly handle taps/clicks instead of silently relying on Radix: add an
onClick handler to the overlay div that calls setIsOpen(false) so tapping the
dim closes the menu (reference: the isOpen state, setIsOpen setter and the
surrounding DropdownMenu component), or if the intent is to make the backdrop
non-interactive, mark it with pointer-events-none to make that behavior
explicit; update the overlay div accordingly to reflect the chosen UX.

In `@src/widgets/header/ui/CheckButton.tsx`:
- Around line 4-14: CheckButton appears to be a generic reusable toggle rather
than header-specific; move the CheckButton component (interface CheckButtonProps
and the exported CheckButton) from its current widgets/header/ui location into a
new shared UI path (e.g., src/shared/ui/check-button/) and update imports where
it's used, or if it truly will remain header-only, document that decision in a
comment near the CheckButton export; ensure the exported name CheckButton and
its props interface remain unchanged so callers need only update import paths.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d229556b-a627-4aaa-a2f8-26c486b0c1b4

📥 Commits

Reviewing files that changed from the base of the PR and between 346bbb2 and e46223e.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • biome.json
  • docs/naming-convention.md
  • docs/ui-markup-guide.md
  • package.json
  • src/app/globals.css
  • src/shared/ui/dropdown-menu.tsx
  • src/shared/ui/icons/IcBack.tsx
  • src/shared/ui/icons/IcCheck.tsx
  • src/shared/ui/icons/IcEdit.tsx
  • src/shared/ui/icons/IcOption.tsx
  • src/shared/ui/icons/IcReport.tsx
  • src/shared/ui/icons/IcSetting.tsx
  • src/shared/ui/icons/IcShare.tsx
  • src/shared/ui/icons/IcTrash.tsx
  • src/shared/ui/icons/index.ts
  • src/shared/ui/option-menu.tsx
  • src/widgets/header/index.ts
  • src/widgets/header/ui/CheckButton.tsx
  • src/widgets/header/ui/Header.tsx

Comment thread src/app/globals.css
--color-secondary: #f7f7f7;
--color-secondary-foreground: #090909;
--color-destructive: #e53e3e;
--color-destructive: #ff3b30;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

#ff3b30의 흰 배경 대비비를 확인해 주세요.

#ff3b30는 흰 배경(#ffffff) 기준 명도 대비가 약 3.76:1로 WCAG AA(일반 텍스트 4.5:1)을 충족하지 못합니다. text-destructive가 본문 텍스트나 작은 아이콘 위에 사용될 경우(예: option-menu.tsxisDestructive 라벨, dropdown-menu.tsx의 destructive variant) 가독성이 떨어질 수 있으니, 텍스트로 사용하는 자리에는 조금 더 어두운 톤(예: #d70015 / #c91a09)을 별도 토큰(--color-destructive-foreground 또는 --color-destructive-text)으로 두는 것을 검토해 주세요. 배경색으로만 쓰일 때는 문제없습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/globals.css` at line 28, The destructive token `--color-destructive:
`#ff3b30`` fails WCAG AA contrast on white; add a darker foreground token (e.g.
`--color-destructive-foreground` or `--color-destructive-text` with a hex like
`#d70015` or `#c91a09`) alongside the existing `--color-destructive` in
src/app/globals.css, leaving `--color-destructive` for background use and using
the new token wherever destructive is rendered as text or small icons; update
the components that render destructive text (search for `isDestructive` in
option-menu.tsx and the destructive variant in dropdown-menu.tsx) to reference
the new foreground token.

Comment thread src/app/globals.css
Comment on lines +59 to +65
button:not(:disabled),
[role="button"] {
cursor: pointer;
}
button:disabled {
cursor: not-allowed;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

aria-disabled 케이스가 누락되어 있습니다.

[role="button"]aria-disabled="true"가 붙은 커스텀 버튼은 비활성 상태임에도 cursor: pointer가 적용됩니다. 셀렉터를 보강해 두면 일관됩니다.

🛡️ 제안 수정
-  button:not(:disabled),
-  [role="button"] {
+  button:not(:disabled),
+  [role="button"]:not([aria-disabled="true"]) {
     cursor: pointer;
   }
-  button:disabled {
+  button:disabled,
+  [role="button"][aria-disabled="true"] {
     cursor: not-allowed;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/globals.css` around lines 59 - 65, The CSS currently applies cursor:
pointer to [role="button"] regardless of aria-disabled, causing disabled custom
buttons to show a pointer; update the selectors so aria-disabled="true" is
treated like :disabled — either narrow the pointer rule to exclude
[aria-disabled="true"] (e.g. button:not(:disabled),
[role="button"]:not([aria-disabled="true"])) or add a rule that sets cursor:
not-allowed for [role="button"][aria-disabled="true"], adjusting the existing
selectors (button:not(:disabled), [role="button"], button:disabled) and the
disabled cursor rule to include the aria-disabled variant.

Comment on lines +35 to +42
<button
type="button"
className="flex items-center justify-center text-gray-700 outline-none"
aria-label="더보기"
>
{trigger ?? <IcOption size={24} className="size-6" />}
</button>
</DropdownMenuTrigger>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Tailwind CSS v4 outline-none vs outline-hidden behavior change

💡 Result:

In Tailwind CSS v4, the behavior around “hiding outlines” changed because the meaning of the old outline-none name was clarified and a new utility was introduced. 1) What happened to outline-none - In Tailwind v3, outline-none did not set outline-style: none; instead it used a transparent/hidden outline so it could still be visible in forced-colors / high-contrast mode. - In Tailwind v4, outline-none was redefined to actually remove the outline by setting outline-style: none. 2) What outline-hidden does in v4 - outline-hidden is the successor to the old outline-none semantics: it hides the default browser outline on normal rendering, but still preserves a visible outline in forced-colors mode for accessibility. 3) Migration / recommended mapping - The Tailwind v4 upgrade guidance says to replace usages of outline-none (from v3) with outline-hidden (in v4) to preserve the old forced-colors-friendly behavior. Practical summary - If you previously used focus:outline-none in v3 specifically to “hide but keep forced-colors accessibility”, switch to focus:outline-hidden in v4. - If you truly want to remove the outline entirely (including in forced-colors mode), use focus:outline-none in v4 and then provide your own focus styling. Sources (authoritative): Tailwind v4 upgrade guide and the outline-style docs, plus the Tailwind CSS PR that explains the rationale and implementation.

Citations:


🏁 Script executed:

find src -name "option-menu.tsx" -o -name "dropdown-menu.tsx" | head -20

Repository: depromeet/18th-team1-FE

Length of output: 129


🏁 Script executed:

wc -l src/shared/ui/option-menu.tsx

Repository: depromeet/18th-team1-FE

Length of output: 100


🏁 Script executed:

cat -n src/shared/ui/option-menu.tsx

Repository: depromeet/18th-team1-FE

Length of output: 2855


🏁 Script executed:

cat -n src/shared/ui/dropdown-menu.tsx | head -50

Repository: depromeet/18th-team1-FE

Length of output: 2452


🏁 Script executed:

rg "outline" src/shared/ui/dropdown-menu.tsx

Repository: depromeet/18th-team1-FE

Length of output: 1696


🏁 Script executed:

rg "key=" src/shared/ui/option-menu.tsx

Repository: depromeet/18th-team1-FE

Length of output: 98


🏁 Script executed:

git log --oneline -n 5 -- src/shared/ui/option-menu.tsx

Repository: depromeet/18th-team1-FE

Length of output: 145


🏁 Script executed:

rg "items.*map|OptionMenuItem" src -t tsx -A 3 | head -40

Repository: depromeet/18th-team1-FE

Length of output: 95


🏁 Script executed:

rg "item\.label" src -t tsx | head -20

Repository: depromeet/18th-team1-FE

Length of output: 95


🏁 Script executed:

rg "OptionMenu" src --max-count=5

Repository: depromeet/18th-team1-FE

Length of output: 365


🏁 Script executed:

rg "OptionMenuItem" src -A 2 | head -30

Repository: depromeet/18th-team1-FE

Length of output: 414


outline-noneoutline-hidden 권장 + 일관성 및 접근성 개선

Tailwind v4부터 outline-noneoutline-style: none을 적용하여 outline을 완전히 제거합니다. 같은 PR의 dropdown-menu.tsxDropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuSubTrigger에서는 이미 outline-hidden을 사용하고 있어 일관성을 맞출 필요가 있습니다. 또한 outline-hidden은 강제 색상 모드(forced-colors)에서도 outline을 표시하여 접근성이 우수하므로, 클래스를 outline-hidden으로 교체하고 focus-visible: 변형을 추가하여 키보드 사용자를 위한 포커스 표시를 제공하는 것을 권장합니다.

추가로, 커스텀 trigger가 전달될 때도 래퍼 <button>aria-label="더보기"가 항상 적용됩니다. 사용자가 이미 라벨링된 요소를 trigger로 넘기는 경우 라벨이 이중/충돌될 수 있고, trigger가 자체 <button>이라면 <button> 중첩으로 인한 잘못된 마크업이 됩니다. trigger 존재 여부에 따라 래퍼 마크업과 라벨을 분기 처리하는 방안을 검토해주세요.

♻️ 제안 diff
-        <DropdownMenuTrigger asChild>
-          <button
-            type="button"
-            className="flex items-center justify-center text-gray-700 outline-none"
-            aria-label="더보기"
-          >
-            {trigger ?? <IcOption size={24} className="size-6" />}
-          </button>
-        </DropdownMenuTrigger>
+        <DropdownMenuTrigger asChild>
+          {trigger ?? (
+            <button
+              type="button"
+              className="flex items-center justify-center text-gray-700 outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
+              aria-label="더보기"
+            >
+              <IcOption size={24} className="size-6" />
+            </button>
+          )}
+        </DropdownMenuTrigger>
📝 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"
className="flex items-center justify-center text-gray-700 outline-none"
aria-label="더보기"
>
{trigger ?? <IcOption size={24} className="size-6" />}
</button>
</DropdownMenuTrigger>
<DropdownMenuTrigger asChild>
{trigger ?? (
<button
type="button"
className="flex items-center justify-center text-gray-700 outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
aria-label="더보기"
>
<IcOption size={24} className="size-6" />
</button>
)}
</DropdownMenuTrigger>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/option-menu.tsx` around lines 35 - 42, Replace the wrapper
button's class "outline-none" with "outline-hidden" and add focus-visible
outline utility classes (e.g., focus-visible:outline focus-visible:outline-2
focus-visible:outline-primary) on the trigger so keyboard focus is visible and
consistent with other dropdown items (refer to DropdownMenuTrigger and
IcOption). Also change rendering so that when a custom trigger prop is provided
you do not wrap it in an extra <button> or unconditionally apply
aria-label="더보기" — instead branch: if trigger exists render the trigger as-is
(letting callers supply accessible labeling) else render the internal button
with the aria-label and the new outline/focus-visible classes to avoid nested
buttons and duplicate labels.

Comment on lines +48 to +68
{items.map((item) => (
<DropdownMenuItem
key={item.label}
className={cn(
"cursor-pointer gap-3.5 p-0 focus:bg-transparent",
item.isDestructive
? "text-destructive focus:text-destructive"
: "text-gray-700 focus:text-gray-700",
)}
onClick={item.onClick}
>
{cloneElement(item.icon, {
size: 24,
className: cn(
"size-6 shrink-0 -translate-y-[0.5px]",
item.isDestructive ? "text-destructive" : "text-gray-700",
),
})}
<span className="subhead4">{item.label}</span>
</DropdownMenuItem>
))}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

keylabel 사용 시 중복 키 위험 및 cloneElement가 caller className을 덮어씀

두 가지를 함께 검토해주세요.

  1. key={item.label} — 같은 라벨을 가진 항목이 둘 이상 있으면 React 키 중복 경고/렌더 이슈가 발생합니다. 인덱스 또는 안정적인 ID를 함께 사용하는 편이 안전합니다.
  2. cloneElement(item.icon, { className: cn(...) }) — 호출 측이 item.icon에 className을 부여했다면 그 값이 그대로 사라집니다. 의도가 사용자 className을 보존하는 것이라면 원본 className을 cn에 합쳐야 합니다.
♻️ 제안 diff
-          {items.map((item) => (
+          {items.map((item, index) => (
             <DropdownMenuItem
-              key={item.label}
+              key={`${item.label}-${index}`}
               className={cn(
                 "cursor-pointer gap-3.5 p-0 focus:bg-transparent",
                 item.isDestructive
                   ? "text-destructive focus:text-destructive"
                   : "text-gray-700 focus:text-gray-700",
               )}
               onClick={item.onClick}
             >
               {cloneElement(item.icon, {
                 size: 24,
                 className: cn(
                   "size-6 shrink-0 -translate-y-[0.5px]",
                   item.isDestructive ? "text-destructive" : "text-gray-700",
+                  item.icon.props.className,
                 ),
               })}
               <span className="subhead4">{item.label}</span>
             </DropdownMenuItem>
           ))}
📝 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
{items.map((item) => (
<DropdownMenuItem
key={item.label}
className={cn(
"cursor-pointer gap-3.5 p-0 focus:bg-transparent",
item.isDestructive
? "text-destructive focus:text-destructive"
: "text-gray-700 focus:text-gray-700",
)}
onClick={item.onClick}
>
{cloneElement(item.icon, {
size: 24,
className: cn(
"size-6 shrink-0 -translate-y-[0.5px]",
item.isDestructive ? "text-destructive" : "text-gray-700",
),
})}
<span className="subhead4">{item.label}</span>
</DropdownMenuItem>
))}
{items.map((item, index) => (
<DropdownMenuItem
key={`${item.label}-${index}`}
className={cn(
"cursor-pointer gap-3.5 p-0 focus:bg-transparent",
item.isDestructive
? "text-destructive focus:text-destructive"
: "text-gray-700 focus:text-gray-700",
)}
onClick={item.onClick}
>
{cloneElement(item.icon, {
size: 24,
className: cn(
"size-6 shrink-0 -translate-y-[0.5px]",
item.isDestructive ? "text-destructive" : "text-gray-700",
item.icon.props.className,
),
})}
<span className="subhead4">{item.label}</span>
</DropdownMenuItem>
))}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/option-menu.tsx` around lines 48 - 68, The current items.map
uses key={item.label} which can produce duplicate React keys; switch to a stable
unique identifier (e.g., item.id) and fall back to index only if no stable id
exists when rendering in DropdownMenuItem, and update any tests/consumers
accordingly; also preserve caller-provided classes when calling
cloneElement(item.icon) by merging the original icon's className
(item.icon.props?.className) into the cn(...) call so the caller's styling isn't
overwritten.

Comment on lines +1 to +2
import { cn } from "@/shared/lib/utils";
import { IcCheck } from "@/shared/ui/icons";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

❓ Verification inconclusive

Script executed:

#!/bin/bash
# CheckButton의 사용처를 찾고, 호출부가 Server Component인지 Client Component인지 확인
rg -nP --type=tsx -C3 '\bCheckButton\b'
# widgets 내부 다른 파일들의 "use client" 사용 패턴 확인
fd -e tsx . src/widgets src/shared/ui --exec sh -c 'echo "=== {} ==="; head -1 {}'

Repository: depromeet/18th-team1-FE


Repository: depromeet/18th-team1-FE
Exit code: 0

stdout:

=== src/widgets/header/ui/Header.tsx ===
"use client";
=== src/shared/ui/dropdown-menu.tsx ===
"use client";
=== src/shared/ui/option-menu.tsx ===
"use client";
=== src/shared/ui/button.tsx ===
import { Slot } from "@radix-ui/react-slot";
=== src/shared/ui/icons/IcOption.tsx ===
import { BaseIcon } from "./BaseIcon";
=== src/widgets/nav-bar/ui/NavBar.tsx ===
"use client";
=== src/shared/ui/icons/IcHome.tsx ===
import { BaseIcon } from "./BaseIcon";
=== src/shared/ui/icons/IcTrash.tsx ===
import { BaseIcon } from "./BaseIcon";
=== src/shared/ui/icons/IcReport.tsx ===
import { BaseIcon } from "./BaseIcon";
=== src/shared/ui/icons/IcShare.tsx ===
import { BaseIcon } from "./BaseIcon";
=== src/shared/ui/icons/IcCheck.tsx ===
import { BaseIcon } from "./BaseIcon";
=== src/shared/ui/icons/IcEdit.tsx ===
import { BaseIcon } from "./BaseIcon";
=== src/widgets/header/ui/CheckButton.tsx ===
import { cn } from "@/shared/lib/utils";
=== src/shared/ui/icons/IcSetting.tsx ===
import { BaseIcon } from "./BaseIcon";
=== src/shared/ui/icons/IcCalendar.tsx ===
import { BaseIcon } from "./BaseIcon";
=== src/shared/ui/icons/IcBack.tsx ===
import { BaseIcon } from "./BaseIcon";
=== src/shared/ui/icons/BaseIcon.tsx ===
import type { IconProps } from "./icon.types";

stderr:

unrecognized file type: tsx


Script executed:

# Find all imports of CheckButton
rg -n "import.*CheckButton" --glob="*.tsx" --glob="*.ts"

# Check if Header.tsx actually uses CheckButton
rg -n "CheckButton" src/widgets/header/ui/Header.tsx -C 5

# View the actual CheckButton component to understand its props
head -30 src/widgets/header/ui/CheckButton.tsx

Repository: depromeet/18th-team1-FE


Repository: depromeet/18th-team1-FE
Exit code: 0

stdout:

import { cn } from "@/shared/lib/utils";
import { IcCheck } from "@/shared/ui/icons";

interface CheckButtonProps {
  isChecked: boolean;
  onClick: () => void;
  className?: string;
}

export const CheckButton = ({
  isChecked,
  onClick,
  className,
}: CheckButtonProps): React.ReactElement => {
  return (
    <button
      type="button"
      onClick={onClick}
      className={cn(
        "flex size-7 items-center justify-center rounded-full transition-colors",
        isChecked ? "bg-gray-700" : "bg-gray-200",
        className,
      )}
      aria-pressed={isChecked}
      aria-label="선택 확인"
    >
      <IcCheck size={24} className="text-gray-0" />
    </button>
  );
};


Script executed:

# Search for CheckButton imports across the entire codebase
rg "CheckButton" --glob="*.tsx" --glob="*.ts" -n

# Also check if there are any direct imports
rg "from.*CheckButton\|import.*CheckButton" -n

Repository: depromeet/18th-team1-FE


Repository: depromeet/18th-team1-FE
Exit code: 1

stdout:

src/widgets/header/ui/CheckButton.tsx:4:interface CheckButtonProps {
src/widgets/header/ui/CheckButton.tsx:10:export const CheckButton = ({
src/widgets/header/ui/CheckButton.tsx:14:}: CheckButtonProps): React.ReactElement => {
src/widgets/header/index.ts:1:export { CheckButton } from "./ui/CheckButton";


Script executed:

# Check what Header.tsx exports and uses
head -50 src/widgets/header/ui/Header.tsx

# Check the index.ts of header to see full exports
cat src/widgets/header/index.ts

# Search for CheckButton usage in Header.tsx
rg -n "CheckButton" src/widgets/header/ui/Header.tsx -B 2 -A 2

Repository: depromeet/18th-team1-FE


Repository: depromeet/18th-team1-FE
Exit code: 1

stdout:

"use client";

import { useRouter } from "next/navigation";
import type { ReactNode } from "react";

import { cn } from "@/shared/lib/utils";
import { IcBack } from "@/shared/ui/icons";

interface HeaderProps {
  title?: string;
  onBack?: () => void;
  right?: ReactNode;
  className?: string;
}

export const Header = ({ title, onBack, right, className }: HeaderProps): React.ReactElement => {
  const router = useRouter();
  const handleBack =
    onBack ??
    ((): void => {
      router.back();
    });

  return (
    <header className={cn("relative flex h-15 w-full items-center justify-between", className)}>
      <button type="button" onClick={handleBack} className="text-gray-700" aria-label="뒤로가기">
        <IcBack size={24} />
      </button>

      {title && (
        <span className="subhead2 absolute left-1/2 -translate-x-1/2 cursor-default select-none text-gray-700">
          {title}
        </span>
      )}

      {right}
    </header>
  );
};
export { CheckButton } from "./ui/CheckButton";
export { Header } from "./ui/Header";


CheckButton에 "use client" 지시문 추가 필요

CheckButtononClick 함수를 props로 받는 인터랙티브 컴포넌트이며, src/widgets/header/index.ts를 통해 별도로 내보내집니다. Next.js App Router에서 Server Component에서 직접 임포트될 경우 함수 prop을 직렬화할 수 없어 런타임 에러가 발생합니다. 같은 경로의 Header.tsx와 다른 위젯 컴포넌트들(NavBar.tsx, dropdown-menu.tsx 등)이 모두 "use client"를 선언하므로, 일관성과 재사용성을 위해 파일 상단에 "use client"를 추가하세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/widgets/header/ui/CheckButton.tsx` around lines 1 - 2, CheckButton is an
interactive component that accepts an onClick prop and can be imported into
Server Components via src/widgets/header/index.ts, which causes runtime
serialization errors; fix it by adding the "use client" directive as the very
first line of src/widgets/header/ui/CheckButton.tsx so the component
(CheckButton) is treated as a Client Component and can accept function props
like onClick safely, keeping behavior consistent with Header, NavBar, and
dropdown-menu.

Comment on lines +19 to +27
className={cn(
"flex size-7 items-center justify-center rounded-full transition-colors",
isChecked ? "bg-gray-700" : "bg-gray-200",
className,
)}
aria-pressed={isChecked}
aria-label="선택 확인"
>
<IcCheck size={24} className="text-gray-0" />

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Unchecked 상태의 시각적 대비 확인 권장

isChecked === false일 때 배경은 bg-gray-200(연회색)인데 내부 체크 아이콘은 text-gray-0(흰색)으로 항상 동일하게 렌더됩니다. 토큰 정의에 따라 다르지만 흰색 체크가 연회색 배경 위에서 거의 보이지 않을 수 있어 사용자가 토글 상태를 인지하기 어려울 수 있습니다. 디자인 의도(빈 상태 표현)이면 그대로 두어도 되지만, 의도가 아니라면 unchecked 시 아이콘 색을 한 단계 어둡게(예: text-gray-400) 조정하거나 아이콘 자체를 숨기는 방법을 검토해주세요.

💡 제안 diff (예시)
-      <IcCheck size={24} className="text-gray-0" />
+      <IcCheck size={24} className={cn(isChecked ? "text-gray-0" : "text-gray-400")} />
📝 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
className={cn(
"flex size-7 items-center justify-center rounded-full transition-colors",
isChecked ? "bg-gray-700" : "bg-gray-200",
className,
)}
aria-pressed={isChecked}
aria-label="선택 확인"
>
<IcCheck size={24} className="text-gray-0" />
className={cn(
"flex size-7 items-center justify-center rounded-full transition-colors",
isChecked ? "bg-gray-700" : "bg-gray-200",
className,
)}
aria-pressed={isChecked}
aria-label="선택 확인"
>
<IcCheck size={24} className={cn(isChecked ? "text-gray-0" : "text-gray-400")} />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/widgets/header/ui/CheckButton.tsx` around lines 19 - 27, The check icon
currently renders with a fixed class "text-gray-0" inside the CheckButton
component regardless of isChecked, causing poor contrast when isChecked is
false; update the rendering logic in CheckButton.tsx so that IcCheck uses a
conditional class (e.g., "text-gray-0" when isChecked is true, and
"text-gray-400" when false) or hide the IcCheck entirely when unchecked; modify
the JSX around the IcCheck usage and the isChecked prop handling to apply the
conditional class or conditional render.

Comment on lines +30 to +36
{title && (
<span className="subhead2 absolute left-1/2 -translate-x-1/2 cursor-default select-none text-gray-700">
{title}
</span>
)}

{right}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

제목이 길 경우 좌/우 영역과 겹칠 가능성

titleabsolute left-1/2 -translate-x-1/2로 화면 정중앙에 고정되어 있어, 한국어 긴 제목이나 right 슬롯에 여러 버튼(예: 공유 + 설정)이 들어올 때 좌측 back 버튼 또는 우측 슬롯과 겹쳐 보일 수 있습니다. 폭 제한과 ellipsis 처리를 추가하는 것을 권장드려요.

💡 제안 diff
       {title && (
-        <span className="subhead2 absolute left-1/2 -translate-x-1/2 cursor-default select-none text-gray-700">
+        <span className="subhead2 absolute left-1/2 max-w-[60%] -translate-x-1/2 cursor-default select-none truncate text-center text-gray-700">
           {title}
         </span>
       )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/widgets/header/ui/Header.tsx` around lines 30 - 36, The centered title
span (the JSX block rendering {title} in Header.tsx) is absolutely positioned
and can overlap the left back button or the {right} slot when the title is long;
update the title span styling to constrain its width and apply truncation (e.g.,
set a max-width based on available header space or use calc(100% -
[leftAreaWidth] - [rightAreaWidth]), and add CSS for overflow-hidden,
white-space-nowrap and text-overflow:ellipsis) so long Korean titles are clipped
with an ellipsis and cannot visually collide with the left button or the right
slot.

@ballsona ballsona left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM 👍👍 고생하셨습니당!

@cindy-chaewon cindy-chaewon merged commit bfb9107 into develop Apr 25, 2026
3 checks passed
@cindy-chaewon cindy-chaewon deleted the feat/#8-header-option branch April 25, 2026 11:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

헤더, 옵션 공통 컴포넌트 마크업

2 participants