# Overlay 弹出层

基础弹出层，支持 `Portal`

```js
import { Overlay } from 'amos-framework';
```

## 案例演示

## 基本用法

---demo
```js
import { Overlay, Button, CardPane } from 'amos-framework';

class Demo extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      visible: false,
      masked: true,
    }
  }

  toggleOverlay = (masked) => {
    this.setState({ visible: !this.state.visible, masked });
  }

  onClose = (masked) => {
    this.setState({ visible: false });
  }

  render() {
    return (
      <div>
        <Button onClick={() => this.toggleOverlay(true)}>点击弹出浮层</Button>
        <Button onClick={() => this.toggleOverlay(false)}>弹出内容没有遮罩层</Button>
        <Overlay
          masked={this.state.masked}
          visible={this.state.visible}
          onClose={this.onClose}
          destroyContent={false}
        >
          <CardPane noBorder={!this.state.masked} style={{ width: 500 }}>
            <h3 style={{margin: 0}}>基础弹出层</h3>
            <div>
              这是一个基础的弹出层组件，其它弹出层组件基于它来扩展比如 Trigger
            </div>
            <br />
            <Button color="red" onClick={() => this.toggleOverlay(false)}>关闭</Button>
          </CardPane>
        </Overlay>
      </div>
    )
  }
}

ReactDOM.render(<Demo />, _react_runner_);
```
---demoend

## 修改 className props 属性

---demo
```js
import { Overlay, Button, CardPane } from 'amos-framework';

let idx = 0;
let olChildIdx = 0;

class Demo extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      visible: false,
      masked: true,
      olCls: '',
      olChildCls: ''
    }
  }

  toggleOverlay = (masked) => {
    this.setState({ visible: !this.state.visible, masked });
  }

  onClose = (masked) => {
    this.setState({ visible: false });
  }

  changeCls = () => {
    idx += 1;
    this.setState({
      olCls: `ol-cls-${idx}`
    });
  }
  changeCls = () => {
    olChildIdx += 1;
    this.setState({
      olChildCls: `ol-child-cls-${olChildIdx}`
    });
  }

  render() {
    const { olCls, olChildCls } = this.state;
    return (
      <div>
        <Button onClick={() => this.toggleOverlay(true)}>点击弹出浮层</Button>
        <Button onClick={() => this.toggleOverlay(false)}>弹出内容没有遮罩层</Button>
        <Overlay
          masked={this.state.masked}
          visible={this.state.visible}
          onClose={this.onClose}
          className={olCls}
        >
          <CardPane className={olChildCls} noBorder={!this.state.masked} style={{ width: 500 }}>
            <h3 style={{margin: 0}}>基础弹出层</h3>
            <div>
              <Button onClick={this.changeCls}>overlay cls</Button>
              <Button onClick={this.changeChildCls}>overlay child cls</Button>
              这是一个基础的弹出层组件，其它弹出层组件基于它来扩展比如 Trigger
            </div>
            <br />
            <Button color="red" onClick={() => this.toggleOverlay(false)}>关闭</Button>
          </CardPane>
        </Overlay>
      </div>
    )
  }
}

ReactDOM.render(<Demo />, _react_runner_);
```
---demoend

### 完全自定义的弹出内容

---demo
```js
import { Overlay, Icon, Button } from 'amos-framework';

class Demo extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      visible: false,
      masked: true,
    }
  }

  toggleOverlay = (masked) => {
    this.setState({ visible: !this.state.visible, masked });
  }

  onClose = (masked) => {
    this.setState({ visible: false });
  }

  render() {
    return (
      <div>
        <Button onClick={() => this.toggleOverlay(true)}>点击弹出浮层</Button>
        <Overlay
          maskProps={{
            style: { backgroundColor: 'rgba(0, 0, 0, 0.5)' }
          }}
          masked={this.state.masked}
          visible={this.state.visible} onClose={this.onClose}
        >
          <div style={{ backgroundColor: '#fff', minWidth: 500 }} >
            <Icon
              onClick={this.onClose}
              icon="cross"
              style={{
                position: 'absolute',
                right: 0,
                top: '-20px',
                color: '#fff',
                cursor: 'pointer',
              }}
            />
            <div
              style={{
                backgroundColor: '#4e6fff',
                color: 'rgb(255, 255, 255)',
                textAlign: 'center',
                padding: '34px 24px',
              }}
            >
              <h1
                style={{
                  fontSize: '28px',
                  fontWeight: '700',
                  color: 'white',
                  lineHeight: '1.2',
                  margin: '0px',
                }}
              >
                完全自定义的弹出内容
              </h1>
              <div style={{ padding: '5px 0' }}>小标题，说明性文字</div>
              <div style={{ fontSize: '18px' }}>展示特殊信息： <b style={{ color: '#af0000', margin: '0 4px' }}>信息</b></div>
            </div>
            <div style={{ padding: '24px' }}>
              <h1 style={{ fontSize: '28px', fontWeight: '700', color: 'rgb(23, 27, 30)', lineHeight: '1.2', margin: '0px 0px 4px' }}>底部标题</h1>
              <div style={{ color: 'rgb(70, 81, 94)' }}>底部标题说明性文字</div>
            </div>
          </div>
        </Overlay>
      </div>
    )
  }
}
ReactDOM.render(<Demo />, _react_runner_);
```
---demoend

