Skip to content

[✨ Feat/#39] Button 공통 컴포넌트 구현#40

Merged
Lseojeong merged 8 commits into
devfrom
feat/#39
Jun 18, 2026
Merged

[✨ Feat/#39] Button 공통 컴포넌트 구현#40
Lseojeong merged 8 commits into
devfrom
feat/#39

Conversation

@Lseojeong

@Lseojeong Lseojeong commented Jun 18, 2026

Copy link
Copy Markdown
Member

#️⃣연관된 이슈

체크 사항

  • UI 동작 및 레이아웃 확인
  • 불필요한 console 제거
  • 기능 정상 동작
  • 팀 컨벤션에 맞게 구현했는지
  • 관련 문서 또는 주석을 갱신했는지

📝작업내용

Button 공통 컴포넌트

  • primary, secondary, ghost, outline variant를 구현했습니다.
  • sm, md, lg, icon size를 지원합니다.
  • disabled, loading, active 상태별 스타일을 적용했습니다.
  • className을 통한 스타일 확장을 지원합니다.
  • 로딩 중에는 Spinner를 표시합니다.
  • 아이콘 전용 버튼 사용 시 aria-label을 타입으로 강제했습니다.

LinkButton 컴포넌트

  • 동작을 실행하는 Button과 페이지를 이동하는 LinkButton을 분리했습니다.
  • 내부 링크는 Next.js Link로 렌더링합니다.
  • 외부 링크는 external prop을 통해 네이티브 a 요소로 렌더링합니다.
  • 내부 링크에서 prefetch, replace, onNavigate를 지원합니다.
  • 외부 링크에서는 Next.js 전용 props를 타입으로 차단했습니다.
  • disabled 및 loading 상태에서 링크 이동과 이벤트 전파를 차단했습니다.
  • target="_blank" 사용 시 noopener noreferrer를 적용했습니다.

Spinner 및 접근성

  • Spinner를 공통 UI 컴포넌트로 분리했습니다.
  • Spinner의 children, aria-hidden 외부 변경을 타입으로 제한했습니다.
  • 로딩 상태에 aria-busy를 적용했습니다.
  • 비활성 링크에 aria-disabledtabIndex 처리를 추가했습니다.

Storybook

  • variant 및 size 예제를 추가했습니다.
  • loading, disabled, icon-only 상태를 문서화했습니다.
  • 내부 링크와 외부 GitHub 링크 예제를 추가했습니다.

스크린샷 (선택)

image

추가한 라이브러리 (선택)

  • 없음

💬리뷰 요구사항(선택)

Summary by CodeRabbit

  • 새로운 기능
    • 버튼 컴포넌트 추가(변형: 기본/보조/유령/아웃라인, 크기: 소/중/대/아이콘)
    • 링크 버튼 추가(내부/외부 링크, 로딩 상태 및 비활성 처리)
    • 로딩 상태용 스피너 컴포넌트 추가
    • 접근성 강화를 위한 로딩/아이콘 전용 속성 지원
  • 문서
    • 버튼/링크 버튼 스토리북 시나리오 추가
  • 스타일
    • 비활성 버튼의 커서 표시를 표준 :disabled 기반으로 개선

장식용 빈 요소라는 컴포넌트 계약을 명확히 하기 위해 children과 aria-hidden 속성을 외부 props에서 제외했습니다.
버튼 variant와 size, disabled 및 loading 상태를 공통화했습니다. 아이콘 전용 버튼의 접근성 이름을 타입으로 강제하고 native button의 기본 동작과 확장 가능한 className을 제공합니다.
페이지 이동과 사용자 동작의 의미를 분리하기 위해 버튼형 링크를 추가했습니다. 내부 링크는 Next.js Link를 사용하고 외부 링크는 명시적인 external 속성으로 anchor를 렌더링합니다. disabled와 loading 상태에서는 이동과 이벤트 전파를 차단합니다.
Button의 variant, size, loading, disabled, iconOnly 상태를 문서화했습니다. LinkButton의 내부 경로와 외부 GitHub 이동 예제를 함께 추가했습니다.
@Lseojeong Lseojeong self-assigned this Jun 18, 2026
@Lseojeong Lseojeong added the ✨Feature 새로운 기능 구현 label Jun 18, 2026
@Lseojeong Lseojeong linked an issue Jun 18, 2026 that may be closed by this pull request
12 tasks
@vercel

vercel Bot commented Jun 18, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
rewrite Ready Ready Preview, Comment Jun 18, 2026 4:39pm

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown

🧪 테스트 결과

항목 결과
✅ Jest 테스트 success

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown

🚦 CI 검증 결과

항목 결과
🧠 TypeScript 타입 체크 success
🧹 ESLint 검사 success
🎨 Prettier 검사 success
🏗️ Build 검증 success

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2443dcbc-7d7c-49bd-a4af-1200e9a8715a

📥 Commits

Reviewing files that changed from the base of the PR and between 5bd6c17 and 3ff54a1.

📒 Files selected for processing (1)
  • src/shared/ui/button/LinkButton.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/shared/ui/button/LinkButton.tsx

Walkthrough

Spinner 컴포넌트와 cva 기반 buttonVariants를 기반으로 Button, ButtonContent, LinkButton 공통 컴포넌트를 신규 추가한다. 전역 CSS의 data-attribute 커서 선택자를 button:disabled로 교체하고, 모든 변형·상태를 커버하는 Storybook 스토리를 함께 작성한다.

Changes

Button 공통 컴포넌트 시스템

Layer / File(s) Summary
Spinner 컴포넌트 및 배럴 내보내기
src/shared/ui/spinner/Spinner.tsx, src/shared/ui/spinner/index.ts
aria-hidden 고정, cn 기반 클래스 병합, JSDoc 접근성 가이드를 포함한 Spinner 컴포넌트와 index 배럴 내보내기를 추가한다.
Button 타입 계약 및 cva 변형 정의
src/shared/ui/button/Button.types.ts, src/shared/ui/button/buttonVariants.ts
ButtonStatePropsiconOnly 판별 유니온인 ButtonAccessibilityProps를 정의하고, cva로 variant/size 조합 Tailwind 클래스를 생성하는 buttonVariantsButtonVariantProps 타입을 추가한다.
ButtonContent 및 Button 컴포넌트 구현
src/shared/ui/button/ButtonContent.tsx, src/shared/ui/button/Button.tsx, src/shared/styles/globals.css
isLoadingSpinner + sr-only children을 렌더링하는 ButtonContent를 추가하고, variant/size/iconOnly/disabled/isLoading을 처리하는 Button 컴포넌트를 구현한다. 전역 CSS에서 data-attribute 기반 커서 선택자를 button:disabled로 대체한다.
LinkButton 컴포넌트 구현
src/shared/ui/button/LinkButton.tsx
external 판별 prop에 따라 native <a> 또는 Next.js <Link>를 렌더링하고, disabled/isLoading 시 클릭 차단·tabIndex 제거·aria-disabled/aria-busy 설정을 적용하며, _blankrel을 안전 값으로 자동 계산한다.
배럴 내보내기 및 Storybook 스토리
src/shared/ui/button/index.ts, src/shared/ui/button/Button.stories.tsx
Button·LinkButton을 index.ts 배럴로 내보내고, Primary/Secondary/Ghost/Outline/Sizes/Loading/Disabled/IconOnly/InternalLink/ExternalLink 스토리를 Storybook에 추가한다.

Sequence Diagram(s)

sequenceDiagram
  participant 호출자
  participant Button
  participant ButtonContent
  participant Spinner

  호출자->>Button: variant, size, isLoading, disabled, iconOnly 전달
  Button->>Button: isLoading 또는 disabled → disabled 속성 설정
  Button->>Button: isLoading=true → aria-busy=true, data-loading=true 설정
  Button->>Button: iconOnly=true → size를 'icon'으로 재해석
  Button->>ButtonContent: children, isLoading 전달
  alt isLoading=true
    ButtonContent->>Spinner: 렌더링 (aria-hidden=true)
    ButtonContent->>ButtonContent: sr-only로 children 렌더링 (접근성 유지)
  else isLoading=false
    ButtonContent->>ButtonContent: children 그대로 렌더링
  end
Loading
sequenceDiagram
  participant 호출자
  participant LinkButton
  participant ButtonContent

  호출자->>LinkButton: href, external, disabled, isLoading, variant 전달
  alt external=true
    LinkButton->>LinkButton: native a 태그 준비
    LinkButton->>LinkButton: target=_blank → rel=noopener noreferrer 계산
  else external=false
    LinkButton->>LinkButton: Next.js Link 준비 (prefetch, replace, onNavigate 허용)
  end
  alt disabled 또는 isLoading
    LinkButton->>LinkButton: onClick 차단, tabIndex=-1, aria-disabled 설정
  end
  LinkButton->>ButtonContent: children, isLoading 전달
  ButtonContent-->>LinkButton: 렌더링 결과 반환
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 Button 공통 컴포넌트 구현이라는 주요 변경사항을 명확하고 간결하게 설명합니다.
Linked Issues check ✅ Passed PR의 모든 구현 사항이 Issue #39의 요구사항을 충족합니다. Button 컴포넌트(4개 variant, 4개 size, disabled/loading/iconOnly 상태), LinkButton(내부/외부 링크), Spinner 재추출, 접근성 속성(aria-label, aria-busy), Storybook 문서화가 모두 포함됨.
Out of Scope Changes check ✅ Passed 모든 변경사항이 Issue #39의 Button 공통 컴포넌트 구현 범위 내에 있습니다. globals.css 스타일 정리, Button/LinkButton/Spinner 컴포넌트 구현, 타입 정의, Storybook 추가는 모두 요구된 기능과 관련 있음.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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/#39

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

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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/shared/ui/button/LinkButton.tsx`:
- Line 85: The `safeRel` assignment in LinkButton is overwriting the `rel` prop
entirely with 'noopener noreferrer' when target equals '_blank', which causes
any existing rel values like 'nofollow' or 'sponsored' provided by the caller to
be lost. Instead of replacing the rel value, merge it with 'noopener noreferrer'
by splitting the existing rel into tokens, adding 'noopener' and 'noreferrer' to
the set of tokens (avoiding duplicates), and then joining them back together
with spaces when target is '_blank'. When target is not '_blank', keep the
original rel value unchanged.
🪄 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: b5660391-89d5-4bdc-9f51-5caba6efeafd

📥 Commits

Reviewing files that changed from the base of the PR and between 4da1e16 and 5bd6c17.

📒 Files selected for processing (10)
  • src/shared/styles/globals.css
  • src/shared/ui/button/Button.stories.tsx
  • src/shared/ui/button/Button.tsx
  • src/shared/ui/button/Button.types.ts
  • src/shared/ui/button/ButtonContent.tsx
  • src/shared/ui/button/LinkButton.tsx
  • src/shared/ui/button/buttonVariants.ts
  • src/shared/ui/button/index.ts
  • src/shared/ui/spinner/Spinner.tsx
  • src/shared/ui/spinner/index.ts
💤 Files with no reviewable changes (1)
  • src/shared/styles/globals.css

Comment thread src/shared/ui/button/LinkButton.tsx Outdated
새 창 링크에서 호출자가 전달한 rel 값을 보존하면서 noopener와
noreferrer를 중복 없이 추가하도록 수정했습니다.
@Lseojeong Lseojeong merged commit c6813e1 into dev Jun 18, 2026
5 checks passed
@Lseojeong Lseojeong deleted the feat/#39 branch June 18, 2026 16:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨Feature 새로운 기능 구현

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[✨Feature] Button 공통 컴포넌트 구현

1 participant