# Modal 模态框

## 基本用法

```jsx
import { useRef, useReducer } from 'react';
import { Modal, List, Button, Select } from 'zarm';

const initState = {
  normal: {
    visible: false,
  },
  hasFooter: {
    visible: false,
  },
  closable: {
    visible: false,
  },
  onlyBody: {
    visible: false,
  },
  animation: {
    visible: false,
    animationType: 'fade',
  },
  customContainer: {
    visible: false,
  },
  overlength: {
    visible: false,
  },
};

const reducer = (state, action) => {
  const { type, key, animationType } = action;

  switch (type) {
    case 'visible':
      return {
        ...state,
        [key]: {
          ...state[key],
          visible: !state[key].visible,
        },
      };

    case 'animation':
      return {
        ...state,
        [key]: {
          ...state[key],
          animationType,
        },
      };

    default:
  }
};

const Demo = () => {
  const myRef = useRef();
  const [state, dispatch] = useReducer(reducer, initState);

  const toggle = (key) => dispatch({ type: 'visible', key });

  return (
    <>
      <List>
        <List.Item
          title="普通"
          suffix={
            <Button size="xs" onClick={() => toggle('normal')}>
              开启
            </Button>
          }
        />
        <List.Item
          title="自定义底部"
          suffix={
            <Button size="xs" onClick={() => toggle('hasFooter')}>
              开启
            </Button>
          }
        />
        <List.Item
          title="遮罩层可关闭"
          suffix={
            <Button size="xs" onClick={() => toggle('closable')}>
              开启
            </Button>
          }
        />
        <List.Item
          title="无头部，无底部"
          suffix={
            <Button size="xs" onClick={() => toggle('onlyBody')}>
              开启
            </Button>
          }
        />
        <List.Item
          title="动画效果"
          suffix={
            <Button size="xs" onClick={() => toggle('animation')}>
              开启
            </Button>
          }
        >
          <Select
            value={state.animation.animationType}
            dataSource={[
              { value: 'fade', label: '淡出淡入效果' },
              { value: 'zoom', label: '缩放效果' },
              { value: 'rotate', label: '旋转效果' },
              { value: 'door', label: '开关门效果' },
              { value: 'flip', label: '翻转效果' },
              { value: 'move-up', label: '向上移入效果' },
              { value: 'move-down', label: '向下移入效果' },
              { value: 'move-left', label: '向左移入效果' },
              { value: 'move-right', label: '向右移入效果' },
              { value: 'slide-up', label: '向上滑入效果' },
              { value: 'slide-down', label: '向下滑入效果' },
              { value: 'slide-left', label: '向左滑入效果' },
              { value: 'slide-right', label: '向右滑入效果' },
            ]}
            itemRender={(data) => data && `${data.label}（${data.value}）`}
            displayRender={(selected) => selected.map((item) => item && item.label)}
            onConfirm={(selected) => {
              dispatch({
                type: 'animation',
                key: 'animation',
                animationType: selected[0],
              });
            }}
          />
        </List.Item>
        <List.Item
          suffix={
            <Button size="xs" onClick={() => toggle('customContainer')}>
              开启
            </Button>
          }
        >
          挂载到指定 DOM 节点
        </List.Item>
        <List.Item
          suffix={
            <Button size="xs" onClick={() => toggle('overlength')}>
              开启
            </Button>
          }
        >
          超长内容
        </List.Item>
      </List>

      <div id="test-div" style={{ position: 'relative', zIndex: 1 }} ref={myRef} />

      <Modal visible={state.normal.visible} title="标题" closable onClose={() => toggle('normal')}>
        模态框内容
      </Modal>

      <Modal
        title="标题"
        visible={state.hasFooter.visible}
        footer={
          <Button block shape="rect" theme="primary" onClick={() => toggle('hasFooter')}>
            确定
          </Button>
        }
      >
        模态框内容
      </Modal>

      <Modal
        visible={state.closable.visible}
        title="标题"
        maskClosable
        onClose={() => toggle('closable')}
      >
        点击遮罩层关闭
      </Modal>

      <Modal visible={state.onlyBody.visible} maskClosable onClose={() => toggle('onlyBody')}>
        无头部，无底部
      </Modal>

      <Modal
        visible={state.animation.visible}
        animationType={state.animation.animationType}
        maskClosable
        onClose={() => toggle('animation')}
      >
        <div style={{ height: 100 }}>
          当前使用的动画类型animationType：'{state.animation.animationType}'
        </div>
      </Modal>

      <Modal
        visible={state.customContainer.visible}
        maskClosable
        onClose={() => toggle('customContainer')}
        mountContainer={() => myRef.current}
      >
        挂载到指定dom节点
      </Modal>

      <Modal
        visible={state.overlength.visible}
        title="标题"
        closable
        onClose={() => toggle('overlength')}
        maskClosable
      >
        {Array.from(Array(100).fill(0)).map((_, index) => (
          <div key={index}>
            模态框内容
            <br />
          </div>
        ))}
      </Modal>
    </>
  );
};

ReactDOM.render(<Demo />, mountNode);
```

