0
1

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 1 year has passed since last update.

TODOアプリにcss(TailwindCSS)を適用

Last updated at Posted at 2023-05-30

はじめに

ReactとTypeScriptで作成したTODOアプリにcss(TailwindCSS)を適用しました。
今回は適用した、cssおよびTailWindCssの導入方法や使い方等を記します。

前準備

今回利用するTODOアプリは以下の記事を参考に作成してください。

バックエンド(API作成)
DockerとLaravel10でAPI作成(Postmanで動作確認)

フロントエンド(環境構築)
Docker + React + TypeScript の 環境構築方法

フロントエンド(アプリ作成)
React+TypeScript+axiosでTODOアプリ作成(Docker環境)

TailwindCSS導入方法

下記コマンドを実行してTailwindCSSインストールします。

npm install -D tailwindcss@latest postcss@latest autoprefixer@latest

tailwind.config.jsを以下に書き換える。

tailwind.config.jsコード
tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./src/**/*.{js,jsx,ts,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

フロントエンドプロジェクト内のsrcフォルダ下にあるindex.cssファイルの先頭に以下を追加

index.css
@tailwind base;
@tailwind components;
@tailwind utilities;

上記のindex.cssファイルから実際に適用するcssファイルを下記コマンドで作成します。

npx tailwindcss -i ./src/index.css -o ./src/components/output.css

フロントエンドプロジェクト内のsrcフォルダ下にあるcomponentsフォルダ下のApp.tsxファイルに、下記文を追加します。

App.tsxコード追加部
App.tsx
import { useRef } from "react";

import { useTodo } from "../hooks/useTodo";
import { Todo } from "../types/Todo";
import { TodoTitle } from "./TodoTitle";
import { TodoAdd } from "./TodoAdd";
import { TodoList } from "./TodoList";

+ import './output.css';

function App() {
  const { todoList, updateTodoListItem, addTodoListItem, deleteTodoListItem } = useTodo();

  const inputEl = useRef<HTMLTextAreaElement>(null);

  const handleAddTodoListItem = () => {
    if (inputEl.current?.value === "") {
      return;
    }
    addTodoListItem(inputEl.current!.value);
    inputEl.current!.value = "";
  };

  const incompletedList = todoList.filter((todo: Todo) => todo.text);


  return (
    <>
  <TodoTitle title="TODOアプリ" as="h1" />
  <TodoAdd buttonText="追加" inputEl={inputEl} handleAddTodoListItem={handleAddTodoListItem} />
  <TodoList todoList={incompletedList} updateTodoListItem={updateTodoListItem} deleteTodoListItem={deleteTodoListItem} title="TODOリスト" as="h2" />
  </>
  );
}

export default App;

css適用部

React時の注意事項

Reactではclassを指定する際、下記のようにclassNameを使用します。

className=""

css適用後画面

通常画面
css画像01.png

更新時画面
css画像07.png

1.TailWindCSS以外のcss部

①背景色をグラデーションにして色をアニメーションで変化させる。

index.cssにコードを追加する。

index.cssコード追加部
index.css
@tailwind base;
@tailwind components;
@tailwind utilities;


body {
+  background: linear-gradient(45deg, #6cb8ff, #fff66c, #ffa36c);
+  background-size: 600% 600%;
+  animation: AnimationName 30s ease infinite;

  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}

+ @keyframes AnimationName {
+  0%{background-position:0% 50%}
+  50%{background-position:100% 50%}
+  100%{background-position:0% 50%}
}

これで、背景色のグラデーションが時間とともに変化していきます。

②文字やボタンに影をつけて立体的にする。

compornentsフォルダ内に3D.cssファイルを作成し、以下のコードを入力します。

3D.cssコード
3D.css
.text-3d {
    text-shadow: 1px 1px 0 #000,
                 1.2px 1.2px 0 #000,
                 1.5px 1.5px 0 #000;
}
.shadow-md {
    box-shadow: 4px 6px 8px -2px rgba(0, 0, 0, 0.8), 4px 4px 6px -2px rgba(0, 0, 0, 0.6);
}

3D.cssファイルをApp.tsxファイルにimportします。

App.tsxコード追加部
App.tsx
import { useRef } from "react";

import { useTodo } from "../hooks/useTodo";
import { Todo } from "../types/Todo";
import { TodoTitle } from "./TodoTitle";
import { TodoAdd } from "./TodoAdd";
import { TodoList } from "./TodoList";

import './output.css';
+ import './3D.css';

function App() {
  const { todoList, updateTodoListItem, addTodoListItem, deleteTodoListItem } = useTodo();

  const inputEl = useRef<HTMLTextAreaElement>(null);

  const handleAddTodoListItem = () => {
    if (inputEl.current?.value === "") {
      return;
    }
    addTodoListItem(inputEl.current!.value);
    inputEl.current!.value = "";
  };

  const incompletedList = todoList.filter((todo: Todo) => todo.text);


  return (
    <>
  <TodoTitle title="TODOアプリ" as="h1" />
  <TodoAdd buttonText="追加" inputEl={inputEl} handleAddTodoListItem={handleAddTodoListItem} />
  <TodoList todoList={incompletedList} updateTodoListItem={updateTodoListItem} deleteTodoListItem={deleteTodoListItem} title="TODOリスト" as="h2" />
  </>
  );
}

export default App;

これで、文字を3Dにしたい時は、text-3dを、影を付けたい時は、shadow-mdを指定することができます。

2.TailwindCSS部

①タイトル部

css画像02.png

css画像03.png

TodoTitle.tsxコード変更部
TodoTitle.tsx
import { memo } from "react";

export const TodoTitle = memo(({ title, as }: { title: string; as: string}) => {
  if (as === "h1") {
+    return <h1 className="text-8xl font-extrabold text-center p-4 m-4 font-mono italic text-green-800 antialiased text-shadow-xs" style={{textShadow: '0 0 6px rgba(0,0,0,0.5)'}}>{title}</h1>;
  } else if (as === "h2") {
+    return <h2 className="text-6xl font-extrabold text-center p-2 m-2 font-mono italic text-green-800 antialiased text-shadow-xs" style={{textShadow: '0 0 6px rgba(0,0,0,0.5)'}}>{title}</h2>;
  } else {
    return <p>{title}</p>;
  }
});
②TODO追加部

css画像04.png

TodoAdd.tsxコード変更部
TodoAdd.tsx
import { RefObject } from "react";

export const TodoAdd = ({ buttonText, inputEl, handleAddTodoListItem }: { buttonText: string; inputEl: RefObject<HTMLTextAreaElement>; handleAddTodoListItem: () => void }) => {
  return (
+    <div className="flex justify-center w-full">
+      <textarea ref={inputEl} className="h-10 w-1/2 border rounded-lg bg-red-200 m-4 p-1 text-lg"/>
+      <button onClick={handleAddTodoListItem} className="m-2 flex w-full justify-center items-center px-0 py-3 text-base font-bold rounded-full shadow-md text-white bg-green-600 md:inline-flex md:w-auto md:px-12 hover:shadow-sm hover:text-black hover:bg-gradient-to-b from-blue-300 to-blue-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-white focus:ring-green-700 dark:ring-offset-gray-900 dark:focus:ring-red-400 antialiased">{buttonText}</button>
    </div>
  );
};
③TODOリスト表題部

css画像05.png

TodoList.tsxコード変更部
TodoList.tsx
import { TodoTitle } from "./TodoTitle";
import { TodoItem } from "./TodoItem";
import { Todo } from "../types/Todo";

export const TodoList = ({
  todoList,
  updateTodoListItem,
  deleteTodoListItem,
  title,
  as,
}: {
  todoList: Todo[];
  updateTodoListItem: (id: string,value: string) => void;
  deleteTodoListItem: (id: string) => void;
  title: string;
  as: string;
}) => {
  return (
    <>
      {todoList.length !== 0 && (
        <>
        <TodoTitle title={title} as={as} />
+        <div className="flex justify-center">
          
+          <table className="font-mono font-extrabold italic text-green-800 text-center text-2xl m-4 p-4 border border-green-900 border-2 bg-gradient-to-b from-blue-200 to-blue-100">
+            <tr className="text-3d font-mono font-extrabold italic text-green-800 text-2xl m-8 p-4 border border-green-900 border-2">
+              <th className="font-extrabold text-center text-4xl m-4 p-4 border border-green-900 border-2">NO</th><th className="font-extrabold text-center text-4xl m-4 p-4 border border-green-900 border-2">     TODO 内容     </th><th className="font-extrabold text-center text-4xl m-4 p-4 border border-green-900 border-2">TODO追加更新日時</th><th className="font-extrabold text-center text-4xl m-4 p-6 border border-green-900 border-2"> 更新 </th><th className="font-extrabold text-center text-4xl m-4 p-4 border border-green-900 border-2"> 削除 </th>
            </tr>
            {todoList.map((todo) => (
              <tr key={todo.id}>
                <TodoItem todo={todo} key={todo.id} updateTodoListItem={updateTodoListItem} deleteTodoListItem={deleteTodoListItem} />
              </tr>
            ))}
          </table>
        </div>
        </>
      )}
    </>
  );
};

④TODO部

更新ボタン無し時
css画像06.png

更新ボタン有り時
css画像08.png

TodoItem.tsxコード変更部
TodoItem.tsx
import React,{useState} from "react";
import { Todo } from "../types/Todo";

export const TodoItem = ({ todo, updateTodoListItem, deleteTodoListItem }: { todo: Todo; updateTodoListItem: any; deleteTodoListItem: any }) => {
  
  const [value, setValue] = useState("");
  const [disabled, setDisabled] = useState(true);
  const [active, setActive] = useState(false);

  const handleChangeText = (event: { target: { value: React.SetStateAction<string>; }; }) => {
    if(event.target.value===""){
      setDisabled(true);
      setActive(false);  
    }else{
      setDisabled(false);
      setActive(true);
      setValue(event.target.value);
    }
  };
  
  const handleUpdateTodoListItem = () =>{
    updateTodoListItem(todo.id,value);
    setDisabled(true);
    setActive(false);
  }
  const handleDeleteTodoListItem = () => deleteTodoListItem(todo.id);

  const year = todo.updated_at.substring(0,4);
  const month = todo.updated_at.substring(5,7);
  const day = todo.updated_at.substring(8,10);
  const hour = String(Number(todo.updated_at.substring(11,13)) + 9);
  const minute = todo.updated_at.substring(14,16);
  const second = todo.updated_at.substring(17,19);
  const datetime = year+"年"+month+"月"+day+"日"+hour+"時"+minute+"分"+second+"秒"

  return (
    <>
+      <td className="text-3d text-center text-2xl mb-8 p-1 pb-2 border border-green-900 border-2">{todo.id}</td>
+      <td className="text-center text-2xl mb-8 p-1 pb-2 border border-green-900 border-2"><input type="text" defaultValue={todo.text} onChange={handleChangeText} className="w-full p-1 pl-6 rounded-lg"/></td>
+      <td className="text-center text-2xl mb-8 p-1 pb-2 border border-green-900 border-2"><input type="text" value={datetime} className="w-full p-1 pl-6 rounded-lg"/></td>
+      <td className="text-center text-2xl mb-8 p-1 pb-2 border border-green-900 border-2"><button id={todo.id} onClick={handleUpdateTodoListItem} disabled={disabled} className={!active ? "" : "flex w-full justify-center items-center px-0 py-3 text-base font-bold rounded-full shadow-md text-white bg-purple-500 md:inline-flex md:w-auto md:px-12 hover:shadow-sm hover:bg-gradient-to-b hover:from-purple-600 hover:to-purple-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-white focus:ring-purple-700 dark:ring-offset-gray-900 dark:focus:ring-red-400"}>{!active ? "":"更新"}</button></td>
+      <td className="text-center text-2xl mb-8 p-1 pb-2 border border-green-900 border-2"><button type="button" onClick={handleDeleteTodoListItem} className="flex w-full justify-center items-center px-0 py-3 text-base font-bold rounded-full shadow-md text-white bg-gray-500 md:inline-flex md:w-auto md:px-12 hover:shadow-sm hover:bg-gradient-to-b hover:from-gray-600 hover:to-gray-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-white focus:ring-gray-600 dark:ring-offset-gray-900 dark:focus:ring-red-400">削除</button></td>
    </>
  );
};

クラスの調べ方

公式サイトやチートシートサイトを参考にしてください。

TailwindCSSの公式サイトになります。

TailwindCSSのチートシートになります。

最後に

最後まで読んで頂きましてありがとうございました。

0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?