Skip to content

Conversation

@suminb99
Copy link
Contributor

@suminb99 suminb99 commented Dec 30, 2025

⚙️ Related ISSUE Number

Related #21



📄 Work Description

tailwind-variants를 활용하여 공통 Button 컴포넌트를 리펙토링 했습니다!
작업한 브랜치 기준 ActionsButton, IconButton 와 기존 Button 컴포넌트를 사용하던 부분에 리펙토링 된 Button 컴포넌트 모두 적용했습니다.

버튼의 역할과 구성을 명확히 하기 위해 Variant를 color, size, content 3가지 기준으로 분리했습니다.

  1. color - 버튼 색상 / 스타일
primary        // 메인 색상 배경
secondary      // 흰 배경 + 검정 텍스트 + 회색 아웃라인
outlinePurple  // 보라색 아웃라인
outlineWhite   // 흰색 아웃라인
tonal          // 톤 다운된 버튼 - icon + text
ghost          // 배경 없는 텍스트 버튼
ghostWhite     // 흰 배경 텍스트 버튼
  1. size - 버튼 크기 및 형태
default  // 기본 버튼 (w-24 h-10)
compact  // 내용 기반 크기 ( icon + text 조합용)
wide     // 넓은 버튼 (로그인 / 회원가입 등)
none     // padding 제거
icon     // 아이콘 전용 버튼
  1. content - 버튼 내부 구성 방식
text   // 텍스트만 있는 버튼
icon   // 아이콘만 있는 버튼
mixed  // 아이콘 + 텍스트 버튼

content variant는 버튼의 children 구성에 따라서 적용되는 flex, gap, text 관련 스타일이 달라지기 때문에 base 속성에 모든 스타일을 넣는 것을 피하기 위해 추가했습니다!

default variants는 color: 'primary', size: 'default', content: 'text'로 설정했고
className을 통해 추가 스타일 확장 가능해요

1. 텍스트 버튼

Screenshot 2025-12-30 at 23 28 40

2. 아이콘 + 텍스트 버튼

Screenshot 2025-12-30 at 23 28 31

3. 아이콘 버튼

Screenshot 2025-12-30 at 23 28 45

// 1. 텍스트 버튼
<Button color="primary">저장</Button>

// 2. 아이콘 + 텍스트 버튼
<Button color='tonal' size='compact' content='mixed'>
 <AddIcon width={12} height={12} />
 추가
</Button>

// 3. 아이콘 버튼
<Button
 key={index}
 content='icon'
 size='icon'
 aria-label={button.label}>
 {button.icon}
</Button>



📷 Screenshot



💬 To Reviewers



🔗 Reference

https://www.tailwind-variants.org/docs/introduction

Summary by CodeRabbit

Release Notes

  • Style

    • Updated button styling and appearance throughout the application with new color variants and configurations for improved visual consistency.
  • Refactor

    • Restructured button component implementation for better maintainability and flexibility.

✏️ Tip: You can customize this high-level summary in your review settings.

@suminb99 suminb99 requested a review from JiiminHa December 30, 2025 14:56
@suminb99 suminb99 self-assigned this Dec 30, 2025
@suminb99 suminb99 added 🧩 feature 기능 개발 🛠️ refactor 코드 리팩토링 labels Dec 30, 2025
@suminb99 suminb99 linked an issue Dec 30, 2025 that may be closed by this pull request
3 tasks
@coderabbitai
Copy link

coderabbitai bot commented Dec 30, 2025

📝 Walkthrough

Walkthrough

This PR refactors the Button component from a theme-based prop system (theme, text, icon) to a Tailwind-variants-driven API using composable color/size/content variants. The new Button accepts children as React nodes and spreads VariantProps, while all consumer components are updated to the new API. A tailwind-variants dependency is added to package.json.

Changes

Cohort / File(s) Summary
Button Component Refactor
src/components/common/Button.tsx
Complete API redesign: replaces theme/text/icon props with children-based rendering and tailwind-variants configuration (color, size, content). className computed via button variant function. Public API changes from theme-based to color-based with React node children.
Dependency Addition
package.json
Added tailwind-variants ^3.2.2 dependency to support the new Button variant system.
Assignment Page Components
src/components/admin/assignments/AssignmentFormLayout.tsx, src/components/admin/assignments/AssignmentPageLayout.tsx
Updated Button usage from theme props (primaryWhite, primaryPurple) to color props (outlinePurple, primary) with text moved to children. "취소"/"확인" labels replaced with "취소"/"저장".
Navigation & Layout
src/layout/Layout.tsx
Replaced IconButton with Button component; icon now passed as child with content='icon' and size='icon' props. Maintains aria-label for accessibility.
Shared Component Updates
src/components/common/CourseOverview/CourseActionsBar.tsx, src/components/common/Dashboard/CourseList.tsx, src/pages/admin/assignments/AssignmentCreatePage.tsx
Updated Button props from conditional theme logic to fixed color props (outlineWhite, outlinePurple, ghostWhite, tonal). Icons and labels now nested as children; removed theme-based styling.
Page Component Refactors
src/pages/common/LandingPage.tsx, src/pages/common/UserIdInputPage.tsx
Replaced ActionButton with Button component; label props converted to children, selected/onMouseLeave logic refactored for new API. Color logic now uses color='primary'/'secondary' variants instead of ActionButton selected state.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

