アイコンコンポーネントを作成する際、色に関連するプロパティをpropsとして受け取り、classNameに追加したいと思いました。
アイコンはSVGで構成されており、fillとstrokeの値を直接指定する代わりに、Tailwind CSSの自動補完を活用してclassNameで色を指定したいと思いました。
例えば、次のようなコードを書きたいと思いました:
interface IconProps {
fill: string;
stroke: string;
}
const Icon = ({fill, stroke}: IconProps) => {
<svg className={`${fill} ${stroke} `} />
}
Tailwindの自動補完を活用するには、fill
とstroke
の型にstring
ではなく別の型を使用する必要がありました。つまり、Tailwindに設定されている色クラスの名前を取得する必要があります。
型の抽出
まず、Tailwindの基本的な色情報を取得できます。
import defaultColors from "tailwindcss/colors";
export type DefaultColors = keyof typeof defaultColors;
const color: DefaultColors = 'blue'
しかし、カラーキーのみが自動補完され、その後のプロパティは提供されません。(blue
は機能しますが、blue-500
などは自動補完されません)
次のステップとして、Tailwindの全体的な設定を取得しましょう。
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のデフォルトカラーのみを取得し、ユーザーが定義したカラーは取得できないことです。
カスタムカラーの分離
まず、カスタムで適用されたカラーを一つの変数に分離しましょう。
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}`;
}