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>
This commit is contained in:
kiner-tang(文辉) 2023-01-19 11:21:05 +08:00 committed by GitHub
parent ede5121d5f
commit 8b3868ac63
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 2036 additions and 472 deletions

View File

@ -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),
};

View File

@ -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<any>;
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(() => {

View File

@ -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<DropdownToken> = (token) => {
menuCls,
zIndexPopup,
dropdownArrowDistance,
dropdownArrowOffset,
sizePopupArrow,
antCls,
iconCls,
@ -46,7 +45,6 @@ const genBaseStyle: GenerateStyle<DropdownToken> = (token) => {
fontSizeIcon,
controlPaddingHorizontal,
colorBgElevated,
boxShadowPopoverArrow,
} = token;
return [
@ -99,103 +97,6 @@ const genBaseStyle: GenerateStyle<DropdownToken> = (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<DropdownToken> = (token) => {
},
},
// =============================================================
// == Arrow style ==
// =============================================================
getArrowStyle<DropdownToken>(token, {
colorBg: colorBgElevated,
limitVerticalRadius: true,
arrowPlacement: { top: true, bottom: true },
}),
{
// =============================================================
// == Menu ==

View File

@ -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 [
<button
class="ant-btn ant-btn-default"
type="button"
exports[`renders ./components/popover/demo/arrow.tsx extend context correctly 1`] = `
<div
class="demo"
>
<div
class="ant-segmented"
>
<span>
Align edge / 边缘对齐
</span>
</button>,
<div>
<div
class="ant-popover"
style="opacity:0"
class="ant-segmented-group"
>
<label
class="ant-segmented-item ant-segmented-item-selected"
>
<input
checked=""
class="ant-segmented-item-input"
type="radio"
/>
<div
class="ant-segmented-item-label"
title="Show"
>
Show
</div>
</label>
<label
class="ant-segmented-item"
>
<input
class="ant-segmented-item-input"
type="radio"
/>
<div
class="ant-segmented-item-label"
title="Hide"
>
Hide
</div>
</label>
<label
class="ant-segmented-item"
>
<input
class="ant-segmented-item-input"
type="radio"
/>
<div
class="ant-segmented-item-label"
title="Center"
>
Center
</div>
</label>
</div>
</div>
<div
class="ant-divider ant-divider-horizontal ant-divider-with-text ant-divider-with-text-center"
role="separator"
>
<span
class="ant-divider-inner-text"
>
Content
</span>
</div>
<div
style="margin-left:70px;white-space:nowrap"
>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
TL
</span>
</button>
<div>
<div
class="ant-popover-content"
class="ant-popover"
style="opacity:0"
>
<div
class="ant-popover-arrow"
>
<span
class="ant-popover-arrow-content"
/>
</div>
<div
class="ant-popover-inner"
role="tooltip"
class="ant-popover-content"
>
<div
class="ant-popover-title"
class="ant-popover-arrow"
>
Title
<span
class="ant-popover-arrow-content"
/>
</div>
<div
class="ant-popover-inner-content"
class="ant-popover-inner"
role="tooltip"
>
<p>
Content
</p>
<p>
Content
</p>
<div
class="ant-popover-title"
>
<span>
Title
</span>
</div>
<div
class="ant-popover-inner-content"
>
<div>
<p>
Content
</p>
<p>
Content
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>,
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
Arrow points to center / 箭头指向中心
</span>
</button>,
<div>
<div
class="ant-popover"
style="opacity:0"
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
Top
</span>
</button>
<div>
<div
class="ant-popover-content"
class="ant-popover"
style="opacity:0"
>
<div
class="ant-popover-arrow"
>
<span
class="ant-popover-arrow-content"
/>
</div>
<div
class="ant-popover-inner"
role="tooltip"
class="ant-popover-content"
>
<div
class="ant-popover-title"
class="ant-popover-arrow"
>
Title
<span
class="ant-popover-arrow-content"
/>
</div>
<div
class="ant-popover-inner-content"
class="ant-popover-inner"
role="tooltip"
>
<p>
Content
</p>
<p>
Content
</p>
<div
class="ant-popover-title"
>
<span>
Title
</span>
</div>
<div
class="ant-popover-inner-content"
>
<div>
<p>
Content
</p>
<p>
Content
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>,
]
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
TR
</span>
</button>
<div>
<div
class="ant-popover"
style="opacity:0"
>
<div
class="ant-popover-content"
>
<div
class="ant-popover-arrow"
>
<span
class="ant-popover-arrow-content"
/>
</div>
<div
class="ant-popover-inner"
role="tooltip"
>
<div
class="ant-popover-title"
>
<span>
Title
</span>
</div>
<div
class="ant-popover-inner-content"
>
<div>
<p>
Content
</p>
<p>
Content
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div
style="width:70px;float:left"
>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
LT
</span>
</button>
<div>
<div
class="ant-popover"
style="opacity:0"
>
<div
class="ant-popover-content"
>
<div
class="ant-popover-arrow"
>
<span
class="ant-popover-arrow-content"
/>
</div>
<div
class="ant-popover-inner"
role="tooltip"
>
<div
class="ant-popover-title"
>
<span>
Title
</span>
</div>
<div
class="ant-popover-inner-content"
>
<div>
<p>
Content
</p>
<p>
Content
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
Left
</span>
</button>
<div>
<div
class="ant-popover"
style="opacity:0"
>
<div
class="ant-popover-content"
>
<div
class="ant-popover-arrow"
>
<span
class="ant-popover-arrow-content"
/>
</div>
<div
class="ant-popover-inner"
role="tooltip"
>
<div
class="ant-popover-title"
>
<span>
Title
</span>
</div>
<div
class="ant-popover-inner-content"
>
<div>
<p>
Content
</p>
<p>
Content
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
LB
</span>
</button>
<div>
<div
class="ant-popover"
style="opacity:0"
>
<div
class="ant-popover-content"
>
<div
class="ant-popover-arrow"
>
<span
class="ant-popover-arrow-content"
/>
</div>
<div
class="ant-popover-inner"
role="tooltip"
>
<div
class="ant-popover-title"
>
<span>
Title
</span>
</div>
<div
class="ant-popover-inner-content"
>
<div>
<p>
Content
</p>
<p>
Content
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div
style="width:70px;margin-left:304px"
>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
RT
</span>
</button>
<div>
<div
class="ant-popover"
style="opacity:0"
>
<div
class="ant-popover-content"
>
<div
class="ant-popover-arrow"
>
<span
class="ant-popover-arrow-content"
/>
</div>
<div
class="ant-popover-inner"
role="tooltip"
>
<div
class="ant-popover-title"
>
<span>
Title
</span>
</div>
<div
class="ant-popover-inner-content"
>
<div>
<p>
Content
</p>
<p>
Content
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
Right
</span>
</button>
<div>
<div
class="ant-popover"
style="opacity:0"
>
<div
class="ant-popover-content"
>
<div
class="ant-popover-arrow"
>
<span
class="ant-popover-arrow-content"
/>
</div>
<div
class="ant-popover-inner"
role="tooltip"
>
<div
class="ant-popover-title"
>
<span>
Title
</span>
</div>
<div
class="ant-popover-inner-content"
>
<div>
<p>
Content
</p>
<p>
Content
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
RB
</span>
</button>
<div>
<div
class="ant-popover"
style="opacity:0"
>
<div
class="ant-popover-content"
>
<div
class="ant-popover-arrow"
>
<span
class="ant-popover-arrow-content"
/>
</div>
<div
class="ant-popover-inner"
role="tooltip"
>
<div
class="ant-popover-title"
>
<span>
Title
</span>
</div>
<div
class="ant-popover-inner-content"
>
<div>
<p>
Content
</p>
<p>
Content
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div
style="margin-left:70px;clear:both;white-space:nowrap"
>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
BL
</span>
</button>
<div>
<div
class="ant-popover"
style="opacity:0"
>
<div
class="ant-popover-content"
>
<div
class="ant-popover-arrow"
>
<span
class="ant-popover-arrow-content"
/>
</div>
<div
class="ant-popover-inner"
role="tooltip"
>
<div
class="ant-popover-title"
>
<span>
Title
</span>
</div>
<div
class="ant-popover-inner-content"
>
<div>
<p>
Content
</p>
<p>
Content
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
Bottom
</span>
</button>
<div>
<div
class="ant-popover"
style="opacity:0"
>
<div
class="ant-popover-content"
>
<div
class="ant-popover-arrow"
>
<span
class="ant-popover-arrow-content"
/>
</div>
<div
class="ant-popover-inner"
role="tooltip"
>
<div
class="ant-popover-title"
>
<span>
Title
</span>
</div>
<div
class="ant-popover-inner-content"
>
<div>
<p>
Content
</p>
<p>
Content
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
BR
</span>
</button>
<div>
<div
class="ant-popover"
style="opacity:0"
>
<div
class="ant-popover-content"
>
<div
class="ant-popover-arrow"
>
<span
class="ant-popover-arrow-content"
/>
</div>
<div
class="ant-popover-inner"
role="tooltip"
>
<div
class="ant-popover-title"
>
<span>
Title
</span>
</div>
<div
class="ant-popover-inner-content"
>
<div>
<p>
Content
</p>
<p>
Content
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`renders ./components/popover/demo/basic.tsx extend context correctly 1`] = `

View File

@ -1,24 +1,183 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders ./components/popover/demo/arrow-point-at-center.tsx correctly 1`] = `
Array [
<button
class="ant-btn ant-btn-default"
type="button"
exports[`renders ./components/popover/demo/arrow.tsx correctly 1`] = `
<div
class="demo"
>
<div
class="ant-segmented"
>
<span>
Align edge / 边缘对齐
</span>
</button>,
<button
class="ant-btn ant-btn-default"
type="button"
<div
class="ant-segmented-group"
>
<label
class="ant-segmented-item ant-segmented-item-selected"
>
<input
checked=""
class="ant-segmented-item-input"
type="radio"
/>
<div
class="ant-segmented-item-label"
title="Show"
>
Show
</div>
</label>
<label
class="ant-segmented-item"
>
<input
class="ant-segmented-item-input"
type="radio"
/>
<div
class="ant-segmented-item-label"
title="Hide"
>
Hide
</div>
</label>
<label
class="ant-segmented-item"
>
<input
class="ant-segmented-item-input"
type="radio"
/>
<div
class="ant-segmented-item-label"
title="Center"
>
Center
</div>
</label>
</div>
</div>
<div
class="ant-divider ant-divider-horizontal ant-divider-with-text ant-divider-with-text-center"
role="separator"
>
<span>
Arrow points to center / 箭头指向中心
<span
class="ant-divider-inner-text"
>
Content
</span>
</button>,
]
</div>
<div
style="margin-left:70px;white-space:nowrap"
>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
TL
</span>
</button>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
Top
</span>
</button>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
TR
</span>
</button>
</div>
<div
style="width:70px;float:left"
>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
LT
</span>
</button>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
Left
</span>
</button>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
LB
</span>
</button>
</div>
<div
style="width:70px;margin-left:304px"
>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
RT
</span>
</button>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
Right
</span>
</button>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
RB
</span>
</button>
</div>
<div
style="margin-left:70px;clear:both;white-space:nowrap"
>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
BL
</span>
</button>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
Bottom
</span>
</button>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
BR
</span>
</button>
</div>
</div>
`;
exports[`renders ./components/popover/demo/basic.tsx correctly 1`] = `

View File

@ -1,7 +0,0 @@
## zh-CN
设置了 `arrowPointAtCenter` 后,箭头将指向目标元素的中心。
## en-US
The arrow points to the center of the target element, which set `arrowPointAtCenter`.

View File

@ -1,22 +0,0 @@
import React from 'react';
import { Button, Popover } from 'antd';
const content = (
<>
<p>Content</p>
<p>Content</p>
</>
);
const App: React.FC = () => (
<>
<Popover placement="topLeft" title="Title" content={content}>
<Button>Align edge / </Button>
</Popover>
<Popover placement="topLeft" title="Title" content={content} arrowPointAtCenter>
<Button>Arrow points to center / </Button>
</Popover>
</>
);
export default App;

View File

@ -0,0 +1,27 @@
## zh-CN
通过 `arrow` 属性隐藏箭头。
## en-US
Hide arrow by `arrow`.
<style>
.code-box-demo .demo {
overflow: auto;
}
.code-box-demo .ant-btn {
margin-right: 8px;
margin-bottom: 8px;
}
.code-box-demo .ant-btn-rtl {
margin-right: 0;
margin-left: 8px;
margin-bottom: 8px;
}
#components-popover-demo-arrow .ant-btn {
width: 70px;
text-align: center;
padding: 0;
}
</style>

View File

@ -0,0 +1,81 @@
import React, { useMemo, useState } from 'react';
import { Button, Divider, Popover, Segmented } from 'antd';
const text = <span>Title</span>;
const content = (
<div>
<p>Content</p>
<p>Content</p>
</div>
);
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 (
<div className="demo">
<Segmented
options={['Show', 'Hide', 'Center']}
onChange={(val) => {
setShowArrow(val !== 'Hide');
setArrowAtCenter(val === 'Center');
}}
/>
<Divider orientation="center">Content</Divider>
<div style={{ marginLeft: buttonWidth, whiteSpace: 'nowrap' }}>
<Popover placement="topLeft" title={text} content={content} arrow={mergedArrow}>
<Button>TL</Button>
</Popover>
<Popover placement="top" title={text} content={content} arrow={mergedArrow}>
<Button>Top</Button>
</Popover>
<Popover placement="topRight" title={text} content={content} arrow={mergedArrow}>
<Button>TR</Button>
</Popover>
</div>
<div style={{ width: buttonWidth, float: 'left' }}>
<Popover placement="leftTop" title={text} content={content} arrow={mergedArrow}>
<Button>LT</Button>
</Popover>
<Popover placement="left" title={text} content={content} arrow={mergedArrow}>
<Button>Left</Button>
</Popover>
<Popover placement="leftBottom" title={text} content={content} arrow={mergedArrow}>
<Button>LB</Button>
</Popover>
</div>
<div style={{ width: buttonWidth, marginLeft: buttonWidth * 4 + 24 }}>
<Popover placement="rightTop" title={text} content={content} arrow={mergedArrow}>
<Button>RT</Button>
</Popover>
<Popover placement="right" title={text} content={content} arrow={mergedArrow}>
<Button>Right</Button>
</Popover>
<Popover placement="rightBottom" title={text} content={content} arrow={mergedArrow}>
<Button>RB</Button>
</Popover>
</div>
<div style={{ marginLeft: buttonWidth, clear: 'both', whiteSpace: 'nowrap' }}>
<Popover placement="bottomLeft" title={text} content={content} arrow={mergedArrow}>
<Button>BL</Button>
</Popover>
<Popover placement="bottom" title={text} content={content} arrow={mergedArrow}>
<Button>Bottom</Button>
</Popover>
<Popover placement="bottomRight" title={text} content={content} arrow={mergedArrow}>
<Button>BR</Button>
</Popover>
</div>
</div>
);
};
export default App;

View File

@ -21,8 +21,8 @@ Comparing with `Tooltip`, besides information `Popover` card can also provide ac
<code src="./demo/basic.tsx">Basic</code>
<code src="./demo/triggerType.tsx">Three ways to trigger</code>
<code src="./demo/placement.tsx">Placement</code>
<code src="./demo/arrow.tsx">Arrow</code>
<code src="./demo/control.tsx">Controlling the close of the dialog</code>
<code src="./demo/arrow-point-at-center.tsx">Arrow pointing</code>
<code src="./demo/hover-with-click.tsx">Hover with click popover</code>
<code src="./demo/render-panel.tsx" debug>_InternalPanelDoNotUseOrYouWillBeFired</code>
<code src="./demo/wireframe.tsx" debug>Wireframe</code>

View File

@ -44,6 +44,8 @@ const Popover = React.forwardRef<unknown, PopoverProps>((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<unknown, PopoverProps>((props, ref) => {
const overlayCls = classNames(overlayClassName, hashId);
const mergedArrowPointAtCenter =
(typeof arrow !== 'boolean' && arrow?.arrowPointAtCenter) ?? arrowPointAtCenter ?? false;
const mergedArrow = arrow ?? { arrowPointAtCenter: mergedArrowPointAtCenter };
return wrapSSR(
<Tooltip
placement={placement}
arrow={mergedArrow}
trigger={trigger}
mouseEnterDelay={mouseEnterDelay}
mouseLeaveDelay={mouseLeaveDelay}

View File

@ -22,8 +22,8 @@ demo:
<code src="./demo/basic.tsx">基本</code>
<code src="./demo/triggerType.tsx">三种触发方式</code>
<code src="./demo/placement.tsx">位置</code>
<code src="./demo/arrow.tsx">箭头展示</code>
<code src="./demo/control.tsx">从浮层内关闭</code>
<code src="./demo/arrow-point-at-center.tsx">箭头指向</code>
<code src="./demo/hover-with-click.tsx">悬停点击弹出窗口</code>
<code src="./demo/render-panel.tsx" debug>_InternalPanelDoNotUseOrYouWillBeFired</code>
<code src="./demo/wireframe.tsx" debug>线框风格</code>

View File

@ -84,7 +84,9 @@ const genBaseStyle: GenerateStyle<PopoverToken> = (token) => {
},
// Arrow Style
getArrowStyle(token, { colorBg: 'var(--antd-arrow-background-color)' }),
getArrowStyle(token, {
colorBg: 'var(--antd-arrow-background-color)',
}),
// Pure Render
{

View File

@ -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 extends TokenWithCommonCls<AliasToken>>(
token: Token,
options: {
@ -32,22 +33,29 @@ export default function getArrowStyle<Token extends TokenWithCommonCls<AliasToke
showArrowCls?: string;
contentRadius?: number;
limitVerticalRadius?: boolean;
arrowDistance?: number;
arrowPlacement?: {
left?: boolean;
right?: boolean;
top?: boolean;
bottom?: boolean;
};
},
): CSSInterpolation {
const {
componentCls,
sizePopupArrow,
marginXXS,
borderRadiusXS,
borderRadiusOuter,
boxShadowPopoverArrow,
} = token;
const { componentCls, sizePopupArrow, borderRadiusXS, borderRadiusOuter, boxShadowPopoverArrow } =
token;
const {
colorBg,
showArrowCls,
contentRadius = token.borderRadiusLG,
limitVerticalRadius,
arrowDistance = 0,
arrowPlacement = {
left: true,
right: true,
top: true,
bottom: true,
},
} = options;
const { dropdownArrowOffsetVertical, dropdownArrowOffset } = getArrowOffset({
@ -56,7 +64,6 @@ export default function getArrowStyle<Token extends TokenWithCommonCls<AliasToke
borderRadiusOuter,
limitVerticalRadius,
});
const dropdownArrowDistance = sizePopupArrow / 2 + marginXXS;
return {
[componentCls]: {
@ -84,166 +91,134 @@ export default function getArrowStyle<Token extends TokenWithCommonCls<AliasToke
// ========================== Placement ==========================
// Here handle the arrow position and rotate stuff
// >>>>> 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,
},
},
}),
},
};
}

View File

@ -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 [
<button
class="ant-btn ant-btn-default"
type="button"
exports[`renders ./components/tooltip/demo/arrow.tsx extend context correctly 1`] = `
<div
class="demo"
>
<div
class="ant-segmented"
>
<span>
Align edge / 边缘对齐
</span>
</button>,
<div>
<div
class="ant-tooltip"
style="opacity:0"
class="ant-segmented-group"
>
<label
class="ant-segmented-item ant-segmented-item-selected"
>
<input
checked=""
class="ant-segmented-item-input"
type="radio"
/>
<div
class="ant-segmented-item-label"
title="Show"
>
Show
</div>
</label>
<label
class="ant-segmented-item"
>
<input
class="ant-segmented-item-input"
type="radio"
/>
<div
class="ant-segmented-item-label"
title="Hide"
>
Hide
</div>
</label>
<label
class="ant-segmented-item"
>
<input
class="ant-segmented-item-input"
type="radio"
/>
<div
class="ant-segmented-item-label"
title="Center"
>
Center
</div>
</label>
</div>
</div>
<div
class="ant-divider ant-divider-horizontal ant-divider-with-text ant-divider-with-text-center"
role="separator"
>
<span
class="ant-divider-inner-text"
>
Content
</span>
</div>
<div
style="margin-left:70px;white-space:nowrap"
>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
TL
</span>
</button>
<div>
<div
class="ant-tooltip-content"
class="ant-tooltip"
style="opacity:0"
>
<div
class="ant-tooltip-arrow"
class="ant-tooltip-content"
>
<span
class="ant-tooltip-arrow-content"
/>
</div>
<div
class="ant-tooltip-inner"
role="tooltip"
>
Prompt Text
<div
class="ant-tooltip-arrow"
>
<span
class="ant-tooltip-arrow-content"
/>
</div>
<div
class="ant-tooltip-inner"
role="tooltip"
>
<span>
prompt text
</span>
</div>
</div>
</div>
</div>
</div>,
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
Arrow points to center / 箭头指向中心
</span>
</button>,
<div>
<div
class="ant-tooltip"
style="opacity:0"
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
Top
</span>
</button>
<div>
<div
class="ant-tooltip-content"
class="ant-tooltip"
style="opacity:0"
>
<div
class="ant-tooltip-arrow"
class="ant-tooltip-content"
>
<span
class="ant-tooltip-arrow-content"
/>
</div>
<div
class="ant-tooltip-inner"
role="tooltip"
>
Prompt Text
<div
class="ant-tooltip-arrow"
>
<span
class="ant-tooltip-arrow-content"
/>
</div>
<div
class="ant-tooltip-inner"
role="tooltip"
>
<span>
prompt text
</span>
</div>
</div>
</div>
</div>
</div>,
]
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
TR
</span>
</button>
<div>
<div
class="ant-tooltip"
style="opacity:0"
>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-arrow"
>
<span
class="ant-tooltip-arrow-content"
/>
</div>
<div
class="ant-tooltip-inner"
role="tooltip"
>
<span>
prompt text
</span>
</div>
</div>
</div>
</div>
</div>
<div
style="width:70px;float:left"
>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
LT
</span>
</button>
<div>
<div
class="ant-tooltip"
style="opacity:0"
>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-arrow"
>
<span
class="ant-tooltip-arrow-content"
/>
</div>
<div
class="ant-tooltip-inner"
role="tooltip"
>
<span>
prompt text
</span>
</div>
</div>
</div>
</div>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
Left
</span>
</button>
<div>
<div
class="ant-tooltip"
style="opacity:0"
>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-arrow"
>
<span
class="ant-tooltip-arrow-content"
/>
</div>
<div
class="ant-tooltip-inner"
role="tooltip"
>
<span>
prompt text
</span>
</div>
</div>
</div>
</div>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
LB
</span>
</button>
<div>
<div
class="ant-tooltip"
style="opacity:0"
>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-arrow"
>
<span
class="ant-tooltip-arrow-content"
/>
</div>
<div
class="ant-tooltip-inner"
role="tooltip"
>
<span>
prompt text
</span>
</div>
</div>
</div>
</div>
</div>
<div
style="width:70px;margin-left:304px"
>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
RT
</span>
</button>
<div>
<div
class="ant-tooltip"
style="opacity:0"
>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-arrow"
>
<span
class="ant-tooltip-arrow-content"
/>
</div>
<div
class="ant-tooltip-inner"
role="tooltip"
>
<span>
prompt text
</span>
</div>
</div>
</div>
</div>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
Right
</span>
</button>
<div>
<div
class="ant-tooltip"
style="opacity:0"
>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-arrow"
>
<span
class="ant-tooltip-arrow-content"
/>
</div>
<div
class="ant-tooltip-inner"
role="tooltip"
>
<span>
prompt text
</span>
</div>
</div>
</div>
</div>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
RB
</span>
</button>
<div>
<div
class="ant-tooltip"
style="opacity:0"
>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-arrow"
>
<span
class="ant-tooltip-arrow-content"
/>
</div>
<div
class="ant-tooltip-inner"
role="tooltip"
>
<span>
prompt text
</span>
</div>
</div>
</div>
</div>
</div>
<div
style="margin-left:70px;clear:both;white-space:nowrap"
>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
BL
</span>
</button>
<div>
<div
class="ant-tooltip"
style="opacity:0"
>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-arrow"
>
<span
class="ant-tooltip-arrow-content"
/>
</div>
<div
class="ant-tooltip-inner"
role="tooltip"
>
<span>
prompt text
</span>
</div>
</div>
</div>
</div>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
Bottom
</span>
</button>
<div>
<div
class="ant-tooltip"
style="opacity:0"
>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-arrow"
>
<span
class="ant-tooltip-arrow-content"
/>
</div>
<div
class="ant-tooltip-inner"
role="tooltip"
>
<span>
prompt text
</span>
</div>
</div>
</div>
</div>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
BR
</span>
</button>
<div>
<div
class="ant-tooltip"
style="opacity:0"
>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-arrow"
>
<span
class="ant-tooltip-arrow-content"
/>
</div>
<div
class="ant-tooltip-inner"
role="tooltip"
>
<span>
prompt text
</span>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`renders ./components/tooltip/demo/auto-adjust-overflow.tsx extend context correctly 1`] = `

View File

@ -1,24 +1,183 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders ./components/tooltip/demo/arrow-point-at-center.tsx correctly 1`] = `
Array [
<button
class="ant-btn ant-btn-default"
type="button"
exports[`renders ./components/tooltip/demo/arrow.tsx correctly 1`] = `
<div
class="demo"
>
<div
class="ant-segmented"
>
<span>
Align edge / 边缘对齐
</span>
</button>,
<button
class="ant-btn ant-btn-default"
type="button"
<div
class="ant-segmented-group"
>
<label
class="ant-segmented-item ant-segmented-item-selected"
>
<input
checked=""
class="ant-segmented-item-input"
type="radio"
/>
<div
class="ant-segmented-item-label"
title="Show"
>
Show
</div>
</label>
<label
class="ant-segmented-item"
>
<input
class="ant-segmented-item-input"
type="radio"
/>
<div
class="ant-segmented-item-label"
title="Hide"
>
Hide
</div>
</label>
<label
class="ant-segmented-item"
>
<input
class="ant-segmented-item-input"
type="radio"
/>
<div
class="ant-segmented-item-label"
title="Center"
>
Center
</div>
</label>
</div>
</div>
<div
class="ant-divider ant-divider-horizontal ant-divider-with-text ant-divider-with-text-center"
role="separator"
>
<span>
Arrow points to center / 箭头指向中心
<span
class="ant-divider-inner-text"
>
Content
</span>
</button>,
]
</div>
<div
style="margin-left:70px;white-space:nowrap"
>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
TL
</span>
</button>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
Top
</span>
</button>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
TR
</span>
</button>
</div>
<div
style="width:70px;float:left"
>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
LT
</span>
</button>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
Left
</span>
</button>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
LB
</span>
</button>
</div>
<div
style="width:70px;margin-left:304px"
>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
RT
</span>
</button>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
Right
</span>
</button>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
RB
</span>
</button>
</div>
<div
style="margin-left:70px;clear:both;white-space:nowrap"
>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
BL
</span>
</button>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
Bottom
</span>
</button>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
BR
</span>
</button>
</div>
</div>
`;
exports[`renders ./components/tooltip/demo/auto-adjust-overflow.tsx correctly 1`] = `

View File

@ -45,3 +45,60 @@ exports[`Tooltip should hide when mouse leave antd disabled component Switch 1`]
</button>
</span>
`;
exports[`Tooltip support arrow props by default 1`] = `
<div>
<div
class="target ant-tooltip-open"
>
target
</div>
<div>
<div
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast"
style="opacity: 0;"
>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-arrow"
>
<span
class="ant-tooltip-arrow-content"
/>
</div>
<div
class="ant-tooltip-inner"
role="tooltip"
/>
</div>
</div>
</div>
</div>
`;
exports[`Tooltip support arrow props pass false to hide arrow 1`] = `
<div>
<div
class="target ant-tooltip-open"
>
target
</div>
<div>
<div
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast"
style="opacity: 0;"
>
<div
class="ant-tooltip-content"
>
<div
class="ant-tooltip-inner"
role="tooltip"
/>
</div>
</div>
</div>
</div>
`;

View File

@ -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<HTMLDivElement>('.point-center-element')?.style?.left!,
container2.querySelector<HTMLDivElement>('.point-center-element')?.style?.left!,
10,
);
expect(popupLeftArrowPointAtCenter - popupLeftDefault).toBe(
triggerWidth / 2 - horizontalArrowShift - arrowWidth,
);
const { container: container3 } = render(
<Tooltip
title="xxxxx"
trigger="click"
mouseEnterDelay={0}
mouseLeaveDelay={0}
placement="bottomLeft"
arrow={{ arrowPointAtCenter: true }}
overlayClassName="point-center-element"
>
<button type="button" style={{ width: triggerWidth }}>
Hello world!
</button>
</Tooltip>,
);
fireEvent.click(container3.getElementsByTagName('button')[0]);
const popupLeftArrowPointAtCenter2 = parseInt(
container3.querySelector<HTMLDivElement>('.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(
<Tooltip open arrow={false}>
<div className="target">target</div>
</Tooltip>,
);
expect(container).toMatchSnapshot();
});
it('support arrow props by default', () => {
const { container } = render(
<Tooltip open>
<div className="target">target</div>
</Tooltip>,
);
expect(container).toMatchSnapshot();
});
});

View File

@ -1,7 +0,0 @@
## zh-CN
设置了 `arrowPointAtCenter` 后,箭头将指向目标元素的中心。
## en-US
By specifying `arrowPointAtCenter` prop, the arrow will point to the center of the target element.

View File

@ -1,15 +0,0 @@
import React from 'react';
import { Button, Tooltip } from 'antd';
const App: React.FC = () => (
<>
<Tooltip placement="topLeft" title="Prompt Text">
<Button>Align edge / </Button>
</Tooltip>
<Tooltip placement="topLeft" title="Prompt Text" arrowPointAtCenter>
<Button>Arrow points to center / </Button>
</Tooltip>
</>
);
export default App;

View File

@ -0,0 +1,27 @@
## zh-CN
通过 `arrow` 属性隐藏箭头。
## en-US
Hide arrow by `arrow`.
<style>
.code-box-demo .demo {
overflow: auto;
}
.code-box-demo .ant-btn {
margin-right: 8px;
margin-bottom: 8px;
}
.code-box-demo .ant-btn-rtl {
margin-right: 0;
margin-left: 8px;
margin-bottom: 8px;
}
#components-tooltip-demo-arrow .ant-btn {
width: 70px;
text-align: center;
padding: 0;
}
</style>

View File

@ -0,0 +1,75 @@
import React, { useMemo, useState } from 'react';
import { Button, Divider, Segmented, Tooltip } from 'antd';
const text = <span>prompt text</span>;
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 (
<div className="demo">
<Segmented
options={['Show', 'Hide', 'Center']}
onChange={(val) => {
setShowArrow(val !== 'Hide');
setArrowAtCenter(val === 'Center');
}}
/>
<Divider orientation="center">Content</Divider>
<div style={{ marginLeft: buttonWidth, whiteSpace: 'nowrap' }}>
<Tooltip placement="topLeft" title={text} arrow={mergedArrow}>
<Button>TL</Button>
</Tooltip>
<Tooltip placement="top" title={text} arrow={mergedArrow}>
<Button>Top</Button>
</Tooltip>
<Tooltip placement="topRight" title={text} arrow={mergedArrow}>
<Button>TR</Button>
</Tooltip>
</div>
<div style={{ width: buttonWidth, float: 'left' }}>
<Tooltip placement="leftTop" title={text} arrow={mergedArrow}>
<Button>LT</Button>
</Tooltip>
<Tooltip placement="left" title={text} arrow={mergedArrow}>
<Button>Left</Button>
</Tooltip>
<Tooltip placement="leftBottom" title={text} arrow={mergedArrow}>
<Button>LB</Button>
</Tooltip>
</div>
<div style={{ width: buttonWidth, marginLeft: buttonWidth * 4 + 24 }}>
<Tooltip placement="rightTop" title={text} arrow={mergedArrow}>
<Button>RT</Button>
</Tooltip>
<Tooltip placement="right" title={text} arrow={mergedArrow}>
<Button>Right</Button>
</Tooltip>
<Tooltip placement="rightBottom" title={text} arrow={mergedArrow}>
<Button>RB</Button>
</Tooltip>
</div>
<div style={{ marginLeft: buttonWidth, clear: 'both', whiteSpace: 'nowrap' }}>
<Tooltip placement="bottomLeft" title={text} arrow={mergedArrow}>
<Button>BL</Button>
</Tooltip>
<Tooltip placement="bottom" title={text} arrow={mergedArrow}>
<Button>Bottom</Button>
</Tooltip>
<Tooltip placement="bottomRight" title={text} arrow={mergedArrow}>
<Button>BR</Button>
</Tooltip>
</div>
</div>
);
};
export default App;

View File

@ -19,7 +19,7 @@ A simple text popup tip.
<!-- prettier-ignore -->
<code src="./demo/basic.tsx">Basic</code>
<code src="./demo/placement.tsx">Placement</code>
<code src="./demo/arrow-point-at-center.tsx">Arrow pointing at the center</code>
<code src="./demo/arrow.tsx">Arrow</code>
<code src="./demo/auto-adjust-overflow.tsx" debug>Adjust placement automatically</code>
<code src="./demo/destroy-tooltip-on-hide.tsx" debug>Destroy tooltip when hidden</code>
<code src="./demo/colorful.tsx">Colorful Tooltip</code>
@ -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 |

View File

@ -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<unknown, TooltipProps>((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<unknown, TooltipProps>((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<unknown, TooltipProps>((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<unknown, TooltipProps>((props, ref) => {
return wrapSSR(
<RcTooltip
{...otherProps}
showArrow={mergedShowArrow}
placement={placement}
mouseEnterDelay={mouseEnterDelay}
mouseLeaveDelay={mouseLeaveDelay}

View File

@ -21,7 +21,7 @@ demo:
<!-- prettier-ignore -->
<code src="./demo/basic.tsx">基本</code>
<code src="./demo/placement.tsx">位置</code>
<code src="./demo/arrow-point-at-center.tsx">箭头指向</code>
<code src="./demo/arrow.tsx">箭头展示</code>
<code src="./demo/auto-adjust-overflow.tsx" debug>自动调整位置</code>
<code src="./demo/destroy-tooltip-on-hide.tsx" debug>隐藏后销毁</code>
<code src="./demo/colorful.tsx">多彩文字提示</code>
@ -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 |

View File

@ -107,7 +107,6 @@ const genTooltipStyle: GenerateStyle<TooltipToken> = (token) => {
}),
{
colorBg: 'var(--antd-arrow-background-color)',
showArrowCls: '',
contentRadius: tooltipBorderRadius,
limitVerticalRadius: true,
},

View File

@ -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<TourProps> & { _InternalPanelDoNotUseOrYouWillBeFired: typeof PurePanel } = (
props,
@ -22,6 +24,14 @@ const Tour: React.FC<TourProps> & { _InternalPanelDoNotUseOrYouWillBeFired: type
const { getPrefixCls, direction } = useContext<ConfigConsumerProps>(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<TourProps> & { _InternalPanelDoNotUseOrYouWillBeFired: type
current={current}
animated
renderPanel={mergedRenderPanel}
builtinPlacements={builtinPlacements}
/>,
);
};

View File

@ -230,7 +230,6 @@ const genBaseStyle: GenerateStyle<TourToken> = (token) => {
// ============================= Arrow ===========================
getArrowStyle<TourToken>(token, {
colorBg: 'var(--antd-arrow-background-color)',
showArrowCls: '',
contentRadius: tourBorderRadius,
limitVerticalRadius: true,
}),

View File

@ -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",