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>
);
});