基礎から学ぶReact/React Hooks学習メモ 7 Chakra UIでアプリにデザインを組み込む

# 7 Chakra UIでアプリにデザインを組み込む

参考
[基礎から学ぶReact/React Hooks](https://www.amazon.co.jp/dp/B09CP8T4TX)

## Chakra UIとは？

- カスタマイズ可能なReact UIコンポーネント
- 定番コンポーネントが提供されている

### Default Theme

- Default Themeから継承や追加ができる

https://chakra-ui.com/docs/theming/theme

### Style Props

- コンポーネントにpropsを渡すだけでコンポーネントのスタイルを変更する

https://chakra-ui.com/docs/features/style-props


## ChakraUIのインストール

```sh
yarn add @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^4
```

- Chakra UI iconsのインストール

```sh
yarn add @chakra-ui/icons
```

### themeファイルの追加

```
src/
┣ apis/
┃ ┗ todos.js
┣ components/
┃ ┣ App.js
┃ ┣ TodoAdd.js
┃ ┣ TodoItem.js
┃ ┣ TodoList.js
┃ ┗ TodoTitle.js
┣ hooks/
┃ ┗ useTodo.js
┣ theme/       ← 追加
┃ ┗ theme.js   ← 追加
┗ index.js
```

- グローバルなテーマを設定する

```js:src\theme\theme.js
import { extendTheme } from "@chakra-ui/react";

const theme = extendTheme({
  styles: {
    // グローバルなテーマの上書き、追加
    global: {
      body: {
        // bodyに指定したいstyle
        backgroundColor: "orange.50",
        color: "gray.800",
      },
      p: {
        // mdを境に、PCとSP表示を切り替える
        fontSize: { base: "md", md: "lg" },
        // 文字列の行の間隔
        lineHeight: "tall",
      },
    },
  },
});

export default theme;
```

- Providerの指定をする
    - トップルートで`ChakraProvider`の設定が必要
    - `theme`の設定を渡す
- `ChakraProvider`には、resetCSSが含まれている。
    - resetCSSを使用しない場合、`ChakraProvider`に`resetCSS={false}`を追加する
- src\index.js

```js:src\index.js
import React from "react";
import ReactDOM from "react-dom";
import { ChakraProvider } from "@chakra-ui/react";
import theme from "./theme/theme";
import App from "./components/App";

const rootElement = document.getElementById("root");
ReactDOM.render(
  <React.StrictMode>
    <ChakraProvider theme={theme}>
      <App />
    </ChakraProvider>
  </React.StrictMode>,
  rootElement
);
```
- src\components\App.js
    - `Container` で全体を囲む
        - centerContent でセンタリング
        - p={{ base: "4", md: "6" }}
        - maxWidth="3xl"
    - `TodoTitle`
        - fontSize={{ base: "2xl", md: "3xl" }}
    - `TodoAdd`
    - `TodoList`
        - fontSize={{ base: "xl", md: "2xl" }}

```js:src\components\App.js
import React, { useRef } from "react";

import { Container } from "@chakra-ui/react";
import { AddIcon } from "@chakra-ui/icons";

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

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

  const inputEl = useRef(null);

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

  console.log("TODOリスト:", todoList);

  // 未完了リスト
  const incompletedList = todoList.filter((todo) => !todo.done);
  // 完了リスト
  const completedList = todoList.filter((todo) => todo.done);

  return (
    <>
      <Container centerContent p={{ base: "4", md: "6" }} maxWidth="3xl">
        <TodoTitle title="TODO進捗管理" as="h1" fontSize={{ base: "2xl", md: "3xl" }} />
        <TodoAdd placeholder="ADD TODO" leftIcon={<AddIcon />} buttonText="TODOを追加" inputEl={inputEl} handleAddTodoListItem={handleAddTodoListItem} />
        <TodoList
          todoList={incompletedList}
          toggleTodoListItemStatus={toggleTodoListItemStatus}
          deleteTodoListItem={deleteTodoListItem}
          title="未完了TODOリスト"
          as="h2"
          fontSize={{ base: "xl", md: "2xl" }}
        />
        <TodoList
          todoList={completedList}
          toggleTodoListItemStatus={toggleTodoListItemStatus}
          deleteTodoListItem={deleteTodoListItem}
          title="完了TODOリスト"
          as="h2"
          fontSize={{ base: "xl", md: "2xl" }}
        />
      </Container>
    </>
  );
}

export default App;
```

- src\components\TodoAdd.js
    - textareaを`Textarea`に変更
        - mt="8"
        - borderColor="gray.400"
    - buttonを`Button`に変更
        - colorScheme="blue"
        - leftIcon={leftIcon}
        - mt="8"

```js:src\components\TodoAdd.js
import { Textarea, Button } from "@chakra-ui/react";

export const TodoAdd = ({ placeholder, leftIcon, buttonText, inputEl, handleAddTodoListItem }) => {
  return (
    <>
      <Textarea placeholder={placeholder} mt="8" borderColor="gray.400" ref={inputEl} />
      <Button onClick={handleAddTodoListItem} colorScheme="blue" leftIcon={leftIcon} mt="8">
        {buttonText}
      </Button>
    </>
  );
};
```

- src\components\TodoItem.js
    - `ListItem`で全体を囲む
    - 文字列は、`Text`で囲む
    - `Flex`でボタンを囲む
    - buttonを`Button`に変更
    - buttonを`IconButton`に変更

```js:src\components\TodoItem.js
import { ListItem, Text, Flex, Button, IconButton } from "@chakra-ui/react";
import { DeleteIcon } from "@chakra-ui/icons";

// 1つのTodo、内容と移動・削除ボタン
export const TodoItem = ({ todo, toggleTodoListItemStatus, deleteTodoListItem }) => {
  // onClickイベントが発生したら、useTodoフックを呼び出す
  const handleToggleTodoListItemStatus = () => toggleTodoListItemStatus(todo.id, todo.done);
  const handleDeleteTodoListItem = () => deleteTodoListItem(todo.id);
  const label = todo.done ? "未完了リストへ" : "完了リストへ";
  const setColorScheme = todo.done ? "ping" : "blue";

  return (
    <ListItem borderWidth="1px" p="4" mt="4" bg="white" borderRadius="md" borderColor="gray.300">
      <Text mb="6">{todo.content}</Text>
      <Flex align="center" justify="flex-end">
        <Button colorScheme={setColorScheme} variant="outline" size="sm" onClick={handleToggleTodoListItemStatus}>
          {label}
        </Button>
        <IconButton icon={<DeleteIcon />} variant="unstyled" aria-label="delete" onClick={handleDeleteTodoListItem}>
          削除
        </IconButton>
      </Flex>
    </ListItem>
  );
};
```

- src\components\TodoList.js
    - `TodoTitle`
        - fontSize={fontSize}
        - mt="12"
    - ulを`List`に変更
        - w="full"

```js:src\components\TodoList.js
import { List } from "@chakra-ui/react";

import { TodoTitle } from "./TodoTitle";
import { TodoItem } from "./TodoItem";

// TodoItemをループして表示
// todoListが0件の場合、タイトルとTODOリストを表示しない
export const TodoList = ({ fontSize, todoList, toggleTodoListItemStatus, deleteTodoListItem, title, as }) => {
  return (
    <>
      {todoList.length !== 0 && (
        <>
          <TodoTitle title={title} as={as} fontSize={fontSize} mt="12" />
          <List w="full">
            {todoList.map((todo) => (
              <TodoItem todo={todo} key={todo.id} toggleTodoListItemStatus={toggleTodoListItemStatus} deleteTodoListItem={deleteTodoListItem} />
            ))}
          </List>
        </>
      )}
    </>
  );
};
```

- src\components\TodoTitle.js
    - h1を`Heading`に変更
    - mt={mt} 
    - as={as} 
    - fontSize={fontSize} 
    - w="full"

```js:src\components\TodoTitle.js
import React, { memo } from "react";
import { Heading } from "@chakra-ui/react";

// タイトルの表示コンポーネント
export const TodoTitle = memo(({ title, as, fontSize, mt }) => {
  return (
    <Heading mt={mt} as={as} fontSize={fontSize} w="full">
      {title}
    </Heading>
  );
});
```

## 完成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88686/3de89b6d-0012-96e1-755e-781a7714109a.png)