前回までの記事 アプリ開発を楽しむ【#1:アプリの概要】 アプリ開発を楽しむ【#2:環境構築1(React+TypeScript)】 アプリ開発を楽しむ【#3:環境構築2 (ESLint+Prettier)】

今回は、ヘッダーをつくっていきたいと思います。 完成イメージ スクリーンショット 2022-08-18 1.17.46.png

togo_appのコンテナを起動していない場合は起動させておいてください。

コンテナを起動
docker compose up -d

コンテナ内に入る
docker compose exec frontend bash

ローカルサーバーを起動
npm run dev

1.必要のない(使わない)ファイルを削除する

まず、必要のないファイルを削除しておきます。 frontend/app/src/App.css frontend/app/assets/react.svg frontend/app/public/vite.svg

2.ファビコンの変更など

①frontend/app/src/index.cssに書かれているcssはすべて削除します。 (ファイル自体は残しておいてください。)

②frontend/app/src/App.tsxをとりあえず次のようにしておきます。(エラーになってしまうので。。。)

const App = () => <div>ToGo App</div>;

export default App;

③frontend/app/public/favicon.svgをつくって次のコードをコピペしてください。 ファビコン(ブラウザのタブに表示されるアイコン)を変更しています。

<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="#fff" stroke-width="2">
  <path stroke-linecap="round" stroke-linejoin="round" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
  <path stroke-linecap="round" stroke-linejoin="round" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>

④frontend/app/index.htmlを次のように編集します。ここを変更することで、ブラウザのタブに表示されるアイコンと文字(タイトル)が変わります。

<!-- ここから変更 -->
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<!-- ここまで変更 -->
 
<!-- ここから変更 -->
<title>ToGo App</title>
<!-- ここまで変更 -->

スクリーンショット 2022-08-17 23.39.44.png

3.ヘッダーのコンポーネントをつくる

ヘッダーは、Material UIのApp barコンポーネントを利用します。 https://mui.com/material-ui/react-app-bar/

Headerコンポーネントは、frontend/app/components/layout/Header.tsxにつくります。 変更しているところは、コメントを入れてあるところになります。

import * as React from 'react';
import {
  AppBar,
  Box,
  Button,
  Container,
  IconButton,
  Link,
  Toolbar,
  Typography,
  Menu,
  MenuItem,
} from '@mui/material';
// ここから変更
import { Menu as MenuIcon, LocationOn as LocationOnIcon } from '@mui/icons-material';
// ここまで変更

// ここから変更
const pages = ['MyList', 'Posts', 'Map'];
const settings = ['Account', 'Logout'];
// ここまで変更