🎨 ui

Suggested reviewers

  • JiiminHa

Poem

🐰 A button's rebirth, from themes to hues,
With variants trimmed, cleaner views,
Children flow in, old props retire,
Tailwind variants lift us higher!

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main objective: refactoring the Button component using tailwind-variants, which is the primary focus of all changes across the codebase.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings

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

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
src/components/admin/assignments/AssignmentFormLayout.tsx (1)

27-32: Consider specifying the content variant for consistency.

These Button components don't specify the content prop, relying on default behavior. For consistency with other Button usages in the codebase (e.g., AssignmentCreatePage.tsx line 66), consider explicitly setting content='text' since these buttons contain only text children.

🔎 Suggested enhancement for consistency
-          <Button color='outlinePurple' onClick={onCancel}>
+          <Button color='outlinePurple' content='text' onClick={onCancel}>
            취소
          </Button>
-          <Button color='primary' onClick={onConfirm}>
+          <Button color='primary' content='text' onClick={onConfirm}>
            저장
          </Button>
src/components/admin/assignments/AssignmentPageLayout.tsx (1)

41-42: Consider specifying size and content variants for consistency.

Similar to other files, these Buttons omit the size and content props. Consider adding content='text' for consistency with the variant-based API.

🔎 Suggested enhancement
-          <Button color='outlinePurple'>취소</Button>
-          <Button color='primary'>등록</Button>
+          <Button color='outlinePurple' content='text'>취소</Button>
+          <Button color='primary' content='text'>등록</Button>
src/components/common/CourseOverview/CourseActionsBar.tsx (1)

6-7: Consider adding content variant for consistency.

These text-only Buttons should explicitly specify content='text' to align with the variant-driven API used throughout the codebase.

🔎 Suggested enhancement
-      <Button color='outlineWhite'>학생 목록</Button>
-      <Button color='outlinePurple'>단원 추가</Button>
+      <Button color='outlineWhite' content='text'>학생 목록</Button>
+      <Button color='outlinePurple' content='text'>단원 추가</Button>
src/pages/common/LandingPage.tsx (1)

93-98: Consider using the Button component for consistency.

The Google login button uses a native <button> with custom styling. For consistency across the codebase, consider using the refactored Button component with appropriate variants (e.g., color='ghost', size='none') and passing the flex/gap styles via className.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2db5fba and 39a5da2.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (10)
  • package.json
  • src/components/admin/assignments/AssignmentFormLayout.tsx
  • src/components/admin/assignments/AssignmentPageLayout.tsx
  • src/components/common/Button.tsx
  • src/components/common/CourseOverview/CourseActionsBar.tsx
  • src/components/common/Dashboard/CourseList.tsx
  • src/layout/Layout.tsx
  • src/pages/admin/assignments/AssignmentCreatePage.tsx
  • src/pages/common/LandingPage.tsx
  • src/pages/common/UserIdInputPage.tsx
🔇 Additional comments (8)
src/components/admin/assignments/AssignmentPageLayout.tsx (1)

35-38: LGTM - Proper usage of mixed content variant.

The Button correctly specifies content='mixed' for icon + text combination, which aligns with the new variant API.

src/pages/common/UserIdInputPage.tsx (1)

125-132: LGTM - Proper disabled state handling.

The Button correctly uses the disabled prop with appropriate className for cursor styling. The color='secondary' and size='wide' variants are clearly specified.

src/layout/Layout.tsx (1)

45-51: LGTM - Excellent accessibility practice.

The Button correctly specifies content='icon' and size='icon' for icon-only buttons, and properly maintains aria-label for accessibility. The icon is correctly passed as a child.

src/pages/admin/assignments/AssignmentCreatePage.tsx (1)

63-70: All specified Button variants are properly defined in the component. The color='tonal', size='compact', and content='mixed' variants are present in Button.tsx, and the usage in AssignmentCreatePage.tsx is correct.

package.json (1)

18-18: tailwind-variants ^3.2.2 is compatible and secure.

Verified: v3.2.2 is built for Tailwind CSS v4.x and compatible with v4.1.11. No security advisories found. This version is the latest stable release.

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

3-35: LGTM!

