ダイナミックなテーマを作成する


テーマを追加することは通常、あなたが新しいプロジェクトを始めるとき、あなたが考えている最初のことではありません、しかし、それが本当に設定するのが簡単であるならば、どうですか?あなたがデフォルトのvscodeまたはスラックカラースキームに制限された世界での生活を想像してみてください😱
我々は素晴らしい方法を使用して独自のダイナミックなテーマを作成する方法を見に行くつもりですCSS Variables , 我々は我々のアプリの中から我々の全体のカラースキームを変更する動的に更新できるようになります.使っているTailwindCSS CSSのフレームワークとして、ここでは、簡単に美しく、カスタムユーザーインターフェイスを構築する必要があるすべてを提供します.CSS変数が普遍的なWeb概念であることを知っていてください.そして、あなたはこの記事のテーマのテクニックをどんなJavaScriptプロジェクトにも適用できます.

目次
  • Project Setup
  • Adding and configuring TailwindCSS
  • Setting up our themes
  • Getting Tailwind to use our theme
  • Implementing our theme switcher
  • Conclusions

  • プロジェクト設定

    We are going to make use of create-react-app as an easy starting point for our themed app. Just run the npx command below to get your project going.

    npx create-react-app my-themed-react-app --template typescript
    

    Note: We are going using react with typescript in this tutorial. Feel free to go with the standard template, it won't make too much difference in what we're trying to cover here.


    TailWindCSSの追加と設定

    Now, we're going to add tailwind as a devDependency to our project. Run the below code:

    yarn add tailwindcss -D
    

    Then we're going to generate a config file to allow us to customise our tailwind installation. This step is important as this config file will act as a bridge between our theme colours and our tailwind classes.

    npx tailwind init tailwind.js
    
    We're going to add tailwind as a PostCSS プラグインと追加autoprefixer CSSの値を使用してCSSルールをCSS規則に追加するCan I Use . また、私たちはpostcss-import プラグインは、複数のファイル間で私たちのCSSを分割できるようにする.
    yarn add postcss-cli autoprefixer postcss-import -D
    
    それから、私たちはpostcss.config.js ルートディレクトリのファイル
    // postcss.config.js
    const tailwindcss = require('tailwindcss');
    
    module.exports = {
      plugins: [
        require('postcss-import'),
        tailwindcss('./tailwind.js'),
        require('autoprefixer'),
      ],
    };
    
    今ここで物事が面白い取得です.PostcssはCSSを処理し、新しいCSSファイルを生成します.この新しい自動生成されたファイルは、すべてのアプリケーションのCSSだけでなく、すべてのtailwindクラスがあります.
    では、どうやってこうするのでしょうか
  • 我々は、現在を動かしますsrc/App.css 新しいディレクトリにsrc/css/app.css .
  • 私たちはPostcssをsrc/css/app.css 新しいCSSファイルをオリジナルに出力するsrc/App.css .
  • 我々は、我々のTruwind輸入のために新しいCSSファイルを作成して、そのファイルを輸入しますsrc/css/app.css .
  • 我々はアプリケーションを開始する前にPostCSSを実行するスクリプトを作成します.
  • 良い処置のために、我々は加えますsrc/App.css to .gitignore , プロジェクトを実行するたびに、それは再現されます.
  • /* src/css/tailwind.css */
    @import 'tailwindcss/base';
    @import 'tailwindcss/components';
    @import 'tailwindcss/utilities';
    
    それから、src/css/app.css :
    /* src/css/app.css */
    @import './tailwind.css';
    
    では、スクリプトを追加しますpackage.json 我々のアプリを起動する前に実行します.これは、私たちのアプリケーションで使用されるCSSファイルを生成するPostCSSを教えてくれます.
      "scripts": {
        ...,
        "prestart": "postcss src/css/app.css -o src/App.css"
      },
    

    Be sure that your main App.tsx file is importing the auto-generated css src/App.css. If you followed all the naming conventions, it should be doing so by default.


    そして、それ!今我々は我々のアプリを起動すると、我々はすべてのtailwindグッズの私たちを作ることができるでしょう.デフォルトのアプリケーション画面の背景色を変更するためにTailwindクラスを追加することによってそれをテストしましょう.
    // src/App.tsx
    <div className="App bg-red-900">
    

    ET Vil il!

    テーマの設定

    I just want to take a second to think about what we are trying to achieve here. We want to create a theme, configured in a central location and applied across the whole app. We want to be able to create many different themes, and dynamically apply them. As a bonus, it would be great to be able to extend an existing theme (For example, to create a Dark Mode ).

    So I'm going to start off by creating a new folder src/themes , and in it create a file called base.ts . In this file I'm going to store some variables for our theme.

    // src/themes/base.ts
    export default {
      primary: '#61DAFB',
      secondary: '#254E70',
      negative: '#e45b78',
      positive: '#A3D9B1',
      textPrimary: '#333',
      backgroundPrimary: '#efefef',
      backgroundSecondary: '#F6F9FC',
    };
    

    Now we are going to need a way to take these variables, and map them to css variables to be used by our app. Let's create a new file in the src/themes called utils.ts . Here we will create a function to map our theme variables.

    // src/themes/utils.ts
    export interface ITheme {
      [key: string]: string;
    }
    
    export interface IThemes {
      [key: string]: ITheme;
    }
    
    export interface IMappedTheme {
      [key: string]: string | null;
    }
    
    export const mapTheme = (variables: ITheme): IMappedTheme => {
      return {
        '--color-primary': variables.primary || '',
        '--color-secondary': variables.secondary || '',
        '--color-positive': variables.positive || '',
        '--color-negative': variables.negative || '',
        '--color-text-primary': variables.textPrimary || '',
        '--background-primary': variables.backgroundPrimary || '',
        '--background-sec': variables.backgroundSecondary || '',
      };
    };
    

    Now we are going to need to create a new function to take this theme, and apply the css variables to the :root element of our document. This function, applyTheme , is going to take the string name of our theme, map the variables, then apply it to the :root element.

    First, let's create a way to export all our themes in one place, src/themes/index.ts .

    // src/themes/index.ts
    import base from './base';
    import { IThemes } from './utils';
    
    /**
     * The default theme to load
     */
    export const DEFAULT_THEME: string = 'base';
    
    export const themes: IThemes = {
      base,
    };
    

    Now we can import the list of themes into our new applyTheme function in utils.ts . This function will take the name of our theme, look for it in our list of exported themes, map the css variables, then loop over the mapped object and apply each style to the :root element.

    // src/themes/utils.ts
    import { themes } from './index';
    
    ...
    
    export const applyTheme = (theme: string): void => {
      const themeObject: IMappedTheme = mapTheme(themes[theme]);
      if (!themeObject) return;
    
      const root = document.documentElement;
    
      Object.keys(themeObject).forEach((property) => {
        if (property === 'name') {
          return;
        }
    
        root.style.setProperty(property, themeObject[property]);
      });
    };
    

    If you are wondering, the reason why we map our variables instead of just starting with a mapped object, is so that we can make use of the original javascript theme variables anywhere in our components later on if we need.

    Now we can call applyTheme anywhere in our app, and it will dynamically apply our new themes variables. As a finishing touch, let's add a function to utils.ts that will allow us to extend an existing theme, and then create a dark theme that we can switch to.

    Our extend function will take an existing theme, and then make use of the ES6 spread operator to clone the existing theme and then override it with any new variables that we pass it.

    // src/themes/utils.ts
    
    ...
    
    export const extend = (
      extending: ITheme,
      newTheme: ITheme
    ): ITheme => {
      return { ...extending, ...newTheme };
    };
    

    Now we can create our dark theme, and export it.

    // src/themes/dark.ts
    import { extend } from './utils';
    import base from './base';
    
    export default extend(base, {
      backgroundPrimary: '#444444',
      backgroundSecondary: '#7b7b7b',
      textPrimary: '#fff',
    });
    
    

    我々のテーマを使用するために

    Now we need to tell Tailwind to make use of our css variables, so that when we make use of a tailwind class like text-primary , it uses the colour we supplied in our active theme. Tailwind makes this pretty easy for us to do; all we need is to add the variables that we have created into the root tailwind.js file.

    // tailwind.js
    module.exports = {
      theme: {
        extend: {
          colors: {
            primary: 'var(--color-primary)',
            secondary: 'var(--color-secondary)',
            negative: 'var(--color-negative)',
            positive: 'var(--color-positive)',
            'primary-background': 'var(--background-primary)',
            'sec-background': 'var(--background-sec)',
            'primary-text': 'var(--color-text-primary)',
          },
        },
        backgroundColor: (theme) => ({
          ...theme('colors'),
        }),
      },
      variants: {
        backgroundColor: ['active'],
      },
      plugins: [],
    };
    

    And that's it! Now we can make use of the tailwind classes, and those classes should make use of our active theme. Let's test it out by changing the background colour of our app to our primary-background colour.

    First we need to apply our default theme when the app loads. To do this we will make use of the useEffect フックを実行するapplyTheme 関数は非常に最初の時間は、アプリケーションの負荷は、我々が変更するたびにtheme 状態.アクティブなテーマを追跡するためにコンポーネント状態変数を作成し、初期値をデフォルトのテーマに設定します.
    // src/App.tsx
    import React, { useEffect, useState } from 'react';
    import { DEFAULT_THEME } from './themes';
    import { applyTheme } from './themes/utils';
    import logo from './logo.svg';
    import './App.css';
    
    function App() {
      const [theme, setTheme ] = useState(DEFAULT_THEME);
    
      /**
       * Run the applyTheme function every time the theme state changes
       */
      useEffect(() => {
        applyTheme(theme);
      }, [theme]);
    
      return (
        <div className="App bg-primary-background">
          <header className="App-header">
            <img src={logo} className="App-logo" alt="logo" />
            <p>
              Edit <code>src/App.tsx</code> and save to reload.
            </p>
            <a
              className="App-link"
              href="https://reactjs.org"
              target="_blank"
              rel="noopener noreferrer"
            >
              Learn React
            </a>
          </header>
        </div>
      );
    }
    
    export default App;
    
    アプリケーションを起動し、ルート要素を調べた場合、設定したすべてのCSS変数を見ることができます.

    では、バックグラウンドクラスを変更して主な背景色を使いましょう.
    // src/App.tsx
    <div className="App bg-primary-background">
    

    ものすごい右?今私たちのカスタムの色でTailwindのクラスを使用することができますし、我々のドキュメントのルートでCSSの変数がこれまでに変更された場合、我々のアプリ全体のすべての色があります.

    テーマスイッチャの実装

    Now that we have our theme set up, let's create a way to switch between different themes. What I'm going to do is create a simple button component that we can use to demonstrate our theme switching. This button will make use of the tailwind colour classes, so we can better see how our theme changes as we click the button. Let's create a Button.tsx component in a new folder src/components .

    // src/components/Button.tsx
    import React from 'react';
    
    type ButtonProps = {
      children?: React.ReactNode;
      onClick?: () => void;
    };
    
    export const Button: React.FunctionComponent<ButtonProps> = ({
      children,
      onClick = () => {},
    }: ButtonProps) => {
      const baseClasses: string =
        'border-2 outline-none focus:outline-none normal-case tracking-wide font-semibold rounded shadow-xl text-xs px-4 py-2';
    
      const colourClasses: string =
        'border-primary active:bg-primary-background text-primary bg-sec-background';
    
      /**
       * Render the button
       */
      return (
        <button className={`${baseClasses} ${colourClasses}`} type="button" onClick={() => onClick()}>
          {children}
        </button>
      );
    };
    

    We can now import our button into our main App.tsx component. Let's use some conditional rendering to show one button for our base theme, and another for our dark theme. In this example we are just going to assume that only these two themes exist. Each button will execute our setTheme function, which which update our state variable and in turn execute the applyTheme function in the useEffect hook.

    // src/App.tsx
    import React, { useEffect, useState } from 'react';
    import { DEFAULT_THEME } from './themes';
    import { applyTheme } from './themes/utils';
    import { Button } from './components/Button';
    import logo from './logo.svg';
    import './App.css';
    
    function App() {
      const [theme, setTheme] = useState(DEFAULT_THEME);
    
      /**
       * Run the applyTheme function every time the theme state changes
       */
      useEffect(() => {
        applyTheme(theme);
      }, [theme]);
    
      return (
        <div className="App bg-primary-background">
          <header className="App-header">
            <img src={logo} className="App-logo" alt="logo" />
            <p className="text-primary-text">
              Edit <code>src/App.tsx</code> and save to reload.
            </p>
            <div className="mt-4">
              {theme === 'base' ? (
                <Button onClick={() => setTheme('dark')}>Apply Dark Theme</Button>
              ) : (
                <Button onClick={() => setTheme('base')}>Apply Light Theme</Button>
              )}
            </div>
          </header>
        </div>
      );
    }
    
    export default App;
    

    And now we can finally see our theme switching in action!



    結論

    And that's a wrap! Thanks for getting to the end, I hope you found the techniques in this article useful. I just want to reiterate that the main concepts that we used here are not specific to react projects or tailwind. You can create your own classes/styles using the css variables that we set in our themes - that's the awesome thing about css variables!

    This post got pretty long, so I'm going to follow it up with another one which will cover providing theme context to your whole app using the Context API , optimising your css build for production, handling dynamic css classes and writing unit tests for everything.

    Source code: https://github.com/ohitslaurence/react-dynamic-theming