Package Exports
- tooning-color-token
- tooning-color-token/colors
Readme
@tooning/color-token
🎨 투닝 디자인 시스템 색상 토큰 패키지
엔터프라이즈급 확장성과 성능을 갖춘 투닝 서비스별 브랜드 컬러 토큰 시스템입니다.
✨ 주요 특징
- 🎯 브랜드별 색상 시스템: 6개 투닝 서비스 (main, character, chat, magic, editor, board)
- 🔧 타입 안전성: 완전한 TypeScript 지원
- ⚡ 성능 최적화: 캐싱 및 메모이제이션 지원
- 🌙 테마 시스템: 다크 모드, 고대비 모드 확장 준비
- 📱 접근성: WCAG 가이드라인 준수
- 🛠️ 개발자 친화적: 풍부한 JSDoc 문서화
설치
npm install @tooning/color-token
🚀 기본 사용법
브랜드별 색상 함수 (권장)
import { getBrandButton, getText, getBorder, getBrandIcon } from '@tooning/color-token';
// 브랜드별 버튼 색상
const mainButton = getBrandButton('main', 'primary'); // '#776EFF'
const hoverButton = getBrandButton('main', 'primary', 'hover'); // 'rgba(119, 110, 255, 0.08)'
const characterBtn = getBrandButton('character', 'secondary'); // '#ffffff'
// 브랜드별 텍스트 색상
const highlightText = getText('chat', 'highlight'); // '#00C1B7'
const primaryText = getText('magic', 'primary'); // '#242424'
const errorText = getText('editor', 'error'); // '#EB5757'
// 브랜드별 테두리 색상
const activeBorder = getBorder('board', 'activated'); // '#4766FF'
const defaultBorder = getBorder('main', 'subtle'); // '#E0E0E0'
// 브랜드별 아이콘 색상
const brandIcon = getBrandIcon('character', 'highlight'); // '#2CA06E'
const disabledIcon = getBrandIcon('magic', 'disabled'); // '#C2C2C2'
구조화된 색상 토큰 접근
import { colorTokens } from '@tooning/color-token';
// 레이어 색상
const background = colorTokens.layer.background; // '#ffffff'
const surface = colorTokens.layer.surface01; // '#F9F9F9'
const errorLayer = colorTokens.layer.error; // '#FDF2F2'
// 텍스트 색상
const primaryText = colorTokens.text.primary; // '#242424'
const secondaryText = colorTokens.text.secondary; // '#595959'
const mutedText = colorTokens.text.muted; // '#969696'
// 버튼 상태
const buttonStates = colorTokens.button.states;
// { base: 'base', hover: 'hover', pressed: 'pressed', ... }
브랜드 타입 정의
import type { BrandType, ButtonLevel, ButtonState } from '@tooning/color-token';
// 지원되는 브랜드
type Brand = 'main' | 'character' | 'chat' | 'magic' | 'editor' | 'board';
// 버튼 레벨
type Level = 'primary' | 'secondary' | 'tertiary';
// 버튼 상태
type State = 'base' | 'hover' | 'pressed' | 'activated' | 'disabled';
🎨 브랜드별 색상 가이드
각 브랜드 컬러
브랜드 | 색상 | 용도 |
---|---|---|
🟣 main | #776EFF |
메인 서비스, 기본 브랜딩 |
🟢 character | #2CA06E |
캐릭터, 아바타 관련 |
🟦 chat | #00C1B7 |
채팅, 커뮤니케이션 |
🟠 magic | #FF7700 |
매직, 특수 효과 |
🩷 editor | #FF2778 |
에디터, 창작 도구 |
🔵 board | #4766FF |
보드, 협업 기능 |
사용 예시
// 서비스별 CTA 버튼
<Button color={getBrandButton('main', 'primary')}>메인 서비스</Button>
<Button color={getBrandButton('character', 'primary')}>캐릭터 생성</Button>
<Button color={getBrandButton('chat', 'primary')}>채팅 시작</Button>
<Button color={getBrandButton('magic', 'primary')}>매직 적용</Button>
<Button color={getBrandButton('editor', 'primary')}>에디터 열기</Button>
<Button color={getBrandButton('board', 'primary')}>보드 생성</Button>
// 호버 상태
<Button
color={getBrandButton('main', 'primary')}
hoverColor={getBrandButton('main', 'primary', 'hover')}
>
호버 효과
</Button>
🛠️ 고급 기능
성능 최적화 (캐싱)
import { getBrandButtonCached, memoizedUtils, cacheUtils } from '@tooning/color-token';
// 캐시된 색상 함수 (반복 호출 시 성능 향상)
const cachedColor = getBrandButtonCached('main', 'primary', 'hover');
// 메모이제이션된 유틸리티 함수들
const rgbaColor = memoizedUtils.withOpacity('#776EFF', 0.5);
const rgbValues = memoizedUtils.hexToRgb('#776EFF');
const luminance = memoizedUtils.getLuminance('#776EFF');
// 캐시 관리
console.log(cacheUtils.getStats()); // { colorCacheSize: 42 }
cacheUtils.clearAll(); // 모든 캐시 초기화
색상 유틸리티 함수
import {
isValidHexColor,
hexToRgb,
rgbToHex,
getLuminance,
isLightColor,
getContrastColor
} from '@tooning/color-token';
// 색상 유효성 검사
isValidHexColor('#776EFF'); // true
isValidHexColor('#invalid'); // false
// 색상 변환
hexToRgb('#776EFF'); // { r: 119, g: 110, b: 255 }
rgbToHex(119, 110, 255); // '#776eff'
// 밝기 계산 (0-255)
getLuminance('#776EFF'); // 약 128
getLuminance('#ffffff'); // 255
getLuminance('#000000'); // 0
// 밝기 판단
isLightColor('#776EFF'); // false
isLightColor('#ffffff'); // true
// 접근성을 위한 대비 색상
getContrastColor('#776EFF'); // '#ffffff' (어두운 배경에 밝은 텍스트)
getContrastColor('#ffffff'); // '#000000' (밝은 배경에 어두운 텍스트)
투명도 적용
import { withOpacity, withOpacityLevel, opacityLevels } from '@tooning/color-token';
// 직접 투명도 지정
withOpacity('#776EFF', 0.5); // 'rgba(119, 110, 255, 0.5)'
// 미리 정의된 투명도 레벨 사용
withOpacityLevel('#776EFF', 'light'); // 'rgba(119, 110, 255, 0.08)'
withOpacityLevel('#776EFF', 'medium'); // 'rgba(119, 110, 255, 0.12)'
withOpacityLevel('#776EFF', 'strong'); // 'rgba(119, 110, 255, 0.16)'
// 사용 가능한 투명도 레벨
console.log(opacityLevels);
// {
// subtle: 0.04, // 4%
// light: 0.08, // 8%
// medium: 0.12, // 12%
// strong: 0.16, // 16%
// bold: 0.24 // 24%
// }
🌙 테마 시스템
테마 관리
import { themeManager, lightTheme, ThemeConfig } from '@tooning/color-token';
// 현재 테마 확인
const currentTheme = themeManager.getCurrentTheme();
console.log(currentTheme.name); // 'light'
// 테마 변경 리스너
themeManager.addThemeListener((theme) => {
console.log(`테마가 ${theme.name}으로 변경되었습니다`);
// UI 업데이트 로직
});
// 커스텀 테마 생성 (향후 다크 모드 등)
const customTheme: ThemeConfig = {
name: 'custom',
colors: { /* 커스텀 색상 */ },
config: { /* 커스텀 설정 */ }
};
// 테마 적용
themeManager.setTheme(customTheme);
📦 Raw 색상 팔레트 접근
import { brandPrimary, grayscale, brandSubcategory } from '@tooning/color-token';
// 브랜드 기본 색상 팔레트
console.log(brandPrimary.main[500]); // '#776EFF'
console.log(brandPrimary.character[500]); // '#2CA06E'
console.log(brandPrimary.chat[500]); // '#00C1B7'
// 그레이스케일
console.log(grayscale[0]); // '#ffffff' (흰색)
console.log(grayscale[500]); // '#969696' (중간 회색)
console.log(grayscale[900]); // '#242424' (어두운 회색)
// 서브카테고리 색상
console.log(brandSubcategory.error[500]); // '#EB5757' (에러)
console.log(brandSubcategory.info[500]); // '#5676FF' (정보)
🎯 컴포넌트 라이브러리 통합
Angular 예시
// brand-button.component.ts
import { Component, Input, HostListener } from '@angular/core';
import { getBrandButton, getText } from '@tooning/color-token';
import type { BrandType } from '@tooning/color-token';
@Component({
selector: 'app-brand-button',
template: `
<button
[style.background-color]="backgroundColor"
[style.color]="textColor"
[style.border]="borderStyle"
[style.padding]="'12px 24px'"
[style.border-radius]="'8px'"
[style.cursor]="'pointer'"
[style.transition]="'all 0.2s ease'"
>
<ng-content></ng-content>
</button>
`
})
export class BrandButtonComponent {
@Input() brand: BrandType = 'main';
backgroundColor: string = '';
textColor: string = '';
borderStyle: string = '';
ngOnInit() {
this.updateColors();
}
ngOnChanges() {
this.updateColors();
}
private updateColors() {
this.backgroundColor = getBrandButton(this.brand, 'primary');
this.textColor = getText(this.brand, 'inverse');
this.borderStyle = `1px solid ${getBrandButton(this.brand, 'primary')}`;
}
@HostListener('mouseenter')
onMouseEnter() {
this.backgroundColor = getBrandButton(this.brand, 'primary', 'hover');
}
@HostListener('mouseleave')
onMouseLeave() {
this.backgroundColor = getBrandButton(this.brand, 'primary');
}
@HostListener('mousedown')
onMouseDown() {
this.backgroundColor = getBrandButton(this.brand, 'primary', 'pressed');
}
@HostListener('mouseup')
onMouseUp() {
this.backgroundColor = getBrandButton(this.brand, 'primary');
}
}
<!-- 사용법 -->
<app-brand-button brand="main">메인 서비스</app-brand-button>
<app-brand-button brand="character">캐릭터 생성</app-brand-button>
<app-brand-button brand="chat">채팅 시작</app-brand-button>
<app-brand-button brand="magic">매직 적용</app-brand-button>
<app-brand-button brand="editor">에디터 열기</app-brand-button>
<app-brand-button brand="board">보드 생성</app-brand-button>
Angular Service 패턴
// color-token.service.ts
import { Injectable } from '@angular/core';
import {
getBrandButton,
getText,
getBorder,
getBrandIcon,
colorTokens,
type BrandType,
type ButtonLevel,
type ButtonState
} from '@tooning/color-token';
@Injectable({
providedIn: 'root'
})
export class ColorTokenService {
// 브랜드별 버튼 색상
getBrandButtonColor(
brand: BrandType,
level: ButtonLevel = 'primary',
state: ButtonState = 'base'
): string {
return getBrandButton(brand, level, state);
}
// 브랜드별 텍스트 색상
getBrandTextColor(brand: BrandType, type: string = 'primary'): string {
return getText(brand, type as any);
}
// 공통 색상 토큰
getLayerColor(type: keyof typeof colorTokens.layer): string {
return colorTokens.layer[type];
}
getTextColor(type: keyof typeof colorTokens.text): string {
return colorTokens.text[type];
}
// 테마별 색상 세트 반환
getBrandColorSet(brand: BrandType) {
return {
primary: this.getBrandButtonColor(brand, 'primary'),
primaryHover: this.getBrandButtonColor(brand, 'primary', 'hover'),
primaryPressed: this.getBrandButtonColor(brand, 'primary', 'pressed'),
secondary: this.getBrandButtonColor(brand, 'secondary'),
text: this.getBrandTextColor(brand, 'primary'),
textHighlight: this.getBrandTextColor(brand, 'highlight'),
};
}
}
Angular Directive 패턴
// brand-color.directive.ts
import { Directive, Input, ElementRef, OnInit, OnChanges } from '@angular/core';
import { getBrandButton } from '@tooning/color-token';
import type { BrandType, ButtonLevel, ButtonState } from '@tooning/color-token';
@Directive({
selector: '[appBrandColor]'
})
export class BrandColorDirective implements OnInit, OnChanges {
@Input() appBrandColor: BrandType = 'main';
@Input() level: ButtonLevel = 'primary';
@Input() state: ButtonState = 'base';
@Input() property: 'background-color' | 'color' | 'border-color' = 'background-color';
constructor(private el: ElementRef) {}
ngOnInit() {
this.applyColor();
}
ngOnChanges() {
this.applyColor();
}
private applyColor() {
const color = getBrandButton(this.appBrandColor, this.level, this.state);
this.el.nativeElement.style[this.property] = color;
}
}
<!-- Directive 사용법 -->
<button appBrandColor="main" level="primary" property="background-color">
메인 버튼
</button>
<div appBrandColor="character" level="primary" property="border-color"
style="border: 2px solid; padding: 16px;">
캐릭터 테마 박스
</div>
<span appBrandColor="chat" level="primary" property="color">
채팅 브랜드 텍스트
</span>
Angular Pipe 패턴
// brand-color.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
import { getBrandButton, getText, getBorder, getBrandIcon } from '@tooning/color-token';
import type { BrandType, ButtonLevel, ButtonState } from '@tooning/color-token';
@Pipe({
name: 'brandColor',
pure: true
})
export class BrandColorPipe implements PipeTransform {
transform(
brand: BrandType,
type: 'button' | 'text' | 'border' | 'icon' = 'button',
level: ButtonLevel = 'primary',
state: ButtonState = 'base'
): string {
switch (type) {
case 'button':
return getBrandButton(brand, level, state);
case 'text':
return getText(brand, level as any);
case 'border':
return getBorder(brand, level as any);
case 'icon':
return getBrandIcon(brand, level as any);
default:
return getBrandButton(brand, level, state);
}
}
}
<!-- Pipe 사용법 -->
<button [style.background-color]="'main' | brandColor:'button':'primary"
[style.color]="'main' | brandColor:'text':'inverse'"
(mouseenter)="hoverColor = ('main' | brandColor:'button':'primary':'hover')"
(mouseleave)="hoverColor = null"
[style.background-color]="hoverColor || ('main' | brandColor:'button':'primary')">
메인 버튼
</button>
<!-- 다양한 브랜드 적용 -->
<div class="brand-showcase">
<button [style.background-color]="'character' | brandColor:'button':'primary'">
캐릭터 버튼
</button>
<span [style.color]="'chat' | brandColor:'text':'highlight'">
채팅 텍스트
</span>
<div [style.border-color]="'magic' | brandColor:'border':'highlight'"
style="border: 2px solid; padding: 8px;">
매직 박스
</div>
</div>
Angular Module 설정
// color-token.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BrandButtonComponent } from './components/brand-button.component';
import { BrandColorDirective } from './directives/brand-color.directive';
import { BrandColorPipe } from './pipes/brand-color.pipe';
import { ColorTokenService } from './services/color-token.service';
@NgModule({
declarations: [
BrandButtonComponent,
BrandColorDirective,
BrandColorPipe
],
imports: [
CommonModule
],
providers: [
ColorTokenService
],
exports: [
BrandButtonComponent,
BrandColorDirective,
BrandColorPipe
]
})
export class ColorTokenModule { }
## 📊 성능 및 최적화
### 캐싱 전략
- **색상 계산 캐싱**: 동일한 매개변수로 호출된 색상 함수 결과를 캐시
- **메모이제이션**: 복잡한 색상 변환 함수들의 결과를 메모리에 저장
- **LRU 캐시**: 최대 1000개 항목까지 캐시, 오래된 항목 자동 제거
### 번들 크기 최적화
```typescript
// 필요한 함수만 import (tree shaking 지원)
import { getBrandButton } from '@tooning/color-token';
// 전체 import 대신 개별 import 권장
import { getBrandButton, getText, getBorder } from '@tooning/color-token';
🔧 설정 및 커스터마이징
색상 시스템 설정 접근
import { colorSystemConfig } from '@tooning/color-token';
// 기본 색상 설정 확인
console.log(colorSystemConfig.defaultColors.disabled); // '#E0E0E0'
console.log(colorSystemConfig.defaultColors.error); // '#EB5757'
// 그레이스케일 매핑 확인
console.log(colorSystemConfig.grayscaleMap.textPrimary); // '#242424'
console.log(colorSystemConfig.grayscaleMap.textSecondary); // '#595959'
🧪 테스트 및 디버깅
색상 유효성 검사
import { isValidHexColor } from '@tooning/color-token';
// 개발 환경에서 색상 유효성 검사
const validateColors = (colors: string[]) => {
colors.forEach(color => {
if (!isValidHexColor(color)) {
console.warn(`Invalid color detected: ${color}`);
}
});
};
캐시 모니터링
import { cacheUtils } from '@tooning/color-token';
// 개발 도구에서 캐시 상태 모니터링
setInterval(() => {
console.log('Cache stats:', cacheUtils.getStats());
}, 5000);
📚 타입 정의
// 주요 타입들
export type BrandType = 'main' | 'character' | 'chat' | 'magic' | 'editor' | 'board';
export type ButtonLevel = 'primary' | 'secondary' | 'tertiary';
export type ButtonState = 'base' | 'hover' | 'pressed' | 'activated' | 'disabled';
export type BorderType = 'subtle' | 'strong' | 'highlight' | 'disabled' | 'activated' | 'error';
export type TextType = 'primary' | 'secondary' | 'muted' | 'inverse' | 'highlight' | 'error' | 'disabled' | 'activated';
export type IconType = 'primary' | 'secondary' | 'tertiary' | 'inverse' | 'highlight' | 'error' | 'disabled' | 'activated';
export type OpacityLevel = 'subtle' | 'light' | 'medium' | 'strong' | 'bold';
export type ColorValue = string; // hex 또는 rgba 형태
🚀 마이그레이션 가이드
v1.x에서 v2.x로
// 이전 버전 (deprecated)
import { serviceBrands } from '@tooning/color-token';
const color = serviceBrands.main.primary[500];
// 새 버전 (권장)
import { getBrandButton } from '@tooning/color-token';
const color = getBrandButton('main', 'primary');
🤝 기여하기
- 이슈 생성 또는 기존 이슈 확인
- 브랜치 생성:
git checkout -b feature/새기능
- 변경사항 커밋:
git commit -m '새 기능 추가'
- 브랜치 푸시:
git push origin feature/새기능
- Pull Request 생성
📄 라이센스
MIT License - 자세한 내용은 LICENSE 파일을 참조하세요.