[ComitChu 개발기] 버튼과 입력창을 원자화하다 – 재사용 가능한 UI 컴포넌트 설계기

Suyo·2025년 7월 22일
0

ComitChu

목록 보기
1/5

ComitChu는 GitHub 커밋 활동을 기반으로 펫이 진화하는 재미있는 웹 서비스다. 서비스가 점점 커지면서 공통 UI 요소를 효율적으로 관리하는 구조의 필요성을 절감했고, 이를 해결하기 위해 버튼과 입력창을 컴포넌트화(원자화) 하는 작업을 진행했다.


리팩토링이 필요한 이유

초기 코드베이스에는 다음과 같은 문제가 있었다:

  • 버튼, 인풋 등의 기본 요소에 대한 전역 스타일index.css에 혼재
  • 여러 컴포넌트에서 중복된 UI 정의
  • 특정 버튼 색상 하나 바꾸기 위해 여러 CSS 파일에 흩어진 코드 수정이 필요함

디자인 일관성과 유지보수성을 위해 공통 UI 컴포넌트 정리 및 원자화 작업이 필요했다.


원자적 디자인(Atomic Design) 접근

UI 컴포넌트를 설계하면서 Brad Frost의 Atomic Design 개념을 도입했다. 이 방법론은 UI를 구성하는 요소들을 화학 구조처럼 작은 단위부터 큰 단위로 계층화하여 조직적으로 설계할 수 있도록 돕는다.

Atomic Design 계층 및 ComitChu의 실제 예시

계층설명ComitChu에서의 예시
Atoms가장 기본적인 UI 요소, 더 이상 분해 불가Button, Input, Label, Icon
Molecules여러 원자가 결합한 작은 단위 기능검색폼 = Input + Button, 유저정보 표시 = Avatar + Username
Organisms여러 분자 및 원자가 모인 UI 블록Header = 로고 + 유저정보 + 로그아웃 버튼
TemplatesOrganism을 배치한 레이아웃 구조대시보드 템플릿 레이아웃
Pages실제 콘텐츠가 채워진 화면/dashboard, /setting 등 페이지들

Atoms 단계에서 만든 ButtonInput 컴포넌트는 이후 Molecule과 Organism 단계의 기반이 되며, 전체 UI의 일관성을 지키는 핵심 요소다.


1. Button 컴포넌트 구조

📁 구조

src/
└─ components/
   └─ common/
      ├─ Button.tsx
      └─ Button.module.css

✅ 핵심 변경 사항

  • variant prop을 통해 스타일 분기 처리 (예: primary, danger)
  • 공통 스타일은 Button.module.css에 모듈화
  • 기존의 <button> 태그를 전부 Button 컴포넌트로 교체

Button.tsx 코드

import styles from "./Button.module.css";

interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  children: React.ReactNode;
  variant?: 'primary' | 'danger';
}

const Button: React.FC<ButtonProps> = ({
  children,
  variant = 'primary',
  className,
  ...props
}) => {
  const buttonClassName = `${styles.button} ${styles[variant]} ${className || ''}`;
  return (
    <button className={buttonClassName} {...props}>
      {children}
    </button>
  );
};

export default Button;

✅ 스타일 예시 (Button.module.css)

.button {
  padding: 0.5rem 1rem;
  border-radius: 6px;
  font-weight: 600;
  border: none;
  cursor: pointer;
}

.primary {
  background-color: #4a90e2;
  color: white;
}

.danger {
  background-color: #e74c3c;
}
.danger:hover {
  background-color: #c0392b;
}

✅ 사용 예시

<Button>저장</Button>
<Button variant="danger">삭제</Button>

2. Input 컴포넌트 구조

📁 구조

src/
└─ components/
   └─ common/
      ├─ Input.tsx
      └─ Input.module.css

✅ 핵심 변경 사항

  • input 태그를 감싼 Input 컴포넌트 생성
  • 필요한 경우 label, error 처리 등 확장 가능하도록 설계

Input.tsx 코드

import styles from "./Input.module.css";

const Input = ({ className = "", ...props }: React.InputHTMLAttributes<HTMLInputElement>) => {
  return <input className={`${styles.input} ${className}`} {...props} />;
};

export default Input;

✅ 스타일 예시

.input {
  padding: 0.5rem;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 1rem;
}

3. 로그아웃 버튼 커스터마이징 사례

Header.tsx에 있던 로그아웃 버튼에 variant="danger"를 적용하여 스타일을 분리했다. 이로써 색상 커스터마이징이 단순해졌고, 로직과 스타일 간 결합도 낮아졌다.

<Button onClick={handleLogout} variant="danger">
  Logout
</Button>

개선 효과 요약

항목개선 전개선 후
재사용성낮음높음
유지보수성여러 파일 수정 필요컴포넌트 단위 수정 가능
스타일 일관성흐트러짐공통 컴포넌트로 통일
확장성낮음variant 등으로 분기 가능

마무리


UI의 기초 단위를 공통 컴포넌트로 정리하면서, 구조적 일관성과 유지보수성이 크게 향상되었다. 단순한 스타일링 분리처럼 보일 수 있지만, 실제로는 전체 프론트엔드 아키텍처의 기반을 정리하는 핵심 작업이었다. 프로젝트가 커질수록 이런 구조는 점점 더 큰 힘을 발휘하게 된다.

profile
Mee-

0개의 댓글