Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

More than 3 years have passed since last update.

番外編:CSS フレームワークについて

Posted at

本編はこちらです。


本編では扱いませんでしたが、実開発ではデザインについても考えなければいけません。
世の中には Bootstrap をはじめ様々な CSS フレームワークがありますので、それらから選択するのが無難です。ちなみに React 開発においては、Material UI という CSS フレームワークが一番人気だそうです。
参考までに、本記事の構成に Material UI の AppBar コンポーネントを取り入れた例をご紹介しておきます。

Material UI のインストール

npm で開発環境にインストールします。
Material UI 本体の他に、アイコン集もインストールします。

npm install -D @material-ui/core @material-ui/icons 

App コンポーネントの実装

src/client/App.tsx ファイルを下記の内容に書き換えます。

import * as React from "react";
import { Switch, Route, Link } from "react-router-dom";
import AppBar from "@material-ui/core/AppBar";
import Toolbar from "@material-ui/core/Toolbar";
import Typography from "@material-ui/core/Typography";
import IconButton from "@material-ui/core/IconButton";
import MenuIcon from "@material-ui/icons/Menu";
import Drawer from "@material-ui/core/Drawer";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
import ListItemIcon from "@material-ui/core/ListItemIcon";
import ListItemText from "@material-ui/core/ListItemText";
import Container from "@material-ui/core/Container";
import HomeIcon from "@material-ui/icons/Home";
import HomeWorkIcon from "@material-ui/icons/HomeWork";

import "./favicon.ico";
import * as styles from "./App.styl";
import Home from "./Home";
import HomeWork from "./HomeWork";

const App = () => {
    const [drawerState, setDrawerState] = React.useState(false);
    const toggleDrawer = (state: boolean) => (event: any) => {
        if (event.type === "keydown" && (event.key === "Tab" || event.key === "Shift")) {
            return;
        }
        setDrawerState(state);
    };

    return (
        <>
            <AppBar position="static">
                <Toolbar>
                    <IconButton edge="start" className={styles.menuButton} color="inherit" aria-label="menu" onClick={toggleDrawer(true)}>
                        <MenuIcon />
                    </IconButton>
                    <Typography variant="h6" className={styles.title}>
                        Sample
                    </Typography>
                </Toolbar>
            </AppBar>
            <Drawer open={drawerState} role="presentation" className={styles.list} onClose={toggleDrawer(false)} onClick={toggleDrawer(false)} onKeyDown={toggleDrawer(false)}>
                <List>
                    <ListItem button key="home" component={Link} to="/">
                        <ListItemIcon><HomeIcon /></ListItemIcon>
                        <ListItemText primary="Home" />
                    </ListItem>
                    <ListItem button key="homework" component={Link} to="/homework">
                        <ListItemIcon><HomeWorkIcon /></ListItemIcon>
                        <ListItemText primary="Home Work" />
                    </ListItem>
                </List>
            </Drawer>
            <Container maxWidth="sm">
                <Switch>
                    <Route exact path="/" component={Home} />
                    <Route exact path="/homework" component={HomeWork} />
                </Switch>
            </Container>
        </>
    );
};

export default App;

src/client/App.styl ファイルを下記の内容に書き換えます。

$spacing = 8px
$basicBackgroundColor = #A0A0FF
$basicForegroundColor = #0000A0

$heading
    color: $basicForegroundColor


:global(body)
    background-color: $basicBackgroundColor

.root
    flex-grow: 1

.menuButton
    margin-right: $spacing * 2

.title
    flex-grow: 1

.list
    width: 250

実行

実行すると見慣れたバーが追加されています。
image.png

メニューボタンを押すとメニューが開きます。
image.png

テスト

src/client/App.spec.tsx ファイルを下記の内容に書き換えます。

import * as React from "react";
import { MemoryRouter } from "react-router-dom";
import { render, cleanup, fireEvent } from "@testing-library/react";
import "@testing-library/jest-dom/extend-expect";

const homeMock = jest.fn(() => <></>);
jest.mock("./Home", () => ({ __esModule: true, default: homeMock }));

const homeworkMock = jest.fn(() => <></>);
jest.mock("./HomeWork", () => ({ __esModule: true, default: homeworkMock }));

import App from "./App";

afterEach(cleanup);
afterEach(jest.clearAllMocks);
afterAll(() => {
    jest.unmock("./Home");
    jest.unmock("./HomeWork");
});

describe("App", () => {
    it("最初に Home を表示すること", () => {
        render(<MemoryRouter><App /></MemoryRouter>);
        expect(homeMock).toBeCalled();
    });

    it("メニューを開けること", () => {
        const root = render(<MemoryRouter><App /></MemoryRouter>);

        const menuButton = root.getByLabelText("menu");
        fireEvent.click(menuButton);

        expect(root.getByRole("presentation")).not.toBeNull();
    });

    it("メニューをクリックするとメニューが閉じること", () => {
        const root = render(<MemoryRouter><App /></MemoryRouter>);

        const menuButton = root.getByLabelText("menu");
        fireEvent.click(menuButton);

        const menu = root.getByRole("presentation");
        fireEvent.click(menu);

        expect(() => root.getByRole("presentation")).toThrow();
    });

    it("キーを押下するとメニューが閉じること", () => {
        const root = render(<MemoryRouter><App /></MemoryRouter>);

        const menuButton = root.getByLabelText("menu");
        fireEvent.click(menuButton);

        const menu = root.getByRole("presentation");
        fireEvent.keyDown(menu, { key: "a", code: 65 });

        expect(() => root.getByRole("presentation")).toThrow();
    });

    it.each([
        ["Tab", 9],
        ["Shift", 16]
    ])("一部のキーを押下してもメニューが閉じないこと [%s, %d]", (key, code) => {
        const root = render(<MemoryRouter><App /></MemoryRouter>);

        const menuButton = root.getByLabelText("menu");
        fireEvent.click(menuButton);

        const menu = root.getByRole("presentation");
        fireEvent.keyDown(menu, { key, code });

        expect(root.getByRole("presentation")).not.toBeNull();
    });

    it("メニューから Home を表示できること", () => {
        const root = render(<MemoryRouter><App /></MemoryRouter>);
        expect(homeMock).toBeCalled();
        homeMock.mockClear();

        const menuButton = root.getByLabelText("menu");
        fireEvent.click(menuButton);

        const menuHomeButton = root.getByText("Home", { exact: true });
        fireEvent.click(menuHomeButton);

        expect(() => root.getByRole("presentation")).toThrow();
        expect(homeMock).toBeCalled();
    });

    it("メニューから Home Work を表示できること", () => {
        const root = render(<MemoryRouter><App /></MemoryRouter>);
        expect(homeworkMock).not.toBeCalled();

        const menuButton = root.getByLabelText("menu");
        fireEvent.click(menuButton);

        const menuHomeWorkButton = root.getByText("Home Work", { exact: true });
        fireEvent.click(menuHomeWorkButton);

        expect(() => root.getByRole("presentation")).toThrow();
        expect(homeworkMock).toBeCalled();
    });
});

いい感じです。
image.png

0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?