はじめに
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コード
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./src/**/*.{js,jsx,ts,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
フロントエンドプロジェクト内のsrcフォルダ下にある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コード追加部
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適用後画面
1.TailWindCSS以外の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コード
.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コード追加部
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部
①タイトル部
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追加部
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リスト表題部
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部
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のチートシートになります。
最後に
最後まで読んで頂きましてありがとうございました。