아이콘 컴포넌트를 만들 때, props로 색상에 관련된 속성을 받아 className에 추가하고 싶었다.
아이콘은 SVG로 구성되어 있으며, fill과 stroke 값을 직접 지정하는 대신 className에 Tailwind CSS의 자동완성을 활용하여 색상을 지정하고 싶었다.
예를 들어, 다음과 같은 코드를 작성하고 싶었다.
interface IconProps {
fill: string;
stroke: string;
}
const Icon = ({fill, stroke}: IconProps) => {
<svg className={`${fill} ${stroke} `} />
}
tailwind의 자동완성의 힘을 빌리기 위해선 fill
과 stroke
의 타입에 string
대신 다른 타입을 넣어줘야 했다. 즉, tailwind에 설정되어 있는 color class들의 이름을 가져와야 한다.
타입 추출하기
우선, tailwind의 기본 color 정보들을 가져올 수 있다.
import defaultColors from "tailwindcss/colors";
export type DefaultColors = keyof typeof defaultColors;
const color: DefaultColors = 'blue'
그러나 컬러 key 만 자동완성이 될 뿐, 그 뒤에 속성들은 제공이 되지 않는다. (blue
는 되지만 blue-500
등의 자동완성이 되지 않음)
다음 단계로, tailwind의 전체 config를 가져오자.
import resolveConfig from "tailwindcss/resolveConfig";
import tailwindConfig from "web/tailwind.config.ts"; // tailwind 설정 파일
const fullConfig = resolveConfig(tailwindConfig);
그럼 fullConfig
에서는 다음과 같은 정보들을 가져올 수 있다.
위의 정보들을 조합해서 ColorShade
라는 새로운 타입을 만들어보자
type ColorShade<T extends DefaultColors> = keyof ColorConfig[T] extends
| string
| number
? keyof ColorConfig[T]
: never;
const shade: ColorShade<'blue'> = '500'
이제 컬러 뒤의 속성도 가져올 수 있다.
이를 조합하여 컬러와 밝기를 조합한 이름을 추출해낼 수 있게 됐다.
type TailwindColorClass = {
[P in DefaultColors]: ColorShade<P> extends never ? P : `${P}-${ColorShade<P>}`;
}[DefaultColors];
적용
이제 내가 원하던 기능이었던 fill에는 fill에 들어갈 수 있는 className, stroke에는 stroke에만 들어갈 수 있는 className을 만들자.
interface IconProps {
fill: `fill-${TailwindColorClass}`;
stroke: `storke-${TailwindColorClass}`;
}
커스텀 컬러 적용
위 코드의 문제점은 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}`;
}