TODOアプリ
前回に引き続きReactを用いてTODOアプリを作成していこうと思います。
ただし、React初心者なので以下の手順で作成していこうと思います。
- JavaScriptで作成(サーバー側はJSON Serverを使用しaxiosを用いてデータの取得、更新を行う)
- JavaScript → TypeScriptに変換
- Material UIを使用してデザインをつける
- Java(Spring Boot)を用いてサーバー側を構築
- AWS?を使用してデプロイする
今回はMaterial UIを用いたTODOアプリのデザイン化を行っていきます。
前回まで
前回まではデザイン化を行う前の事前準備を行いました。
成果物
Netlifty
GitHub
ツリー構成
src
┣apis ← モックサーバーと通信するファイルを格納するディレクトリ
┃ ┗todos.js ← サーバーとの通信用のファイル(CRUD)
┣components ← コンポーネントを格納するディレクトリ
┃ ┣App.js ← コンポーネントをまとめるファイル
┃ ┣TodoAdd.js ← TODOを新規追加するコンポーネント
┃ ┣TodoAddCheckItem.js ← チェックリスト単体用のコンポーネント
┃ ┣TodoAddCheckList.js ← チェックリストをまとめるコンポーネント
┃ ┣TodoItem.js ← TODO単体用のコンポーネント
┃ ┣TodoList.js ← TODOをリスト化するコンポーネント
┃ ┗TodoTitle.js ← タイトル用のコンポーネント
┣css ← CSSファイルをまとめるディレクトリ
┃ ┣AppStyle.module.css ← Appコンポーネントに適用するCSS Module
┃ ┣common.css ← ファイル全体に適用させる共通CSS
┃ ┣reset.css ← ファイル全体に適用させるリセットCSS
┃ ┣TodoAddCheckItemSytle.module.css ← TodoAddCheckItemコンポーネントに適用するCSS Module
┃ ┣TodoAddModalStyle.module.css ← TodoAddModalコンポーネントに適用するCSS Module
┃ ┣TodoItemStyle.module.css ← TodoItemStyleコンポーネントに適用するCSS Module
┃ ┗TodoTitleStyle.module.css ← TodoTitleStyleコンポーネントに適用するCSS Module
┣hooks ← カウタムフックを格納するディレクトリ
┃ ┗useTodo.js ← TODOの状態を管理するカスタムフック(todos.jsの具体的な実装部分)
┣index.js ← TODOアプリのトップルート
┗types.ts ← TypeScriptで使用する型定義がまとめられたファイル
// 省略
db.json ← モックサーバー
global.d.ts ← CSS Moduleを適用するための設定が書かれたファイル
Material UIを用いた各コンポーネント
index.tsx
Material UI適用前
index.tsx
import { createRoot } from "react-dom/client";
import App from "./components/App";
// as HTMLElementという型情報のコードを追加する
const rootElement = (document.getElementById("root") as HTMLElement);
const root = createRoot(rootElement);
root.render(<App />);
Material UI適用後
index.tsx
import { createRoot } from "react-dom/client";
import App from "./components/App";
// リセットCSSをインポートする
import "./css/reset.css";
// 共通CSSをインポートする
import "./css/common.css";
// as HTMLElementという型情報のコードを追加する
const rootElement = (document.getElementById("root") as HTMLElement);
const root = createRoot(rootElement);
root.render(<App />);
reset.css
reset.css
は、ChromeやFirefox、Safariなど各ブラウザによって異なるデフォルトのCSSを初期化し、ブラウザ間の表示を統一させるためのcssファイルです。
全体に適用するためにindex.tsx
でインポートしておきます。
reset.css
html, body, div, span, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
abbr, address, cite, code,
del, dfn, em, img, ins, kbd, q, samp,
small, strong, sub, sup, var,
b, i,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, figcaption, figure,
footer, header, hgroup, menu, nav, section, summary,
time, mark, audio, video {
margin:0;
padding:0;
border:0;
outline:0;
font-size:100%;
vertical-align:baseline;
background:transparent;
}
body {
line-height:1;
}
article,aside,details,figcaption,figure,
footer,header,hgroup,menu,nav,section {
display:block;
}
nav ul {
list-style:none;
}
blockquote, q {
quotes:none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content:'';
content:none;
}
a {
margin:0;
padding:0;
font-size:100%;
vertical-align:baseline;
background:transparent;
}
/* change colours to suit your needs */
ins {
background-color:#ff9;
color:#000;
text-decoration:none;
}
/* change colours to suit your needs */
mark {
background-color:#ff9;
color:#000;
font-style:italic;
font-weight:bold;
}
del {
text-decoration: line-through;
}
abbr[title], dfn[title] {
border-bottom:1px dotted;
cursor:help;
}
table {
border-collapse:collapse;
border-spacing:0;
}
/* change border colour to suit your needs */
hr {
display:block;
height:1px;
border:0;
border-top:1px solid #cccccc;
margin:1em 0;
padding:0;
}
input, select {
vertical-align:middle;
}
common.css
common.css
はファイル全体に共通で適用するCSSファイルです。
全体に適用するためにindex.tsx
でインポートしておきます。
common.css
/* 入力フォーム */
.input {
border: none;
border-radius: 5px;
background-color: rgba(0, 0, 0, 0.3);
width: 95%;
padding: 4% 2.5%;
margin: 1.5% 0;
}
/* 入力フォーム(hover) */
.input:hover {
animation-name: fadeInInput;
animation-duration: .3s;
animation-timing-function: ease-in-out;
animation-fill-mode: forwards;
}
/* 入力フォーム(focus) */
.input:focus {
background-color: rgba(0, 0, 0, 0.1);
}
/* 入力フォーム(focus-visible) */
.input:focus-visible {
outline: none;
}
/* フェードインアニメーション */
.fadeIn {
opacity: 0;
animation-name: fadeIn; /* アニメーション名を指定 */
animation-duration: .5s; /* アニメーションの変化時間 */
animation-timing-function: ease-in-out; /* アニメーションの進行具合を操作→開始時と終了時は、かなり緩やかに変化 */
animation-fill-mode: forwards; /* アニメーションの開始と終了時の状態を指定 forwards→元の状態に戻らずアニメーション最後の状態を維持 */
}
/* フェードインアニメーション */
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
/* フェードインアニメーション(入力フォーム) */
@keyframes fadeInInput {
0% {
background-color: rgba(0, 0, 0, 0.3);
}
100% {
background-color: rgba(0, 0, 0, 0.1);
}
}
App.tsx
Material UI適用前
App.tsx
// useRefを利用できるようインポートする
import { useState } from "react";
// useTodo()カスタムフックをインポートする
import { useTodo } from "../hooks/useTodo";
// チェックリストの型をインポートする
import { CheckListType } from "../types"
// TodoTitleコンポーネントをインポートする
import TodoTitle from "./TodoTitle";
// TodoListコンポーネントをインポートする
import TodoList from "./TodoList";
// TodoAddコンポーネントをインポートする
import TodoAdd from "./TodoAddModal";
const App = () => {
// useTodo()カスタムフックで作成したのを利用する
// カスタムフックで定義したコンポーネントはApp.tsxで定義し、propsを用いて下の階層に渡す
// 下の階層で定義すると親コンポーネント(App.tsx)がレンダリングされず処理は実行されているが画面は更新されない
const {todoList, addTodoItem, toggleTodoItemStatus, changeTodoItem, deleteTodoItem} = useTodo();
// 選択されたTODOリストのinputId、idを更新する関数setInputId
const [id, setId] = useState<string>("");
// 現在のタイトルの現在の状態変数inputTitle、inputTitleを更新する関数setInputTitle
const [title, setTitle] = useState<string>("");
// 現在のメモの現在の状態変数inputMemo、inputTitleを更新する関数setInputMemo
const [memo, setMemo] = useState<string>("");
// 現在のチェックリストの状態変数todoList、todoListを変更する関数setTodoList
const [checkList, setCheckList] = useState<CheckListType>([]);
// 現在の重要度の状態変数priority、priorityを更新する関数setPriority
const [priority, setPriority] = useState<string>("低");
// 現在の難易度の状態変数difficulty、difficultyを更新する関数setDifficulty
const [difficulty, setDifficulty] = useState<string>("普");
// 現在の期限の状態変数inputDeadLine、inputDeadLineを更新する関数setInputDeadLine
const [deadLine, setDeadLine] = useState<Date>(new Date());
// モーダルの表示の有無を設定する変数isShowModal、sShowModalを更新する関数setIsShowModal
const [isShowModal, setIsShowModal] = useState<boolean>(false);
// 追加ボタンと編集ボタンの変更を管理する変数changeFlg、changeFlgを更新する関数setChageFlg
// False = 追加ボタン、True = 編集ボタン
const [changeFlg, setChangeFlg] = useState<boolean>(false);
// 漢字変換・予測変換(サジェスト)選択中か否かの判定
// 変換中か否かの判定を行い、変換を確定させるエンターに反応しないように振り分ける
// true=変換中、false=変換中ではない
const [composing, setComposition] = useState<boolean>(false);
/*
##############################
各State更新用の関数
##############################
*/
// id確認用の関数handleSetId
const handleSetId = (id: string) => setId(id);
// タイトル更新用の関数handleSetTitle
const handleSetTitle = (title: string) => setTitle(title);
// メモ更新用の関数handleSetMemo
const handleSetMemo = (memo: string) => setMemo(memo);
// チェックリスト更新用の関数handleSetCheckList
const handleSetCheckList = (checkList: CheckListType) => setCheckList(checkList);
// 重要度更新用の関数handleSetDifficulty
const handleSetDifficulty = (difficulty: string) => setDifficulty(difficulty);
// 難易度更新用の関数handleSetPriority
const handleSetPriority = (priority: string) => setPriority(priority);
// 期限更新用の関数handleSetDeadLine
const handleSetDeadLine = (deadLine: Date) => setDeadLine(new Date(deadLine));
// モーダル表示更新用の関数handleSetIsShowModal
const handleSetIsShowModal = (isShowModal: boolean) => setIsShowModal(isShowModal);
// 作成/編集更新用の関数handleSetChangeFlg
const handleSetChangeFlg = (changeFlg: boolean) => setChangeFlg(changeFlg);
// 変換開始
const startComposition = () => setComposition(true);
// 変換終了
const endComposition = () => setComposition(false);
/*
##############################
未完了のTODOリストを表示する
##############################
*/
// filter()メソッドを使用してTODOリスト内のdoneがfalseのTODOを取得する
const inCompletedList = todoList.filter((todoItem) => {
return !todoItem.done;
});
/*
##############################
完了済みのTODOリストを表示する
##############################
*/
// filter()メソッドを使用してTODOリスト内のdoneがfalseのTODOを取得する
// 現在は使用していない
const completedList = todoList.filter((todoItem) => {
return todoItem.done;
});
/*
##############################
リセット処理
##############################
*/
const reset = () => {
setTitle("");
setMemo("");
setCheckList([]);
setDifficulty("普");
setPriority("低");
setDeadLine(new Date());
}
/*
##############################
TODOリストの順番変更処理
##############################
*/
const reorder = (
list: Array<any>,
startIndex: number,
endIndex: number) => {
// Array.from()メソッドは、反復可能オブジェクトや配列風オブジェクトから
// シャローコピーされた、新しいArrayインスタンスを生成する
const result = Array.from(list);
// Array.splice()メソッドは、配列を操作するメソッド
// 第1引数には操作を開始する配列のインデックス、第1引数のみの場合、指定したインデックス以降を取り除く
// 第2引数はオプション、第1引数に3、第2引数に1を指定した場合、3番目の要素を配列から取り出す
const [removed] = result.splice(startIndex, 1);
// 第3引数はオブション、第3引数に設定した値が配列に追加される
result.splice(endIndex, 0, removed);
return result;
};
/*
##############################
モーダルを非表示処理
##############################
*/
const closeModal = () => {
setIsShowModal(false);
setChangeFlg(!changeFlg);
// 入力された内容をリセットする
reset();
};
/*
##############################
TODOリスト簡易追加処理(エンターキーによる操作)
##############################
*/
const onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>, key: string) => {
switch (key) {
// エンターキーが押された際に以下の処理を実行する
case "Enter":
// input入力中にエンターを押すとデフォルトではsubmit(送信)になるため
// e.preventDefault();で阻止する
e.preventDefault();
// 変換中ならそのまま何の処理も行わない
if (composing) break;
// 変換中でないなら、TODOを追加
addTodoItem(
title,
memo,
checkList,
difficulty,
priority,
deadLine
);
// 追加後に入力フォームからフォーカスを外す
(document.getElementById("simpleAddInput") as HTMLElement).blur();
// 入力内容をクリアする
setTitle("");
break;
default:
break;
}
}
return (
<>
<div>
{/* TODOを作成するためのモーダルを表示する */}
<button
onClick={() => {
setIsShowModal(true);
setChangeFlg(false);
}}
>
TODOの作成
</button>
</div>
{/* onKeyDownでキーが押された際に処理を実行する */}
{/* onCompositionStart/onCompositionEndで入力が確定しているかどうかを判断する */}
<div>
<input
type="text"
id="simpleAddInput"
placeholder="TODOの追加"
value={title}
onChange={(e) => setTitle(e.target.value)}
onKeyDown={(e) => onKeyDown(e, e.key)}
onCompositionStart={startComposition}
onCompositionEnd={endComposition}
/>
</div>
{/* 現在は使わないのでコメントアウト中 */}
{/* <button>新しいパケットの追加</button> */}
<div>
<TodoTitle title="TODO" as="h2" />
{/* TODOを追加するモーダルを表示する */}
<TodoAdd
id={id}
title={title}
memo={memo}
checkList={checkList}
priority={priority}
difficulty={difficulty}
deadLine={deadLine}
isShowModal={isShowModal}
changeFlg={changeFlg}
composing={composing}
handleSetId={handleSetId}
handleSetTitle={handleSetTitle}
handleSetMemo={handleSetMemo}
handleSetCheckList={handleSetCheckList}
handleSetDifficulty={handleSetDifficulty}
handleSetPriority={handleSetPriority}
handleSetDeadLine={handleSetDeadLine}
addTodoItem={addTodoItem}
toggleTodoItemStatus={toggleTodoItemStatus}
changeTodoItem={changeTodoItem}
deleteTodoItem={deleteTodoItem}
closeModal={closeModal}
startComposition={startComposition}
endComposition={endComposition}
reorder={reorder}
/>
{/* 登録されたTODOリストを表示する */}
<TodoList
todoList={inCompletedList}
id={id}
title={title}
memo={memo}
checkList={checkList}
priority={priority}
difficulty={difficulty}
deadLine={deadLine}
isShowModal={isShowModal}
changeFlg={changeFlg}
composing={composing}
handleSetId={handleSetId}
handleSetTitle={handleSetTitle}
handleSetMemo={handleSetMemo}
handleSetCheckList={handleSetCheckList}
handleSetDifficulty={handleSetDifficulty}
handleSetPriority={handleSetPriority}
handleSetDeadLine={handleSetDeadLine}
handleSetIsShowModal={handleSetIsShowModal}
handleSetChangeFlg={handleSetChangeFlg}
addTodoItem={addTodoItem}
toggleTodoItemStatus={toggleTodoItemStatus}
changeTodoItem={changeTodoItem}
deleteTodoItem={deleteTodoItem}
closeModal={closeModal}
startComposition={startComposition}
endComposition={endComposition}
reorder={reorder}
/>
</div>
</>
)
}
export default App;
Material UI適用後
App.tsx
// useRefを利用できるようインポートする
import { useState } from "react";
// useTodo()カスタムフックをインポートする
import { useTodo } from "../hooks/useTodo";
// チェックリストの型をインポートする
import { CheckListType, TodoListType } from "../types"
// TodoTitleコンポーネントをインポートする
import TodoTitle from "./TodoTitle";
// TodoListコンポーネントをインポートする
import TodoList from "./TodoList";
// TodoAddコンポーネントをインポートする
import TodoAdd from "./TodoAddModal";
// CSSファイルをインポートする
import AppStyle from "../css/AppStyle.module.css";
// Material UIのコンポーネントをインポートする
// Layout
import { Container, Box } from "@mui/material";
// Inputs
import { Button } from "@mui/material";
// Material Icons
import AddRoundedIcon from '@mui/icons-material/AddRounded';
const App = () => {
// useTodo()カスタムフックで作成したのを利用する
// カスタムフックで定義したコンポーネントはApp.tsxで定義し、propsを用いて下の階層に渡す
// 下の階層で定義すると親コンポーネント(App.tsx)がレンダリングされず処理は実行されているが画面は更新されない
const {todoList, setTodoList, addTodoItem, toggleTodoItemStatus, changeTodoItem, deleteTodoItem} = useTodo();
// 選択されたTODOリストのinputId、idを更新する関数setInputId
const [id, setId] = useState<string>("");
// 現在のタイトルの現在の状態変数inputTitle、inputTitleを更新する関数setInputTitle
const [title, setTitle] = useState<string>("");
// 現在のメモの現在の状態変数inputMemo、inputTitleを更新する関数setInputMemo
const [memo, setMemo] = useState<string>("");
// 現在のチェックリストの状態変数todoList、todoListを変更する関数setTodoList
const [checkList, setCheckList] = useState<CheckListType>([]);
// 現在の重要度の状態変数priority、priorityを更新する関数setPriority
const [priority, setPriority] = useState<string>("低");
// 現在の難易度の状態変数difficulty、difficultyを更新する関数setDifficulty
const [difficulty, setDifficulty] = useState<string>("普");
// 現在の期限の状態変数inputDeadLine、inputDeadLineを更新する関数setInputDeadLine
const [deadLine, setDeadLine] = useState<Date>(new Date());
// モーダルの表示の有無を設定する変数isShowModal、sShowModalを更新する関数setIsShowModal
const [isShowModal, setIsShowModal] = useState<boolean>(false);
// 追加ボタンと編集ボタンの変更を管理する変数changeFlg、changeFlgを更新する関数setChageFlg
// False = 追加ボタン、True = 編集ボタン
const [changeFlg, setChangeFlg] = useState<boolean>(false);
// 漢字変換・予測変換(サジェスト)選択中か否かの判定
// 変換中か否かの判定を行い、変換を確定させるエンターに反応しないように振り分ける
// true=変換中、false=変換中ではない
const [composing, setComposition] = useState<boolean>(false);
/*
##############################
各State更新用の関数
##############################
*/
// TODOリスト更新用の関数handleSetTodoList
const handleSetTodoList = (todoList: TodoListType) => setTodoList(todoList);
// id確認用の関数handleSetId
const handleSetId = (id: string) => setId(id);
// タイトル更新用の関数handleSetTitle
const handleSetTitle = (title: string) => setTitle(title);
// メモ更新用の関数handleSetMemo
const handleSetMemo = (memo: string) => setMemo(memo);
// チェックリスト更新用の関数handleSetCheckList
const handleSetCheckList = (checkList: CheckListType) => setCheckList(checkList);
// 重要度更新用の関数handleSetDifficulty
const handleSetDifficulty = (difficulty: string) => setDifficulty(difficulty);
// 難易度更新用の関数handleSetPriority
const handleSetPriority = (priority: string) => setPriority(priority);
// 期限更新用の関数handleSetDeadLine
const handleSetDeadLine = (deadLine: Date) => setDeadLine(new Date(deadLine));
// モーダル表示更新用の関数handleSetIsShowModal
const handleSetIsShowModal = (isShowModal: boolean) => setIsShowModal(isShowModal);
// 作成/編集更新用の関数handleSetChangeFlg
const handleSetChangeFlg = (changeFlg: boolean) => setChangeFlg(changeFlg);
// 変換開始
const startComposition = () => setComposition(true);
// 変換終了
const endComposition = () => setComposition(false);
/*
##############################
未完了のTODOリストを表示する
##############################
*/
// filter()メソッドを使用してTODOリスト内のdoneがfalseのTODOを取得する
const inCompletedList = todoList.filter((todoItem) => {
return !todoItem.done;
});
/*
##############################
完了済みのTODOリストを表示する
##############################
*/
// filter()メソッドを使用してTODOリスト内のdoneがfalseのTODOを取得する
// 現在は使用していない
const completedList = todoList.filter((todoItem) => {
return todoItem.done;
});
/*
##############################
リセット処理
##############################
*/
const reset = () => {
setTitle("");
setMemo("");
setCheckList([]);
setDifficulty("普");
setPriority("低");
setDeadLine(new Date());
}
/*
##############################
TODOリストの順番変更処理
##############################
*/
const reorder = (
list: Array<any>,
startIndex: number,
endIndex: number) => {
// Array.from()メソッドは、反復可能オブジェクトや配列風オブジェクトから
// シャローコピーされた、新しいArrayインスタンスを生成する
const result = Array.from(list);
// Array.splice()メソッドは、配列を操作するメソッド
// 第1引数には操作を開始する配列のインデックス、第1引数のみの場合、指定したインデックス以降を取り除く
// 第2引数はオプション、第1引数に3、第2引数に1を指定した場合、3番目の要素を配列から取り出す
const [removed] = result.splice(startIndex, 1);
// 第3引数はオブション、第3引数に設定した値が配列に追加される
result.splice(endIndex, 0, removed);
return result;
};
/*
##############################
モーダルを非表示処理
##############################
*/
const closeModal = () => {
setIsShowModal(false);
setChangeFlg(!changeFlg);
// 入力された内容をリセットする
reset();
};
/*
##############################
TODOリスト簡易追加処理(エンターキーによる操作)
##############################
*/
const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>, key: string) => {
switch (key) {
// エンターキーが押された際に以下の処理を実行する
case "Enter":
// input入力中にエンターを押すとデフォルトではsubmit(送信)になるため
// e.preventDefault();で阻止する
e.preventDefault();
// 変換中ならそのまま何の処理も行わない
if (composing) break;
// 変換中でないなら、TODOを追加
addTodoItem(
title,
memo,
checkList,
difficulty,
priority,
deadLine
);
// 追加後に入力フォームからフォーカスを外す
(document.getElementById("simpleAddInput") as HTMLElement).blur();
// 入力内容をクリアする
setTitle("");
break;
default:
break;
}
}
return (
<>
<Box>
{/* TODOを作成するためのモーダルを表示する */}
<Button
className={AppStyle.button}
variant="contained" // ボタンの背景を塗りつぶす
onClick={() => {
setIsShowModal(true);
setChangeFlg(false);
}}
>
{/* sx={{...}}で直接CSSを適用する */}
<AddRoundedIcon sx={{fontSize: 17.5 }}/>
TODOの作成
</Button>
</Box>
<Container maxWidth="xs">
<TodoTitle title="TODO" as="h1" />
<Box className={AppStyle.TodoList}>
{/* onKeyDownでキーが押された際に処理を実行する */}
{/* onCompositionStart/onCompositionEndで入力が確定しているかどうかを判断する */}
<input
className="input"
type="text"
id="simpleAddInput"
placeholder="TODOの追加"
value={title}
onChange={(e) => setTitle(e.target.value)}
onKeyDown={(e) => onKeyDown(e, e.key)}
onCompositionStart={startComposition}
onCompositionEnd={endComposition}
/>
{/* 現在は使わないのでコメントアウト中 */}
{/* <button>新しいパケットの追加</button> */}
{/* TODOを追加するモーダルを表示する */}
<TodoAdd
id={id}
title={title}
memo={memo}
checkList={checkList}
priority={priority}
difficulty={difficulty}
deadLine={deadLine}
isShowModal={isShowModal}
changeFlg={changeFlg}
composing={composing}
handleSetId={handleSetId}
handleSetTitle={handleSetTitle}
handleSetMemo={handleSetMemo}
handleSetCheckList={handleSetCheckList}
handleSetDifficulty={handleSetDifficulty}
handleSetPriority={handleSetPriority}
handleSetDeadLine={handleSetDeadLine}
addTodoItem={addTodoItem}
toggleTodoItemStatus={toggleTodoItemStatus}
changeTodoItem={changeTodoItem}
deleteTodoItem={deleteTodoItem}
closeModal={closeModal}
startComposition={startComposition}
endComposition={endComposition}
reorder={reorder}
/>
{/* 登録されたTODOリストを表示する */}
<TodoList
todoList={inCompletedList}
id={id}
title={title}
memo={memo}
checkList={checkList}
priority={priority}
difficulty={difficulty}
deadLine={deadLine}
isShowModal={isShowModal}
changeFlg={changeFlg}
composing={composing}
handleSetTodoList={handleSetTodoList}
handleSetId={handleSetId}
handleSetTitle={handleSetTitle}
handleSetMemo={handleSetMemo}
handleSetCheckList={handleSetCheckList}
handleSetDifficulty={handleSetDifficulty}
handleSetPriority={handleSetPriority}
handleSetDeadLine={handleSetDeadLine}
handleSetIsShowModal={handleSetIsShowModal}
handleSetChangeFlg={handleSetChangeFlg}
addTodoItem={addTodoItem}
toggleTodoItemStatus={toggleTodoItemStatus}
changeTodoItem={changeTodoItem}
deleteTodoItem={deleteTodoItem}
closeModal={closeModal}
startComposition={startComposition}
endComposition={endComposition}
reorder={reorder}
/>
</Box>
</Container>
</>
)
}
export default App;
AppStyle.module.css
AppStyle.module.css
/* TODOリスト全体 */
.TodoList {
padding: 2.5%;
border-radius: 5px;
background-color: #edecee;
}
/* TODOの作成ボタン */
.button {
margin: 2.5%;
}
TodoAddCheckItem.tsx
Material UI適用前
TodoAddCheckItem.tsx
// propsで渡される値の型定義を行う
type TodoAddCheckItemProps = {
updateCheckList: (index: number, e: React.ChangeEvent<HTMLInputElement>) => void
deleteCheckList: (index: number) => void
checkItem : {
checkItem: string
}
index: number
}
const TodoCheckItem = (props: TodoAddCheckItemProps) => {
return (
// チェックリストの各要素
<>
{/* 現在時点でチェックボックスは機能していない */}
<input type="checkbox"></input>
<input
type="text"
value={props.checkItem.checkItem}
onChange={e => props.updateCheckList(props.index, e)}
/>
<button onClick={() => props.deleteCheckList(props.index)}>削除</button>
</>
);
}
export default TodoCheckItem;
Material UI適用後
TodoAddCheckItem.tsx
import { useState } from "react";
import TodoAddCheckItemStyle from "../css/TodoAddCheckItemStyle.module.css";
// Material UI
// Inputs
import { IconButton, InputAdornment, FormControl, Input, Checkbox } from "@mui/material";
// Layout
import { Box } from "@mui/material";
// Material icons
import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded';
import DragIndicatorRoundedIcon from '@mui/icons-material/DragIndicatorRounded';
// propsで渡される値の型定義を行う
type TodoAddCheckItemProps = {
updateCheckList: (index: number, e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void
deleteCheckList: (index: number) => void
checkItem : {
checkItem: string
}
index: number
}
const TodoCheckItem = (props: TodoAddCheckItemProps) => {
// メニュー表示ボタンを表示する変数displayMenu、displayMuneを更新する関数setDisplayMenu
const [displayBtn, setDisplayBtn] = useState<boolean>(false);
return (
// チェックリストの各要素
<FormControl fullWidth>
{/* 現在時点でチェックボックスは機能していない */}
<Input
className={TodoAddCheckItemStyle.input}
type="text"
value={props.checkItem.checkItem}
onChange={e => props.updateCheckList(props.index, e)}
onMouseEnter={() => setDisplayBtn(true)}
onMouseLeave={() => setDisplayBtn(false)}
startAdornment={
<InputAdornment position="start">
<Box className={TodoAddCheckItemStyle.icon}>
{ displayBtn && <DragIndicatorRoundedIcon /> }
</Box>
<Checkbox />
</InputAdornment>
}
endAdornment={
<InputAdornment position="end">
<IconButton onClick={() => props.deleteCheckList(props.index)}>
{ displayBtn && <DeleteRoundedIcon/> }
</IconButton>
</InputAdornment>
}
/>
</FormControl>
);
}
export default TodoCheckItem;
Inputタグ
<Input
startAdornment={
<InputAdornment position="start">
// 省略
</InputAdornment>
}
endAdornment={
<InputAdornment position="end">
// 省略
</InputAdornment>
startAdorment
とendAdornment
を指定することで要素を横に並べることが出てきまます。
startAdorment
とendAdornment
はどちらか片方のみの指定も可能です。
TodoAddCheckItemStyle.module.css
TodoAddCheckItemStyle.module.css
/* チェックリスト */
.input {
border-bottom: 1px solid rgba(0, 0, 0, 0.42);
}
/* チェックリスト(ホバー時) */
.input::before {
display: none;
}
/* チェックリスト(クリック時) */
.input::after {
display:none;
}
/* ドラッグ&ドロップ用アイコン */
.icon {
position: absolute;
top: 15%;
left: -5%;
}
/* ドラッグ&ドロップ用アイコン(ホバー時) */
.icon:hover {
cursor: pointer;
}
TodoAddCheckList.tsx
Material UI適用前
TodoAddCheckList.tsx
import React, { useState } from 'react';
// 一意なidを生成するulidをインポートする
import {ulid} from "ulid";
// ドラッグ&ドロップのライブラリreact-beautiful-dndをインポートする
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
// チェックリストの型をインポートする
import { CheckListType } from "../types"
import TodoCheckItem from "./TodoAddCheckItem";
// propsで渡される値の型定義を行う
type TodoCheckListProps = {
composing: boolean
reorder: (list: CheckListType, startIndex: number, endIndex: number) => any[]
checkList: CheckListType
handleSetCheckList: (checkList: CheckListType) => void
startComposition: () => void
endComposition: () => void
}
const TodoAddCheckList = (props: TodoCheckListProps) => {
// チェックリストを追加するinputの現在の状態変数inputValue
// inputValueを更新する関数setInputValueを定義する
const [inputValue, setInputValue] = useState<string>("");
/*
##############################
チェックリストのCSS
##############################
*/
// 引数:isDraggingOver を使用してドラッグ中とそうでない時のCSSを変更することができる
const getListStyle = (isDraggingOver: boolean) => ({
background: 'white',
/* isDraggingOverの型は真偽値、true=ドラッグ中、false=ドラッグ中ではない */
/* border: isDraggingOver ? 'solid 5px lightgray' : 'solid 5px white', */
textAlign: 'left',
});
/*
##############################
チェックアイテムのCSS
##############################
*/
const getItemStyle = (draggableStyle: any) => ({
marginBottom: '0.5rem',
...draggableStyle
});
/*
##############################
チェックリスト追加処理(エンターキーによる操作)
##############################
*/
// エンターキーで新たなチェックリストを追加できるようにする
const onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>, key: string) => {
switch (key) {
// 変換中でない時にEnterキーでinputを増やす
case "Enter":
// input入力中にエンターを押すとデフォルトではsubmit(送信)になるため
// e.preventDefault();で阻止する
e.preventDefault();
// 変換中ならそのまま何の処理も行わない
if (props.composing) break;
// 変換中でないなら、addCheckList()メソッドでチェックリストを追加
addCheckList();
break;
default:
break;
}
}
/*
##############################
チェックリストの順番変更処理
##############################
*/
const onDragEnd = (result: any) => {
// ドロップ先がない場合、そのまま処理を抜ける
if (!result.destination) return;
// 配列の順番を入れ替える
const movedCheckItem = props.reorder(
props.checkList, // 順番を入れ替えたい配列
result.source.index, // 元の配列での位置
result.destination.index // 移動先の配列での位置
);
props.handleSetCheckList(movedCheckItem);
};
/*
##############################
チェックリスト追加処理
##############################
*/
const addCheckList = () => {
// inputが空白ならそのまま何の処理も行わない
if (inputValue === "") return;
// 既存の配列に新たにチェックリストを加える
// チェックリスト内要素の識別に使用されるidはstring(文字列)型でないと警告文が発生してしまう
props.handleSetCheckList([...props.checkList, ...[{id: ulid(), checkItem: inputValue, done: false}]]);
// チェックリストに追加した後、入力内容をクリアする
setInputValue("");
}
/*
##############################
チェックリスト内容変更処理
##############################
*/
const updateCheckList = (index: number, e:React.ChangeEvent<HTMLInputElement>) => {
// slice()メソッドを使用してチェックリストのコピーを作成する
const copyCheckList = props.checkList.slice();
// index を使用して対象のチェックリストの内容を書き換える
copyCheckList[index].checkItem = e.target.value;
props.handleSetCheckList(copyCheckList);
}
/*
##############################
チェックリスト削除処理
##############################
*/
const deleteCheckList = (index: number) => {
// Array.from()メソッドは、反復可能オブジェクトや配列風オブジェクトから
// シャローコピーされた、新しいArrayインスタンスを生成する
const result = Array.from(props.checkList);
// Array.splice()メソッドは、配列を操作するメソッド
// 第2引数はオプション、第1引数に3、第2引数に2を指定した場合、3、4番目の要素を配列から取り出す
result.splice(index, 1);
props.handleSetCheckList(result);
}
return (
// onDragEnd={onDragEnd}→ドラッグ後のイベント処理、タスクの状態や順番を変更する
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="droppable">
{/* Droppableタグでsnapshotは以下のプロパティを持っている */}
{/* snapshot.isDraggingOver:リスト上でアイテムがドラッグ中かどうか */}
{(provided, snapshot) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
// Reactコンポーネント内でCSSを使用するとエラーが発生してしまうので as any をつける
style={getListStyle(snapshot.isDraggingOver) as any}
>
{props.checkList.map((checkItem, index: number) => (
<Draggable key={checkItem.id} draggableId={checkItem.id} index={index}>
{/* Draggaleタグでsnapshotは以下のプロパティを持っている */}
{/* snapshot.isDragging:アイテムがドラッグ中かどうか */}
{(provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(provided.draggableProps.style)}
>
<TodoCheckItem
index={index}
checkItem={checkItem}
updateCheckList={updateCheckList}
deleteCheckList={deleteCheckList}
/>
</div>
)}
</Draggable>
))}
{/* ここにドラッグ可能なアイテムを配置 */}
{provided.placeholder}
{/* 新しいチェックリストを追加するボタン/入力フォーム */}
<button onClick={() => addCheckList()}>追加</button>
<input
type="text"
value={inputValue}
placeholder="新しいチェックリストを追加"
onChange={(e) => setInputValue(e.target.value)}
onKeyDown={(e) => onKeyDown(e, e.key)}
onCompositionStart={props.startComposition}
onCompositionEnd={props.endComposition}
>
</input>
</div>
)}
</Droppable>
</DragDropContext>
);
}
export default TodoAddCheckList;
Material UI適用後
TodoAddCheckList.tsx
import React, { useState } from 'react';
// 一意なidを生成するulidをインポートする
import {ulid} from "ulid";
// ドラッグ&ドロップのライブラリreact-beautiful-dndをインポートする
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
// チェックリストの型をインポートする
import { CheckListType } from "../types"
import TodoCheckItem from "./TodoAddCheckItem";
// Material UI
// Layout
import { Box } from "@mui/material";
// Inputs
import { IconButton, Input, InputAdornment } from "@mui/material";
// Material icons
import AddRoundedIcon from '@mui/icons-material/AddRounded';
// propsで渡される値の型定義を行う
type TodoCheckListProps = {
composing: boolean
reorder: (list: CheckListType, startIndex: number, endIndex: number) => any[]
checkList: CheckListType
handleSetCheckList: (checkList: CheckListType) => void
startComposition: () => void
endComposition: () => void
}
const TodoAddCheckList = (props: TodoCheckListProps) => {
// チェックリストを追加するinputの現在の状態変数inputValue
// inputValueを更新する関数setInputValueを定義する
const [inputValue, setInputValue] = useState<string>("");
/*
##############################
チェックリストのCSS
##############################
*/
// 引数:isDraggingOver を使用してドラッグ中とそうでない時のCSSを変更することができる
const getListStyle = (isDraggingOver: boolean) => ({
background: 'white',
/* isDraggingOverの型は真偽値、true=ドラッグ中、false=ドラッグ中ではない */
/* border: isDraggingOver ? 'solid 5px lightgray' : 'solid 5px white', */
textAlign: 'left',
});
/*
##############################
チェックアイテムのCSS
##############################
*/
const getItemStyle = (draggableStyle: any) => ({
marginBottom: '0.5rem',
...draggableStyle
});
/*
##############################
チェックリスト追加処理(エンターキーによる操作)
##############################
*/
// エンターキーで新たなチェックリストを追加できるようにする
const onKeyDown = (e: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>, key: string) => {
switch (key) {
// 変換中でない時にEnterキーでinputを増やす
case "Enter":
// input入力中にエンターを押すとデフォルトではsubmit(送信)になるため
// e.preventDefault();で阻止する
e.preventDefault();
// 変換中ならそのまま何の処理も行わない
if (props.composing) break;
// 変換中でないなら、addCheckList()メソッドでチェックリストを追加
addCheckList();
break;
default:
break;
}
}
/*
##############################
チェックリストの順番変更処理
##############################
*/
const onDragEnd = (result: any) => {
// ドロップ先がない場合、そのまま処理を抜ける
if (!result.destination) return;
// 配列の順番を入れ替える
const movedCheckItem = props.reorder(
props.checkList, // 順番を入れ替えたい配列
result.source.index, // 元の配列での位置
result.destination.index // 移動先の配列での位置
);
props.handleSetCheckList(movedCheckItem);
};
/*
##############################
チェックリスト追加処理
##############################
*/
const addCheckList = () => {
// inputが空白ならそのまま何の処理も行わない
if (inputValue === "") return;
// 既存の配列に新たにチェックリストを加える
// チェックリスト内要素の識別に使用されるidはstring(文字列)型でないと警告文が発生してしまう
props.handleSetCheckList([...props.checkList, ...[{id: ulid(), checkItem: inputValue, done: false}]]);
// チェックリストに追加した後、入力内容をクリアする
setInputValue("");
}
/*
##############################
チェックリスト内容変更処理
##############################
*/
const updateCheckList = (index: number, e:React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
// slice()メソッドを使用してチェックリストのコピーを作成する
const copyCheckList = props.checkList.slice();
// index を使用して対象のチェックリストの内容を書き換える
copyCheckList[index].checkItem = e.target.value;
props.handleSetCheckList(copyCheckList);
}
/*
##############################
チェックリスト削除処理
##############################
*/
const deleteCheckList = (index: number) => {
// Array.from()メソッドは、反復可能オブジェクトや配列風オブジェクトから
// シャローコピーされた、新しいArrayインスタンスを生成する
const result = Array.from(props.checkList);
// Array.splice()メソッドは、配列を操作するメソッド
// 第2引数はオプション、第1引数に3、第2引数に2を指定した場合、3、4番目の要素を配列から取り出す
result.splice(index, 1);
props.handleSetCheckList(result);
}
return (
// onDragEnd={onDragEnd}→ドラッグ後のイベント処理、タスクの状態や順番を変更する
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="droppable">
{/* Droppableタグでsnapshotは以下のプロパティを持っている */}
{/* snapshot.isDraggingOver:リスト上でアイテムがドラッグ中かどうか */}
{(provided, snapshot) => (
<Box
{...provided.droppableProps}
ref={provided.innerRef}
// Reactコンポーネント内でCSSを使用するとエラーが発生してしまうので as any をつける
style={getListStyle(snapshot.isDraggingOver) as any}
>
{props.checkList.map((checkItem, index: number) => (
<Draggable key={checkItem.id} draggableId={checkItem.id} index={index}>
{/* Draggaleタグでsnapshotは以下のプロパティを持っている */}
{/* snapshot.isDragging:アイテムがドラッグ中かどうか */}
{(provided) => (
<Box
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(provided.draggableProps.style)}
>
<TodoCheckItem
index={index}
checkItem={checkItem}
updateCheckList={updateCheckList}
deleteCheckList={deleteCheckList}
/>
</Box>
)}
</Draggable>
))}
{/* ここにドラッグ可能なアイテムを配置 */}
{provided.placeholder}
{/* 新しいチェックリストを追加するボタン/入力フォーム */}
<Input
type="text"
value={inputValue}
placeholder="新しいチェックリストを追加"
fullWidth
onChange={(e) => setInputValue(e.target.value)}
onKeyDown={(e) => onKeyDown(e, e.key)}
onCompositionStart={props.startComposition}
onCompositionEnd={props.endComposition}
startAdornment={
<InputAdornment position='start'>
<IconButton onClick={() => addCheckList()}>
<AddRoundedIcon />
</IconButton>
</InputAdornment>
}
/>
</Box>
)}
</Droppable>
</DragDropContext>
);
}
export default TodoAddCheckList;
TodoAddModal.tsx
Material UI適用前
TodoAddModal.tsx
// モーダル表示するためにreact-modalをインポートする
import Modal from "react-modal";
// 期限を入力するためのライブラリとしてDatePickerをインポートする
import DatePicker from "react-datepicker";
// DatePickerで使用するカレンダーのCSSをインポートする
import "react-datepicker/dist/react-datepicker.css"
// バリデーションを行うためのreact-hook-formをインポートする
// 現時点では使用しない
import { useForm } from 'react-hook-form';
// チェックリストの型をインポートする
import { CheckListType } from "../types"
import TodoAddCheckList from "./TodoAddCheckList";
// propsで渡される値の型定義を行う
type TodoAddProps = {
id: string
title: string
memo: string
checkList: CheckListType
priority: string
difficulty: string
deadLine: Date
isShowModal: boolean
changeFlg: boolean
composing: boolean
handleSetId: (id: string) => void
handleSetTitle: (title: string) => void
handleSetMemo: (memo: string) => void
handleSetCheckList: (checkList: CheckListType) => void
handleSetDifficulty: (difficulty: string) => void
handleSetPriority: (priority: string) => void
handleSetDeadLine: (deadLine: Date) => void
addTodoItem: (
title: string,
memo: string,
checkList: CheckListType,
priority: string,
difficulty: string,
deadLine: Date
) => void
toggleTodoItemStatus: (id: string, done: boolean) => void
changeTodoItem: (
id: string,
title: string,
memo: string,
checkList: CheckListType,
priority: string,
difficulty: string,
deadLine: Date
) => void
deleteTodoItem: (id: string) => void
closeModal: () => void
startComposition: () => void
endComposition: () => void
reorder: (list: Array<any>, startIndex: number, endIndex: number) => any[]
}
// 重要度用の配列priorityItemsを定義する
const priorityItems = [
{id: 1, value: "低"},
{id: 2, value: "高"}
]
// 難易度用の配列difficultyItemsを定義する
const diffcultyItems = [
{id: 1, value: "易"},
{id: 2, value: "普"},
{id: 3, value: "難"}
]
// モーダル画面のデザインを設定
const customStyles = {
//モーダルの中身
content: {
width: "500px",
height: "700px",
top: "0",
left: "0",
right: "0",
bottom: "0",
margin: "auto",
border: "none",
padding: "30px 120px",
background: "white",
},
//モーダルの外側の部分はoverlayを使用する
overlay: {
background: "rgba(62, 62, 62, 0.75)"
}
};
// react-modalを使用するために宣言する必要あり
// 任意のアプリを設定する create-react-appなら#root
Modal.setAppElement("#root");
const TodoAddModal = (props: TodoAddProps) => {
// react-hook-formの初期設定(現状は使用しない)
// const { register, handleSubmit, watch, formState: { errors } } = useForm();
/*
##############################
TODO編集処理
##############################
*/
const handleChangeTodoItem = () => {
props.changeTodoItem(
props.id,
props.title,
props.memo,
props.checkList,
props.priority,
props.difficulty,
props.deadLine
);
props.closeModal();
}
/*
##############################
TODO追加処理
##############################
*/
const handleAddTodoItem = () => {
props.addTodoItem(
props.title,
props.memo,
props.checkList,
props.priority,
props.difficulty,
props.deadLine
)
props.closeModal();
}
return (
<div>
{/* モーダル表示したい部分をModalタグで囲む */}
<Modal
// モーダルをの表示処理isOpen
// 表示/非表示はStateのisShowModalで管理する
isOpen={props.isShowModal}
// モーダルが表示された後の処理
// モーダルが表示されている間、背景のスクロールを禁止する
onAfterOpen={() => (document.getElementById("root") as HTMLElement).style.position = "fixed"}
// モーダルが非表示になった後の処理
// モーダルを閉じた後に画面スクロールできるようにする
onAfterClose={() => (document.getElementById("root") as HTMLElement).style.position = "unset"}
// ↓を記述するとモーダル画面の外側をクリックした際にモーダルが閉じる
// onRequestClose={closeModal}
// モーダルの中身/背景のデザインを設定する
style={customStyles}
>
<div>
<div>
<p>タイトル*</p>
<input
type="text"
defaultValue={props.title}
onChange={(e) => props.handleSetTitle(e.target.value)}
/>
</div>
<div>
<p>メモ</p>
<textarea value={props.memo} onChange={(e) => props.handleSetMemo(e.target.value)}/>
</div>
<div>
<p>チェックリスト</p>
{/* チェックリストはコンポーネント化して別定義する */}
<TodoAddCheckList
checkList={props.checkList}
handleSetCheckList={props.handleSetCheckList}
reorder={props.reorder}
composing={props.composing}
startComposition={props.startComposition}
endComposition={props.endComposition}
/>
</div>
<div>
<p>重要度</p>
{/* map()メソッドを使用して重要度用の配列priorityItemsから要素を取り出す */}
{priorityItems.map((priorityItem) => (
<label key={priorityItem.id}>
<input
type="radio"
value={priorityItem.value}
onChange={(e) => props.handleSetPriority(e.target.value)}
checked={props.priority === priorityItem.value}
/>
{priorityItem.value}
</label>
))}
</div>
<div>
<p>難易度</p>
{/* map()メソッドを使用して難易度用の配列difficultyItemsから要素を取り出す */}
<select defaultValue={props.difficulty} onChange={(e) => props.handleSetDifficulty(e.target.value)}>
{diffcultyItems.map((diffcultyItem) => (
<option key={diffcultyItem.id} value={diffcultyItem.value}>
{diffcultyItem.value}
</option>
))}
</select>
</div>
<div>
<p>期限</p>
{/* 期限はDatePickerを使用する */}
{/* slectedをで初期値を設定できる */}
{/* minDateを設定することで選択できる日付を制限できる */}
{/* minDate={new Date()}のように設定すると本日より前の日付は選択できないようになる */}
<DatePicker
dateFormat="yyyy/MM/dd"
selected={props.deadLine}
onChange={(date) => props.handleSetDeadLine(date!)}
minDate={new Date()}
/>
</div>
<div>
{/* changeFlgの値により表示するボタンを変更する */}
{props.changeFlg ?
<button type="submit" onClick={handleChangeTodoItem}>編集する</button> :
<button type="submit" onClick={handleAddTodoItem}>作成する</button>
}
<button onClick={props.closeModal}>キャンセル</button>
</div>
</div>
</Modal>
</div>
);
}
export default TodoAddModal;
Material UI適用後
TodoAddModal.tsx
// モーダル表示するためにreact-modalをインポートする
import Modal from "react-modal";
// 期限を入力するためのライブラリとしてDatePickerをインポートする
import DatePicker from "react-datepicker";
// DatePickerで使用するカレンダーのCSSをインポートする
import "react-datepicker/dist/react-datepicker.css"
// バリデーションを行うためのreact-hook-formをインポートする
// 現時点では使用しない
import { useForm } from 'react-hook-form';
// チェックリストの型をインポートする
import { CheckListType } from "../types"
import TodoAddCheckList from "./TodoAddCheckList";
// 入力フォームに適応するCSSファイル
import "../css/common.css"
import TodoAddModalStyle from "../css/TodoAddModalStyle.module.css"
// Material UI
// Layout
import { Box } from "@mui/material";
// Inputs
import { Button, MenuItem, FormControl, Select, FormControlLabel, Radio } from "@mui/material";
// propsで渡される値の型定義を行う
type TodoAddProps = {
id: string
title: string
memo: string
checkList: CheckListType
priority: string
difficulty: string
deadLine: Date
isShowModal: boolean
changeFlg: boolean
composing: boolean
handleSetId: (id: string) => void
handleSetTitle: (title: string) => void
handleSetMemo: (memo: string) => void
handleSetCheckList: (checkList: CheckListType) => void
handleSetDifficulty: (difficulty: string) => void
handleSetPriority: (priority: string) => void
handleSetDeadLine: (deadLine: Date) => void
addTodoItem: (
title: string,
memo: string,
checkList: CheckListType,
priority: string,
difficulty: string,
deadLine: Date
) => void
toggleTodoItemStatus: (id: string, done: boolean) => void
changeTodoItem: (
id: string,
title: string,
memo: string,
checkList: CheckListType,
priority: string,
difficulty: string,
deadLine: Date
) => void
deleteTodoItem: (id: string) => void
closeModal: () => void
startComposition: () => void
endComposition: () => void
reorder: (list: Array<any>, startIndex: number, endIndex: number) => any[]
}
// 重要度用の配列priorityItemsを定義する
const priorityItems = [
{id: 1, value: "低"},
{id: 2, value: "高"}
]
// 難易度用の配列difficultyItemsを定義する
const diffcultyItems = [
{id: 1, value: "易"},
{id: 2, value: "普"},
{id: 3, value: "難"}
]
// モーダル画面のデザインを設定
const customStyles = {
//モーダルの中身
content: {
width: "500px",
height: "700px",
top: "0",
left: "0",
right: "0",
bottom: "0",
margin: "auto",
border: "none",
padding: "30px 120px",
background: "white",
},
//モーダルの外側の部分はoverlayを使用する
overlay: {
background: "rgba(62, 62, 62, 0.75)"
}
};
// react-modalを使用するために宣言する必要あり
// 任意のアプリを設定する create-react-appなら#root
Modal.setAppElement("#root");
const TodoAddModal = (props: TodoAddProps) => {
// react-hook-formの初期設定(現状は使用しない)
// const { register, handleSubmit, watch, formState: { errors } } = useForm();
/*
##############################
TODO編集処理
##############################
*/
const handleChangeTodoItem = () => {
props.changeTodoItem(
props.id,
props.title,
props.memo,
props.checkList,
props.priority,
props.difficulty,
props.deadLine
);
props.closeModal();
}
/*
##############################
TODO追加処理
##############################
*/
const handleAddTodoItem = () => {
props.addTodoItem(
props.title,
props.memo,
props.checkList,
props.priority,
props.difficulty,
props.deadLine
)
props.closeModal();
}
return (
<Box>
{/* モーダル表示したい部分をModalタグで囲む */}
<Modal
// モーダルをの表示処理isOpen
// 表示/非表示はStateのisShowModalで管理する
isOpen={props.isShowModal}
// モーダルが表示された後の処理
// モーダルが表示されている間、背景のスクロールを禁止する
onAfterOpen={() => (document.getElementById("root") as HTMLElement).style.position = "fixed"}
// モーダルが非表示になった後の処理
// モーダルを閉じた後に画面スクロールできるようにする
onAfterClose={() => (document.getElementById("root") as HTMLElement).style.position = "unset"}
// ↓を記述するとモーダル画面の外側をクリックした際にモーダルが閉じる
// onRequestClose={closeModal}
// モーダルの中身/背景のデザインを設定する
style={customStyles}
>
<Box>
<Box className={TodoAddModalStyle.InputForm}>
<h3 className={TodoAddModalStyle.title}>タイトル*</h3>
<input
// 2つのクラスを適用する
className={`input ${TodoAddModalStyle.input}`}
type="text"
defaultValue={props.title}
onChange={(e) => props.handleSetTitle(e.target.value)}
/>
</Box>
<Box className={TodoAddModalStyle.InputForm}>
<h3 className={TodoAddModalStyle.title}>メモ</h3>
<textarea
// 2つのクラスを適用する
className={`input ${TodoAddModalStyle.textarea}`}
value={props.memo}
onChange={(e) => props.handleSetMemo(e.target.value)}
/>
</Box>
<Box className={TodoAddModalStyle.InputForm}>
<h3 className={TodoAddModalStyle.title}>チェックリスト</h3>
{/* チェックリストはコンポーネント化して別定義する */}
<TodoAddCheckList
checkList={props.checkList}
handleSetCheckList={props.handleSetCheckList}
reorder={props.reorder}
composing={props.composing}
startComposition={props.startComposition}
endComposition={props.endComposition}
/>
</Box>
<Box className={TodoAddModalStyle.InputForm}>
<h3 className={TodoAddModalStyle.title}>重要度</h3>
{/* map()メソッドを使用して重要度用の配列priorityItemsから要素を取り出す */}
{priorityItems.map((priorityItem) => (
<FormControlLabel
key={priorityItem.id}
value={priorityItem.value}
control={
<Radio
onChange={(e) => props.handleSetPriority(e.target.value)}
checked={props.priority === priorityItem.value}
/>
}
label={priorityItem.value}
/>
))}
</Box>
<Box className={TodoAddModalStyle.InputForm}>
<h3 className={TodoAddModalStyle.title}>難易度</h3>
<FormControl fullWidth size="small">
{/* map()メソッドを使用して難易度用の配列difficultyItemsから要素を取り出す */}
<Select
className={TodoAddModalStyle.select}
defaultValue={props.difficulty}
onChange={(e) => props.handleSetDifficulty(e.target.value)}
>
{diffcultyItems.map((diffcultyItem) => (
<MenuItem key={diffcultyItem.id} value={diffcultyItem.value}>
{diffcultyItem.value}
</MenuItem>
))}
</Select>
</FormControl>
</Box>
<Box className={TodoAddModalStyle.InputForm}>
<h3 className={TodoAddModalStyle.title}>期限</h3>
{/* 期限はDatePickerを使用する */}
{/* slectedをで初期値を設定できる */}
{/* minDateを設定することで選択できる日付を制限できる */}
{/* minDate={new Date()}のように設定すると本日より前の日付は選択できないようになる */}
<DatePicker
className={TodoAddModalStyle.input}
dateFormat="yyyy/MM/dd"
selected={props.deadLine}
onChange={(date) => props.handleSetDeadLine(date!)}
minDate={new Date()}
/>
</Box>
<Box>
{/* changeFlgの値により表示するボタンを変更する */}
{props.changeFlg ?
<Button variant="contained" type="submit" onClick={handleChangeTodoItem}>編集する</Button> :
<Button variant="contained" type="submit" onClick={handleAddTodoItem}>作成する</Button>
}
<Button className={TodoAddModalStyle.button} variant="contained" onClick={props.closeModal}>キャンセル</Button>
</Box>
</Box>
</Modal>
</Box>
);
}
export default TodoAddModal;
TodoAddModalStyle.module.css
TodoAddModalStyle.module.css
/* テキスト入力フォーム(モーダル画面) */
.input {
padding: 2.5%;
}
/* テキスト入力フォーム(メモ) */
.textarea {
height: 100px
}
/* テキストエリア入力フォーム(focus-visible) */
.textarea:focus-visible {
outline: none;
}
/* 各入力フォーム */
.InputForm {
margin-bottom: 5%;
}
/* 入力フォームタイトル */
.title {
margin-bottom: 2.5%;
}
/* ボタン */
.button {
margin-left: 5%;
}
TodoItem.tsx
Material UI適用前
TodoItem.tsx
import { useState, useEffect } from "react";
// TODOリスト、チェックリストの型をインポートする
import { TodoItemType, CheckListType } from "../types"
import TodoAddModal from "./TodoAddModal";
type TodoItemProps = {
todoItem: TodoItemType
id: string
title: string
memo: string
checkList: CheckListType
priority: string
difficulty: string
deadLine: Date
isShowModal: boolean
changeFlg: boolean
composing: boolean
handleSetId: (id: string) => void
handleSetTitle: (title: string) => void
handleSetMemo: (memo: string) => void
handleSetCheckList: (checkList: CheckListType) => void
handleSetDifficulty: (difficulty: string) => void
handleSetPriority: (priority: string) => void
handleSetDeadLine: (deadLine: Date) => void
handleSetIsShowModal: (isShowModal: boolean) => void
handleSetChangeFlg: (changeFlg: boolean) => void
addTodoItem: (
title: string,
memo: string,
checkList: CheckListType,
priority: string,
difficulty: string,
deadLine: Date
) => void
toggleTodoItemStatus: (id: string, done: boolean) => void
changeTodoItem: (
id: string,
title: string,
memo: string,
checkList: CheckListType,
priority: string,
difficulty: string,
deadLine: Date
) => void
deleteTodoItem: (id: string) => void
closeModal: () => void
startComposition: () => void
endComposition: () => void
reoreder: (list: Array<any>, startIndex: number, endIndex: number) => any[]
}
const TodoItem = (props: TodoItemProps) => {
// 期限までの時間を設定する変数deadLine
// deadLineを更新する関数setDeadLineを定義する
const [deadLine, setDeadLine] = useState("");
/*
##############################
モーダル表示処理
##############################
*/
const handleChangeTodo = () => {
props.handleSetId(props.todoItem.id!); // 対象のTODOからIDを取得する
props.handleSetTitle(props.todoItem.title!); // 対象のTODOからタイトルを取得する
props.handleSetMemo(props.todoItem.memo!); // 対象のTODOからメモを取得する
props.handleSetCheckList(props.todoItem.checkList!); // 対象のTODOからチェックリストを取得する
props.handleSetDifficulty(props.todoItem.difficulty!); // 対象のTODOから難易度を取得する
props.handleSetPriority(props.todoItem.priority!); // 対象のTODOから重要度を取得する
props.handleSetDeadLine(props.todoItem.deadLine!); // 対象のTODOから期限を取得する
// 編集ボタンを表示するよう変更する
props.handleSetChangeFlg(true);
// モーダルを表示する
props.handleSetIsShowModal(true);
}
/*
##############################
期限までの残り時間を表示する
##############################
*/
const handleCountDown = () => {
const nowDate = new Date(); // 本日の日時を取得
const deadLineDate = new Date(props.todoItem.deadLine!); // 期限の日時を取得
const diffDate = deadLineDate.getTime() - nowDate.getTime(); // 期限までの残り時間を取得
// 期限が過ぎていない場合
if (diffDate > 0) {
setDeadLine(`${Math.floor(diffDate / 1000 / 60 / 60 / 24)}日後`);
// 期限が今日の場合
} else if (diffDate === 0) {
setDeadLine("今日");
// 期限が過ぎている場合
} else {
setDeadLine("期限切れ")
}
}
// useEffectを使用してコンポーネントのマウント後に関数handleCountDownを実行する
// useEffectの第2引数にprops.todoItem.deadLineを設定することでprops.todoItem.deadLineが
// 更新される度に関数handleCountDownを実行する
useEffect(handleCountDown, [props.todoItem.deadLine]);
/*
##############################
チェックボックス更新処理
##############################
*/
// 現在時点で関数handleChangeCheckは機能していない
const handleChangeCheck = () => {
};
return (
<div>
<h3>{props.todoItem.title}</h3>
<input type="hidden"></input>
<p>{props.todoItem.memo}</p>
{/* map()を利用してcheckListの要素を1つひとつ取り出す */}
{/* map()メソッドを使用する対象の配列に「! = undifined」を設定するとエラー */}
{props.todoItem.checkList?.map((checkItem) => (
<label key={checkItem.id} style={{display: "block"}}>
<input type="checkbox" value={checkItem.checkItem} onChange={handleChangeCheck}/>
{checkItem.checkItem}
</label>
))}
<p>期限:
{/* 最初にタスクの完了/未完了を判定する、その後期限が過ぎていないか判定する */}
{/* 上記の条件をクリアした場合、期限までの時間を表示する */}
{props.todoItem.done ? "完了済み" : deadLine}
</p>
{/* ボタンをクリックすることで関数handleToggleTodoItemStatusを実行する */}
{/* ボタンをクリックすることでTODOの状態(完了/未完了)を反転させる */}
<button onClick={() => {props.toggleTodoItemStatus(props.todoItem.id!, props.todoItem.done!)}}>
{props.todoItem.done ? "未完了リストへ" : "完了リストへ"}
</button>
{/* ボタンをクリックすることで関数handleDeleteTodoItemを実行する */}
{/* ボタンをクリックすることでTODOを削除する */}
<button onClick={() => {props.deleteTodoItem(props.todoItem.id!)}}>削除</button>
{/* ボタンをクリックすることで関数handleChangeTodoItemを実行する */}
{/* 関数handleChangeTodoItemが実行されるとモーダル画面が表示される */}
<button onClick={handleChangeTodo}>編集</button>
{props.isShowModal &&
<TodoAddModal
id={props.id}
title={props.title}
memo={props.memo}
checkList={props.checkList}
priority={props.priority}
difficulty={props.difficulty}
deadLine={props.deadLine}
isShowModal={props.isShowModal}
changeFlg={props.changeFlg}
composing={props.composing}
handleSetId={props.handleSetId}
handleSetTitle={props.handleSetTitle}
handleSetMemo={props.handleSetMemo}
handleSetCheckList={props.handleSetCheckList}
handleSetDifficulty={props.handleSetDifficulty}
handleSetPriority={props.handleSetPriority}
handleSetDeadLine={props.handleSetDeadLine}
addTodoItem={props.addTodoItem}
toggleTodoItemStatus={props.toggleTodoItemStatus}
changeTodoItem={props.changeTodoItem}
deleteTodoItem={props.deleteTodoItem}
closeModal={props.closeModal}
startComposition={props.startComposition}
endComposition={props.endComposition}
reorder={props.reoreder}
/>
}
</div>
);
}
export default TodoItem;
Material UI適用後
TodoItem.tsx
import { useState, useEffect } from "react";
// TODOリスト、チェックリストの型をインポートする
import { TodoItemType, CheckListType } from "../types"
import TodoAddModal from "./TodoAddModal";
import TodoItemStyle from "../css/TodoItemStyle.module.css"
// Material UI
// Layout
import { Box } from "@mui/material";
// Inputs
import { IconButton, Menu, MenuItem, Tooltip, FormGroup, FormControlLabel, Checkbox } from "@mui/material";
// Data Display
import { List, ListItemButton, ListItemText, Collapse } from "@mui/material";
// Material icons
import MoreVertRoundedIcon from '@mui/icons-material/MoreVertRounded';
import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded';
import EditRoundedIcon from '@mui/icons-material/EditRounded';
import FormatListBulletedRoundedIcon from '@mui/icons-material/FormatListBulletedRounded';
import ExpandLess from '@mui/icons-material/ExpandLess';
import ExpandMore from '@mui/icons-material/ExpandMore';
type TodoItemProps = {
todoItem: TodoItemType
id: string
title: string
memo: string
checkList: CheckListType
priority: string
difficulty: string
deadLine: Date
isShowModal: boolean
changeFlg: boolean
composing: boolean
handleSetId: (id: string) => void
handleSetTitle: (title: string) => void
handleSetMemo: (memo: string) => void
handleSetCheckList: (checkList: CheckListType) => void
handleSetDifficulty: (difficulty: string) => void
handleSetPriority: (priority: string) => void
handleSetDeadLine: (deadLine: Date) => void
handleSetIsShowModal: (isShowModal: boolean) => void
handleSetChangeFlg: (changeFlg: boolean) => void
addTodoItem: (
title: string,
memo: string,
checkList: CheckListType,
priority: string,
difficulty: string,
deadLine: Date
) => void
toggleTodoItemStatus: (id: string, done: boolean) => void
changeTodoItem: (
id: string,
title: string,
memo: string,
checkList: CheckListType,
priority: string,
difficulty: string,
deadLine: Date
) => void
deleteTodoItem: (id: string) => void
closeModal: () => void
startComposition: () => void
endComposition: () => void
reoreder: (list: Array<any>, startIndex: number, endIndex: number) => any[]
}
const TodoItem = (props: TodoItemProps) => {
// 期限までの時間を設定する変数deadLine、deadLineを更新する関数setDeadLine
const [deadLine, setDeadLine] = useState("");
// メニュー表示ボタンを表示する変数displayMenu、displayMuneを更新する関数setDisplayMenu
const [displayMenu, setDisplayMenu] = useState<boolean>(false);
// 現在のメニュー表示の状態変数anchorMenu、anchorElを更新する関数setAnchorMenu
const [anchorMenu, setAnchorMenu] = useState<null>(null);
// 現在のメニュー表示の状態変数anchorMenu、anchorElを更新する関数setAnchorMenu
const [anchorList, setAnchorList] = useState<boolean>(false);
// メニュー非表示処理
const handleClose = () => setAnchorMenu(null);
/*
##############################
期限までの残り時間を表示する
##############################
*/
const handleCountDown = () => {
const nowDate = new Date(); // 本日の日時を取得
const deadLineDate = new Date(props.todoItem.deadLine!); // 期限の日時を取得
const diffDate = deadLineDate.getTime() - nowDate.getTime(); // 期限までの残り時間を取得
// 期限が過ぎていない場合
if (diffDate > 0) {
setDeadLine(`${Math.floor(diffDate / 1000 / 60 / 60 / 24)}日後`);
// 期限が今日の場合
} else if (diffDate === 0) {
setDeadLine("今日");
// 期限が過ぎている場合
} else {
setDeadLine("期限切れ")
}
}
// useEffectを使用してコンポーネントのマウント後に関数handleCountDownを実行する
// useEffectの第2引数にprops.todoItem.deadLineを設定することでprops.todoItem.deadLineが
// 更新される度に関数handleCountDownを実行する
useEffect(handleCountDown, [props.todoItem.deadLine]);
/*
##############################
完了/未完了変更処理
##############################
*/
const handletoggleTodo = () => {
props.toggleTodoItemStatus(props.todoItem.id!, props.todoItem.done!);
// メニューを非表示にする
handleClose();
}
/*
##############################
TODO削除処理
##############################
*/
const handleDeleteTodo = () => {
// 実行確認ダイアログを表示する
const checkFlg = window.confirm("本当に削除してもよろしいですか");
// 「はい」が押された場合、削除処理を実行する
if (checkFlg) props.deleteTodoItem(props.todoItem.id!);
// メニューを非表示にする
handleClose();
}
/*
##############################
モーダル表示処理
##############################
*/
const handleChangeTodo = () => {
props.handleSetId(props.todoItem.id!); // 対象のTODOからIDを取得する
props.handleSetTitle(props.todoItem.title!); // 対象のTODOからタイトルを取得する
props.handleSetMemo(props.todoItem.memo!); // 対象のTODOからメモを取得する
props.handleSetCheckList(props.todoItem.checkList!); // 対象のTODOからチェックリストを取得する
props.handleSetDifficulty(props.todoItem.difficulty!); // 対象のTODOから難易度を取得する
props.handleSetPriority(props.todoItem.priority!); // 対象のTODOから重要度を取得する
props.handleSetDeadLine(props.todoItem.deadLine!); // 対象のTODOから期限を取得する
// 編集ボタンを表示するよう変更する
props.handleSetChangeFlg(true);
// モーダルを表示する
props.handleSetIsShowModal(true);
// メニューを非表示にする
handleClose();
}
/*
##############################
チェックボックス更新処理
##############################
*/
// 現在時点で関数handleChangeCheckは機能していない
const handleChangeCheck = () => {
};
return (
// TODOアイテム
<Box
className={TodoItemStyle.TodoItem}
onMouseEnter={() => setDisplayMenu(true)}
onMouseLeave={() => setDisplayMenu(false)}
>
{/* TODOチェックボックス */}
<Box className={TodoItemStyle.TodoItemLeft}>
{/* チェックボックスをクリックすることで関数handleToggleTodoItemStatusを実行する */}
{/* ボタンをクリックすることでTODOの状態(完了/未完了)を反転させる */}
<Checkbox
className={TodoItemStyle.checkbox}
onChange={handletoggleTodo}
/>
</Box>
{/* TODOの内容 */}
<Box className={TodoItemStyle.TodoItemRight}>
<p className={TodoItemStyle.todoItemTitle}>{props.todoItem.title}</p>
{displayMenu &&
<Box className={`fadeIn ${TodoItemStyle.menu}`}>
<IconButton onClick={(e: any) => setAnchorMenu(e.currentTarget)}>
<Tooltip title="オプション" placement="top">
<MoreVertRoundedIcon />
</Tooltip>
</IconButton>
<Menu
anchorEl={anchorMenu}
open={Boolean(anchorMenu)}
onClose={() =>setAnchorMenu(null)}
PaperProps={{ style: { width: '200px' }}}
>
{/* ボタンをクリックすることで関数handleDeleteTodoItemを実行する */}
{/* ボタンをクリックすることでTODOを削除する */}
<MenuItem onClick={handleDeleteTodo}>
<DeleteRoundedIcon />
削除
</MenuItem>
{/* ボタンをクリックすることで関数handleChangeTodoItemを実行する */}
{/* 関数handleChangeTodoItemが実行されるとモーダル画面が表示される */}
<MenuItem onClick={handleChangeTodo}>
<EditRoundedIcon />
編集
</MenuItem>
</Menu>
</Box>
}
<p className={TodoItemStyle.todoItemMemo}>{props.todoItem.memo}</p>
<ListItemButton className={TodoItemStyle.list} onClick={() => setAnchorList(!anchorList)}>
<FormatListBulletedRoundedIcon className={TodoItemStyle.listIcon} />
<ListItemText className={TodoItemStyle.listText} primary="チェックリスト" />
{anchorList ? <ExpandMore className={TodoItemStyle.listToggleIcon} /> : <ExpandLess className={TodoItemStyle.listToggleIcon} />}
</ListItemButton>
<Collapse in={anchorList}>
<List>
{/* map()を利用してcheckListの要素を1つひとつ取り出す */}
{/* map()メソッドを使用する対象の配列に「! = undifined」を設定するとエラー */}
{props.todoItem.checkList?.map((checkItem) => (
<FormGroup key={checkItem.id} style={{display: "block"}}>
<FormControlLabel
className={TodoItemStyle.checkItem}
control={
<Checkbox
value={checkItem.checkItem}
onChange={handleChangeCheck}
/>
}
label={checkItem.checkItem}
/>
</FormGroup>
))}
</List>
</Collapse>
<p className={TodoItemStyle.deadLine}>期限:
{/* 最初にタスクの完了/未完了を判定する、その後期限が過ぎていないか判定する */}
{/* 上記の条件をクリアした場合、期限までの時間を表示する */}
{props.todoItem.done ? "完了済み" : deadLine}
</p>
</Box>
{props.isShowModal &&
<TodoAddModal
id={props.id}
title={props.title}
memo={props.memo}
checkList={props.checkList}
priority={props.priority}
difficulty={props.difficulty}
deadLine={props.deadLine}
isShowModal={props.isShowModal}
changeFlg={props.changeFlg}
composing={props.composing}
handleSetId={props.handleSetId}
handleSetTitle={props.handleSetTitle}
handleSetMemo={props.handleSetMemo}
handleSetCheckList={props.handleSetCheckList}
handleSetDifficulty={props.handleSetDifficulty}
handleSetPriority={props.handleSetPriority}
handleSetDeadLine={props.handleSetDeadLine}
addTodoItem={props.addTodoItem}
toggleTodoItemStatus={props.toggleTodoItemStatus}
changeTodoItem={props.changeTodoItem}
deleteTodoItem={props.deleteTodoItem}
closeModal={props.closeModal}
startComposition={props.startComposition}
endComposition={props.endComposition}
reorder={props.reoreder}
/>
}
</Box>
);
}
export default TodoItem;
TodoItemStyle.module.css
TodoItemStyle.module.css
/* TODOアイテム全体 */
.TodoItem {
border-radius: 5px;
display: flex;
justify-content: space-between;
align-items: stretch;
}
/* TODOアイテム全体(ホバー時) */
.TodoItem:hover {
border: 1px solid #2665c0;
}
/* TODOアイテム左側(チェックボックス) */
.TodoItemLeft {
border-radius: 5px 0 0 5px;
width: 15%;
background-color: #50b5e9;
}
/* チェックボックス */
.checkbox svg {
width: 100%;
height: 100%;
}
/* TODOアイテム右側(内容) */
.TodoItemRight {
position: relative;
width: 85%;
padding: 10px;
}
/* メニューボタン */
.menu {
position: absolute;
top: 0;
right: 0;
}
/* TODOアイテムタイトル */
.todoItemTitle {
color: #7a787e;
font-size: 18px;
}
/* TODOアイテムメモ */
.todoItemMemo {
font-size: 12px;
color: #BBB9C1;
margin-top: 5%;
}
/* TODOアイテムチェックリスト */
.list {
padding: unset !important;
margin: 2.5% 0 !important;
}
/* TODOアイテムチェックリスト(要素) */
.listText {
flex: none !important;
}
/* TODOアイテムチェックリスト(テキスト) */
.listText span {
font-size: 14px;
color: #7a787e;
}
/* TODOチェックリストアイコン */
.listIcon {
width: 5% !important;
height: 5% !important;
padding-right: 2.5%;
}
/* TODOチェックリストトグルアイコン */
.listToggleIcon {
width: 7.5%;
height: 7.5%;
}
/* TODOチェックアイテム */
.checkItem {
margin: -2.5% 0;
font-size: 14px;
color: #7a787e;
}
/* TODOアイテム期限 */
.deadLine {
font-size: 14px;
color: #7a787e;
}
TodoTitle.tsx
Material UI適用前
TodoTitle.tsx
// TodoTitleコンポーネントを作成する
// 親コンポーネント(App)から受け取ったprops(as)の値により使用するタグを変更
// propsで渡される値の型定義を行う
type TodoTitleProps = {
title: string
as : string
}
const TodoTitle = (props: TodoTitleProps) => {
// asがh1ならば、タイトルはh1タグを使用
if (props.as === "h1") {
return <h1>{props.title}</h1>
// asがh2ならば、タイトルはh2タグを使用
} else if (props.as === "h2") {
return <h2>{props.title}</h2>
// それ以外ならば、タイトルはpタグを使用
} else {
return <p>{props.title}</p>
}
}
export default TodoTitle;
Material UI適用後
TodoTitle.tsx
// cssファイルをインポートする
import TodoTitleStyle from "../css/TodoTitleStyle.module.css"
// propsで渡される値の型定義を行う
type TodoTitleProps = {
title: string
as : string
}
const TodoTitle = (props: TodoTitleProps) => {
// asがh1ならば、タイトルはh1タグを使用
if (props.as === "h1") {
return <h1 className={TodoTitleStyle.title}>{props.title}</h1>
// asがh2ならば、タイトルはh2タグを使用
} else if (props.as === "h2") {
return <h2>{props.title}</h2>
// それ以外ならば、タイトルはpタグを使用
} else {
return <p>{props.title}</p>
}
}
export default TodoTitle;
TodoTitleStyle.module.css
TodoTitleStyle.module.css
/* タイトル */
.title {
font-size: 24px;
color: #34313a;
margin-bottom: 2.5%;
}