const Header = () => {
  const [anchorElNav, setAnchorElNav] = React.useState<null | HTMLElement>(null);
  const [anchorElUser, setAnchorElUser] = React.useState<null | HTMLElement>(null);

  const handleOpenNavMenu = (event: React.MouseEvent<HTMLElement>) => {
    setAnchorElNav(event.currentTarget);
  };

  const handleOpenUserMenu = (event: React.MouseEvent<HTMLElement>) => {
    setAnchorElUser(event.currentTarget);
  };

  const handleCloseNavMenu = () => {
    setAnchorElNav(null);
  };

  const handleCloseUserMenu = () => {
    setAnchorElUser(null);
  };

  return (
    <AppBar position="absolute">
      <Container maxWidth="xl">
        <Toolbar disableGutters>
          // ここから変更(アイコンを変更)
          <LocationOnIcon sx={{ display: { xs: 'none', md: 'flex' }, mr: 1 }} />
          // ここまで変更
          <Typography
            variant="h6"
            noWrap
            component="a"
            href="/"
            sx={{
              mr: 2,
              display: { xs: 'none', md: 'flex' },
              fontFamily: 'monospace',
              fontWeight: 700,
              letterSpacing: '.1rem',
              color: 'inherit',
              textDecoration: 'none',
            }}
          >
          // ここから変更
            TOGO LIST
          // ここまで変更
          </Typography>

          <Box sx={{ flexGrow: 1, display: { xs: 'flex', md: 'none' } }}>
            <IconButton
              size="large"
              aria-label="account of current user"
              aria-controls="menu-appbar"
              aria-haspopup="true"
              onClick={handleOpenNavMenu}
              color="inherit"
            >
              <MenuIcon />
            </IconButton>
            <Menu
              id="menu-appbar"
              anchorEl={anchorElNav}
              anchorOrigin={{
                vertical: 'bottom',
                horizontal: 'left',
              }}
              keepMounted
              transformOrigin={{
                vertical: 'top',
                horizontal: 'left',
              }}
              open={Boolean(anchorElNav)}
              onClose={handleCloseNavMenu}
              sx={{
                display: { xs: 'block', md: 'none' },
              }}
            >
              {pages.map((page) => (
                <MenuItem key={page}>
                  <Typography textAlign="center">{page}</Typography>
                </MenuItem>
              ))}
            </Menu>
          </Box>
          // ここから変更(アイコンを変更)
          <LocationOnIcon sx={{ display: { xs: 'flex', md: 'none' }, mr: 1 }} />
          // ここまで変更
          <Typography
            variant="h5"
            noWrap
            component="a"
            href=""
            sx={{
              mr: 2,
              display: { xs: 'flex', md: 'none' },
              flexGrow: 1,
              fontFamily: 'monospace',
              fontWeight: 700,
              letterSpacing: '.1rem',
              color: 'inherit',
              textDecoration: 'none',
            }}
          >
          // ここから変更
            TOGO LIST
          // ここまで変更
          </Typography>
          <Box sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }}>
            {pages.map((page) => (
              <Button key={page} sx={{ my: 2, color: 'white', fontSize: '15px', display: 'block' }}>
                {page}
              </Button>
            ))}
          </Box>

          <Box sx={{ flexGrow: 0 }}>
          // ここから変更
            <Link color="inherit" underline="none" href="/login">
              Login
            </Link>
          // ここまで変更
            <Menu
              sx={{ mt: '45px' }}
              id="menu-appbar"
              anchorEl={anchorElUser}
              anchorOrigin={{
                vertical: 'top',
                horizontal: 'right',
              }}
              keepMounted
              transformOrigin={{
                vertical: 'top',
                horizontal: 'right',
              }}
              open={Boolean(anchorElUser)}
              onClose={handleCloseUserMenu}
            >
              {settings.map((setting) => (
                <MenuItem key={setting} onClick={handleCloseUserMenu}>
                  <Typography textAlign="center">{setting}</Typography>
                </MenuItem>
              ))}
            </Menu>
          </Box>
        </Toolbar>
      </Container>
    </AppBar>
  );
};

export default Header;

4.Headerコンポーネントを表示させる

frontend/app/src/App.tsxを次のように編集します。

先程(2の②)はとりあえず、次のようにしておきましたが、全て修正します。

const App = () => <div>ToGo App</div>;

export default App;

修正後は、次のとおりとなります。

import { Box, CssBaseline, Container } from '@mui/material';
import Header from './components/layout/Header';

const App = () => (
  <Box sx={{ display: 'flex' }}>
    <CssBaseline />
    <Header />
    <Box
      component="main"
      sx={{
        backgroundColor: (theme) =>
          theme.palette.mode === 'light' ? theme.palette.grey[100] : theme.palette.grey[900],
        flexGrow: 1,
        height: '100vh',
        overflow: 'auto',
        pt: { xs: 9, sm: 11, md: 12 },
      }}
    >
      <Container maxWidth="lg">ToGo App</Container>
    </Box>
  </Box>
);

export default App;

先程つくったHeaderコンポーネントを読み込んで表示させています。 Box、CssBaseline、Container(Material UI)を使って、デザインを調整しています。

今回はここまでです。次回は、ToGoリストのコンポーネントをつくっていきたいと思います。 スクリーンショット 2022-08-18 11.39.32.png

コードはGitHubに置いてありますのでよければ参考にしてください。 mainブランチは常に最新のものになります。 今回の内容はblog_4のブランチを参照してください。 https://github.com/KINE-M/togo_app