## 带操作按钮

```jsx
import { useState } from 'react';
import { Modal, List, Button } from 'zarm';

const Demo = () => {
  const [visible, setVisible] = useState(false);
  const toggle = () => setVisible(!visible);

  return (
    <>
      <List>
        <List.Item
          title="自定义操作按钮"
          suffix={
            <Button size="xs" onClick={toggle}>
              开启
            </Button>
          }
        />
      </List>

      <Modal
        visible={visible}
        title="标题"
        actions={[
          {
            key: 'online',
            text: '在线阅读',
            theme: 'default',
          },
          {
            key: 'download',
            text: '下载文件',
            theme: 'default',
            disabled: true,
          },
          [
            {
              key: 'cancel',
              text: '取消',
            },
            {
              key: 'delete',
              text: '删除',
              bold: true,
              theme: 'danger',
            },
          ],
        ]}
        onAction={async (action) => {
          switch (action.key) {
            case 'cancel':
              toggle();
              break;
            default:
              // 模拟异步操作
              await new Promise((resolve) => setTimeout(resolve, 3000));
              toggle();
          }
          console.log(action);
        }}
      >
        模态框内容
      </Modal>
    </>
  );
};

ReactDOM.render(<Demo />, mountNode);
```

## 警告框 Alert

```jsx
import { List, Button, Modal, Toast } from 'zarm';

const Demo = () => {
  return (
    <List>
      <List.Item
        title="静态方法关闭"
        suffix={
          <Button
            size="xs"
            onClick={() => {
              Modal.alert({
                className: 'test',
                title: '警告框标题',
                content: '这里是警告框的内容部分',
                onConfirm: () => {
                  console.log('点击确认');
                },
              });
            }}
          >
            开启
          </Button>
        }
      />
      <List.Item
        title="使用 Promise 关闭"
        suffix={
          <Button
            size="xs"
            onClick={() => {
              Modal.alert({
                title: '警告框标题',
                content: '这里是警告框的内容部分，点击关闭按钮，将触发 Promise 关闭警告框',
                onConfirm: async () => {
                  await new Promise((resolve) => setTimeout(resolve, 3000));
                  Toast.show({ content: '提交成功' });
                },
              });
            }}
          >
            开启
          </Button>
        }
      />
    </List>
  );
};

ReactDOM.render(<Demo />, mountNode);
```

## 确认框 Confirm

```jsx
import { List, Button, Modal, Toast } from 'zarm';

const Demo = () => {
  return (
    <List>
      <List.Item
        title="静态方法关闭"
        suffix={
          <Button
            size="xs"
            onClick={() => {
              Modal.confirm({
                title: '确认信息',
                content: '这里是确认框的内容部分',
                onCancel: () => {
                  console.log('点击cancel');
                },
                onConfirm: () => {
                  console.log('点击ok');
                },
              });
            }}
          >
            开启
          </Button>
        }
      />
      <List.Item
        title="使用 Promise 关闭"
        suffix={
          <Button
            size="xs"
            onClick={() => {
              Modal.confirm({
                title: '确定要删除吗？',
                content: '这里是确认框的内容部分，点击确定按钮，将触发 Promise 关闭确认框',
                onConfirm: async () => {
                  await new Promise((resolve) => setTimeout(resolve, 3000));
                  Toast.show({ content: '提交成功' });
                },
              });
            }}
          >
            开启
          </Button>
        }
      />
    </List>
  );
};

ReactDOM.render(<Demo />, mountNode);
```

## API

