📝

【MUI v5】メモしておきたいMenuの使い方

2023/01/10に公開

開発環境

詳しい開発環境はこちら
https://zenn.dev/koharu2739/articles/1a5f1a16f0cb55

はじめに

新人エンジニアのこはるです。
仕事では主にJavaを使っているSES社員です。

個人開発をするときに調べたMUIコンポーネントの使い方をメモする為に書きました。
なので、使い方の全ては網羅していません。ご了承ください。

Menuコンポーネント編です。

↓公式ドキュメント
https://mui.com/material-ui/api/menu/

勉強しながらコードを書いていっている途中なので、もし間違いとかがありましたら優しく指摘してください。

他のコンポーネントについてはこちら
https://zenn.dev/koharu2739/articles/1a5f1a16f0cb55

使い方早見

簡単なコードの使い方早見です。
今回はMenuコンポーネントのメモなので、他コンポーネントの記述は最小限です。

Menu.tsx
const Menu: FC = () => {
  // メニューの開閉を管理
  const [open, setOpen] = useState<boolean>(false);
  // メニューを配置するHTML要素を格納する
  const anchorEl = useRef<HTMLButtonElement>(null);
  // メニュー開閉ハンドル
  const handleClick = () => {
    setOpen(!open);
  };
  // メニューを閉めるハンドル
  const handleClose = () => {
    setOpen(false);
  };

  return (
    <>
      <Button
        // ボタンのHTML要素
        ref={anchorEl}
        // ボタンのタイプ
        variant="contained"
        // クリックアクション
        onClick={handleClick}
        // Style
        sx={{ m: 5 }}
      >
        ボタン
      </Button>
      <Menu
        // ここでボタンの位置にメニューを紐づける
        // この紐づけのお陰でメニューがボタンの隣に出現する
        // これが無いと画面の変なところでメニューが出現することになる
        anchorEl={anchorEl.current}
        // メニューの出現を管理
        open={open}
        // Falseだと、メニューを開いた時にメニューアイテムがフォーカスの対象になる
        disableAutoFocusItem={false}
        // Trueだとメニューが開いた時に一番上のメニューアイテムのオートフォーカスされる
        autoFocus={false}
        // 主にメニューを閉めたいときに発生するイベント
        onClose={handleClose}
        // Trueにすると、メニューが閉じている状態でもメニューのノードが存在するようになる
        keepMounted
        // メニューの開閉のアニメーション速度を設定できる
        transitionDuration={"auto"}
        // CSS in JS を記述できる(HTMLのstyle属性の役割を果たす)
        sx={{}}
        // 紐づけたHTML要素のどこを標準位置にしてメニューを配置するか設定できる
        anchorOrigin={{
          vertical: "bottom",
          horizontal: "right"
        }}
        // メニューの起点を設定できる。アニメーションもこの起点から生えるように出現する
        transformOrigin={{
          vertical: "top",
          horizontal: "right"
        }}
        // Menuコンポーネント内部で使用されているMenuListコンポーネントのPropsを変更できる
        MenuListProps={{}}
        // Menuコンポーネント内部で使用されているPaperコンポーネントのPropsを変更できる
        PaperProps={{
          // PaperProps.elevationはメニューのシャドーを調整できる(超重要!)
          elevation: 3
        }}
      >
        <MenuItem onClick={handleClose}>メニュー1</MenuItem>
        <MenuItem onClick={handleClose}>メニュー2</MenuItem>
        <MenuItem onClick={handleClose}>メニュー3</MenuItem>
      </Menu>
    </>
  );
};

CodeSandboxのサンプル

CodeSandboxにサンプルを作成しました。
挙動はこちらで確認してください。

解説

公式の使用例とは少し違う形です。
opensetOpenで開閉を制御しています。
こっちの方が自分としては分かりやすく、融通が利くと思っています。

ただこれは好みもあると思いますので、自身のやりやすいコードか、現場に合ったコードで書いていけばいいでしょう。

また、ひとつMenuの注意点として、
見落としやすいですがMenuはMenu APIで記載されているPropsの他にPopover APIとModal APIのPropsも使うことができます。


https://mui.com/material-ui/api/popover/
https://mui.com/material-ui/api/modal/



↓ここからPropsの解説に入ります。


anchorEl

ここに紐づけ用のHTML要素を指定すると、メニューがそのHTML要素を目印にポップするようになります。
これが無いと、メニューは画面の変なところからポップします。

早見では、MUIのButtonコンポーネントのref属性からuseRefでHTML要素を取得して、それをanchorElに指定しています。
これにより、メニューはButtonの隣にポップするようになります。



open

メニューの出現を管理します。

Trueで開く。
Falseで閉じる。

分かりやすいですね。

早見では、useStateopne変数で制御しています。



disableAutoFocusItem

Falseだと、メニューを開いた時にメニューアイテムがフォーカスの対象になります。

デフォルトはFalseです。

CodeSandboxのサンプルブラウザーをクリックした後に、Tabでボタンをフォーカス

Enterでメニューを開いてください

メニュー1が灰色にオートフォーカスされましたでしょうか?
Trueに設定すると、このオートフォーカスが起こらなくなります。

