【material-ui】「要素hover→別要素pop」を簡単に記述するコンポーネント

#環境

- React.js
- TypeScript
- material-ui

#目的
お気持ち: 
- シンプルなwebサイトにしたいが、詳細も載せたい。
- 別リンクに飛ぶ詳細は面倒。
- **要素にhoverした時に詳細がpopしてほしい**。
- ↑を可能にする[Popper(material-ui)](https://material-ui.com/components/popper/)はコード量が30行くらいと多い。
- pop付要素を手軽に実装できるようにラップしよう。

#概要
[Popper(material-ui)](https://material-ui.com/components/popper/)をラップし、手軽な実装を実現。

##作成したコンポーネント`AddPopper`
**AddPopperの子要素にhoverすると、props.popに渡したReact要素が表示される。**

- 使用例 
![Videotogif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/470036/80f771dc-a0e7-b06a-f613-3e6002b8c516.gif)



Webサイトに表示した、`カプースチンのソナタファンタジーいいよね`という文字列の「カプースチン」と「ソナタファンタジー」の部分にhoverした時、補足説明が現れる(popする)ようにする。

```react:app.tsx
function App() {
  return (
    <div className="App">
      <AddPopper
        //捕捉説明①
        pop={<div>作曲家: Nicolai Kapustin(1937-2020, ウクライナ)</div>}
      >
        {/*このspanにhoverすると捕捉説明①が表示される。*/}
        <span>カプースチン</span>
      </AddPopper>
      の
      {/*補足説明②*/}
      <AddPopper pop={<div>Op.39 4楽章構成</div>}>
        {/*このspanにhoverすると捕捉説明②が表示される。*/}
        <span>ソナタファンタジー</span>
      </AddPopper>
      いいよね。
    </div>
  );
}

```

- propsの定義

```typescript 
interface Props {
  pop: React.ReactElement<any> //hover時に表示する要素
  type: 'span' | 'div' //被hover要素がinlineかどうかを指定
  children: React.ReactElement<any> //この要素にhoverすると発火
  placement?: PopperPlacementType; //popの位置を決定
}
```

#`AddPopper`のソースコード

```react
import { Fade, PopperPlacementType } from "@material-ui/core";
import { Popper } from "@material-ui/core";
import React, { useCallback, useState } from "react";

interface Props {
  pop: React.ReactElement<any>;
  children: React.ReactElement<any>;
  placement?: PopperPlacementType;
  type?: "span" | "div";
}

export function AddPopper(props: Props) {
  //被hover要素の位置を受け取る。
  const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);

  //hover時に発火
  const openPop = useCallback((event: React.MouseEvent<HTMLElement>) => {
    setAnchorEl(event.currentTarget);
  }, []);

  //hoverが外れた時に発火
  const closePop = useCallback(() => {
    setAnchorEl(null);
  }, []);

  //hoverの有無をbooleanで持つ
  const open = Boolean(anchorEl);

  //被hover要素に、カーソル設定とhover時背景色設定を追加している。
  const Base = () =>
    React.cloneElement(props.children, {
      style: {
        cursor: "default",
        backgroundColor: open ? "lightgrey" : undefined,
      },
    });

  //被hover要素とPop要素にマウスイベントを付加
  return React.createElement(
    props.type ?? "div",
    { onMouseEnter: openPop, onMouseLeave: closePop },
    [
      <Base key="base" />,
      <Popper
        key="popper"
        open={open}
        anchorEl={anchorEl}
        transition
        placement={props.placement ?? "bottom-start"}
      >
        {({ TransitionProps }) => (
          <Fade {...TransitionProps} timeout={350}>
            {props.pop}
          </Fade>
        )}
      </Popper>,
    ]
  );
}
```

#ソースコードの説明
- 基本的には[Popper(material-ui)](https://material-ui.com/components/popper/)の書き方に倣って書いている。
- props.childrenとして受け取った要素(AddPopperの子要素)には、hover時のカーソルと、hover時の背景色が自動的に指定される。
- 被hover要素が、inlineの時はspanを、blockの時はdiv、という使い分けのために、React.CreateElementによってtype(タグ名)をpropsに応じて指定可能にしている。
- props.placementでpopの位置を調整。defaultは`bottom-start`となっている。参照->[PopperPlacementTypeの定義](https://material-ui.com/api/popper/)