7 Chakra UIでアプリにデザインを組み込む
Chakra UIとは?
- カスタマイズ可能なReact UIコンポーネント
- 定番コンポーネントが提供されている
Default Theme
- Default Themeから継承や追加ができる
Style Props
- コンポーネントにpropsを渡すだけでコンポーネントのスタイルを変更する
ChakraUIのインストール
yarn add @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^4
- Chakra UI iconsのインストール
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
- グローバルなテーマを設定する
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}を追加する
 
- resetCSSを使用しない場合、
- src\index.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" }}
 
 
- 
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"
 
 
- textareaを
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に変更
 
- 
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"
 
 
- 
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"
 
- h1を
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>
  );
});
