Skip to content

Commit

Permalink
feat(Dialog): Dialog组件支持自定义弹窗打开/关闭动画原点 (#2056)
Browse files Browse the repository at this point in the history
Co-authored-by: lion <[email protected]>
  • Loading branch information
Jonesau and lion authored Jun 14, 2024
1 parent 4a3a890 commit b0629a3
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 37 deletions.
7 changes: 5 additions & 2 deletions packages/zent/__tests__/dialog/Dialog.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,12 @@ describe('Dialog component', () => {
button.style.left = '100px';
button.style.top = '100px';
button.addEventListener('click', () => open());

const clickEvent = new MouseEvent('click', {
clientX: 100,
clientY: 100,
});
document.body.appendChild(button);
button.click();
button.dispatchEvent(clickEvent);

jest.runAllTimers();

Expand Down
8 changes: 8 additions & 0 deletions packages/zent/src/dialog/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import isBrowser from '../utils/isBrowser';
import { DialogElWrapper, DialogInnerEl, IMousePosition } from './DialogEl';
import { openDialog, closeDialog } from './open';
import { addEventListener } from '../utils/component/event-handler';
import { DialogPosition, IDialogPositionType } from './position';

const TIMEOUT = 300; // ms

Expand All @@ -16,6 +17,9 @@ if (isBrowser) {
document.documentElement,
'click',
(e: MouseEvent) => {
if (e.clientX === 0 || e.clientY === 0) {
return;
}
mousePosition = {
x: e.clientX,
y: e.clientY,
Expand All @@ -36,6 +40,7 @@ export interface IDialogProps {
maskClosable?: boolean;
className?: string;
style?: React.CSSProperties;
position?: IDialogPositionType;
onOpened?: () => void;
onClosed?: () => void;
}
Expand All @@ -51,6 +56,7 @@ export class Dialog extends Component<IDialogProps, IDialogState> {
visible: false,
className: '',
style: {},
position: DialogPosition.auto,
title: '',
closeBtn: true,
mask: true,
Expand Down Expand Up @@ -108,6 +114,7 @@ export class Dialog extends Component<IDialogProps, IDialogState> {
visible,
closeBtn,
style,
position,
onOpened,
onClosed,
mask,
Expand Down Expand Up @@ -151,6 +158,7 @@ export class Dialog extends Component<IDialogProps, IDialogState> {
{...props}
style={style}
closeBtn={closeBtn}
position={position}
mousePosition={this.lastMousePosition}
>
{children}
Expand Down
25 changes: 18 additions & 7 deletions packages/zent/src/dialog/DialogEl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Component, createRef } from 'react';
import cx from 'classnames';
import focusWithoutScroll from '../utils/dom/focusWithoutScroll';
import Icon from '../icon';
import { IDialogPositionType, getPositionTransformOrigin } from './position';

export interface IMousePosition {
x: number;
Expand All @@ -16,6 +17,7 @@ export interface IDialogInnerElProps {
style?: React.CSSProperties;
footer?: React.ReactNode;
mousePosition?: IMousePosition | null;
position?: IDialogPositionType;
}

export class DialogInnerEl extends Component<IDialogInnerElProps> {
Expand All @@ -29,22 +31,31 @@ export class DialogInnerEl extends Component<IDialogInnerElProps> {
this.resetTransformOrigin();
}

setTransformOrigin(style: CSSStyleDeclaration, origin: string) {
['Webkit', 'Moz', 'Ms', 'ms'].forEach(prefix => {
style[`${prefix}TransformOrigin`] = origin;
});
style.transformOrigin = origin;
}

resetTransformOrigin(props = this.props) {
const { mousePosition } = props;
const { mousePosition, position } = props;
let origin = getPositionTransformOrigin(position, this.dialogEl);

if (
origin === undefined &&
mousePosition &&
mousePosition.x >= 0 &&
mousePosition.y >= 0 &&
this.dialogEl &&
this.dialogEl.getBoundingClientRect
) {
const { left: x, top: y } = this.dialogEl.getBoundingClientRect();
const origin = `${mousePosition.x - x}px ${mousePosition.y - y}px 0`;
const style = this.dialogEl.style;
['Webkit', 'Moz', 'Ms', 'ms'].forEach(prefix => {
style[`${prefix}TransformOrigin` as any] = origin;
});
style.transformOrigin = origin;
origin = `${mousePosition.x - x}px ${mousePosition.y - y}px 0`;
}

if (origin && this.dialogEl) {
this.setTransformOrigin(this.dialogEl.style, origin);
}
}

Expand Down
38 changes: 24 additions & 14 deletions packages/zent/src/dialog/README_en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,21 @@ Modal window with background mask; It is often used to carry the feedback of det

### API

| Props | Description | Type | Default |
| ------------ | -------------------------------------------------------- | ----------------- | ------- |
| title | Dialog title | `ReactNode` | `''` |
| children | Content of the dialog` | `ReactNode` | `null` |
| footer | Content of the dialog footer | `ReactNode` | `null` |
| visible | Visibility of the dialog | `boolean` | `false` |
| closeBtn | Visibility of the close button at the upper right corner | `boolean` | `true` |
| onClose | Close callback | `(event) => void` | `noop` |
| onClosed | Callback when dialog closing animation is done | `() => void` | |
| onOpened | Callback when dialog opening animation is done | `() => void` | |
| mask | Visibility of the mask | `boolean` | `true` |
| maskClosable | Click on the mask to close the dialog | `boolean` | `true` |
| className | Custom classname | `string` | `''` |
| style | Custom style object | `CSSProperties` | `{}` |
| Props | Description | Type | Default |
| ------------ | -------------------------------------------------------- | --------------------- | ------- |
| title | Dialog title | `ReactNode` | `''` |
| children | Content of the dialog` | `ReactNode` | `null` |
| footer | Content of the dialog footer | `ReactNode` | `null` |
| visible | Visibility of the dialog | `boolean` | `false` |
| closeBtn | Visibility of the close button at the upper right corner | `boolean` | `true` |
| onClose | Close callback | `(event) => void` | `noop` |
| onClosed | Callback when dialog closing animation is done | `() => void` | |
| onOpened | Callback when dialog opening animation is done | `() => void` | |
| mask | Visibility of the mask | `boolean` | `true` |
| maskClosable | Click on the mask to close the dialog | `boolean` | `true` |
| className | Custom classname | `string` | `''` |
| style | Custom style object | `CSSProperties` | `{}` |
| position | Position way | `IDialogPositionType` | `auto` |

#### openDialog

Expand Down Expand Up @@ -72,6 +73,15 @@ Set a `style` prop on Dialog can specify its width, e.g. `style={{ width: '600px

By default the pop-up window width will adapt its content, meanwhile it has a minimum width and maximum width.

#### Position

The `position` prop can be used to specify the opening position of the dialog.

| value | description |
| -------- | ---------------------------------------------------------- |
| `auto` | Automatically chooses the opening position, default value. |
| `center` | The dialog will be displayed in the center of the screen. |

#### The following functions is obsolete in the new design system and is only used as a reference for the old version

<!-- demo-slot-4 -->
Expand Down
38 changes: 24 additions & 14 deletions packages/zent/src/dialog/README_zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,21 @@ scatter: true

### API

| 参数 | 说明 | 类型 | 默认值 |
| ------------ | --------------------------------- | ----------------- | ------- |
| title | 自定义弹框标题 | `ReactNode` | `''` |
| children | 弹框内容: `<Dialog>xxxx</Dialog>` | `ReactNode` | `null` |
| footer | 底部内容 | `ReactNode` | `null` |
| visible | 是否打开对话窗 | `boolean` | `false` |
| closeBtn | 是否显示右上角关闭按钮 | `boolean` | `true` |
| onClose | 关闭操作回调函数 | `(event) => void` | `noop` |
| onOpened | 对话窗打开动画结束后的回调函数 | `() => void` | |
| onClosed | 对话窗关闭动画结束后的回调函数 | `() => void` | |
| mask | 是否显示遮罩 | `boolean` | `true` |
| maskClosable | 点击遮罩是否可以关闭 | `boolean` | `true` |
| className | 自定义额外类名 | `string` | `''` |
| style | 自定义样式 | `CSSProperties` | `{}` |
| 参数 | 说明 | 类型 | 默认值 |
| --------------- | --------------------------------- | --------------------- | ------- |
| title | 自定义弹框标题 | `ReactNode` | `''` |
| children | 弹框内容: `<Dialog>xxxx</Dialog>` | `ReactNode` | `null` |
| footer | 底部内容 | `ReactNode` | `null` |
| visible | 是否打开对话窗 | `boolean` | `false` |
| closeBtn | 是否显示右上角关闭按钮 | `boolean` | `true` |
| onClose | 关闭操作回调函数 | `(event) => void` | `noop` |
| onOpened | 对话窗打开动画结束后的回调函数 | `() => void` | |
| onClosed | 对话窗关闭动画结束后的回调函数 | `() => void` | |
| mask | 是否显示遮罩 | `boolean` | `true` |
| maskClosable | 点击遮罩是否可以关闭 | `boolean` | `true` |
| className | 自定义额外类名 | `string` | `''` |
| style | 自定义样式 | `CSSProperties` | `{}` |
| position | 对话窗打开位置 | `IDialogPositionType` | `auto` |

#### openDialog

Expand Down Expand Up @@ -77,6 +78,15 @@ scatter: true

默认情况下弹出窗口会自适应内容的宽度, 同时有最小宽度和最大宽度.

#### Position API

`position` 属性用于指定对话窗打开位置

| 支持的值 | 描述 |
| -------- | -------------------- |
| `auto` | 自动选择打开位置, 默认值 |
| `center` | 从屏幕中心位置打开 |

#### 以下功能新版设计语言已废弃,仅作为老版使用的参考

<!-- demo-slot-4 -->
Expand Down
25 changes: 25 additions & 0 deletions packages/zent/src/dialog/position.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export const enum DialogPosition {
auto = 'auto',
center = 'center',
}

export type IDialogPositionType = keyof typeof DialogPosition;

/**
* 根据传入的 position 参数,计算出弹窗的 transformOrigin 属性值
* @param position 弹窗位置,可选值为auto、center
* @param el 弹窗元素
* @returns 返回 transformOrigin 属性值
*/
export const getPositionTransformOrigin = (
position?: IDialogPositionType,
_el?: HTMLDivElement // 后续增加其他位置信息,需要使用 el 计算位置
) => {
switch (position) {
case DialogPosition.center:
return 'center center 0';
case DialogPosition.auto:
default:
return undefined;
}
};

0 comments on commit b0629a3

Please sign in to comment.