MUI のコンポーネントを組み合わせて作成した、ダッシュボードを紹介します。サンプルアプリケーションは下記よりダウンロード可能です。
利用している主な MUI コンポーネント
- AppBar - 画面上部のタイトルバー
- Drawer - 画面左側の縦型メニュー
- Grid - レスポンシブレイアウトのためのコンテナー
- Card - 画面中央上部に配置しているカード
- DataGrid - 画面下部のデータテーブル
アプリケーションの動作
画面幅が広い場合:
画面幅が狭い場合:
実装コード
利用するコンポーネントは App.tsx と MyGrid.tsx の二つになっています。
App.tsx
import * as React from "react"; import { styled, useTheme, Theme, CSSObject } from "@mui/material/styles"; import { Paper, Box, CssBaseline, Toolbar, IconButton, Typography, Divider, List, ListItemButton, ListItemIcon, ListItemText, Grid, Card, CardContent, CardActions, Button, } from "@mui/material"; import MuiDrawer from "@mui/material/Drawer"; import MuiAppBar, { AppBarProps as MuiAppBarProps } from "@mui/material/AppBar"; import MenuIcon from "@mui/icons-material/Menu"; import ChevronLeftIcon from "@mui/icons-material/ChevronLeft"; import ChevronRightIcon from "@mui/icons-material/ChevronRight"; import DashboardIcon from "@mui/icons-material/Dashboard"; import ShoppingCartIcon from "@mui/icons-material/ShoppingCart"; import PeopleIcon from "@mui/icons-material/People"; import { MyGrid } from "./components/MyGrid"; const drawerWidth = 240; const openedMixin = (theme: Theme): CSSObject => ({ width: drawerWidth, transition: theme.transitions.create("width", { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.enteringScreen, }), overflowX: "hidden", }); const closedMixin = (theme: Theme): CSSObject => ({ transition: theme.transitions.create("width", { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.leavingScreen, }), overflowX: "hidden", width: `calc(${theme.spacing(7)} + 1px)`, [theme.breakpoints.up("sm")]: { width: `calc(${theme.spacing(8)} + 1px)`, }, }); const DrawerHeader = styled("div")(({ theme }) => ({ display: "flex", alignItems: "center", justifyContent: "flex-end", padding: theme.spacing(0, 1), // necessary for content to be below app bar ...theme.mixins.toolbar, })); interface AppBarProps extends MuiAppBarProps { open?: boolean; } const AppBar = styled(MuiAppBar, { shouldForwardProp: (prop) => prop !== "open", })<AppBarProps>(({ theme, open }) => ({ zIndex: theme.zIndex.drawer + 1, transition: theme.transitions.create(["width", "margin"], { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.leavingScreen, }), ...(open && { marginLeft: drawerWidth, width: `calc(100% - ${drawerWidth}px)`, transition: theme.transitions.create(["width", "margin"], { easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.enteringScreen, }), }), })); const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== "open", })(({ theme, open }) => ({ width: drawerWidth, flexShrink: 0, whiteSpace: "nowrap", boxSizing: "border-box", ...(open && { ...openedMixin(theme), "& .MuiDrawer-paper": openedMixin(theme), }), ...(!open && { ...closedMixin(theme), "& .MuiDrawer-paper": closedMixin(theme), }), })); const Item = styled(Paper)(({ theme }) => ({ backgroundColor: theme.palette.mode === "dark" ? "#1A2027" : "#fff", ...theme.typography.body2, padding: theme.spacing(1), textAlign: "center", color: theme.palette.text.secondary, })); export default function App() { const theme = useTheme(); const [open, setOpen] = React.useState(false); const handleToggleDrawer = () => { setOpen(!open); }; return ( <Box sx={{ display: "flex" }}> <CssBaseline /> <AppBar position="fixed" open={open}> <Toolbar> <IconButton color="inherit" aria-label="open drawer" onClick={handleToggleDrawer} edge="start" sx={{ marginRight: 5, ...(open && { display: "none" }), }} > <MenuIcon /> </IconButton> <Typography variant="h6" noWrap component="div"> MUI: Dashboard Demo </Typography> </Toolbar> </AppBar> <Drawer variant="permanent" open={open}> <DrawerHeader> <IconButton onClick={handleToggleDrawer}> {theme.direction === "rtl" ? ( <ChevronRightIcon /> ) : ( <ChevronLeftIcon /> )} </IconButton> </DrawerHeader> <Divider /> <List> <ListItemButton key="dashboard" sx={{ minHeight: 48, justifyContent: open ? "initial" : "center", px: 2.5, }} > <ListItemIcon sx={{ minWidth: 0, mr: open ? 3 : "auto", justifyContent: "center", }} > <DashboardIcon /> </ListItemIcon> <ListItemText primary="Dashboard" sx={{ opacity: open ? 1 : 0 }} /> </ListItemButton> <ListItemButton key="orders" sx={{ minHeight: 48, justifyContent: open ? "initial" : "center", px: 2.5, }} > <ListItemIcon sx={{ minWidth: 0, mr: open ? 3 : "auto", justifyContent: "center", }} > <ShoppingCartIcon /> </ListItemIcon> <ListItemText primary="Orders" sx={{ opacity: open ? 1 : 0 }} /> </ListItemButton> <ListItemButton key="customers" sx={{ minHeight: 48, justifyContent: open ? "initial" : "center", px: 2.5, }} > <ListItemIcon sx={{ minWidth: 0, mr: open ? 3 : "auto", justifyContent: "center", }} > <PeopleIcon /> </ListItemIcon> <ListItemText primary="Customers" sx={{ opacity: open ? 1 : 0 }} /> </ListItemButton> </List> </Drawer> <Box component="main" sx={{ flexGrow: 1, p: 3 }}> <DrawerHeader /> <Grid container spacing={2}> <Grid item xs={12} md={8}> <Card sx={{ minWidth: 275 }}> <CardContent> <Typography variant="h5" color="primary.main"> 売上高 </Typography> <Typography variant="h2" component="div"> 57,857,400 円 </Typography> </CardContent> <CardActions> <Button size="small">詳細</Button> </CardActions> </Card> </Grid> <Grid item xs={12} md={4}> <Card sx={{ minWidth: 275 }}> <CardContent> <Typography variant="h5" color="primary.main"> 未対応件数 </Typography> <Typography variant="h2" component="div"> 12 件 </Typography> </CardContent> <CardActions> <Button size="small">詳細</Button> </CardActions> </Card> </Grid> <Grid item xs={12}> <Item> <div style={{ height: 400, width: "100%" }}> <MyGrid></MyGrid> </div> </Item> </Grid> </Grid> </Box> </Box> ); }
MyGrid.tsx
import React from "react"; import { DataGrid, GridColDef, GridValueGetterParams } from "@mui/x-data-grid"; const columns: GridColDef[] = [ { field: "id", headerName: "ID", width: 120 }, { field: "firstName", headerName: "First name", width: 200, editable: true, }, { field: "lastName", headerName: "Last name", width: 200, editable: true, }, { field: "age", headerName: "Age", type: "number", width: 110, editable: true, }, { field: "fullName", headerName: "Full name", description: "This column has a value getter and is not sortable.", sortable: false, width: 250, valueGetter: (params: GridValueGetterParams) => `${params.row.firstName || ""} ${params.row.lastName || ""}`, }, ]; const rows = [ { id: 1, lastName: "Snow", firstName: "Jon", age: 35 }, { id: 2, lastName: "Lannister", firstName: "Cersei", age: 42 }, { id: 3, lastName: "Lannister", firstName: "Jaime", age: 45 }, { id: 4, lastName: "Stark", firstName: "Arya", age: 16 }, { id: 5, lastName: "Targaryen", firstName: "Daenerys", age: null }, { id: 6, lastName: "Melisandre", firstName: null, age: 150 }, { id: 7, lastName: "Clifford", firstName: "Ferrara", age: 44 }, { id: 8, lastName: "Frances", firstName: "Rossini", age: 36 }, { id: 9, lastName: "Roxie", firstName: "Harvey", age: 65 }, ]; export const MyGrid = () => { return ( <DataGrid rows={rows} columns={columns} pageSize={5} rowsPerPageOptions={[5]} checkboxSelection disableSelectionOnClick /> ); };
サンプルのダウンロード
サンプルアプリケーションのダウンロードはこちらからどうぞ。