Chapter 06

第5章 ページのレイアウト

てべすてん
てべすてん
2022.03.11に更新

この本で作るツールは1ページのみです。
そのページはApp.tsxに記述していきます。

ページのレイアウトを考えてみましょう。

ドロワーやメニューはとりあえず置いておいて、編集画面を作っていきましょう。

編集画面は大きく

  • ヘッダー
  • 編集パネル
  • サイドバー

の3つのパーツに分かれています。

これら3つはAppコンポーネントの子コンポーネントなのでプロジェクトルート/src/components/App/に配置していきます。

それぞれ

パーツ ファイル名
ヘッダー Header.tsx
編集パネル BuildPanel.tsx
サイドバー Sidebar.tsx

と名付けることにします。

グローバルCSS

画面いっぱいに表示させるためにグローバルCSSが必要です。

今回はこちらで用意したCSSを使ってください。

プロジェクトルート/src/App.css
html,
body,
#root{
    width: 100%;
    height: 100%;
    padding: 0;
    margin: 0;
}

(あとでこのファイルをApp.tsxから読み込みます)

Header.tsx

ヘッダーは以下の通りです。

プロジェクトルート/src/components/App/Header.tsx
import AppBar, { AppBarProps } from "@mui/material/AppBar";
import IconButton from "@mui/material/IconButton/IconButton";
import Toolbar from "@mui/material/Toolbar/Toolbar";
import Typography from "@mui/material/Typography/Typography";
import MenuIcon from "@mui/icons-material/Menu" ;
import { FC } from "react";

export type HeaderProps = {} & AppBarProps;

const Header: FC<HeaderProps> = (props) => {
    return (
        <AppBar position="static">
            <Toolbar>
                <IconButton
                    size="large"
                    edge="start"
                    color="inherit"
                    sx={{ mr: 2 }}
                >
                    <MenuIcon />
                </IconButton>
                <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
                    タイトル未設定
                </Typography>
            </Toolbar>
        </AppBar>
    );
};
export default Header;

いきなり見慣れないコンポーネントがたくさん出てきましたがあまり気にする必要はありません。ちょっといい感じのデザインにするためのコンポーネントだと思ってください。

なお、このコードは公式ドキュメントをちょっといじっただけです。こちらを参照してください。

https://mui.com/components/app-bar/

BuildPane.tsx

編集パネルは今現在特に表示させたいものがないので空のdivタグを置いておきます。後でこの中に記号やフローを追加していきます。

プロジェクトルート/src/components/App/Header.tsx
import {FC} from "react" ; 

export interface BuildPanelProps{
}

const BuildPanel :FC<BuildPanelProps> = ({})=>{
  return (
    <div>
      this is build panel
    </div>
  )
} 
export default BuildPanel ;

Sidebar.tsx

サイドバーも今は表示したいものがないので空のdivタグを置いておきましょう。

プロジェクトルート/src/components/App/Header.tsx
import {FC} from "react" ; 

export interface SidebarProps{
}

const Sidebar :FC<SidebarProps> = ({})=>{
  return (
    <div>
      this is sidebar
    </div>
  )
} 
export default Sidebar ;

App.tsxで読み込む

これまで作った

  • App.css
  • Header.tsx
  • BuildPanel.tsx
  • Sidebar.tsx

を読み込んでみましょう。

プロジェクトルート/src/App.tsx(未完成)
import BuildPanel from 'components/App/BuildPanel';
import Header from 'components/App/Header';
import Sidebar from 'components/App/Sidebar';
import { FC } from 'react';

import "./App.css" ;

interface AppProps{}
const App :FC<AppProps> = ()=>{
  return (
    <div>
      <Header />
      <BuildPanel />
      <Sidebar />
    </div>
  ) ;
};

export default App;

Appコンポーネントのdivタグに何もスタイルがかかっていないため、このままではヘッダー・編集パネル・サイドバーが縦並びに表示されてしまいます。ヘッダーはそのままでも大丈夫ですが、編集パネルとサイドバーは横並びになってほしいので別途CSSをあてなければなりません。

そんなときに便利なのがMUIのGridコンポーネントです。

Gridコンポーネントは<Grid container><Grid item>の2つの使い方があります。

container の中に item を配置します。

Gridのイメージ

<Grid container>
  <Grid item> 1 </Grid>
  <Grid item> 2 </Grid>
</Grid>

また各itemにはxs propsを指定でき、その設定値によって様々な挙動をします。
xs propsを使うことでitemの横幅を指定することができます。

xs に数値をした場合

<Grid item xs={} >と指定した場合、そのitemはcontainerの横幅を12としたときの設定値分の割合の大きさを指定したことになります。

例えば<Grid item xs={6} >は 12分の6 = 半分の大きさなので親要素のちょうど半分の大きさになります。

xs を指定した場合

<Grid item xs>と指定した場合、そのitemは隙間を埋めるようになるべく大きくなります

例えば以下のような場合、

<Grid container>
  <Grid item xs={2}> item-1 </Grid>
  <Grid item xs> item-2 </Grid>
</Grid>

item-1はcontainerの12分の2、item-2はcontainerの12分の10になります。

xs に"auto"を指定した場合

<Grid item xs="auto"> と指定した場合、そのitemは親要素に影響されず、子要素の内容に合わせて自動的に決まります。(おそらく内部的にwidth: auto;が指定されているのでしょう)

これを使って横幅の大きさを指定することでいい感じのレイアウトを組むことができます。

ここまで理解できた方は是非ご自身でGridを使ったレイアウトを考えてみてください。

ここには一例を示しておきます。

プロジェクトルート/src/App.tsx(完成)
import Grid from '@mui/material/Grid';
import BuildPanel from 'components/App/BuildPanel';
import Header from 'components/App/Header';
import Sidebar from 'components/App/Sidebar';
import { FC } from 'react';

