Skip to content

Commit

Permalink
feat(tooltip): 支持plcement为mouse位置
Browse files Browse the repository at this point in the history
fix #608
  • Loading branch information
carolin913 committed Apr 21, 2022
1 parent 213c67d commit 26afab8
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 31 deletions.
37 changes: 29 additions & 8 deletions src/popup/Popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import React, {
useCallback,
useRef,
useEffect,
useImperativeHandle,
} from 'react';
import { CSSTransition } from 'react-transition-group';
import classNames from 'classnames';
import { usePopper } from 'react-popper';
import { Placement } from '@popperjs/core';

import { StyledProps } from '../common';
import useDefault from '../_util/useDefault';
import useAnimation from '../_util/useAnimation';
Expand All @@ -31,10 +31,15 @@ export interface PopupProps extends TdPopupProps, StyledProps {
expandAnimation?: boolean;
}

export interface PopupRefProps {
setModifiers?: (v: Array<any>) => void;
}

function getPopperPlacement(placement: TdPopupProps['placement']) {
return placement.replace(/-(left|top)$/, '-start').replace(/-(right|bottom)$/, '-end') as Placement;
}
const Popup = forwardRef<HTMLDivElement, PopupProps>((props, ref) => {

const Popup = forwardRef<PopupRefProps, PopupProps>((props, ref) => {
const {
trigger = 'hover',
content = null,
Expand Down Expand Up @@ -70,11 +75,22 @@ const Popup = forwardRef<HTMLDivElement, PopupProps>((props, ref) => {

const contentRef = useRef<HTMLDivElement>(null);
const referenceRef = useRef<HTMLDivElement>(null);
const popupRef = useRef<HTMLDivElement>(null);
const popupRef = useRef(null);

const popperRef = useRef(null);
const portalRef = useRef(null);

const [modifiers, setPopupModifiers] = useState([]);

useImperativeHandle(
ref,
(): PopupRefProps => ({
setModifiers: (v: Array<any>) => {
setPopupModifiers([...modifiers, ...v]);
},
}),
);

// 展开时候动态判断上下左右翻转
const onPopperFirstUpdate = useCallback((state) => {
const referenceElmRect = referenceRef.current.getBoundingClientRect();
Expand Down Expand Up @@ -105,13 +121,18 @@ const Popup = forwardRef<HTMLDivElement, PopupProps>((props, ref) => {
popperRef.current.update();
}, []);

popperRef.current = usePopper(triggerRef, overlayRef, {
placement: getPopperPlacement(placement),
onFirstUpdate: onPopperFirstUpdate,
});
const options = useMemo(
() => ({
placement: getPopperPlacement(placement),
onFirstUpdate: onPopperFirstUpdate,
modifiers,
}),
[modifiers, onPopperFirstUpdate, placement],
);

const { styles, attributes } = popperRef.current;
popperRef.current = usePopper(triggerRef, overlayRef, options);

const { styles, attributes } = popperRef.current;
const defaultStyles = useMemo(() => {
if (triggerRef && typeof overlayStyle === 'function') return { ...overlayStyle(triggerRef, overlayRef) };
return { ...overlayStyle };
Expand Down
2 changes: 1 addition & 1 deletion src/popup/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import _Popup from './Popup';

import './style/index.js';

export type { PopupProps } from './Popup';
export type { PopupProps, PopupRefProps } from './Popup';
export * from './type';

export const Popup = _Popup;
Expand Down
4 changes: 2 additions & 2 deletions src/popup/popup.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ disabled | Boolean | false | 是否禁用组件 | N
hideEmptyPopup | Boolean | false | 【开发中】浮层是否隐藏空内容,默认不隐藏 | N
overlayClassName | String / Object / Array | - | 浮层类名,示例:'name1 name2 name3' 或 `['name1', 'name2']``[{ 'name1': true }]`。TS 类型:`ClassName`[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N
overlayStyle | Boolean / Object / Function | - | 浮层样式,第一个参数 `triggerElement` 表示触发元素 DOM 节点,第二个参数 `popupElement` 表示浮层元素 DOM 节点。TS 类型:`Styles | ((triggerElement: HTMLElement, popupElement: HTMLElement) => Styles)`[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N
placement | String | top | 浮层出现位置。可选项:top/left/right/bottom/top-left/top-right/bottom-left/bottom-right/left-top/left-bottom/right-top/right-bottom | N
placement | String | top | 浮层出现位置。TS 类型:`PopupPlacement` `type PopupPlacement = 'top'|'left'|'right'|'bottom'|'top-left'|'top-right'|'bottom-left'|'bottom-right'|'left-top'|'left-bottom'|'right-top'|'right-bottom'`[详细类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/popup/type.ts) | N
showArrow | Boolean | false | 是否显示浮层箭头 | N
trigger | String | hover | 触发浮层出现的方式。可选项:hover/click/focus/context-menu | N
triggerElement | TNode | - | 触发元素。TS 类型:`string | TNode`[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N
visible | Boolean | false | 是否显示浮层。TS 类型:`boolean` | N
defaultVisible | Boolean | false | 是否显示浮层。非受控属性。TS 类型:`boolean` | N
zIndex | Number | - | 组件层级,Web 侧样式默认为 5500,移动端和小程序样式默认为 1500 | N
onScroll | Function | | TS 类型:`(context: { e: WheelEvent }) => void`<br/>下拉选项滚动事件 | N
onVisibleChange | Function | | TS 类型:`(visible: boolean, context: PopupVisibleChangeContext) => void`<br/>当浮层隐藏或显示时触发。[详细类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/popup/type.ts)。<br/>`interface PopupVisibleChangeContext { e?: PopupTriggerEvent; trigger?: PopupTriggerSource }`<br/><br/>`type PopupTriggerEvent = MouseEvent | FocusEvent | KeyboardEvent`<br/><br/>`type PopupTriggerSource = 'document' | 'trigger-element-click' | 'trigger-element-hover' | 'trigger-element-blur' | 'trigger-element-focus' | 'context-menu' | 'keydown-esc'`<br/> | N
onVisibleChange | Function | | TS 类型:`(visible: boolean, context: PopupVisibleChangeContext) => void`<br/>当浮层隐藏或显示时触发`trigger=document` 表示点击非浮层元素触发;`trigger=document` 表示右击触发[详细类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/popup/type.ts)。<br/>`interface PopupVisibleChangeContext { e?: PopupTriggerEvent; trigger?: PopupTriggerSource }`<br/><br/>`type PopupTriggerEvent = MouseEvent | FocusEvent | KeyboardEvent`<br/><br/>`type PopupTriggerSource = 'document' | 'trigger-element-click' | 'trigger-element-hover' | 'trigger-element-blur' | 'trigger-element-focus' | 'context-menu' | 'keydown-esc'`<br/> | N
30 changes: 16 additions & 14 deletions src/popup/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,7 @@ export interface TdPopupProps {
* 浮层出现位置
* @default top
*/
placement?:
| 'top'
| 'left'
| 'right'
| 'bottom'
| 'top-left'
| 'top-right'
| 'bottom-left'
| 'bottom-right'
| 'left-top'
| 'left-bottom'
| 'right-top'
| 'right-bottom';
placement?: PopupPlacement;
/**
* 是否显示浮层箭头
* @default false
Expand Down Expand Up @@ -94,11 +82,25 @@ export interface TdPopupProps {
*/
onScroll?: (context: { e: WheelEvent<HTMLDivElement> }) => void;
/**
* 当浮层隐藏或显示时触发
* 当浮层隐藏或显示时触发,`trigger=document` 表示点击非浮层元素触发;`trigger=document` 表示右击触发
*/
onVisibleChange?: (visible: boolean, context: PopupVisibleChangeContext) => void;
}

export type PopupPlacement =
| 'top'
| 'left'
| 'right'
| 'bottom'
| 'top-left'
| 'top-right'
| 'bottom-left'
| 'bottom-right'
| 'left-top'
| 'left-bottom'
| 'right-top'
| 'right-bottom';

export interface PopupVisibleChangeContext {
e?: PopupTriggerEvent;
trigger?: PopupTriggerSource;
Expand Down
28 changes: 24 additions & 4 deletions src/tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { forwardRef, useState, useEffect, useRef, useImperativeHandle } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import Popup from '../popup';
import Popup, { PopupVisibleChangeContext, PopupRefProps } from '../popup';
import useConfig from '../_util/useConfig';
import { TdTooltipProps } from './type';

Expand All @@ -19,12 +19,13 @@ const Tooltip = forwardRef((props: TdTooltipProps, ref) => {
overlayClassName,
children,
duration = 0,
placement = 'top',
...restProps
} = props;
const { classPrefix } = useConfig();
const [isTipShowed, setTipshow] = useState(duration !== 0);
const [timeup, setTimeup] = useState(false);
const popupRef = useRef<HTMLDivElement>();
const popupRef = useRef<PopupRefProps>();
const timerRef = useRef<number | null>(null);
const toolTipClass = classNames(
`${classPrefix}-tooltip`,
Expand All @@ -39,8 +40,26 @@ const Tooltip = forwardRef((props: TdTooltipProps, ref) => {
setTipshow(v);
};

const handleShowTip = (visible: boolean) => {
const calculatePos = (e) => {
const rect = (e.target as HTMLElement).getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
return {
x,
y,
};
};

const handleShowTip = (visible: boolean, { e, trigger }: PopupVisibleChangeContext) => {
if (duration === 0 || (duration !== 0 && timeup)) {
if (
visible &&
placement === 'mouse' &&
(trigger === 'trigger-element-hover' || trigger === 'trigger-element-click')
) {
const { x } = calculatePos(e);
popupRef.current.setModifiers([{ name: 'offset', options: { offset: [x, 0] } }]);
}
setTipshow(visible);
}
};
Expand Down Expand Up @@ -68,10 +87,11 @@ const Tooltip = forwardRef((props: TdTooltipProps, ref) => {
<Popup
ref={popupRef}
destroyOnClose={destroyOnClose}
showArrow={showArrow}
showArrow={placement === 'mouse' ? false : showArrow}
overlayClassName={toolTipClass}
visible={isTipShowed}
onVisibleChange={handleShowTip}
placement={placement === 'mouse' ? 'bottom-left' : placement}
{...restProps}
>
{children}
Expand Down
19 changes: 19 additions & 0 deletions src/tooltip/__tests__/__snapshots__/tooltip.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,25 @@ exports[`duration.jsx 1`] = `
</DocumentFragment>
`;

exports[`mouse.jsx 1`] = `
<DocumentFragment>
<div
class="tdesign-tooltip-demo"
>
<div
class="t-popup__reference"
>
<a
href="#"
id="testa"
>
文案比较长...
</a>
</div>
</div>
</DocumentFragment>
`;

exports[`no-arrow.jsx 1`] = `
<DocumentFragment>
<div
Expand Down
8 changes: 7 additions & 1 deletion src/tooltip/tooltip.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
:: BASE_DOC ::

### 模拟原生title
{{ mouse }}

### 定时消失
{{ duration }}
Expand All @@ -10,8 +12,12 @@

名称 | 类型 | 默认值 | 说明 | 必传
-- | -- | -- | -- | --
className | String | - | 类名 | N
style | Object | - | 样式,TS 类型:`React.CSSProperties` | N
delay | Number | - | 【议案讨论中】延迟出现提示,用于异步加载提示信息需要延迟显示的业务场景下 | N
destroyOnClose | Boolean | true | 是否在关闭浮层时销毁浮层 | N
duration | Number | - | 用于设置提示默认显示多长时间之后消失,初始第一次有效,单位:毫秒 | N
placement | String | top | 浮层出现位置。TS 类型:`'mouse' | PopupPlacement`[Popup API Documents](./popup?tab=api)[详细类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/tooltip/type.ts) | N
showArrow | Boolean | true | 是否显示浮层箭头 | N
theme | String | default | 文字提示风格。可选项:default/primary/success/danger/warning/light | N
PopupProps | - | - | 继承 `PopupProps` 中的全部 API | N
`Omit<PopupProps, 'placement'>` | - | - | 继承 `Omit<PopupProps, 'placement'>` 中的全部 API | N
12 changes: 11 additions & 1 deletion src/tooltip/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */

import { PopupPlacement } from '../popup';
import { PopupProps } from '../popup';

export interface TdTooltipProps extends PopupProps {
export interface TdTooltipProps extends Omit<PopupProps, 'placement'> {
/**
* 【议案讨论中】延迟出现提示,用于异步加载提示信息需要延迟显示的业务场景下
*/
delay?: number;
/**
* 是否在关闭浮层时销毁浮层
* @default true
Expand All @@ -16,6 +21,11 @@ export interface TdTooltipProps extends PopupProps {
* 用于设置提示默认显示多长时间之后消失,初始第一次有效,单位:毫秒
*/
duration?: number;
/**
* 浮层出现位置
* @default top
*/
placement?: 'mouse' | PopupPlacement;
/**
* 是否显示浮层箭头
* @default true
Expand Down
2 changes: 2 additions & 0 deletions test/ssr/__snapshots__/ssr.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,8 @@ exports[`ssr snapshot test renders ./src/tooltip/_example/base.jsx correctly 1`]

exports[`ssr snapshot test renders ./src/tooltip/_example/duration.jsx correctly 1`] = `"<div data-reactroot=\\"\\"><div class=\\"t-popup__reference\\"><button type=\\"button\\" class=\\"t-button t-button--theme-default t-button--variant-text\\"><span class=\\"t-button__text\\">定时消失</span></button></div><button type=\\"button\\" class=\\"t-button t-button--theme-default t-button--variant-outline\\"><span class=\\"t-button__text\\">点击再次查看</span></button></div>"`;

exports[`ssr snapshot test renders ./src/tooltip/_example/mouse.jsx correctly 1`] = `"<div class=\\"tdesign-tooltip-demo\\" data-reactroot=\\"\\"><div class=\\"t-popup__reference\\"><a id=\\"testa\\" href=\\"#\\">文案比较长...</a></div></div>"`;

exports[`ssr snapshot test renders ./src/tooltip/_example/no-arrow.jsx correctly 1`] = `"<div class=\\"t-popup__reference\\"><button type=\\"button\\" class=\\"t-button t-button--theme-default t-button--variant-outline\\"><span class=\\"t-button__text\\">不带箭头等文字提示</span></button></div>"`;

exports[`ssr snapshot test renders ./src/tooltip/_example/theme.jsx correctly 1`] = `"<div class=\\"tdesign-tooltip-demo\\" data-reactroot=\\"\\"><div class=\\"t-popup__reference\\"><button type=\\"button\\" class=\\"t-button t-button--theme-default t-button--variant-base\\"><span class=\\"t-button__text\\">default</span></button></div><div class=\\"t-popup__reference\\"><button type=\\"button\\" class=\\"t-button t-button--theme-primary t-button--variant-base\\"><span class=\\"t-button__text\\">primary</span></button></div><div class=\\"t-popup__reference\\"><button type=\\"button\\" class=\\"t-button t-button--theme-success t-button--variant-base\\"><span class=\\"t-button__text\\">success</span></button></div><div class=\\"t-popup__reference\\"><button type=\\"button\\" class=\\"t-button t-button--theme-danger t-button--variant-base\\"><span class=\\"t-button__text\\">danger</span></button></div><div class=\\"t-popup__reference\\"><button type=\\"button\\" class=\\"t-button t-button--theme-warning t-button--variant-base\\"><span class=\\"t-button__text\\">warning</span></button></div><div class=\\"t-popup__reference\\"><button type=\\"button\\" class=\\"t-button t-button--theme-default t-button--variant-outline\\"><span class=\\"t-button__text\\">light</span></button></div></div>"`;
Expand Down

0 comments on commit 26afab8

Please sign in to comment.