The variant configuration is well-structured with clear separation of concerns across color, size, and content axes. Default variants are appropriately set.

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

73-88: Hover handlers won't work until the Button component is fixed.

The onMouseEnter and onMouseLeave handlers passed here are not applied to the underlying <button> element due to the bug in Button.tsx. Once that fix is applied, this code will function correctly.


42-48: LGTM!

The ghost button usage is appropriate for this navigation action. The className override for custom leading/text size will work once the Button component properly applies the className prop.

Comment on lines +49 to 60
const Button = ({
children,
onClick,
type = 'button',
disabled = false,
...props
}: ButtonProps) => {
return (
<button
className={`py-1.5 px-3 rounded-[10px] cursor-pointer ${buttonTheme[theme]}`}>
{icon}
<span className='text-btn'>{text}</span>
<button onClick={onClick} disabled={disabled} className={button(props)}>
{children}
</button>
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Missing type, onMouseEnter, and onMouseLeave attributes on the button element.

The type prop is destructured but never applied to the <button>. Additionally, onMouseEnter and onMouseLeave are accepted as props but remain in the ...props spread—they're passed to the button() tv function (which ignores them) instead of being wired to the actual element. This breaks hover interactions in consumers like LandingPage.

🔎 Proposed fix
 const Button = ({
   children,
   onClick,
   type = 'button',
   disabled = false,
+  onMouseEnter,
+  onMouseLeave,
+  className,
   ...props
 }: ButtonProps) => {
   return (
-    <button onClick={onClick} disabled={disabled} className={button(props)}>
+    <button
+      type={type}
+      onClick={onClick}
+      disabled={disabled}
+      onMouseEnter={onMouseEnter}
+      onMouseLeave={onMouseLeave}
+      className={button({...props, className})}>
       {children}
     </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
const Button = ({
children,
onClick,
type = 'button',
disabled = false,
...props
}: ButtonProps) => {
return (
<button
className={`py-1.5 px-3 rounded-[10px] cursor-pointer ${buttonTheme[theme]}`}>
{icon}
<span className='text-btn'>{text}</span>
<button onClick={onClick} disabled={disabled} className={button(props)}>
{children}
</button>
);
const Button = ({
children,
onClick,
type = 'button',
disabled = false,
onMouseEnter,
onMouseLeave,
className,
...props
}: ButtonProps) => {
return (
<button
type={type}
onClick={onClick}
disabled={disabled}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
className={button({...props, className})}>
{children}
</button>
);
};
🤖 Prompt for AI Agents
In src/components/common/Button.tsx around lines 49 to 60, the destructured type
prop is not applied to the <button> and the onMouseEnter/onMouseLeave props are
swallowed by the props spread (passed only to the style function), so hover
callbacks never reach the DOM element; fix by adding type={type} on the
<button>, explicitly pass onMouseEnter={onMouseEnter} and
onMouseLeave={onMouseLeave} to the element (keep the remaining ...props spread
for other attributes), and ensure the button() tv function only receives the
props it needs (or pass a copy that excludes event handlers).

Comment on lines +49 to +55
<Button
color='ghostWhite'
size='compact'
className='hover:opacity-70'>
<AddIcon width={12} height={12} />
<span>추가</span>
</button>
추가
</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 | 🟠 Major

Add content='mixed' variant for icon + text combination.

This Button contains both an icon and text but doesn't specify content='mixed'. This is inconsistent with similar usage in AssignmentCreatePage.tsx (line 66) and AssignmentPageLayout.tsx (line 35).

🔎 Proposed fix
          <Button
            color='ghostWhite'
            size='compact'
+           content='mixed'
            className='hover:opacity-70'>
            <AddIcon width={12} height={12} />
            추가
          </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
color='ghostWhite'
size='compact'
className='hover:opacity-70'>
<AddIcon width={12} height={12} />
<span>추가</span>
</button>
추가
</Button>
<Button
color='ghostWhite'
size='compact'
content='mixed'
className='hover:opacity-70'>
<AddIcon width={12} height={12} />
추가
</Button>
🤖 Prompt for AI Agents
In src/components/common/Dashboard/CourseList.tsx around lines 49 to 55, the
Button contains both an icon and text but is missing the content='mixed' prop;
update the Button declaration to include content='mixed' so it matches similar
usages (e.g., AssignmentCreatePage.tsx line 66 and AssignmentPageLayout.tsx line
35) to ensure consistent styling/behavior for icon+text buttons.

@suminb99 suminb99 changed the title #21 refactor: tailwind-variants 기반 Button 컴포넌트 리팩터링 #21 refactor: Button 컴포넌트 리팩터링 (tailwind-variants 적용) Dec 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🧩 feature 기능 개발 🛠️ refactor 코드 리팩토링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

refactor: 공통 컴포넌트 분리 및 재사용성 개선

2 participants