import "./App.css" ;


interface AppProps{}
const App :FC<AppProps> = ()=>{
  return (
    <Grid container>
      <Grid item xs={12}>
        <Header />
      </Grid>
      <Grid item xs>
        <BuildPanel />
      </Grid>
      <Grid item xs="auto">
        <Sidebar />
      </Grid>
    </Grid>
  ) ;
};

export default App;

見た目を整える

最後に見た目を整えるために、BuildPanelとSidebarをMUIのCardコンポーネントで囲いましょう。

プロジェクトルート/src/App.tsx(完成)

import Card from '@mui/material/Card'; // 追加
import CardContent from '@mui/material/CardContent'; // 追加
import Grid from '@mui/material/Grid';
import BuildPanel from 'components/App/BuildPanel';
import Header from 'components/App/Header';
import Sidebar from 'components/App/Sidebar';
import { FC } from 'react';

import "./App.css" ;


interface AppProps{}
const App :FC<AppProps> = ()=>{
  return (
    <Grid container>
      <Grid item xs={12}>
        <Header />
      </Grid>
      <Grid item xs>
        <Card> {/* 追加 */}
          <CardContent> {/* 追加 */}
            <BuildPanel />
          </CardContent> {/* 追加 */}
        </Card> {/* 追加 */}
      </Grid>
      <Grid item xs="auto">
        <Card> {/* 追加 */}
          <CardContent> {/* 追加 */}
           <Sidebar />
          </CardContent> {/* 追加 */}
        </Card> {/* 追加 */}
      </Grid>
    </Grid>
  ) ;
};

export default App;


ようやくレイアウトが完成しました。

ここで各ファイルの内容を確認しておきたい方は以下をご利用ください。

プロジェクトルート/src/components/App/BuildPane.tsx
import {FC} from "react" ; 


export interface BuildPanelProps{
}

const BuildPanel :FC<BuildPanelProps> = ({})=>{
  return (
    <div>
      this is build panel
    </div>
  )
} 
export default BuildPanel ;
プロジェクトルート/src/components/App/Header.tsx
import AppBar, { AppBarProps } from "@mui/material/AppBar";
import IconButton from "@mui/material/IconButton/IconButton";
import Toolbar from "@mui/material/Toolbar/Toolbar";
import Typography from "@mui/material/Typography/Typography";
import MenuIcon from "@mui/icons-material/Menu" ;
import { FC } from "react";

export type HeaderProps = {} & AppBarProps;

const Header: FC<HeaderProps> = (props) => {
    return (
        <AppBar position="static">
            <Toolbar>
                <IconButton
                    size="large"
                    edge="start"
                    color="inherit"
                    sx={{ mr: 2 }}
                >
                    <MenuIcon />
                </IconButton>
                <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
                    タイトル未設定
                </Typography>
            </Toolbar>
        </AppBar>
    );
};
export default Header;
プロジェクトルート/src/components/App/Sidebar.tsx
import {FC} from "react" ; 


export interface SidebarProps{
}

const Sidebar :FC<SidebarProps> = ({})=>{
  return (
    <div>
      this is sidebar
    </div>
  )
} 
export default Sidebar ;
プロジェクトルート/src/components/util/Button.tsx
import { FC } from "react";
import MUIButton, { ButtonProps as MUIButtonProps } from "@mui/material/Button";

export type ButtonProps = {
} & MUIButtonProps;

const Button: FC<ButtonProps> = (props) => {
    const {
        children,
        ...muiProps
    } = props;
    return (
        <MUIButton variant="outlined" {...muiProps}>
            {children}
        </MUIButton>
    );
};
export default Button;
プロジェクトルート/src/components/util/Checkbox.tsx
import { FC } from "react";
import MUICheckbox, { CheckboxProps as MUICheckboxProps } from "@mui/material/Checkbox";

export type CheckboxProps = {
} & MUICheckboxProps;

const Checkbox: FC<CheckboxProps> = (props) => {
    const {
        ...muiProps
    } = props;
    return (
        <MUICheckbox {...muiProps}>
        </MUICheckbox>
    );
};
export default Checkbox;
プロジェクトルート/src/components/util/TextField.tsx
import { FC } from "react";
import MUITextField, { TextFieldProps as MUITextFieldProps } from "@mui/material/TextField";

export type TextFieldProps = {
} & MUITextFieldProps;

const TextField: FC<TextFieldProps> = (props) => {
    const {
        ...muiProps
    } = props;
    return (
        <MUITextField {...muiProps}>
        </MUITextField>
    );
};
export default TextField;
プロジェクトルート/src/components/App.css
html,
body,
#root{
    width: 100%;
    height: 100%;
    padding: 0;
    margin: 0;
}
プロジェクトルート/src/components/App.tsx
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import Grid from '@mui/material/Grid';
import BuildPanel from 'components/App/BuildPanel';
import Header from 'components/App/Header';
import Sidebar from 'components/App/Sidebar';
import { FC } from 'react';

import "./App.css" ;


interface AppProps{}
const App :FC<AppProps> = ()=>{
  return (
    <Grid container>
      <Grid item xs={12}>
        <Header />
      </Grid>
      <Grid item xs>
        <Card>
          <CardContent>
            <BuildPanel />
          </CardContent>
        </Card>
      </Grid>
      <Grid item xs="auto">
        <Card>
          <CardContent>
           <Sidebar />
          </CardContent>
        </Card>
      </Grid>
    </Grid>
  ) ;
};

export default App;
プロジェクトルート/src/components/index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

質問・指摘などはこちらから

https://zenn.dev/tbsten/scraps/7123b1257c2097