FrontEnd Develop/Project : Team Nova MJ Search
MJS_V2 : Atomic Design + Barrel Pattern 기반 Button 컴포넌트 구성 전략
Frisbeen
2025. 6. 20. 22:14
개요
진행하고 있는 MJS 프로젝트에서 내가 주장했던 버튼 컴포넌트를 atomic design 패턴에 따라
Button.tsx, Button.type.ts, index.ts로 구성한다.
또한 Barrel Export를 통해 외부에서 접근성과 재사용성을 높인다.
이 구조로 디자이너와의 협업에서 나온 디자인을 프론트엔드 동료 개발자분들이 쉽게 분기된 컴포넌트를 사용할 수 있기를 바랬다.
구성 구조
src/components/atomic/Button/
├── Button.tsx # 실제 UI 및 로직 구현
├── Button.type.ts # 타입 정의
├── index.ts # barrel export
1. Button.type.ts
- variant, size 등의 옵션을 타입으로 분리해 관리.
- 모든 props는 ButtonHTMLAttributes를 확장한 ButtonProps로 정의.
export type ButtonVariant = 'main' | 'sub' | 'basic' | 'danger';
export type ButtonSize = 'sm' | 'md' | 'lg';
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: ButtonVariant;
size?: ButtonSize;
disabled: boolean;
fullWidth: boolean;
}
2. Button.tsx
- variant, size, fullWidth, disabled 등의 Props 속성들을 기반으로 className을 조건 분기하여 Tailwind 클래스 적용.
- 디자인은 디자이너의 Design System을 활용한다.
- 클래스 분기는 가독성을 위해 객체 맵핑 방식으로 처리.
- fullWidth 여부에 따라 w-full 클래스 유무 제어.
import type { ButtonProps } from './Button.type';
import { colors } from '../../../styles/color';
const Button = ({
variant = 'main',
size = 'md',
children,
disabled,
fullWidth,
...props
}: ButtonProps) => {
const sizeClassName = {
sm: 'px-3 py-1 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-5 py-3 text-lg',
};
const styleMap = {
main: {
backgroundColor: disabled ? colors.grey20 : colors.mju_primary,
color: colors.white,
},
sub: {
backgroundColor: disabled ? colors.grey20 : '#FFD700',
color: disabled ? colors.grey40 : colors.mju_primary,
},
basic: {
backgroundColor: disabled ? colors.grey20 : colors.blue20,
color: disabled ? colors.grey40 : colors.white,
},
danger: {
backgroundColor: disabled ? colors.grey20 : colors.error,
color: disabled ? colors.grey40 : colors.white,
},
chip: {
backgroundColor: disabled ? colors.grey02 : colors.grey02,
color: disabled ? colors.grey40 : colors.black,
},
};
const { backgroundColor, color } = styleMap[variant];
return (
<button
style={{
backgroundColor,
color,
width: fullWidth ? '100%' : undefined,
borderRadius: '9999px',
fontWeight: 500,
transition: 'all 0.2s ease-in-out',
}}
className={`${sizeClassName[size]} font-medium`}
disabled={disabled}
{...props}
>
{children}
</button>
);
};
export default Button;
3. index.ts
- 외부에서는 import { Button } from '@/components/atomic/Button' 형태로 간결하게 접근 가능.
- Barrel Pattern 적용
디자이너 협업 관점에서는 왜 좋은가?
경험론적으로 봤을때 총 3가지다.
- 색상, 크기 등의 디자인 시스템 기준을 코드 구조와 1:1로 매핑 가능
- variant, size 등의 네이밍은 디자이너가 전달하는 스타일 가이드와 직접 대응돼 커뮤니케이션 효율 증가
- 현재 프로젝트 같이 프론트엔드 개발자가 여러명일 경우, 정해진 컴포넌트 스타일링 분리 컨벤션이 명확할수록 좋다.
Tailwind 글로벌 설정이 불가능한 환경에서의 대안이라는 취지
대개 프론트엔드 프로젝트에서 디자이너가 정해준 글로벌 컬러링을 테일윈드로 하고는 한다.
그러나 테일윈드 버전이 4가 되며 여러 불안전한 환경이 상당했다.
나는 아래와 같은 애로사항이 있었다.
- 프로젝트에 따라 tailwind.config.js나 tailwind.config.ts 없이 시작된 경우, 커스텀 컬러 클래스(bg-mju_primary 등)를 사용할 수 없다.
이럴 경우, color.ts 파일을 별도로 관리하고, 각 variant에 따라 HEX 코드 분기로 처리하는 방법이 대안이 될 수 있다.
export const colors = {
mju_primary: '#002968',
mju_secondary: '#0386D0',
black: '#17171B',
white: '#FFFFFF',
error: '#DA4747',
grey40: '#979999',
grey20: '#CACCCC',
grey10: '#E3E6E6',
grey02: '#F9FAFB',
blue35: '#124CA5',
blue20: '#2F65CB',
blue15: '#4762DA',
blue10: '#6C9EC6',
};
이 방식은 디자인 시스템을 기반으로 스타일을 분기하고자 할 때 유용하며, Tailwind의 클래스명 충돌/미반영 이슈를 우회할 수 있다.