LoginSignup
5

More than 1 year has passed since last update.

posted at

updated at

Reactのカラーテーマはこうしてこう[Material-UI, styled-componentsの例]

TL;DR

この記事は、メタップス Advent Calendar 20207日目の記事です。
本当にやりたかったテーマ(Reactで新規プロジェクト立ち上げるときはこうしてこう)に間に合わなかったため、やや細かい内容になってしまいました。お許しください。

ここでは、ReactでThemingするとき、カラーテーマをどんな感じでやると良いか、Material-UIやstyled-componentsのThemeProviderを例にとって、In My Opinionで解説します!

方針

原則として、UIライブラリ(Material-UI)のthemeを大きく変えずに拡張する。
それをCSS in JS(styled-components)のテーマにも適用することで、型システムを利用できるようにする。

  • primaryyssecondaryは、プロジェクトで決めているテーマカラーを素直に入れていく
  • それで足りない色は型を注入してユーザー定義していく
  • Material-UIとstyled-componentsのThemePrivider両方で共通のthemeオブジェクトを利用する
  • 当然typescriptでコードヒントを効かす

解説と実装例

Material-UIのThemeProvider

Material-UIのColor paletteは、primary, secondary, error, warning, ...と存在し、以下のような構造のインタフェースになっています。

export interface SimplePaletteColorOptions {
  light?: string;
  main: string;
  dark?: string;
  contrastText?: string;
}

...

export interface Color {
  50: string;
  100: string;
  200: string;
  300: string;
  400: string;
  500: string;
  600: string;
  700: string;
  800: string;
  900: string;
  A100: string;
  A200: string;
  A400: string;
  A700: string;
}

...

export type ColorPartial = Partial<Color>;

...

export type PaletteColorOptions = SimplePaletteColorOptions | ColorPartial;

...

export interface PaletteOptions {
  primary?: PaletteColorOptions;
  secondary?: PaletteColorOptions;
  ...
}

かなり細かく設定できますが、primary、secondaryを除けば、infoやsuccessなどの汎用的なカラーリングであり、プロジェクト規模がある程度まで行くと、これでは足りなくなってしまいます。

例えば、Material-UIで定義されているパレットに加えて、red, pink, greenを、定義済みのパレットと同じようなインタフェースで新設したいときは、どうすればよいのでしょうか。

たまに見かけるやり方で、、theme.tsxなどに、Material-UIのcreateMuiThemeとは別にカラーのオブジェクトを定義してエクスポートし、利用する場所でimportするというのがあります。

theme.tsx
export const theme = createMuiTheme({
...
});

export const userPalette = {
    red: {
        ...
    }
}

しかし、毎回importするのは面倒ですし、これではコードヒントも効きません。
themeはネストが深くなるので、きちんと型をつけてコードヒントの恩恵を受けたいです。

なのでここは型を注入します。

types/createPalette.d.ts
import * as createPalette from "@material-ui/core/styles/createPalette";
import { PaletteColorOptions } from "@material-ui/core/styles/createPalette";

interface CustomPalette {
  red: PaletteColorOptions; // light, main, dark, 50, 100, ..., 900, A100, ..., A700
  pink: PaletteColorOptions;
  green: PaletteColorOptions;
}

declare module "@material-ui/core/styles/createPalette" {
  // eslint-disable-next-line
  interface PaletteOptions extends CustomPalette {}
  // eslint-disable-next-line
  interface Palette extends CustomPalette {}
}

これで型が拡張されました。
ThemePrivider.tsx で、拡張した型を追加でthemingしてみます。

またここでも、Material-UIのカラーパレットシステムを利用します。(自分で一つずつ設定しても良いですが)
Material-UIでは、ある程度一般的な色のパレットをimportできるように準備されています。
https://material-ui.com/customization/color/#color-palette

これを利用すると、以下のように非常にシンプルに記述することができます。

ThemePrivider.tsx
import { red, pink, green } from "@material-ui/core/colors";

export const theme = createMuiTheme({
  palette: {
    red: {
      ...red,
      light: "#e57373",
      main: "#f44336",
      dark: "#d32f2f",
    },
    pink: {
      // redと同様
    },
    green: {
      // redと同様
    },
  },
});

これで新たに、17色 * 3の色がテーマから使えるようになっています。

Screen Shot 2020-12-07 at 6.30.52.png

あとは、受け取ったchildrenをラップするようにコンポーネントを定義し、ルートコンポーネントでラップするだけです。

ThemeProvider.tsx
import { ThemeProvider as MuiThemeProvider } from "@material-ui/styles";

export const ThemeProvider: React.FC<Props> = ({ children }: Props) => {
  return (
    <MuiThemeProvider theme={createMuiTheme(theme)}>
      {children}
    </MuiThemeProvider>
  );
};

styled-componentsのThemeProvider

以上でMaterial-UIでのThemingはできました。
次はstyled-componentsのThemeProviderにも、同じthemeオブジェクトを渡して、styled内でpropsとして利用できるようにしたいです。

しかし、そのままThemeProviderにthemeを渡すと、tscでエラーが出るので、ここでも型を注入します。

Material-UIのThemeインタフェースを利用してシンプルに書くことができます。

types/styled-components.d.ts
import "styled-components";
import { Theme } from "@material-ui/core";

declare module "styled-components" {
  // eslint-disable-next-line @typescript-eslint/no-empty-interface
  export interface DefaultTheme extends Theme {}
}

こうしておいて、ThemeProviderでさらにラップすれば良いです。

ThemeProvider.tsx
import { ThemeProvider as MuiThemeProvider } from "@material-ui/styles";
import { ThemeProvider as StyledThemeProvider } from "styled-components";

export const ThemeProvider: React.FC<Props> = ({ children }: Props) => {
  return (
    <MuiThemeProvider theme={createMuiTheme(theme)}>
      <StyledThemeProvider theme={theme}>{children}</StyledThemeProvider>
    </MuiThemeProvider>
  );
};

あとは普通に使うだけ。
styled-componentsのThemeProviderは、下位にある全てのコンポーネントツリーに対してこのthemeを送るため、全てのstyledでthemeにアクセスできるようになり、importする必要はなくなります。
コードヒントも有効になります。

const StyledRedWrapper = styled.div`
  background-color: ${(props) => props.theme.palette.red[50]};
`;

おわり

これで、Material-UIのテーマを拡張して適用し、また、styled-compoenentsでも同じものを利用できるようになりました。
今回はMaterial-UI & styled-componentsでしたが、Material-UIがAntDesignでも、styled-componentsがemotionでも、あるいはどちらか片方しか使わなくても、やることは基本的に同じだと思います。

方針に書きましたが、

原則として、UIライブラリ(Material-UI)のthemeを大きく変えずに拡張する。
それをCSS in JS(styled-components)のテーマにも適用することで、型システムを利用できるようにする。

で良いのかなと思います。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
5