メニューが開いた状態でもう一度Tabを押下するとメニュー1にフォーカスされます。あくまでMenuの子要素(この場合はMenuItem)に対するオートフォーカスが無くなるだけで、Menuにはフォーカスされます

特別な理由が無い限り、ここはデフォルトのFalseで良いでしょう。



autoFocus

Trueだとメニューが開いた時に一番上のメニューアイテムにオートフォーカスされます。

デフォルトはTrueです。

これは前述のdisableAutoFocusItemに似ていて非常に分かりにくいのですが、

disableAutoFocusItemTrueに設定すると
Menuの子要素にはフォーカスされないが、Menu自体にはフォーカスが残る」
のに対し、

autoFocusFalseに設定すると
Menu自体にもフォーカスされない」
という挙動が起こります。

autoFocusFalseにすると「アクセシビリティに重大な影響を与える」とMUIの公式ドキュメントにも記載がありますので、こちらも特別な理由が無い限り、デフォルトのTrueにしてください。



onClose

主にメニューを閉めたいときに発生するイベントハンドラーです。

メニューのポップ中に

  • Escapeキーの押下
  • Tabキーの押下
  • メニュー以外の場所をクリック

のいづれかが発生したときにonCloseのイベントが発火します。

メニューを閉じる関数を指定するのが一般的でしょう。



keepMounted

Trueにすると、メニューが閉じている状態でもMenuのノードが存在するようになります。

デフォルトはFalseです。

Google ChromeのDevToolsを使ったら分かるのですが、Menuは閉じているときDOMにノードが存在しません。

keepMountedTrueにすることで、Menuが閉じている状態でも「見えないだけでノードは存在している」というふうにできます。

これはSEOの対策や、メニューのポップ速度を最大化したいときに使用できます。



transitionDuration

メニューの開閉のアニメーション速度を設定できます。

デフォルトはautoです。

速度の単位はms(ミリ秒)で、Number型で設定できます。
他にも、Object{ appear?: number, enter?: number, exit?: number }でそれぞれのメンバーを個別に設定することで、出現時、エンター時、終了時それぞれの速度を細かく設定できます。



sx

CSS in JS を記述できます。
役割はHTMLのstyle属性とほぼ同じです。
但し、型はObject型です。

詳しくは公式ドキュメントを参照
https://mui.com/system/getting-started/the-sx-prop/



anchorOrigin

紐づけたHTML要素のどこを標準位置にしてメニューを配置するか設定できます。

データ型は、

{
  horizontal: 'center' | 'left' | 'right' | number,
  vertical: 'bottom' | 'center' | 'top' | number
}

です。

早見では右下{vertical: "bottom",horizontal: "right"}に設定しているため、メニューはボタンの右下からポップします。



transformOrigin

メニューの起点を設定できます。
アニメーションもこの起点から生えるように出現します。

データ型は、

{
  horizontal: 'center' | 'left' | 'right' | number,
  vertical: 'bottom' | 'center' | 'top' | number
}

です。

早見では右上{vertical: "top",horizontal: "right"}に設定しているため、メニューは右上を起点にポップします。



Menu内部で使用されているMenuListのPropsを変更できます。

MenuListのPropsについては公式ドキュメントを参考にしてください。
https://mui.com/material-ui/api/menu-list/



PaperProps

Menu内部で使用されているPaperの属性を変更できます。

PaperProps.elevationを利用すると、メニューのシャドーを調整できます!
ここ重要です!

PaperProps={{
  elevation: 3
}}

PaperのPropsについては公式ドキュメントを参考にしてください。
https://mui.com/material-ui/api/paper/


カスタマイズしたメニュー

こちらでは、カスタマイズしたメニューを載せていきます。
普通のMenuとは違う機能を使いたいときに利用します。

身も蓋もありませんが、
ぶっちゃけ、私はMenuよりもこっちを基本的に使う事になると思っています。

何故かというと、デフォルトのMenuはユーザビリティで欠点を抱えていると私は考えているからです。

CodeSandboxのサンプルを確認していただきたいのですが、左側のボタンのメニューを開いた状態で、右側のボタンを押そうとすると直ぐに押せないですよね。

一度クリックでメニューを閉じないと、他の操作ができません。

これはマウス操作に慣れている人だと、恐らく2、3回はクリックを空振りすると思います。
(スマホでも、タップしても中々操作ができず数回タップして「おっ、やっと開いた」となるはずです)

こうした戸惑いやちょっとしたイラだちは、ユーザー体験(UX)的にあまりよろしくないでしょう。
これが、デフォルトのMenuの欠点です。

(これ、実はMenuだけでどうにかなるんでしょうか? できるなら恥ずかしいのですが……(/ω\))

カスタマイズのCodeSandboxのサンプル

この欠点を無くしたカスタマイズがこちらです。
メニューがポップしている状態でも、別の場所をクリックできるようになっています。

Menuは元々、PopperGrowPaperなどのMUIの別コンポーネントの組み合わせなので、それを自分でつくった感じになります。

コンポーネントの分割などちゃんと出来ていないので、このままだと使いずらいですが……
いずれもっと使いやすいように改良します。

まとめ

今回はMenuの使い方をメモしました。
他のコンポーネントについてはまた次回。
結構まとめるのに時間が掛かったので、ちょっとずつ追加していこうと思います。

それではまた(´・ω・`)ノシ

Discussion