sanghyeon.dev

Tailwind CSS에서 color 속성 가져오기

TailwindCSS

아이콘 컴포넌트를 만들 때, props로 색상에 관련된 속성을 받아 className에 추가하고 싶었다.

아이콘은 SVG로 구성되어 있으며, fill과 stroke 값을 직접 지정하는 대신 className에 Tailwind CSS의 자동완성을 활용하여 색상을 지정하고 싶었다.

예를 들어, 다음과 같은 코드를 작성하고 싶었다.

interface IconProps {
  fill: string;
  stroke: string;
}
const Icon = ({fill, stroke}: IconProps) => {
  <svg className={`${fill} ${stroke} `} />
}

tailwind의 자동완성의 힘을 빌리기 위해선 fillstroke 의 타입에 string 대신 다른 타입을 넣어줘야 했다. 즉, tailwind에 설정되어 있는 color class들의 이름을 가져와야 한다.

타입 추출하기

우선, tailwind의 기본 color 정보들을 가져올 수 있다.

import defaultColors from "tailwindcss/colors";
export type DefaultColors = keyof typeof defaultColors;

const color: DefaultColors = 'blue'

Image.png

그러나 컬러 key 만 자동완성이 될 뿐, 그 뒤에 속성들은 제공이 되지 않는다. (blue 는 되지만 blue-500 등의 자동완성이 되지 않음)

다음 단계로, tailwind의 전체 config를 가져오자.

import resolveConfig from "tailwindcss/resolveConfig";
import tailwindConfig from "web/tailwind.config.ts"; // tailwind 설정 파일

const fullConfig = resolveConfig(tailwindConfig);

그럼 fullConfig 에서는 다음과 같은 정보들을 가져올 수 있다.

Image.png

위의 정보들을 조합해서 ColorShade라는 새로운 타입을 만들어보자

type ColorShade<T extends DefaultColors> = keyof ColorConfig[T] extends
  | string
  | number
  ? keyof ColorConfig[T]
  : never;

const shade: ColorShade<'blue'> = '500'

Image.png

이제 컬러 뒤의 속성도 가져올 수 있다.

이를 조합하여 컬러와 밝기를 조합한 이름을 추출해낼 수 있게 됐다.

type TailwindColorClass = {
  [P in DefaultColors]: ColorShade<P> extends never ? P : `${P}-${ColorShade<P>}`;
}[DefaultColors];

Image.png

적용

이제 내가 원하던 기능이었던 fill에는 fill에 들어갈 수 있는 className, stroke에는 stroke에만 들어갈 수 있는 className을 만들자.

interface IconProps {
  fill: `fill-${TailwindColorClass}`;
  stroke: `storke-${TailwindColorClass}`;
}

Image.png

커스텀 컬러 적용

위 코드의 문제점은 tailwind의 기본 색상들만 가져오고, 사용자가 정의한 색상은 가져올 수 없다.

커스텀 컬러 분리

우선 커스텀으로 적용된 color들을 하나의 변수로 분리해주자.

const color = {
  ...formalColors,
  ...keyColors,
  ...accentColors,
  ...grayColor,
  ...theme.colors,
}

그리고 위에서 사용한 DefaultColors 대신 아래의 타입을 만들자

type CustomColors = typeof color;
type DefaultColors = typeof defaultColors;
export type Colors = CustomColors & DefaultColors;

최종 코드

tailwin.config.ts

type CustomColors = typeof color;
export type DefaultColors = typeof defaultColors;
export type Colors = CustomColors & DefaultColors;

Component.tsx

const fullConfig = resolveConfig(tailwindConfig);
const colorTheme = fullConfig.theme.colors as Colors;
type ColorConfig = typeof colorTheme;
type ColorKeys = keyof Colors;

type ColorShade<T extends ColorKeys> = keyof ColorConfig[T] extends
  | string
  | number
  ? keyof ColorConfig[T]
  : never;


type TailwindColorClass = {
  [P in ColorKeys]: ColorShade<P> extends never ? P : `${P}-${ColorShade<P>}`;
}[ColorKeys];



interface IconProps {
  fill: `fill-${TailwindColorClass}`;
  stroke: `storke-${TailwindColorClass}`;
}