Mold 컴포넌트 라이브러리 (CCK 자체 디자인 시스템)
TL;DR
- Mold 컴포넌트 라이브러리 (CCK 자체 디자인 시스템)의 핵심 개념과 사용 범위를 한눈에 정리
- 등장 배경과 필요한 이유를 짚고 실무 적용 포인트를 연결
- 주요 특징과 체크리스트를 빠르게 확인
1. 개념
``` Mold = 틀, 금형, 주형
2. 배경
Mold 컴포넌트 라이브러리 (CCK 자체 디자인 시스템)이(가) 등장한 배경과 기존 한계를 정리한다.
3. 이유
이 주제를 이해하고 적용해야 하는 이유를 정리한다.
4. 특징
- 이름의 의미
- 문제 상황
- Mold의 해결책
- 전체 구조
- 디렉토리 구조
5. 상세 내용
작성일: 2026-01-30 카테고리: Frontend / Design System / CCK 포함 내용: Mold, 래퍼 컴포넌트, Carbon, Ant Design, CCK 타이포그래피, 디자이너-개발자 협업
1. Mold란?
이름의 의미
Mold = 틀, 금형, 주형
┌───────────────────────────────────────┐
│ 원재료 (Carbon, Ant Design) │
│ ↓ │
│ ┌─────────────┐ │
│ │ Mold │ ← 틀/금형 │
│ │ (래퍼) │ │
│ └─────────────┘ │
│ ↓ │
│ CCK 스타일의 컴포넌트 │
└───────────────────────────────────────┘
정체: CCK 프로젝트 전용 컴포넌트 래퍼 라이브러리
위치: /common/mold/
역할: Carbon + Ant Design을 CCK 브랜드로 "틀"에 맞춰 찍어냄
2. 등장 배경: 왜 Mold를 만들었나?
문제 상황
┌─────────────────────────────────────────────────────────────────┐
│ Mold 없이 개발할 때의 문제 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 문제 1: 폰트 불일치 │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Carbon 버튼: IBM Plex Sans, 14px │ │
│ │ CCK 브랜드: Pretendard, 13px │ │
│ │ │ │
│ │ → 매번 CSS 오버라이드 필요 │ │
│ │ → 개발자마다 다르게 오버라이드 │ │
│ │ → 일관성 없는 UI │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ 문제 2: 라이브러리 선택 혼란 │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 개발자 A: "DatePicker는 Ant Design이 좋아" │ │
│ │ 개발자 B: "Carbon DatePicker 쓰고 있어" │ │
│ │ 개발자 C: "직접 만들었어" │ │
│ │ │ │
│ │ → 같은 기능, 3가지 구현 │ │
│ │ → 유지보수 악몽 │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ 문제 3: 반복적인 커스터마이징 │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ // accio 앱 │ │
│ │ <Button style= /> │ │
│ │ │ │
│ │ // helloworld 앱 │ │
│ │ <Button style= /> │ │
│ │ │ │
│ │ // jump 앱 │ │
│ │ <Button style= /> │ │
│ │ │ │
│ │ → 같은 코드를 N번 반복 │ │
│ │ → 수정 시 N군데 수정 │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ 문제 4: 디자이너-개발자 소통 │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 디자이너: "이 버튼은 파란색이고 둥근 모서리에요" │ │
│ │ 개발자: "Carbon 버튼? Ant 버튼? MUI 버튼?" │ │
│ │ │ │
│ │ → 어떤 라이브러리의 어떤 variant인지 불명확 │ │
│ │ → 구현할 때마다 해석 필요 │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Mold의 해결책
┌─────────────────────────────────────────────────────────────────┐
│ Mold의 해결책 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ "한 번 래핑하고, 전체에서 재사용" │ │
│ │ │ │
│ │ Carbon Button │ │
│ │ ↓ │ │
│ │ Mold에서 래핑 (CCK 타이포그래피 적용) │ │
│ │ ↓ │ │
│ │ MoldButton │ │
│ │ ↓ │ │
│ │ accio, helloworld, jump 모든 앱에서 import │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ 장점: │
│ ├── 타이포그래피 1곳에서 관리 │
│ ├── 컴포넌트 선택 기준 통일 (Carbon vs Ant) │
│ ├── 커스터마이징 1번만 │
│ └── 디자이너가 "MoldButton primary" 하나만 알면 됨 │
│ │
└─────────────────────────────────────────────────────────────────┘
3. Mold의 아키텍처
전체 구조
┌─────────────────────────────────────────────────────────────────┐
│ Mold 아키텍처 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ 앱 레이어 ││
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ││
│ │ │ accio │ │helloworld│ │ jump │ │ mothership│ ││
│ │ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ ││
│ │ │ │ │ │ ││
│ │ └────────────┴─────┬──────┴────────────┘ ││
│ │ │ ││
│ │ ▼ ││
│ │ ┌─────────────────────────────────────────────────────────┐││
│ │ │ @cck/mold │││
│ │ │ │││
│ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │││
│ │ │ │ MoldButton │ │ MoldInput │ │ MoldTable │ │││
│ │ │ │ MoldModal │ │ MoldDropdown│ │ MoldDatePicker│ │││
│ │ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │││
│ │ │ │ │ │ │││
│ │ │ └────────────────┼────────────────┘ │││
│ │ │ │ │││
│ │ │ ▼ │││
│ │ │ ┌────────────────────────────────────────────────┐ │││
│ │ │ │ CCK 타이포그래피 시스템 │ │││
│ │ │ │ @cck/typography │ │││
│ │ │ └────────────────────────────────────────────────┘ │││
│ │ │ │ │││
│ │ │ ┌────────────────┴────────────────┐ │││
│ │ │ ▼ ▼ │││
│ │ │ ┌─────────────┐ ┌─────────────┐ │││
│ │ │ │ Carbon │ │ Ant Design │ │││
│ │ │ │ @carbon/react│ │ antd │ │││
│ │ │ └─────────────┘ └─────────────┘ │││
│ │ │ │││
│ │ └─────────────────────────────────────────────────────────┘││
│ └─────────────────────────────────────────────────────────────┘│
│ │
└─────────────────────────────────────────────────────────────────┘
디렉토리 구조
/common/mold/
├── src/
│ ├── core/
│ │ └── ui/
│ │ └── components/ # Mold 컴포넌트들
│ │ ├── button/ # 버튼
│ │ ├── input/ # 입력 필드
│ │ ├── dropdown/ # 드롭다운
│ │ ├── modal/ # 모달
│ │ ├── date/ # 날짜 선택
│ │ ├── data-display/ # 데이터 표시
│ │ ├── accordion/ # 아코디언
│ │ ├── tabs/ # 탭
│ │ ├── notification/ # 알림
│ │ ├── loading/ # 로딩
│ │ └── ... # 50개+ 컴포넌트
│ │
│ ├── shared/ # 공유 유틸리티
│ │ ├── hooks/ # 커스텀 훅
│ │ ├── utils/ # 유틸 함수
│ │ └── locale/ # 다국어 설정
│ │
│ ├── features/ # 기능별 컴포넌트
│ │
│ ├── index.ts # 메인 export
│ └── mold.scss # 글로벌 스타일
│
├── package.json
└── tsconfig.json
4. Mold의 핵심 원칙
원칙 1: 타이포그래피만 커스터마이징
┌─────────────────────────────────────────────────────────────────┐
│ 스타일링 원칙 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ✅ 항상 CCK로 교체: │ │
│ │ └── 타이포그래피 (폰트 패밀리, 크기, 굵기) │ │
│ │ │ │
│ │ ❌ 원본 유지 (건드리지 않음): │ │
│ │ ├── 색상 │ │
│ │ ├── 배경색 │ │
│ │ ├── 테두리 │ │
│ │ ├── 간격 (padding, margin) │ │
│ │ └── 애니메이션 │ │
│ │ │ │
│ │ 왜? │ │
│ │ ├── Carbon/Ant의 시각적 완성도를 해치지 않음 │ │
│ │ ├── 접근성 테스트된 색상 조합 유지 │ │
│ │ ├── 유지보수 부담 최소화 │ │
│ │ └── 라이브러리 업데이트 시 충돌 최소화 │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ 예시 SCSS: │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ @use '@cck/typography'; │ │
│ │ @use '@carbon/styles/scss/config' as *; │ │
│ │ │ │
│ │ .mold-button { │ │
│ │ .#{$prefix}--btn { │ │
│ │ @extend %typo_body_01; // CCK 타이포그래피 │ │
│ │ // 색상, 배경, 테두리 건드리지 않음! │ │
│ │ } │ │
│ │ } │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
원칙 2: 기본 라이브러리 선택 기준
┌─────────────────────────────────────────────────────────────────┐
│ Carbon vs Ant Design 선택 기준 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Carbon 사용: │ │
│ │ ├── DataTable (데이터 테이블) │ │
│ │ ├── Form 컴포넌트 (Button, Input, Checkbox, Radio) │ │
│ │ ├── Navigation (Tabs, Breadcrumb, TreeView) │ │
│ │ ├── Notification, Toast │ │
│ │ └── 접근성이 특히 중요한 컴포넌트 │ │
│ │ │ │
│ │ Ant Design 사용: │ │
│ │ ├── DatePicker, TimePicker (기능이 더 풍부) │ │
│ │ ├── Modal (커스터마이징 용이) │ │
│ │ ├── Tooltip, Popover │ │
│ │ ├── Upload (파일 업로드) │ │
│ │ └── ConfigProvider로 locale 쉽게 변경 │ │
│ │ │ │
│ │ 선택 이유: │ │
│ │ ├── Carbon: 엔터프라이즈/접근성 → 데이터 중심 UI │ │
│ │ ├── Ant Design: 풍부한 기능 → 복잡한 인터랙션 UI │ │
│ │ └── 각각의 강점을 살림 │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
원칙 3: Props 인터페이스
┌─────────────────────────────────────────────────────────────────┐
│ Props 설계 원칙 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 원본 Props 모두 전달 (Passthrough) │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ interface MoldButtonProps extends ButtonProps { │ │
│ │ // Carbon Button의 모든 props 상속 │ │
│ │ // 추가 props는 최소화 │ │
│ │ } │ │
│ │ │ │
│ │ const MoldButton = (props: MoldButtonProps) => ( │ │
│ │ <Button {...props} className={classNames('mold-btn', props.className)} /> │
│ │ ); │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ 2. CCK 전용 Props 최소화 │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ // ❌ 잘못된 예 - 불필요한 variant 추가 │ │
│ │ interface MoldButtonProps { │ │
│ │ cckVariant?: 'primary' | 'secondary'; // 이미 Carbon에 있음! │
│ │ } │ │
│ │ │ │
│ │ // ✅ 좋은 예 - 필요한 것만 추가 │ │
│ │ interface MoldButtonProps extends ButtonProps { │ │
│ │ locale?: 'ko_KR' | 'en_US'; // 실제로 필요한 것 │ │
│ │ } │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ 3. 지원 안 되는 Props 처리 │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ // 기본 컴포넌트가 disabled를 지원 안 할 때 │ │
│ │ interface MoldPaginationNavProps { │ │
│ │ disabled?: boolean; // 래퍼에서 처리 │ │
│ │ } │ │
│ │ │ │
│ │ const MoldPaginationNav = ({ disabled, ...props }) => ( │ │
│ │ <div className={classNames({ 'mold--disabled': disabled })}> │
│ │ <PaginationNav {...props} /> │ │
│ │ </div> │ │
│ │ ); │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
5. Mold 컴포넌트 개발 가이드
파일 구조
/common/mold/src/core/ui/components/button/
├── Button.tsx # 메인 컴포넌트
├── _button.scss # 스타일 (폴더당 1개만!)
├── Button.types.ts # 타입 정의 (필요시)
└── index.ts # export
// Button.tsx
import React from 'react';
import { Button as CarbonButton, ButtonProps } from '@carbon/react';
import classNames from 'classnames';
import './_button.scss';
export interface MoldButtonProps extends ButtonProps {
locale?: 'ko_KR' | 'en_US';
}
const MoldButton: React.FC<MoldButtonProps> = ({
className,
children,
...props
}) => {
return (
<CarbonButton
className={classNames('mold-button', className)}
{...props}
>
{children}
</CarbonButton>
);
};
MoldButton.displayName = 'MoldButton';
export default MoldButton;
SCSS 작성 규칙
// _button.scss
@use '@carbon/colors';
@use '@carbon/layout';
@use '@cck/typography';
@use '@carbon/styles/scss/config' as *;
.mold-button {
// Carbon 컴포넌트 타겟팅
.#{$prefix}--btn {
// 타이포그래피만 오버라이드
@extend %typo_body_01;
// ❌ 색상/배경/테두리 건드리지 않음
// background-color: red; // 하지 마세요!
}
// 필요한 경우 상태 스타일
&--disabled {
opacity: 0.5;
pointer-events: none;
}
}
Import 규칙
// src/core/ui/components/SomeComponent.tsx
// ✅ 올바른 import
import React from 'react';
import { Button } from '@carbon/react';
import { someUtil } from '@shared'; // 다른 레이어는 별칭
import InputComponent from 'core/ui/components/InputComponent'; // 같은 레이어는 경로
import './_some-component.scss'; // 스타일은 상대 경로
// ❌ 잘못된 import
import { something } from '@core'; // core 내부에서 @core 사용 금지
import { something } from '../utils'; // 스타일 외 상대 경로 금지
import { something } from '@shared/utils'; // 별칭 확장 금지
6. 다국어 지원 (i18n)
┌─────────────────────────────────────────────────────────────────┐
│ Mold 다국어 지원 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 지원 언어: │
│ ├── ko_KR (한국어) - 기본값 │
│ └── en_US (영어) │
│ │
│ 구현 방법 (Ant Design 컴포넌트): │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ import { ConfigProvider } from 'antd'; │ │
│ │ import koKR from 'antd/locale/ko_KR'; │ │
│ │ import enUS from 'antd/locale/en_US'; │ │
│ │ import { useLocaleValue } from '@shared'; │ │
│ │ │ │
│ │ const MoldDatePicker = ({ locale, ...props }) => { │ │
│ │ const contextLocale = useLocaleValue(); │ │
│ │ const currentLocale = locale ?? contextLocale ?? 'ko_KR'; │ │
│ │ │ │
│ │ const antdLocale = currentLocale === 'en_US' ? enUS : koKR; │
│ │ │ │
│ │ return ( │ │
│ │ <ConfigProvider locale={antdLocale}> │ │
│ │ <DatePicker {...props} /> │ │
│ │ </ConfigProvider> │ │
│ │ ); │ │
│ │ }; │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ 전역 설정: │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ // App 최상위에서 LocaleProvider 설정 │ │
│ │ import { LocaleProvider } from '@cck/mold'; │ │
│ │ │ │
│ │ <LocaleProvider value="ko_KR"> │ │
│ │ <App /> │ │
│ │ </LocaleProvider> │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
7. 사용 예시
기본 사용
// 앱에서 Mold 컴포넌트 사용
import {
MoldButton,
MoldInput,
MoldDatePicker,
MoldDataTable,
MoldModal,
} from '@cck/mold';
function MyComponent() {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
{/* 버튼 - Carbon 기반 */}
<MoldButton kind="primary" onClick={() => setIsOpen(true)}>
추가하기
</MoldButton>
{/* 입력 - Carbon 기반 */}
<MoldInput
id="name"
labelText="이름"
placeholder="이름을 입력하세요"
/>
{/* 날짜 선택 - Ant Design 기반 */}
<MoldDatePicker
locale="ko_KR"
onChange={(date) => console.log(date)}
/>
{/* 모달 - Ant Design 기반 */}
<MoldModal
open={isOpen}
onClose={() => setIsOpen(false)}
title="확인"
>
<p>저장하시겠습니까?</p>
</MoldModal>
</div>
);
}
테이블 사용 (가장 복잡한 예시)
import { MoldDataTable } from '@cck/mold';
const headers = [
{ key: 'name', header: '이름' },
{ key: 'email', header: '이메일' },
{ key: 'department', header: '부서' },
];
const rows = [
{ id: '1', name: '홍길동', email: 'hong@cck.com', department: '개발팀' },
{ id: '2', name: '김철수', email: 'kim@cck.com', department: '디자인팀' },
];
function UserTable() {
return (
<MoldDataTable
rows={rows}
headers={headers}
title="사용자 목록"
toolbar=
pagination=
/>
);
}
8. 디자이너와의 협업
디자이너용 가이드
┌─────────────────────────────────────────────────────────────────┐
│ 디자이너 → 개발자 소통 가이드 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Figma에서: │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 컴포넌트 명명 규칙: │ │
│ │ ├── "MoldButton / primary / lg" │ │
│ │ ├── "MoldInput / default / error" │ │
│ │ ├── "MoldDatePicker / range" │ │
│ │ └── "MoldModal / large" │ │
│ │ │ │
│ │ 개발자가 바로 이해: │ │
│ │ <MoldButton kind="primary" size="lg" /> │ │
│ │ <MoldInput invalid /> │ │
│ │ <MoldDatePicker mode="range" /> │ │
│ │ <MoldModal size="lg" /> │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ 사용 가능한 컴포넌트 목록: │
│ ├── MoldButton (kind: primary, secondary, ghost, danger) │
│ ├── MoldInput (labelText, helperText, invalid) │
│ ├── MoldDropdown (items, selectedItem) │
│ ├── MoldDatePicker (mode: single, range) │
│ ├── MoldModal (size: sm, lg) │
│ ├── MoldDataTable (headers, rows, pagination) │
│ ├── MoldTabs (tabItems) │
│ ├── MoldAccordion (items) │
│ ├── MoldNotification (kind: info, success, warning, error) │
│ └── ... (전체 목록은 Storybook 참조) │
│ │
└─────────────────────────────────────────────────────────────────┘
Storybook
┌─────────────────────────────────────────────────────────────────┐
│ Mold Storybook │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 실행 방법: │
│ $ cd /common/mold │
│ $ pnpm storybook │
│ → http://localhost:6006 │
│ │
│ 포함 내용: │
│ ├── 모든 Mold 컴포넌트 데모 │
│ ├── Props 문서화 │
│ ├── 사용 예시 코드 │
│ ├── 다크 모드 미리보기 │
│ └── 접근성 검사 │
│ │
│ 디자이너가 활용: │
│ ├── 실제 컴포넌트 모습 확인 │
│ ├── 가능한 variant/props 확인 │
│ └── 동작 테스트 │
│ │
└─────────────────────────────────────────────────────────────────┘
9. CCK 앱에서의 Mold
패키지 의존성
// 각 앱의 package.json
{
"dependencies": {
"@cck/mold": "workspace:~1.0.0",
"@cck/typography": "workspace:~1.0.0"
}
}
사용하는 앱들
┌─────────────────────────────────────────────────────────────────┐
│ Mold를 사용하는 CCK 앱들 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ /accio → 금융/회계 애플리케이션 │
│ /helloworld → 재무제표 분석 시스템 │
│ /jump → Jump 애플리케이션 │
│ /mothership → 관리/운영 애플리케이션 │
│ /blahblah → 채팅/대화 애플리케이션 │
│ │
│ 모든 앱이 동일한 컴포넌트 사용: │
│ → UI 일관성 보장 │
│ → 유지보수 1곳에서 │
│ → 디자이너가 1가지 컴포넌트 세트만 알면 됨 │
│ │
└─────────────────────────────────────────────────────────────────┘
10. 정리
┌─────────────────────────────────────────────────────────────────┐
│ Mold 요약 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Mold = CCK 프로젝트 전용 컴포넌트 래퍼 라이브러리 │
│ │
│ 등장 배경: │
│ ├── Carbon/Ant Design 폰트가 CCK 브랜드와 안 맞음 │
│ ├── 개발자마다 다른 컴포넌트 선택 │
│ ├── 반복적인 커스터마이징 작업 │
│ └── 디자이너-개발자 소통 비용 │
│ │
│ 핵심 원칙: │
│ ├── 타이포그래피만 CCK로 교체 (색상/배경/테두리 유지) │
│ ├── Carbon = 데이터 중심 컴포넌트 │
│ ├── Ant Design = 복잡한 인터랙션 컴포넌트 │
│ └── Props는 원본 그대로 전달 (추가 최소화) │
│ │
│ 장점: │
│ ├── 한 번 래핑 → 전체 앱에서 재사용 │
│ ├── 스타일 수정 1곳에서 관리 │
│ ├── 디자이너가 "MoldButton"만 알면 됨 │
│ └── 접근성/테마 등 Carbon의 장점 유지 │
│ │
│ 사용 방법: │
│ ├── import { MoldButton } from '@cck/mold'; │
│ ├── Carbon/Ant와 동일한 API │
│ └── CCK 타이포그래피 자동 적용 │
│ │
└─────────────────────────────────────────────────────────────────┘
관련 키워드
Mold, CCK, 래퍼, Wrapper, Carbon, Ant Design, 디자인 시스템, 타이포그래피, 컴포넌트 라이브러리, Figma, Storybook, 디자이너-개발자 협업