From 8b3868ac635a62d85621c06ee8c5e325d51f5fa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?kiner-tang=28=E6=96=87=E8=BE=89=29?= <1127031143@qq.com> Date: Thu, 19 Jan 2023 11:21:05 +0800 Subject: [PATCH] feat(tooltip): tooltip support arrow prop (#40234) * feat: tooltip support arrow props * docs: update docs * chore: update rc-tooltip version * feat: update snapshots * feat: update snapshots * style: format code * Update components/tooltip/index.tsx Co-authored-by: MadCcc <1075746765@qq.com> * Update components/tooltip/index.zh-CN.md Co-authored-by: MadCcc <1075746765@qq.com> * Update components/tooltip/demo/arrow.md Co-authored-by: MadCcc <1075746765@qq.com> * Update components/tooltip/demo/arrow.md Co-authored-by: MadCcc <1075746765@qq.com> * feat: code optimize * docs: update docs * feat: code optimize * feat: del doc * feat: update snapshots * feat: update snapshots * feat: update snapshots * feat: refactor dropdown arrow style * feat: comment * Update components/tooltip/demo/arrow.tsx Co-authored-by: MadCcc <1075746765@qq.com> * feat: popover support arrow prop * feat: tour arrow style optimize * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: update test case * feat: update test case * feat: update test case * Update components/popover/index.tsx Co-authored-by: MadCcc <1075746765@qq.com> * Update components/tooltip/index.zh-CN.md Co-authored-by: MadCcc <1075746765@qq.com> * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code Co-authored-by: MadCcc <1075746765@qq.com> --- components/_util/placements.ts | 70 +- components/dropdown/dropdown.tsx | 5 + components/dropdown/style/index.ts | 112 +-- .../__snapshots__/demo-extend.test.ts.snap | 717 ++++++++++++++++-- .../__tests__/__snapshots__/demo.test.ts.snap | 191 ++++- .../popover/demo/arrow-point-at-center.md | 7 - .../popover/demo/arrow-point-at-center.tsx | 22 - components/popover/demo/arrow.md | 27 + components/popover/demo/arrow.tsx | 81 ++ components/popover/index.en-US.md | 2 +- components/popover/index.tsx | 7 + components/popover/index.zh-CN.md | 2 +- components/popover/style/index.tsx | 4 +- components/style/placementArrow.ts | 269 +++---- .../__snapshots__/demo-extend.test.ts.snap | 521 +++++++++++-- .../__tests__/__snapshots__/demo.test.ts.snap | 191 ++++- .../__snapshots__/tooltip.test.tsx.snap | 57 ++ components/tooltip/__tests__/tooltip.test.tsx | 54 +- .../tooltip/demo/arrow-point-at-center.md | 7 - .../tooltip/demo/arrow-point-at-center.tsx | 15 - components/tooltip/demo/arrow.md | 27 + components/tooltip/demo/arrow.tsx | 75 ++ components/tooltip/index.en-US.md | 4 +- components/tooltip/index.tsx | 20 +- components/tooltip/index.zh-CN.md | 4 +- components/tooltip/style/index.ts | 1 - components/tour/index.tsx | 11 + components/tour/style/index.ts | 1 - package.json | 4 +- 29 files changed, 2036 insertions(+), 472 deletions(-) delete mode 100644 components/popover/demo/arrow-point-at-center.md delete mode 100644 components/popover/demo/arrow-point-at-center.tsx create mode 100644 components/popover/demo/arrow.md create mode 100644 components/popover/demo/arrow.tsx delete mode 100644 components/tooltip/demo/arrow-point-at-center.md delete mode 100644 components/tooltip/demo/arrow-point-at-center.tsx create mode 100644 components/tooltip/demo/arrow.md create mode 100644 components/tooltip/demo/arrow.tsx diff --git a/components/_util/placements.ts b/components/_util/placements.ts index cec4378658..efef61d075 100644 --- a/components/_util/placements.ts +++ b/components/_util/placements.ts @@ -19,11 +19,12 @@ export interface AdjustOverflow { } export interface PlacementsConfig { - arrowWidth?: number; + arrowWidth: number; horizontalArrowShift?: number; verticalArrowShift?: number; arrowPointAtCenter?: boolean; autoAdjustOverflow?: boolean | AdjustOverflow; + offset: number; } export function getOverflowOptions(autoAdjustOverflow?: boolean | AdjustOverflow) { @@ -36,73 +37,114 @@ export function getOverflowOptions(autoAdjustOverflow?: boolean | AdjustOverflow }; } +type PlacementType = keyof BuildInPlacements; + +function getArrowOffset(type: PlacementType, arrowWidth: number, offset: number): number[] { + switch (type) { + case 'top': + case 'topLeft': + case 'topRight': + return [0, -(arrowWidth / 2 + offset)]; + case 'bottom': + case 'bottomLeft': + case 'bottomRight': + return [0, arrowWidth / 2 + offset]; + case 'left': + case 'leftTop': + case 'leftBottom': + return [-(arrowWidth / 2 + offset), 0]; + case 'right': + case 'rightTop': + case 'rightBottom': + return [arrowWidth / 2 + offset, 0]; + /* istanbul ignore next */ + default: + return [0, 0]; + } +} + +function vertexCalc(point1: number[], point2: number[]): number[] { + return [point1[0] + point2[0], point1[1] + point2[1]]; +} + export default function getPlacements(config: PlacementsConfig) { const { - arrowWidth = 4, + arrowWidth, horizontalArrowShift = 16, verticalArrowShift = 8, autoAdjustOverflow, arrowPointAtCenter, + offset, } = config; + const halfArrowWidth = arrowWidth / 2; + const placementMap: BuildInPlacements = { left: { points: ['cr', 'cl'], - offset: [-4, 0], + offset: [-offset, 0], }, right: { points: ['cl', 'cr'], - offset: [4, 0], + offset: [offset, 0], }, top: { points: ['bc', 'tc'], - offset: [0, -4], + offset: [0, -offset], }, bottom: { points: ['tc', 'bc'], - offset: [0, 4], + offset: [0, offset], }, topLeft: { points: ['bl', 'tc'], - offset: [-(horizontalArrowShift + arrowWidth), -4], + offset: [-(horizontalArrowShift + halfArrowWidth), -offset], }, leftTop: { points: ['tr', 'cl'], - offset: [-4, -(verticalArrowShift + arrowWidth)], + offset: [-offset, -(verticalArrowShift + halfArrowWidth)], }, topRight: { points: ['br', 'tc'], - offset: [horizontalArrowShift + arrowWidth, -4], + offset: [horizontalArrowShift + halfArrowWidth, -offset], }, rightTop: { points: ['tl', 'cr'], - offset: [4, -(verticalArrowShift + arrowWidth)], + offset: [offset, -(verticalArrowShift + halfArrowWidth)], }, bottomRight: { points: ['tr', 'bc'], - offset: [horizontalArrowShift + arrowWidth, 4], + offset: [horizontalArrowShift + halfArrowWidth, offset], }, rightBottom: { points: ['bl', 'cr'], - offset: [4, verticalArrowShift + arrowWidth], + offset: [offset, verticalArrowShift + halfArrowWidth], }, bottomLeft: { points: ['tl', 'bc'], - offset: [-(horizontalArrowShift + arrowWidth), 4], + offset: [-(horizontalArrowShift + halfArrowWidth), offset], }, leftBottom: { points: ['br', 'cl'], - offset: [-4, verticalArrowShift + arrowWidth], + offset: [-offset, verticalArrowShift + halfArrowWidth], }, }; Object.keys(placementMap).forEach((key) => { placementMap[key] = arrowPointAtCenter ? { ...placementMap[key], + offset: vertexCalc( + placementMap[key].offset!, + getArrowOffset(key as PlacementType, arrowWidth, offset), + ), overflow: getOverflowOptions(autoAdjustOverflow), targetOffset, } : { ...placements[key], + offset: vertexCalc( + placements[key].offset!, + getArrowOffset(key as PlacementType, arrowWidth, offset), + ), overflow: getOverflowOptions(autoAdjustOverflow), }; diff --git a/components/dropdown/dropdown.tsx b/components/dropdown/dropdown.tsx index f93be26288..a9286de819 100644 --- a/components/dropdown/dropdown.tsx +++ b/components/dropdown/dropdown.tsx @@ -16,6 +16,7 @@ import warning from '../_util/warning'; import { NoCompactStyle } from '../space/Compact'; import DropdownButton from './dropdown-button'; import useStyle from './style'; +import theme from '../theme'; const Placements = [ 'topLeft', @@ -183,6 +184,8 @@ const Dropdown: CompoundedComponent = (props) => { const prefixCls = getPrefixCls('dropdown', customizePrefixCls); const [wrapSSR, hashId] = useStyle(prefixCls); + const { token } = theme.useToken(); + const child = React.Children.only(children) as React.ReactElement; const dropdownTrigger = cloneElement(child, { @@ -221,6 +224,8 @@ const Dropdown: CompoundedComponent = (props) => { const builtinPlacements = getPlacements({ arrowPointAtCenter: typeof arrow === 'object' && arrow.pointAtCenter, autoAdjustOverflow, + offset: token.marginXXS, + arrowWidth: arrow ? token.sizePopupArrow : 0, }); const onMenuClick = React.useCallback(() => { diff --git a/components/dropdown/style/index.ts b/components/dropdown/style/index.ts index ba3fb8f330..fa6d4ec48b 100644 --- a/components/dropdown/style/index.ts +++ b/components/dropdown/style/index.ts @@ -1,4 +1,4 @@ -import { getArrowOffset } from '../../style/placementArrow'; +import getArrowStyle, { getArrowOffset } from '../../style/placementArrow'; import { initMoveMotion, initSlideMotion, @@ -12,7 +12,7 @@ import type { FullToken, GenerateStyle } from '../../theme/internal'; import { genComponentStyleHook, mergeToken } from '../../theme/internal'; import genButtonStyle from './button'; import genStatusStyle from './status'; -import { genFocusStyle, resetComponent, roundedArrow } from '../../style'; +import { genFocusStyle, resetComponent } from '../../style'; export interface ComponentToken { zIndexPopup: number; @@ -34,7 +34,6 @@ const genBaseStyle: GenerateStyle = (token) => { menuCls, zIndexPopup, dropdownArrowDistance, - dropdownArrowOffset, sizePopupArrow, antCls, iconCls, @@ -46,7 +45,6 @@ const genBaseStyle: GenerateStyle = (token) => { fontSizeIcon, controlPaddingHorizontal, colorBgElevated, - boxShadowPopoverArrow, } = token; return [ @@ -99,103 +97,6 @@ const genBaseStyle: GenerateStyle = (token) => { display: 'none', }, - // ============================================================= - // == Arrow == - // ============================================================= - // Offset the popover to account for the dropdown arrow - [` - &-show-arrow&-placement-topLeft, - &-show-arrow&-placement-top, - &-show-arrow&-placement-topRight - `]: { - paddingBottom: dropdownArrowDistance, - }, - - [` - &-show-arrow&-placement-bottomLeft, - &-show-arrow&-placement-bottom, - &-show-arrow&-placement-bottomRight - `]: { - paddingTop: dropdownArrowDistance, - }, - - // Note: .popover-arrow is outer, .popover-arrow:after is inner - [`${componentCls}-arrow`]: { - position: 'absolute', - zIndex: 1, // lift it up so the menu wouldn't cask shadow on it - display: 'block', - - ...roundedArrow( - sizePopupArrow, - token.borderRadiusXS, - token.borderRadiusOuter, - colorBgElevated, - boxShadowPopoverArrow, - ), - }, - - [` - &-placement-top > ${componentCls}-arrow, - &-placement-topLeft > ${componentCls}-arrow, - &-placement-topRight > ${componentCls}-arrow - `]: { - bottom: dropdownArrowDistance, - transform: 'translateY(100%) rotate(180deg)', - }, - - [`&-placement-top > ${componentCls}-arrow`]: { - left: { - _skip_check_: true, - value: '50%', - }, - transform: 'translateX(-50%) translateY(100%) rotate(180deg)', - }, - - [`&-placement-topLeft > ${componentCls}-arrow`]: { - left: { - _skip_check_: true, - value: dropdownArrowOffset, - }, - }, - - [`&-placement-topRight > ${componentCls}-arrow`]: { - right: { - _skip_check_: true, - value: dropdownArrowOffset, - }, - }, - - [` - &-placement-bottom > ${componentCls}-arrow, - &-placement-bottomLeft > ${componentCls}-arrow, - &-placement-bottomRight > ${componentCls}-arrow - `]: { - top: dropdownArrowDistance, - transform: `translateY(-100%)`, - }, - - [`&-placement-bottom > ${componentCls}-arrow`]: { - left: { - _skip_check_: true, - value: '50%', - }, - transform: `translateY(-100%) translateX(-50%)`, - }, - - [`&-placement-bottomLeft > ${componentCls}-arrow`]: { - left: { - _skip_check_: true, - value: dropdownArrowOffset, - }, - }, - - [`&-placement-bottomRight > ${componentCls}-arrow`]: { - right: { - _skip_check_: true, - value: dropdownArrowOffset, - }, - }, - // ============================================================= // == Motion == // ============================================================= @@ -233,6 +134,15 @@ const genBaseStyle: GenerateStyle = (token) => { }, }, + // ============================================================= + // == Arrow style == + // ============================================================= + getArrowStyle(token, { + colorBg: colorBgElevated, + limitVerticalRadius: true, + arrowPlacement: { top: true, bottom: true }, + }), + { // ============================================================= // == Menu == diff --git a/components/popover/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/popover/__tests__/__snapshots__/demo-extend.test.ts.snap index 61409fa7d0..3c82e97f62 100644 --- a/components/popover/__tests__/__snapshots__/demo-extend.test.ts.snap +++ b/components/popover/__tests__/__snapshots__/demo-extend.test.ts.snap @@ -1,100 +1,687 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`renders ./components/popover/demo/arrow-point-at-center.tsx extend context correctly 1`] = ` -Array [ - , -
+ + + +
+
+ +
+ +
- -
-
-
, - , -
-
+ + Top + + +
- -
-
-
, -] + +
+
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+ +
+ +
+
+
+ +
+
+
+
+ +
+ +
+
+
+ +
+
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+ +
+ +
+
+
+ +
+
+
+
+ +
+ +
+
+
+ +
+
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+ +
+ +
+
+
+ +
+
+
+
+ +
+ +
+
+
+ +
+
+
+
+ +
+ +
+
+
+
+ `; exports[`renders ./components/popover/demo/basic.tsx extend context correctly 1`] = ` diff --git a/components/popover/__tests__/__snapshots__/demo.test.ts.snap b/components/popover/__tests__/__snapshots__/demo.test.ts.snap index 746b1d070c..e4c1e5970f 100644 --- a/components/popover/__tests__/__snapshots__/demo.test.ts.snap +++ b/components/popover/__tests__/__snapshots__/demo.test.ts.snap @@ -1,24 +1,183 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`renders ./components/popover/demo/arrow-point-at-center.tsx correctly 1`] = ` -Array [ - , - , -] + +
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+ `; exports[`renders ./components/popover/demo/basic.tsx correctly 1`] = ` diff --git a/components/popover/demo/arrow-point-at-center.md b/components/popover/demo/arrow-point-at-center.md deleted file mode 100644 index a6d723de17..0000000000 --- a/components/popover/demo/arrow-point-at-center.md +++ /dev/null @@ -1,7 +0,0 @@ -## zh-CN - -设置了 `arrowPointAtCenter` 后,箭头将指向目标元素的中心。 - -## en-US - -The arrow points to the center of the target element, which set `arrowPointAtCenter`. diff --git a/components/popover/demo/arrow-point-at-center.tsx b/components/popover/demo/arrow-point-at-center.tsx deleted file mode 100644 index 0403324922..0000000000 --- a/components/popover/demo/arrow-point-at-center.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import { Button, Popover } from 'antd'; - -const content = ( - <> -

Content

-

Content

- -); - -const App: React.FC = () => ( - <> - - - - - - - -); - -export default App; diff --git a/components/popover/demo/arrow.md b/components/popover/demo/arrow.md new file mode 100644 index 0000000000..6b90e857e3 --- /dev/null +++ b/components/popover/demo/arrow.md @@ -0,0 +1,27 @@ +## zh-CN + +通过 `arrow` 属性隐藏箭头。 + +## en-US + +Hide arrow by `arrow`. + + diff --git a/components/popover/demo/arrow.tsx b/components/popover/demo/arrow.tsx new file mode 100644 index 0000000000..3a23b4bc39 --- /dev/null +++ b/components/popover/demo/arrow.tsx @@ -0,0 +1,81 @@ +import React, { useMemo, useState } from 'react'; +import { Button, Divider, Popover, Segmented } from 'antd'; + +const text = Title; +const content = ( +
+

Content

+

Content

+
+); + +const buttonWidth = 70; + +const App: React.FC = () => { + const [showArrow, setShowArrow] = useState(true); + const [arrowAtCenter, setArrowAtCenter] = useState(false); + + const mergedArrow = useMemo(() => { + if (arrowAtCenter) return { arrowPointAtCenter: true }; + return showArrow; + }, [showArrow, arrowAtCenter]); + + return ( +
+ { + setShowArrow(val !== 'Hide'); + setArrowAtCenter(val === 'Center'); + }} + /> + Content +
+ + + + + + + + + +
+
+ + + + + + + + + +
+
+ + + + + + + + + +
+
+ + + + + + + + + +
+
+ ); +}; + +export default App; diff --git a/components/popover/index.en-US.md b/components/popover/index.en-US.md index be50cc6ca3..a4e09eac8e 100644 --- a/components/popover/index.en-US.md +++ b/components/popover/index.en-US.md @@ -21,8 +21,8 @@ Comparing with `Tooltip`, besides information `Popover` card can also provide ac Basic Three ways to trigger Placement +Arrow Controlling the close of the dialog -Arrow pointing Hover with click popover _InternalPanelDoNotUseOrYouWillBeFired Wireframe diff --git a/components/popover/index.tsx b/components/popover/index.tsx index 8112324070..51e26481e5 100644 --- a/components/popover/index.tsx +++ b/components/popover/index.tsx @@ -44,6 +44,8 @@ const Popover = React.forwardRef((props, ref) => { mouseEnterDelay = 0.1, mouseLeaveDelay = 0.1, overlayStyle = {}, + arrowPointAtCenter, + arrow, ...otherProps } = props; const { getPrefixCls } = React.useContext(ConfigContext); @@ -54,9 +56,14 @@ const Popover = React.forwardRef((props, ref) => { const overlayCls = classNames(overlayClassName, hashId); + const mergedArrowPointAtCenter = + (typeof arrow !== 'boolean' && arrow?.arrowPointAtCenter) ?? arrowPointAtCenter ?? false; + const mergedArrow = arrow ?? { arrowPointAtCenter: mergedArrowPointAtCenter }; + return wrapSSR( 基本 三种触发方式 位置 +箭头展示 从浮层内关闭 -箭头指向 悬停点击弹出窗口 _InternalPanelDoNotUseOrYouWillBeFired 线框风格 diff --git a/components/popover/style/index.tsx b/components/popover/style/index.tsx index 183b3c5104..dba2a2a4f0 100644 --- a/components/popover/style/index.tsx +++ b/components/popover/style/index.tsx @@ -84,7 +84,9 @@ const genBaseStyle: GenerateStyle = (token) => { }, // Arrow Style - getArrowStyle(token, { colorBg: 'var(--antd-arrow-background-color)' }), + getArrowStyle(token, { + colorBg: 'var(--antd-arrow-background-color)', + }), // Pure Render { diff --git a/components/style/placementArrow.ts b/components/style/placementArrow.ts index 12c4d2dbea..4cd6a44013 100644 --- a/components/style/placementArrow.ts +++ b/components/style/placementArrow.ts @@ -1,12 +1,8 @@ -import type { CSSInterpolation } from '@ant-design/cssinjs'; +import type { CSSInterpolation, CSSObject } from '@ant-design/cssinjs'; import type { AliasToken } from '../theme/internal'; import type { TokenWithCommonCls } from '../theme/util/genComponentStyleHook'; import { roundedArrow } from './roundedArrow'; -function connectArrowCls(classList: string[], showArrowCls: string = '') { - return classList.map((cls) => `${showArrowCls}${cls}`).join(','); -} - export const MAX_VERTICAL_CONTENT_RADIUS = 8; export function getArrowOffset(options: { @@ -25,6 +21,11 @@ export function getArrowOffset(options: { return { dropdownArrowOffset, dropdownArrowOffsetVertical }; } +function isInject(valid: boolean, code: CSSObject): CSSObject { + if (!valid) return {}; + return code; +} + export default function getArrowStyle>( token: Token, options: { @@ -32,22 +33,29 @@ export default function getArrowStyle>>>> Top - [[ - `&-placement-top ${componentCls}-arrow`, - `&-placement-topLeft ${componentCls}-arrow`, - `&-placement-topRight ${componentCls}-arrow`, - ].join(',')]: { - bottom: 0, - transform: 'translateY(100%) rotate(180deg)', - }, - - [`&-placement-top ${componentCls}-arrow`]: { - left: { - _skip_check_: true, - value: '50%', + ...isInject(!!arrowPlacement.top, { + [[ + `&-placement-top ${componentCls}-arrow`, + `&-placement-topLeft ${componentCls}-arrow`, + `&-placement-topRight ${componentCls}-arrow`, + ].join(',')]: { + bottom: arrowDistance, + transform: 'translateY(100%) rotate(180deg)', }, - transform: 'translateX(-50%) translateY(100%) rotate(180deg)', - }, - [`&-placement-topLeft ${componentCls}-arrow`]: { - left: { - _skip_check_: true, - value: dropdownArrowOffset, + [`&-placement-top ${componentCls}-arrow`]: { + left: { + _skip_check_: true, + value: '50%', + }, + transform: 'translateX(-50%) translateY(100%) rotate(180deg)', }, - }, - [`&-placement-topRight ${componentCls}-arrow`]: { - right: { - _skip_check_: true, - value: dropdownArrowOffset, + [`&-placement-topLeft ${componentCls}-arrow`]: { + left: { + _skip_check_: true, + value: dropdownArrowOffset, + }, }, - }, + + [`&-placement-topRight ${componentCls}-arrow`]: { + right: { + _skip_check_: true, + value: dropdownArrowOffset, + }, + }, + }), // >>>>> Bottom - [[ - `&-placement-bottom ${componentCls}-arrow`, - `&-placement-bottomLeft ${componentCls}-arrow`, - `&-placement-bottomRight ${componentCls}-arrow`, - ].join(',')]: { - top: 0, - transform: `translateY(-100%)`, - }, - - [`&-placement-bottom ${componentCls}-arrow`]: { - left: { - _skip_check_: true, - value: '50%', + ...isInject(!!arrowPlacement.bottom, { + [[ + `&-placement-bottom ${componentCls}-arrow`, + `&-placement-bottomLeft ${componentCls}-arrow`, + `&-placement-bottomRight ${componentCls}-arrow`, + ].join(',')]: { + top: arrowDistance, + transform: `translateY(-100%)`, }, - transform: `translateX(-50%) translateY(-100%)`, - }, - [`&-placement-bottomLeft ${componentCls}-arrow`]: { - left: { - _skip_check_: true, - value: dropdownArrowOffset, + [`&-placement-bottom ${componentCls}-arrow`]: { + left: { + _skip_check_: true, + value: '50%', + }, + transform: `translateX(-50%) translateY(-100%)`, }, - }, - [`&-placement-bottomRight ${componentCls}-arrow`]: { - right: { - _skip_check_: true, - value: dropdownArrowOffset, + [`&-placement-bottomLeft ${componentCls}-arrow`]: { + left: { + _skip_check_: true, + value: dropdownArrowOffset, + }, }, - }, + + [`&-placement-bottomRight ${componentCls}-arrow`]: { + right: { + _skip_check_: true, + value: dropdownArrowOffset, + }, + }, + }), // >>>>> Left - [[ - `&-placement-left ${componentCls}-arrow`, - `&-placement-leftTop ${componentCls}-arrow`, - `&-placement-leftBottom ${componentCls}-arrow`, - ].join(',')]: { - right: { - _skip_check_: true, - value: 0, + ...isInject(!!arrowPlacement.left, { + [[ + `&-placement-left ${componentCls}-arrow`, + `&-placement-leftTop ${componentCls}-arrow`, + `&-placement-leftBottom ${componentCls}-arrow`, + ].join(',')]: { + right: { + _skip_check_: true, + value: arrowDistance, + }, + transform: 'translateX(100%) rotate(90deg)', }, - transform: 'translateX(100%) rotate(90deg)', - }, - [`&-placement-left ${componentCls}-arrow`]: { - top: { - _skip_check_: true, - value: '50%', + [`&-placement-left ${componentCls}-arrow`]: { + top: { + _skip_check_: true, + value: '50%', + }, + transform: 'translateY(-50%) translateX(100%) rotate(90deg)', }, - transform: 'translateY(-50%) translateX(100%) rotate(90deg)', - }, - [`&-placement-leftTop ${componentCls}-arrow`]: { - top: dropdownArrowOffsetVertical, - }, + [`&-placement-leftTop ${componentCls}-arrow`]: { + top: dropdownArrowOffsetVertical, + }, - [`&-placement-leftBottom ${componentCls}-arrow`]: { - bottom: dropdownArrowOffsetVertical, - }, + [`&-placement-leftBottom ${componentCls}-arrow`]: { + bottom: dropdownArrowOffsetVertical, + }, + }), // >>>>> Right - [[ - `&-placement-right ${componentCls}-arrow`, - `&-placement-rightTop ${componentCls}-arrow`, - `&-placement-rightBottom ${componentCls}-arrow`, - ].join(',')]: { - left: { - _skip_check_: true, - value: 0, + ...isInject(!!arrowPlacement.right, { + [[ + `&-placement-right ${componentCls}-arrow`, + `&-placement-rightTop ${componentCls}-arrow`, + `&-placement-rightBottom ${componentCls}-arrow`, + ].join(',')]: { + left: { + _skip_check_: true, + value: arrowDistance, + }, + transform: 'translateX(-100%) rotate(-90deg)', }, - transform: 'translateX(-100%) rotate(-90deg)', - }, - [`&-placement-right ${componentCls}-arrow`]: { - top: { - _skip_check_: true, - value: '50%', + [`&-placement-right ${componentCls}-arrow`]: { + top: { + _skip_check_: true, + value: '50%', + }, + transform: 'translateY(-50%) translateX(-100%) rotate(-90deg)', }, - transform: 'translateY(-50%) translateX(-100%) rotate(-90deg)', - }, - [`&-placement-rightTop ${componentCls}-arrow`]: { - top: dropdownArrowOffsetVertical, - }, - - [`&-placement-rightBottom ${componentCls}-arrow`]: { - bottom: dropdownArrowOffsetVertical, - }, - - // =========================== Offset ============================ - // Offset the popover to account for the dropdown arrow - // >>>>> Top - [connectArrowCls( - [`&-placement-topLeft`, `&-placement-top`, `&-placement-topRight`], - showArrowCls, - )]: { - paddingBottom: dropdownArrowDistance, - }, - - // >>>>> Bottom - [connectArrowCls( - [`&-placement-bottomLeft`, `&-placement-bottom`, `&-placement-bottomRight`], - showArrowCls, - )]: { - paddingTop: dropdownArrowDistance, - }, - - // >>>>> Left - [connectArrowCls( - [`&-placement-leftTop`, `&-placement-left`, `&-placement-leftBottom`], - showArrowCls, - )]: { - paddingRight: { - _skip_check_: true, - value: dropdownArrowDistance, + [`&-placement-rightTop ${componentCls}-arrow`]: { + top: dropdownArrowOffsetVertical, }, - }, - // >>>>> Right - [connectArrowCls( - [`&-placement-rightTop`, `&-placement-right`, `&-placement-rightBottom`], - showArrowCls, - )]: { - paddingLeft: { - _skip_check_: true, - value: dropdownArrowDistance, + [`&-placement-rightBottom ${componentCls}-arrow`]: { + bottom: dropdownArrowOffsetVertical, }, - }, + }), }, }; } diff --git a/components/tooltip/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/tooltip/__tests__/__snapshots__/demo-extend.test.ts.snap index 4bd155f63e..fc3ba7d0ec 100644 --- a/components/tooltip/__tests__/__snapshots__/demo-extend.test.ts.snap +++ b/components/tooltip/__tests__/__snapshots__/demo-extend.test.ts.snap @@ -1,72 +1,495 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`renders ./components/tooltip/demo/arrow-point-at-center.tsx extend context correctly 1`] = ` -Array [ - , -
+ + + +
+
+ +
+ +
- -
-
-
, - , -
-
+ + Top + + +
- -
-
-
, -] + +
+
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+ +
+ +
+
+
+ +
+
+
+
+ +
+ +
+
+
+ +
+
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+ +
+ +
+
+
+ +
+
+
+
+ +
+ +
+
+
+ +
+
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+ +
+ +
+
+
+ +
+
+
+
+ +
+ +
+
+
+ +
+
+
+
+ +
+ +
+
+
+
+ `; exports[`renders ./components/tooltip/demo/auto-adjust-overflow.tsx extend context correctly 1`] = ` diff --git a/components/tooltip/__tests__/__snapshots__/demo.test.ts.snap b/components/tooltip/__tests__/__snapshots__/demo.test.ts.snap index 57ab722a75..4ed3f0352b 100644 --- a/components/tooltip/__tests__/__snapshots__/demo.test.ts.snap +++ b/components/tooltip/__tests__/__snapshots__/demo.test.ts.snap @@ -1,24 +1,183 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`renders ./components/tooltip/demo/arrow-point-at-center.tsx correctly 1`] = ` -Array [ - , - , -] + +
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+ `; exports[`renders ./components/tooltip/demo/auto-adjust-overflow.tsx correctly 1`] = ` diff --git a/components/tooltip/__tests__/__snapshots__/tooltip.test.tsx.snap b/components/tooltip/__tests__/__snapshots__/tooltip.test.tsx.snap index 111d4601f9..f746af46cd 100644 --- a/components/tooltip/__tests__/__snapshots__/tooltip.test.tsx.snap +++ b/components/tooltip/__tests__/__snapshots__/tooltip.test.tsx.snap @@ -45,3 +45,60 @@ exports[`Tooltip should hide when mouse leave antd disabled component Switch 1`] `; + +exports[`Tooltip support arrow props by default 1`] = ` +
+
+ target +
+
+
+
+
+ +
+ +
+
+
+`; + +exports[`Tooltip support arrow props pass false to hide arrow 1`] = ` +
+
+ target +
+
+
+
+ +
+
+
+`; diff --git a/components/tooltip/__tests__/tooltip.test.tsx b/components/tooltip/__tests__/tooltip.test.tsx index 2d53e1d1c5..7775119a9b 100644 --- a/components/tooltip/__tests__/tooltip.test.tsx +++ b/components/tooltip/__tests__/tooltip.test.tsx @@ -16,6 +16,13 @@ import { resetWarned } from '../../_util/warning'; describe('Tooltip', () => { mountTest(Tooltip); rtlTest(Tooltip); + beforeEach(() => { + jest.useFakeTimers(); + }); + afterEach(() => { + jest.useRealTimers(); + jest.clearAllTimers(); + }); beforeAll(() => { spyElementPrototype(HTMLElement, 'offsetParent', { @@ -188,6 +195,7 @@ describe('Tooltip', () => { const arrowWidth = 5; const horizontalArrowShift = 16; const triggerWidth = 200; + const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); const suit = () => { const { container } = render( @@ -227,13 +235,39 @@ describe('Tooltip', () => { ); fireEvent.click(container2.getElementsByTagName('button')[0]); const popupLeftArrowPointAtCenter = parseInt( - container.querySelector('.point-center-element')?.style?.left!, + container2.querySelector('.point-center-element')?.style?.left!, 10, ); expect(popupLeftArrowPointAtCenter - popupLeftDefault).toBe( triggerWidth / 2 - horizontalArrowShift - arrowWidth, ); + + const { container: container3 } = render( + + + , + ); + fireEvent.click(container3.getElementsByTagName('button')[0]); + const popupLeftArrowPointAtCenter2 = parseInt( + container3.querySelector('.point-center-element')?.style?.left!, + 10, + ); + + expect(popupLeftArrowPointAtCenter2 - popupLeftDefault).toBe( + triggerWidth / 2 - horizontalArrowShift - arrowWidth, + ); + expect(warnSpy).toHaveBeenCalledTimes(1); }; (jest.dontMock as any)('rc-trigger', suit); @@ -567,4 +601,22 @@ describe('Tooltip', () => { expect(container.querySelector('.bamboo')).toBeTruthy(); expect(container.querySelector('.ant-tooltip')).toBeTruthy(); }); + + it('support arrow props pass false to hide arrow', () => { + const { container } = render( + +
target
+
, + ); + expect(container).toMatchSnapshot(); + }); + + it('support arrow props by default', () => { + const { container } = render( + +
target
+
, + ); + expect(container).toMatchSnapshot(); + }); }); diff --git a/components/tooltip/demo/arrow-point-at-center.md b/components/tooltip/demo/arrow-point-at-center.md deleted file mode 100644 index 0689a54ea5..0000000000 --- a/components/tooltip/demo/arrow-point-at-center.md +++ /dev/null @@ -1,7 +0,0 @@ -## zh-CN - -设置了 `arrowPointAtCenter` 后,箭头将指向目标元素的中心。 - -## en-US - -By specifying `arrowPointAtCenter` prop, the arrow will point to the center of the target element. diff --git a/components/tooltip/demo/arrow-point-at-center.tsx b/components/tooltip/demo/arrow-point-at-center.tsx deleted file mode 100644 index 4f91aae1af..0000000000 --- a/components/tooltip/demo/arrow-point-at-center.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -import { Button, Tooltip } from 'antd'; - -const App: React.FC = () => ( - <> - - - - - - - -); - -export default App; diff --git a/components/tooltip/demo/arrow.md b/components/tooltip/demo/arrow.md new file mode 100644 index 0000000000..84c5b7341f --- /dev/null +++ b/components/tooltip/demo/arrow.md @@ -0,0 +1,27 @@ +## zh-CN + +通过 `arrow` 属性隐藏箭头。 + +## en-US + +Hide arrow by `arrow`. + + diff --git a/components/tooltip/demo/arrow.tsx b/components/tooltip/demo/arrow.tsx new file mode 100644 index 0000000000..94bbcdbd80 --- /dev/null +++ b/components/tooltip/demo/arrow.tsx @@ -0,0 +1,75 @@ +import React, { useMemo, useState } from 'react'; +import { Button, Divider, Segmented, Tooltip } from 'antd'; + +const text = prompt text; + +const buttonWidth = 70; + +const App: React.FC = () => { + const [showArrow, setShowArrow] = useState(true); + const [arrowAtCenter, setArrowAtCenter] = useState(false); + + const mergedArrow = useMemo(() => { + if (arrowAtCenter) return { arrowPointAtCenter: true }; + return showArrow; + }, [showArrow, arrowAtCenter]); + + return ( +
+ { + setShowArrow(val !== 'Hide'); + setArrowAtCenter(val === 'Center'); + }} + /> + Content +
+ + + + + + + + + +
+
+ + + + + + + + + +
+
+ + + + + + + + + +
+
+ + + + + + + + + +
+
+ ); +}; + +export default App; diff --git a/components/tooltip/index.en-US.md b/components/tooltip/index.en-US.md index b9fca12b13..89e4161caa 100644 --- a/components/tooltip/index.en-US.md +++ b/components/tooltip/index.en-US.md @@ -19,7 +19,7 @@ A simple text popup tip. Basic Placement -Arrow pointing at the center +Arrow Adjust placement automatically Destroy tooltip when hidden Colorful Tooltip @@ -38,7 +38,7 @@ The following APIs are shared by Tooltip, Popconfirm, Popover. | Property | Description | Type | Default | Version | | --- | --- | --- | --- | --- | | align | This value will be merged into placement's config, please refer to the settings [rc-tooltip](https://github.com/react-component/tooltip) | object | - | | -| arrowPointAtCenter | Whether the arrow is pointed at the center of target | boolean | false | | +| arrow | Change arrow's visible state and change whether the arrow is pointed at the center of target. | boolean \| { arrowPointAtCenter: boolean } | true | 5.2.0 | | autoAdjustOverflow | Whether to adjust popup placement automatically when popup is off screen | boolean | true | | | color | The background color | string | - | 4.3.0 | | defaultOpen | Whether the floating tooltip card is open by default | boolean | false | 4.23.0 | diff --git a/components/tooltip/index.tsx b/components/tooltip/index.tsx index 4071638fed..5fa1ebbfa2 100644 --- a/components/tooltip/index.tsx +++ b/components/tooltip/index.tsx @@ -17,6 +17,9 @@ import warning from '../_util/warning'; import PurePanel from './PurePanel'; import useStyle from './style'; import { parseColor } from './util'; +import theme from '../theme'; + +const { useToken } = theme; export type { AdjustOverflow, PlacementsConfig }; @@ -76,7 +79,9 @@ export interface AbstractTooltipProps extends LegacyTooltipProps { placement?: TooltipPlacement; builtinPlacements?: typeof Placements; openClassName?: string; + /** @deprecated Please use `arrow` instead. */ arrowPointAtCenter?: boolean; + arrow?: boolean | { arrowPointAtCenter: boolean }; autoAdjustOverflow?: boolean | AdjustOverflow; getPopupContainer?: (triggerNode: HTMLElement) => HTMLElement; children?: React.ReactNode; @@ -170,8 +175,13 @@ const Tooltip = React.forwardRef((props, ref) => { children, afterOpenChange, afterVisibleChange, + arrow = true, } = props; + const mergedShowArrow = !!arrow; + + const { token } = useToken(); + const { getPopupContainer: getContextPopupContainer, getPrefixCls, @@ -184,6 +194,7 @@ const Tooltip = React.forwardRef((props, ref) => { ['defaultVisible', 'defaultOpen'], ['onVisibleChange', 'onOpenChange'], ['afterVisibleChange', 'afterOpenChange'], + ['arrowPointAtCenter', 'arrow'], ].forEach(([deprecatedName, newName]) => { warning( !(deprecatedName in props), @@ -214,11 +225,17 @@ const Tooltip = React.forwardRef((props, ref) => { const getTooltipPlacements = () => { const { builtinPlacements, arrowPointAtCenter = false, autoAdjustOverflow = true } = props; + + const mergedArrowPointAtCenter = + (typeof arrow !== 'boolean' && arrow?.arrowPointAtCenter) ?? arrowPointAtCenter; + return ( builtinPlacements || getPlacements({ - arrowPointAtCenter, + arrowPointAtCenter: mergedArrowPointAtCenter, autoAdjustOverflow, + arrowWidth: mergedShowArrow ? token.sizePopupArrow : 0, + offset: token.marginXXS, }) ); }; @@ -313,6 +330,7 @@ const Tooltip = React.forwardRef((props, ref) => { return wrapSSR( 基本 位置 -箭头指向 +箭头展示 自动调整位置 隐藏后销毁 多彩文字提示 @@ -40,7 +40,7 @@ demo: | 参数 | 说明 | 类型 | 默认值 | 版本 | | --- | --- | --- | --- | --- | | align | 该值将合并到 placement 的配置中,设置参考 [rc-tooltip](https://github.com/react-component/tooltip) | object | - | | -| arrowPointAtCenter | 箭头是否指向目标元素中心 | boolean | false | | +| arrow | 修改箭头的显示状态以及修改箭头是否指向目标元素中心 | boolean \| { arrowPointAtCenter: boolean } | true | 5.2.0 | | autoAdjustOverflow | 气泡被遮挡时自动调整位置 | boolean | true | | | color | 背景颜色 | string | - | 4.3.0 | | defaultOpen | 默认是否显隐 | boolean | false | 4.23.0 | diff --git a/components/tooltip/style/index.ts b/components/tooltip/style/index.ts index 5304ba7cbf..922c6c919a 100644 --- a/components/tooltip/style/index.ts +++ b/components/tooltip/style/index.ts @@ -107,7 +107,6 @@ const genTooltipStyle: GenerateStyle = (token) => { }), { colorBg: 'var(--antd-arrow-background-color)', - showArrowCls: '', contentRadius: tooltipBorderRadius, limitVerticalRadius: true, }, diff --git a/components/tour/index.tsx b/components/tour/index.tsx index ce9a35acc2..75b3be3b8e 100644 --- a/components/tour/index.tsx +++ b/components/tour/index.tsx @@ -7,6 +7,8 @@ import { ConfigContext } from '../config-provider'; import useStyle from './style'; import type { TourProps, TourStepProps } from './interface'; import PurePanel from './PurePanel'; +import theme from '../theme'; +import getPlacements from '../_util/placements'; const Tour: React.FC & { _InternalPanelDoNotUseOrYouWillBeFired: typeof PurePanel } = ( props, @@ -22,6 +24,14 @@ const Tour: React.FC & { _InternalPanelDoNotUseOrYouWillBeFired: type const { getPrefixCls, direction } = useContext(ConfigContext); const prefixCls = getPrefixCls('tour', customizePrefixCls); const [wrapSSR, hashId] = useStyle(prefixCls); + const { token } = theme.useToken(); + + const builtinPlacements = getPlacements({ + arrowPointAtCenter: true, + autoAdjustOverflow: true, + offset: token.marginXXS, + arrowWidth: token.sizePopupArrow, + }); const customClassName = classNames( { @@ -43,6 +53,7 @@ const Tour: React.FC & { _InternalPanelDoNotUseOrYouWillBeFired: type current={current} animated renderPanel={mergedRenderPanel} + builtinPlacements={builtinPlacements} />, ); }; diff --git a/components/tour/style/index.ts b/components/tour/style/index.ts index 29eadff0cb..cd2079bdd8 100644 --- a/components/tour/style/index.ts +++ b/components/tour/style/index.ts @@ -230,7 +230,6 @@ const genBaseStyle: GenerateStyle = (token) => { // ============================= Arrow =========================== getArrowStyle(token, { colorBg: 'var(--antd-arrow-background-color)', - showArrowCls: '', contentRadius: tourBorderRadius, limitVerticalRadius: true, }), diff --git a/package.json b/package.json index 896a93a012..24e63c697c 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,7 @@ "@babel/runtime": "^7.18.3", "@ctrl/tinycolor": "^3.4.0", "@rc-component/mutate-observer": "^1.0.0", - "@rc-component/tour": "~1.5.0", + "@rc-component/tour": "~1.6.0", "classnames": "^2.2.6", "copy-to-clipboard": "^3.2.0", "dayjs": "^1.11.1", @@ -145,7 +145,7 @@ "rc-table": "~7.30.2", "rc-tabs": "~12.5.1", "rc-textarea": "~1.0.0", - "rc-tooltip": "~5.2.0", + "rc-tooltip": "~5.3.1", "rc-tree": "~5.7.0", "rc-tree-select": "~5.6.0", "rc-trigger": "^5.3.4",