JSPM

tooning-color-token

1.0.0
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 2
  • Score
    100M100P100Q52923F
  • License MIT

🎨 투닝 디자인 시스템 색상 토큰 패키지 - 엔터프라이즈급 확장성과 성능을 갖춘 브랜드별 색상 시스템

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');

🤝 기여하기

  1. 이슈 생성 또는 기존 이슈 확인
  2. 브랜치 생성: git checkout -b feature/새기능
  3. 변경사항 커밋: git commit -m '새 기능 추가'
  4. 브랜치 푸시: git push origin feature/새기능
  5. Pull Request 생성

📄 라이센스

MIT License - 자세한 내용은 LICENSE 파일을 참조하세요.