### 使用 Portal 模式

[`Portals`](https://reactjs.org/docs/portals.html#event-bubbling-through-portals) 是 react 16 官方解决方案，组件可以脱离父组件层级，从而挂载在 DOM 树的任意位置。`Overlay` 默认挂载至节点，通过将 `usePortal` 设置为 `false` 将 `Overlay` 挂载在父组件所在层级的 DOM 树中。

---demo
```js
import { Overlay, Button, CardPane } from 'amos-framework';

class Demo extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      visible: false,
    }
  }

  toggleOverlay = (e) => {
    this.setState({ visible: !this.state.visible });
  }

  render() {
    return (
      <div>
        <Button onClick={this.toggleOverlay}>点击弹出浮层</Button>
        <Overlay usePortal={false} visible={this.state.visible} onClose={this.toggleOverlay}>
          <CardPane style={{ width: 500 }}>
            <h3 style={{marginTop: 0}}>基础弹出层</h3>
            <p>`Overlay` 默认挂载至节点，通过将 `usePortal` 设置为 `false` 将 `Overlay` 挂载在父组件所在层级的 DOM 树中</p>
            <Button color="red" onClick={this.toggleOverlay}>关闭</Button>
          </CardPane>
        </Overlay>
      </div>
    );
  }
}
ReactDOM.render(<Demo />, _react_runner_);
```
---demoend

### 自定义动画

动画过渡效果采用 [`react-transition-group`](https://reactcommunity.org/react-transition-group/) 组件。动画时长参数 `transitionDuration={1000}` 对应 `react-transition-group` 中的 `timeout`。

---demo
```js
import { Overlay, Button, CardPane } from 'amos-framework';

class Demo extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      visible: false,
    }
  }
  toggleOverlay = (e) => {
    this.setState({ visible: !this.state.visible });
  }
  render() {
    return (
    <div>
      <Button onClick={this.toggleOverlay}>点击弹出浮层</Button>
      <Overlay
        transitionName="fade"
        transitionDuration={300}
        visible={this.state.visible}
        onClose={this.toggleOverlay}
      >
        <CardPane style={{ width: 500 }}>
          <h3 style={{marginTop: 0}}>基础弹出层</h3>
          <p>自定义动画</p>
          <Button color="red" onClick={this.toggleOverlay}>关闭</Button>
        </CardPane>
      </Overlay>
    </div>
    )
  }
}
ReactDOM.render(<Demo />, _react_runner_);
```
---demoend

```scss
@import '~amos-framework/lib/styles/mixins/overlayanim.scss';

@include overlay-animate-kf(fade, amosFade);

@keyframes amosFadeIn {
  0% { opacity: 0; }

  100% { opacity: 1; }
}

@keyframes amosFadeOut {
  0% { opacity: 1; }

  100% { opacity: 0; }
}
```

> 附录

自定义动画中，可以引入 `keyframes` 类型动画，如 [**`animate.css`**](https://animate.style/)

### Overlay props

| params | type | default | description |
|--------- |-------- |--------- |-------- |
| prefixCls | String | `amos-overlay` | 默认样式前缀 |
| className | String | - | 自定义样式 |
| visible | boolean | `false` | 对话框是否可见 |
| usePortal | boolean | `true` | 使用 react 16 提供的官方解决方案 [`Portals`](https://reactjs.org/docs/portals.html#event-bubbling-through-portals)，将模态对话框生成到 `body` 节点。 |
| maskClosable | boolean | `true` | 点击遮罩层是否允许关闭 |
| portalProps | object | `{}` | 设置 [`Portal`](#/framework/portal) 组件属性 |
| maskProps | object | `{}` | 遮罩层组件 props，为 `Html` 自身属性 |
| dialogProps | object | - | 弹出目标(对话框) props，为 `HTML` 自身属性 |
| destroyContent | boolean | `true` | 退出动画时是否卸载组件， 同 `CSSTransition#unmountOnExit` |
| masked | boolean | `true` | 是否有遮罩，同时向 `<body>` 添加样式 `.amos-overlay-open` 防止滚动条出现 |
| transitionName | string | `amos-overlay` | 内部 [CSSTransition](#CSSTransition) 的转换名称。如果自定义该名称，则需要添加相应动画样式。 |
| transitionDuration | `number or object: {enter, appear, exit}` | `300` | 持续时间 |
| onClose | func | - | 点击遮罩层回调函数，该函数中设置 `visible=false` 则关闭浮层。注意与 **`onClosed`** 回调函数的区别。 |
| onEnter | `func(node: HtmlElement, appearing: bool)` | - | 执行顺序 `1`，应用 `enter` 或 `appear` 后立即触发 [CSSTransition](#CSSTransition) 回调。。 |
| onOpening | `func(node: HtmlElement, appearing: bool)` | - | 执行顺序 `2`，**`打开`**立即执行，在应用 `enter-active` 或 `appear-active` 类后立即触发 [CSSTransition](#CSSTransition) 回调。 |
| onOpened | `func(node: HtmlElement, appearing: bool)` | - | 执行顺序 `3`，**`打开`**动画播放完成执行，在应用 `exiting` 状态之前启动回调。 |
| onClosing | `func(node: HtmlElement)` | - | 执行顺序 `4`，**`关闭`**立即执行，应用 `exit-active` 后立即触发 [CSSTransition](#CSSTransition) 回调。 |
| onClosed | `func(node: HtmlElement)` | - | 执行顺序 `5`，**`关闭`**动画播放完成立即执行，删除 `exit` 类后立即触发 [CSSTransition](#CSSTransition) 回调，并将 `exit-done` 类添加到 DOM 节点。 |

支持传递 [CSSTransition](#CSSTransition) 原事件覆盖当前事件，请查看 [CSSTransition](#CSSTransition) 文档。

#### CSSTransition

> 附录 [`<CSSTransition>`](http://reactcommunity.org/react-transition-group/transition/)

## Overlay.Trigger

触发弹出层

基于 Overlay 组件，可实现不同 placement 的弹出。 注意 与 `Popover` 组件的区别

```js
import { Overlay } from 'amos-framework';

const Trigger = Overlay.Trigger;
```

### Trigger 基本使用

---demo
```js
import { Overlay, Button } from 'amos-framework';

const Trigger = Overlay.Trigger;

const styles = {
  box: {
    width: '400px'
  },
  top: {
    textAlign: 'center'
  },
  left: {
    float: 'left',
    width: '60px'
  },
  right: {
    float: 'right',
    width: '60px'
  },
  bottom: {
    clear: 'both',
    textAlign: 'center'
  },
  btnT: {
    marginTop: 8
  },
  btnL: {
    marginLeft: 8
  }
};

const tooltip = (
  <div style={{ backgroundColor: '#fff', border: '1px solid #c5c5c5', padding: 10, borderRadius: 3 }}>
    <strong>Hi amos-framework!</strong>
  </div>
);

ReactDOM.render((
  <div style={styles.box}>
    <div style={styles.top}>
      <Trigger content={tooltip} placement="topLeft">
        <Button>上左</Button>
      </Trigger>
      <Trigger content={tooltip} placement="top">
        <Button style={styles.btnL}>上中</Button>
      </Trigger>
      <Trigger content={tooltip} placement="topRight">
        <Button style={styles.btnL}>上右</Button>
      </Trigger>
    </div>
    <div style={styles.left}>
      <Trigger content={tooltip} placement="leftTop">
        <Button>左上</Button>
      </Trigger>
      <Trigger content={tooltip} placement="left">
        <Button style={styles.btnT}>左中</Button>
      </Trigger>
      <Trigger content={tooltip} placement="leftBottom">
        <Button style={styles.btnT}>左下</Button>
      </Trigger>
    </div>

    <div style={styles.right}>
      <Trigger content={tooltip} placement="rightTop">
        <Button>右上</Button>
      </Trigger>
      <Trigger content={tooltip} placement="right">
        <Button style={styles.btnT}>右中</Button>
      </Trigger>
      <Trigger content={tooltip} placement="rightBottom">
        <Button style={styles.btnT}>右下</Button>
      </Trigger>
    </div>
    <div style={styles.bottom}>
      <Trigger content={tooltip} placement="bottomLeft">
        <Button>下左</Button>
      </Trigger>
      <Trigger content={tooltip} placement="bottom">
        <Button style={styles.btnL}>下中</Button>
      </Trigger>
      <Trigger content={tooltip} placement="bottomRight">
        <Button style={styles.btnL}>下右</Button>
      </Trigger>
    </div>
  </div>
), _react_runner_);
```
---demoend

### 基础用法

最简单的用法。

---demo
```js
import { Overlay } from 'amos-framework';

const Trigger = Overlay.Trigger;

const tooltip = (
  <div style={{ backgroundColor: '#fff', border: '1px solid #c5c5c5', padding: 10, borderRadius: 3 }}>
    <strong>Hi amos-framework!</strong>
  </div>
);
const Demo = () => (
  <Trigger placement="top" triggerMode="click" content={tooltip}>
    <span>鼠标移点击，显示提示框</span>
  </Trigger>
);

ReactDOM.render(<Demo />, _react_runner_);
```
---demoend

### 配合组件使用

下面配合 [`<CardPane />`](#/framework/cardpane) 组件使用。

---demo
```js
import { Overlay, CardPane } from 'amos-framework';

const Trigger = Overlay.Trigger;

const card = (
  <CardPane actived>
    <strong>Hi amos-framework!</strong>
  </CardPane>
);
const Demo = () => (
  <Trigger placement="top" content={card}>
    <span>鼠标移动到此处，显示和消失触发事件</span>
  </Trigger>
)
ReactDOM.render(<Demo />, _react_runner_);
```
---demoend

### 点击事件

---demo
```js
import { Overlay, CardPane, Divider } from 'amos-framework';

const Trigger = Overlay.Trigger;

const card = (
  <CardPane actived>
    <strong>Hi amos-framework!</strong>
  </CardPane>
);

class Demo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      visible: false
    };
  }
  onClick = (visible, evt) => {
    this.setState({ visible });
  }

  render() {
    return (
      <div>
        <Trigger
          placement="top"
          triggerMode="click"
          content={card}
        >
          <span onClick={this.onClick}>鼠标<b>点击</b>此处，显示/隐藏提示框</span>
        </Trigger>
        <Divider />
        <div>状态：{this.state.visible ? '显示' : '隐藏'}</div>
      </div>
    );
  }
}
ReactDOM.render(<Demo />, _react_runner_);
```
---demoend

### 鼠标经过事件

默认离开**触发区域**隐藏弹出目标，设置 `outterClosable` 值为 `true`，在**触发区域**或**弹出目标区域**内，不隐藏**弹出目标**。

---demo
```js
import { Overlay, CardPane, Divider } from 'amos-framework';

const Trigger = Overlay.Trigger;

const card = (
  <CardPane actived>
    <strong>Hi amos-framework!</strong>
  </CardPane>
);

class Demo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      visible: false,
    };
  }
  onVisibleChange = (visible) => {
    this.setState({ visible })
  }
  render() {
    return (
      <div>
        <Trigger
          placement="top"
          outterClosable={true}
          onVisibleChange={this.onVisibleChange}
          content={card}
        >
          <span>鼠标移动到此处，显示提示框</span>
        </Trigger>
        <Divider />
        <div>状态：{this.state.visible ? '显示' : '隐藏'}</div>
      </div>
    )
  }
}
ReactDOM.render(<Demo />, _react_runner_);
```
---demoend


### 延迟进入和消失

延迟属性，只针对 `triggerMode=hover` 有效。

---demo
```js
import { Overlay, CardPane } from 'amos-framework';

const Trigger = Overlay.Trigger;

const card = (
  <CardPane actived>
    <strong>Hi amos-framework!</strong>
  </CardPane>
);
const Demo = () => (
  <Trigger delay={{ show: 0, hide: 2000 }} placement="top" content={card}>
    <span>鼠标移动到此处，显示提示框，延迟 `2s` 消失</span>
  </Trigger>
);

ReactDOM.render(<Demo />, _react_runner_);
```
---demoend

### 受控模式

设置 `visible` 手动控制显隐。

---demo
```js
import { Overlay, CardPane, Divider, Switch } from 'amos-framework';

const Trigger = Overlay.Trigger;

const card = (
  <CardPane actived>
    <strong>Hi amos-framework!</strong>
  </CardPane>
);

class Demo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      visible: false,
    };
  }

  onChange = (visible) => {
    this.setState({ visible });
  }

  onVisibleChange = (visible) => {
    console.log('onVisibleChange: ', visible);
  }

  render() {
    return (
      <div>
        <Trigger
          onVisibleChange={this.onVisibleChange}
          visible={this.state.visible}
          placement="right"
          onEnter={(node, appearing) => {
            console.log('onEnter:', node, appearing);
          }}
          content={card}
        >
          <span>鼠标移动到此处，显示提示框</span>
        </Trigger>
        <Divider />
        <Switch onOff={this.state.visible} onChange={this.onChange} onLabel="显示" offLabel="隐藏" style={{ width: 60 }} />
      </div>
    );
  }
}
ReactDOM.render(<Demo />, _react_runner_);
```
---demoend

### 设置 usePortal

设置 `usePortal={false}` 将对话框添加到父节点所在 `html` 层。

---demo
```js
import { Overlay, CardPane, Divider } from 'amos-framework';

const Trigger = Overlay.Trigger;

const card = (
  <CardPane actived>
    <strong>Hi amos-framework!</strong>
  </CardPane>
);

class Demo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      visible: false,
    };
  }
  onVisibleChange = (visible) => {
    this.setState({ visible })
  }
  render() {
    return (
      <div>
        <div style={{ position: 'relative' }}>
          <Trigger
            usePortal={false}
            outterClosable={true}
            adjustPosition
            placement="top"
            triggerMode="click"
            onVisibleChange={this.onVisibleChange}
            content={card}
          >
            <span>鼠标移点击，显示提示框</span>
          </Trigger>
        </div>
        <Divider />
        <div>状态：{this.state.visible ? '显示' : '隐藏'}</div>
      </div>
    );
  }
}
ReactDOM.render(<Demo />, _react_runner_);
```
---demoend

#### Trigger Props

| params | type | default | description |
|--------- |-------- |--------- |-------- |
| prefixCls | String | `amos-overlay-trigger` | 默认样式前缀 |
| overlayPrefixCls | String | `amos-overlay` | `Overlay` 默认样式前缀 |
| className | String | - | 自定义样式 |
| placement | Enum{`top`, `topLeft`, `topRight`,<br /> `left`, `leftTop`, `leftBottom`,<br /> `right`, `rightTop`, `rightBottom`,<br /> `bottom`, `bottomLeft`, `bottomRight`} | - | 指定弹出框位置 |
| triggerMode | Enum{`hover`, `click`, `focus`} | `hover` | `悬停 or 点击弹出窗口` |
| disabled | Boolean | `false` | 是否禁用弹出目标 |
| content | `func/Element` | - | 弹出内容 |
| delay | `Object or Number`` | - | 延迟进入和消失，`{ show: 2000, hide: 4000 }` 或者直接设置 `2000`，只对 `triggerMode=hover` 有效 |
| visible | Boolean | `false` | 是否显示弹窗 |
| outterClosable | Boolean | `false` | 默认离开**触发区域**隐藏弹出目标。设置值为 `true`，在触发区域和弹出目标区域内，不隐藏**弹出目标**。 |
| isClickOutside | Boolean | `true` | 点击目标区域以外的区域，是否隐藏。 |
| adjustPosition | Boolean | `false` | 弹出层被遮挡时自动调整位置 |
| onVisibleChange | `func(visible: bool)` | - | 显示隐藏的回调 |
| `children.props.onClick` | `func(visible: bool, evt: Event)` | - | 子节点触发显示 |

更多属性文档请参考 [Overlay](#overlay-props)。