| 属性              | 类型                                                                 | 默认值              | 说明                                                                                                                                                              |
| :---------------- | :------------------------------------------------------------------- | :------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| shape             | string                                                               | 'radius'            | 形状，可选值 `rect`、`radius`                                                                                                                                     |
| visible           | boolean                                                              | false               | 是否显示                                                                                                                                                          |
| animationType     | string                                                               | 'fade'              | 动画效果，可选值 `fade`, `door`, `flip`, `rotate`, `zoom`,`move-up`, `move-down`, `move-left`, `move-right`,`slide-up`, `slide-down`, `slide-left`, `slide-right` |
| animationDuration | number                                                               | 200                 | 动画执行时间（单位：毫秒）                                                                                                                                        |
| width             | string &#124; number                                                 | '70%'               | 宽度                                                                                                                                                              |
| mask              | boolean                                                              | true                | 是否展示遮罩层                                                                                                                                                    |
| maskType          | string                                                               | 'normal'            | 遮罩层的类型，可选值 `transparent`, `normal`                                                                                                                      |
| maskClosable      | boolean                                                              | false               | 是否点击遮罩层时关闭，需要和 onCancel 一起使用                                                                                                                    |
| closable          | boolean                                                              | false               | 右上角是否显示关闭按钮，需要和 onCancel 一起使用                                                                                                                  |
| onClose           | () => void                                                           | -                   | maskClosable 或 closable 为 true 时，点击遮罩或者右上角关闭按钮触发的函数                                                                                         |
| title             | ReactNode                                                            | -                   | 标题                                                                                                                                                              |
| footer            | ReactNode                                                            | -                   | 弹窗底部内容                                                                                                                                                      |
| actions           | (ModalActionProps \| ModalActionProps[])[]                           | []                  | 操作按钮配置                                                                                                                                                      |
| onAction          | (action: ModalActionProps, index: number) => void \| Promise\<void\> | -                   | 点击操作按钮后触发的函数                                                                                                                                          |
| destroy           | boolean                                                              | true                | 弹层关闭后是否移除节点                                                                                                                                            |
| afterOpen         | () => void                                                           | -                   | 模态框打开后的回调                                                                                                                                                |
| afterClose        | () => void                                                           | -                   | 模态框关闭后的回调                                                                                                                                                |
| mountContainer    | MountContainer                                                       | () => document.body | 指定 Modal 挂载的 HTML 节点                                                                                                                                       |

### ModalActionProps 操作按钮属性

| 属性     | 类型       | 默认值    | 说明                                            |
| :------- | :--------- | :-------- | :---------------------------------------------- |
| text     | ReactNode  | -         | 按钮文字                                        |
| theme    | string     | 'primary' | 按钮主题，可选值 `default`、`primary`、`danger` |
| disabled | boolean    | false     | 按钮是否禁用                                    |
| bold     | boolean    | false     | 是否加粗                                        |
| onClick  | () => void | -         | 按钮点击后触发的回调函数                        |

## 指令式 API

```tsx
// 显示警告框，不传 onCancel 也可关闭，如需做更多操作，参考下方 Confirm 的例子
const alert = Modal.alert({
  title: '警告框标题',
  content: '这里是警告框的内容部分',
});

// 显示确认框，若关闭时需要 Promise，onConfirm、onCancel 均支持 Promise
const confirm = Modal.confirm({
  title: '确认框标题',
  content: '这里是确认框的内容部分，点击确定按钮，将触发 Promise 关闭确认框',
  onConfirm: () => {
    return fetch.get('xxx.api').then((res) => {
      if(res.code === 0) {
        return true; // 关闭弹窗
      } else {
        return false; // 阻止弹窗关闭
      }
    }).catch(...);
  }
});

```

| 属性        | 类型       | 默认值                        | 说明                                        |
| :---------- | :--------- | :---------------------------- | :------------------------------------------ |
| title       | ReactNode  | -                             | 弹出框的标题                                |
| content     | ReactNode  | -                             | 弹出框的内容                                |
| cancelText  | ReactNode  | '关闭'(Alert)/'取消'(Confirm) | 取消按钮的内容                              |
| confirmText | ReactNode  | '确定'                        | 确定按钮的内容                              |
| onConfirm   | () => void | -                             | 使用 confirm 方法时，点击“确定”后的回调函数 |
| onCancel    | () => void | -                             | 点击“关闭/取消”后的回调函数                 |

## CSS 变量

| 属性                       | 默认值                              | 说明                       |
| :------------------------- | :---------------------------------- | :------------------------- |
| --background               | 'rgb(242, 242, 242)'                | 背景色                     |
| --border-radius            | '14px'                              | 圆角大小                   |
| --shadow                   | '0 7px 21px var(--za-color-shadow)' | 阴影样式                   |
| --title-font-size          | '17px'                              | 标题字体大小               |
| --title-font-weight        | 500                                 | 标题字体粗细               |
| --title-text-color         | 'var(--za-color-text)'              | 标题字体颜色               |
| --close-size               | '20px'                              | 关闭图标字体大小           |
| --close-color              | '#ccc'                              | 关闭图标颜色               |
| --close-active-color       | '#999'                              | 关闭图标激活状态颜色       |
| --body-font-size           | '13px'                              | 内容字体大小               |
| --body-text-color          | 'var(--za-color-text)'              | 内容字体颜色               |
| --body-padding             | '16px'                              | 内容内边距                 |
| --button-background        | 'transparent'                       | 操作按钮背景               |
| --button-height            | '44px'                              | 操作按钮高度               |
| --button-font-size         | '17px'                              | 操作按钮字体大小           |
| --button-font-weight       | 500                                 | 操作按钮字体粗细           |
| --button-text-color        | 'var(--za-theme-primary)'           | 操作按钮字体颜色           |
| --button-active-background | 'var(--za-background-active)'       | 操作按钮选中背景           |
| --button-disabled-opacity  | 'var(--za-opacity-disabled)'        | 操作按钮禁用状态时的透明度 |
