React-admin向けにリッチなUIを実現できる実行確認画面を作ってみた

# 背景

Reactのフロントエンドフレームワークである[React-admin](https://marmelab.com/react-admin/)を使って、アプリケーションを開発中。

何らかのアクションを実行するときに`本当に実行してもよろしいですか？`と確認画面を出すことは多い。
React-adminの標準コンポーネント`<Confirm>`を使用すると、下の図のように

* タイトル文字列
* 本文（確認メッセージ文字列）
* [実行]ボタン
* [キャンセル]ボタン

を表示することができる。

しかし、実際にはこの実行確認画面に、実行対象の詳細情報などを表示した上で、実行確認するのがより親切。
ということで、**本文の部分に文字列ではなく、任意のReactコンポーネント（エレメント）を表示できるような`実行確認画面`を実装してみた**。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/562193/8173f830-d3df-09a8-6093-9ce12eda7a08.png)

# つくったもの

* ra-custom-confirm
    * npm: https://www.npmjs.com/package/ra-custom-confirm
    * GitHub: https://github.com/itTkm/ra-custom-confirm

![ra-custom-confirm.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/562193/79f860e1-4452-6ab9-fa72-2e0d19e2fac8.gif)

表示できるのは、

* 任意のタイトル文字列
* 任意のReactコンポーネント（エレメント）
* [実行]ボタン
* [キャンセル]ボタン

また、[実行]ボタンや[キャンセル]ボタンの、アイコンやラベルも自由に設定可能にしてある。

# 使い方

## インストール

```bash
# npmの場合
npm install --save ra-custom-confirm

# yarnの場合
yarn add ra-custom-confirm
```

## デモ

下記のコマンドを実行することで、デモを確認できる。
コマンド実行後、ブラウザで [http://localhost:8080/](http://localhost:8080/) にアクセス。
ログインを求められた場合は *login/password* を使う。

```bash
git clone https://github.com/itTkm/ra-custom-confirm.git
cd ra-custom-confirm/example/
yarn install
yarn start
```

## 実装例

```js
import React, { Fragment, useState } from 'react';
import Share from '@material-ui/icons/Share';
import ErrorOutline from '@material-ui/icons/ErrorOutline';
import CustomConfirm from 'ra-custom-confirm';

// Define your custom title of confirm dialog
const CustomConfirmTitle = 'Are you sure you want to share?';

// Define your custom contents of confirm dialog
const CustomConfirmContent = props => {
  return (
    <SimpleShowLayout {...props} >
      <TextField source='title' label='title' />
      <TextField source='user' label='user' />
      <TextField source='date' label='date' />
      <RichTextField source='description' label='description' />
    </SimpleShowLayout>
  );
};

const ShareButton = props => {
  const [open, setOpen] = useState(false);

  const handleClick = (event) => {
    event.stopPropagation();            // support with rowClick on Datagrid
    setOpen(true);
  };

  const handleDialogClose = () => setOpen(false);

  const handleConfirm = () => {
    // do something here
    setOpen(false);
  };

  return (
    <Fragment>
      <Button label="Share" onClick={handleClick}><Share /></Button>
      <CustomConfirm {...props}
        isOpen={open}
        title={CustomConfirmTitle}      // your custom title of confirm dialog
        content={CustomConfirmContent}  // your custom contents of confirm dialog
        confirm='Share'                 // label of confirm button (default: 'Confirm')
        confirmColor='primary'          // color of confirm button ('primary' or 'warning', default: 'primary')
        ConfirmIcon={Share}             // icon of confirm button from @material-ui/icons (default: 'CheckCircle')
        cancel='Cancel'                 // label of cancel button (default: 'Cancel')
        CancelIcon={ErrorOutline}       // icon of cancel button from @material-ui/icons (default: 'ErrorOutline')
        onConfirm={handleConfirm}
        onClose={handleDialogClose}
      />
    </Fragment>
  );
}

const PostList = props => {
  const translate = useTranslate();
  return (
    <List {...props} >
      <Datagrid>
        <TextField source='title' label='title' />
        <TextField source='date' label='date' />
        <TextField source='user' label='user' />
        <ShareButton />
      </Datagrid>
    </List>
  );
};

export default PostList;
```

## props

Name|Type|Description|Default
---|---|---|---
title|string|your custom title of confirm dialog|
content|element|your custom contents of confirm|
isOpen|bool|dialog open or not|false
onClose|func|function on close|
onConfirm|func|function on confirm|
confirm|string|label of confirm button|'ra.action.confirm' (`Confirm` in English)
confirmColor|string|color of confirm button ('primary' or 'warning')|'primary'
ConfirmIcon|element|icon of confirm button from [@material-ui/icons](https://www.npmjs.com/package/@material-ui/icons)|![CheckCircle](https://github.com/google/material-design-icons/blob/master/action/drawable-hdpi/ic_check_circle_black_18dp.png?raw=true "import CheckCircle from '@material-ui/icons/CheckCircle';")
cancel|string|label of cancel button|'ra.action.cancel' (`Cancel` in English)
CancelIcon|element|icon of cancel button from [@material-ui/icons](https://www.npmjs.com/package/@material-ui/icons)|![ErrorOutline](https://github.com/google/material-design-icons/blob/master/alert/drawable-hdpi/ic_error_outline_black_18dp.png?raw=true "import ErrorOutline from '@material-ui/icons/ErrorOutline';")