npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

Iโ€™ve always been into building performant and accessible sites, but lately Iโ€™ve been taking it extremely seriously. So much so that Iโ€™ve been building a tool to help me optimize and monitor the sites that I build to make sure that Iโ€™m making an attempt to offer the best experience to those who visit them. If youโ€™re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, ๐Ÿ‘‹, Iโ€™m Ryan Hefnerย  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If youโ€™re interested in other things Iโ€™m working on, follow me on Twitter or check out the open source projects Iโ€™ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soonโ€“ish.

Open Software & Tools

This site wouldnโ€™t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you ๐Ÿ™

ยฉ 2026 โ€“ย Pkg Stats / Ryan Hefner

tooning-color-token

v1.0.0

Published

๐ŸŽจ ํˆฌ๋‹ ๋””์ž์ธ ์‹œ์Šคํ…œ ์ƒ‰์ƒ ํ† ํฐ ํŒจํ‚ค์ง€ - ์—”ํ„ฐํ”„๋ผ์ด์ฆˆ๊ธ‰ ํ™•์žฅ์„ฑ๊ณผ ์„ฑ๋Šฅ์„ ๊ฐ–์ถ˜ ๋ธŒ๋žœ๋“œ๋ณ„ ์ƒ‰์ƒ ์‹œ์Šคํ…œ

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 ํŒŒ์ผ์„ ์ฐธ์กฐํ•˜์„ธ์š”.