27
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

メタップスAdvent Calendar 2020

Day 7

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

Last updated at Posted at 2020-12-06

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)のテーマにも適用することで、型システムを利用できるようにする。

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

27
7
0

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
  3. You can use dark theme
What you can do with signing up
27
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?