Skip to content

Commit 72241ca

Browse files
authored
refactor(module:*): enhance popup arrow positioning and styles (#9360)
- add `placementArrow` mixin function
1 parent 2986153 commit 72241ca

File tree

17 files changed

+299
-492
lines changed

17 files changed

+299
-492
lines changed

components/core/overlay/overlay-position.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,36 @@ export type POSITION_TYPE_HORIZONTAL = Extract<
4242
'bottomLeft' | 'bottomCenter' | 'bottomRight' | 'topLeft' | 'topCenter' | 'topRight'
4343
>;
4444

45-
export const DEFAULT_TOOLTIP_POSITIONS = [POSITION_MAP.top, POSITION_MAP.right, POSITION_MAP.bottom, POSITION_MAP.left];
45+
/**
46+
* @internal
47+
* @param offset offset in pixels which should not be less than 0.
48+
* The default value is `12`, which means `(arrow-size / 2) + 4`
49+
*/
50+
const positionOffsetMapFactory = (offset: number = 12): Record<string, [number, number]> => ({
51+
top: [0, -offset],
52+
topCenter: [0, -offset],
53+
topLeft: [0, -offset],
54+
topRight: [0, -offset],
55+
right: [offset, 0],
56+
rightTop: [offset, 0],
57+
rightBottom: [offset, 0],
58+
bottom: [0, offset],
59+
bottomCenter: [0, offset],
60+
bottomLeft: [0, offset],
61+
bottomRight: [0, offset],
62+
left: [-offset, 0],
63+
leftTop: [-offset, 0],
64+
leftBottom: [-offset, 0]
65+
});
66+
67+
export const TOOLTIP_OFFSET_MAP = positionOffsetMapFactory();
68+
69+
export const DEFAULT_TOOLTIP_POSITIONS = [
70+
setConnectedPositionOffset(POSITION_MAP.top, TOOLTIP_OFFSET_MAP.top),
71+
setConnectedPositionOffset(POSITION_MAP.right, TOOLTIP_OFFSET_MAP.right),
72+
setConnectedPositionOffset(POSITION_MAP.bottom, TOOLTIP_OFFSET_MAP.bottom),
73+
setConnectedPositionOffset(POSITION_MAP.left, TOOLTIP_OFFSET_MAP.left)
74+
];
4675

4776
export const DEFAULT_CASCADER_POSITIONS = [
4877
POSITION_MAP.bottomLeft,
@@ -108,3 +137,20 @@ export const DEFAULT_DATE_PICKER_POSITIONS = [
108137
DATE_PICKER_POSITION_MAP.bottomRight,
109138
DATE_PICKER_POSITION_MAP.topRight
110139
];
140+
141+
export function normalizeConnectedPositionOffset(offset: number | [number, number]): [number, number] {
142+
return Array.isArray(offset) ? offset : [offset, offset];
143+
}
144+
145+
export function setConnectedPositionOffset(
146+
position: ConnectionPositionPair,
147+
offset: number | [number, number]
148+
): ConnectionPositionPair {
149+
const [offsetX, offsetY] = normalizeConnectedPositionOffset(offset);
150+
// return new object
151+
return {
152+
...position,
153+
offsetX: offsetX,
154+
offsetY: offsetY
155+
};
156+
}

components/date-picker/style/index.less

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,9 @@
257257
// ======================= Dropdown =======================
258258
&-dropdown {
259259
.reset-component();
260+
261+
--antd-arrow-background-color: @calendar-bg;
262+
260263
position: absolute;
261264
// Fix incorrect position of picker popup
262265
// https://github.com/ant-design/ant-design/issues/35590
@@ -268,19 +271,19 @@
268271
display: none;
269272
}
270273

271-
&-placement-bottomLeft {
274+
&-placement-bottomLeft, &-placement-bottomRight {
272275
.@{picker-prefix-cls}-range-arrow {
273-
top: (@arrow-size / 2) - (@arrow-size / 3) + 0.7px;
276+
top: 0;
274277
display: block;
275-
transform: rotate(-135deg) translateY(1px);
278+
transform: translateY(-100%);
276279
}
277280
}
278281

279-
&-placement-topLeft {
282+
&-placement-topLeft, &-placement-topRight {
280283
.@{picker-prefix-cls}-range-arrow {
281-
bottom: (@arrow-size / 2) - (@arrow-size / 3) + 0.7px;
284+
bottom: 0;
282285
display: block;
283-
transform: rotate(45deg);
286+
transform: translateY(100%) rotate(180deg);
284287
}
285288
}
286289

@@ -310,7 +313,7 @@
310313
}
311314

312315
&-dropdown-range {
313-
padding: (@arrow-size * 2 / 3) 0;
316+
margin-block: (@arrow-size * 2 / 3);
314317

315318
&-hidden {
316319
display: none;
@@ -356,12 +359,10 @@
356359
&-range-arrow {
357360
position: absolute;
358361
z-index: 1;
359-
width: @arrow-size;
360-
height: @arrow-size;
362+
box-sizing: content-box;
361363
margin-left: @input-padding-horizontal-base * 1.5;
362-
box-shadow: 2px 2px 6px -2px fade(@black, 10%); // use spread radius to hide shadow over popover
363364
transition: left @animation-duration-slow ease-out;
364-
.roundedArrow(@arrow-size, 5px, @calendar-bg);
365+
.roundedArrow(@arrow-size, 4px, @arrow-border-radius, var(--antd-arrow-background-color), @popover-arrow-box-shadow);
365366
}
366367

367368
&-panel-container {
@@ -381,17 +382,13 @@
381382
.@{picker-prefix-cls}-panel {
382383
vertical-align: top;
383384
background: transparent;
384-
border-width: 0 0 @border-width-base;
385+
border: none;
385386
border-radius: 0;
386387

387388
.@{picker-prefix-cls}-content,
388389
table {
389390
text-align: center;
390391
}
391-
392-
&-focused {
393-
border-color: @border-color-split;
394-
}
395392
}
396393
}
397394

components/dropdown/dropdown.directive.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,20 @@ import { BehaviorSubject, EMPTY, Subject, combineLatest, fromEvent, merge } from
2727
import { auditTime, distinctUntilChanged, filter, map, switchMap } from 'rxjs/operators';
2828

2929
import { NzConfigKey, NzConfigService, WithConfig } from 'ng-zorro-antd/core/config';
30-
import { POSITION_MAP, getPlacementName } from 'ng-zorro-antd/core/overlay';
30+
import {
31+
POSITION_MAP,
32+
getPlacementName,
33+
POSITION_TYPE,
34+
setConnectedPositionOffset,
35+
TOOLTIP_OFFSET_MAP
36+
} from 'ng-zorro-antd/core/overlay';
3137
import { IndexableObject } from 'ng-zorro-antd/core/types';
3238

3339
import { NzDropdownMenuComponent, NzPlacementType } from './dropdown-menu.component';
3440

3541
const NZ_CONFIG_MODULE_NAME: NzConfigKey = 'dropDown';
3642

37-
const listOfPositions = [
38-
POSITION_MAP.bottomLeft,
39-
POSITION_MAP.bottomRight,
40-
POSITION_MAP.topRight,
41-
POSITION_MAP.topLeft
42-
];
43+
const listOfPositions: POSITION_TYPE[] = ['bottomLeft', 'bottomRight', 'topRight', 'topLeft'];
4344

4445
const normalizePlacementForClass = (p: NzPlacementType): NzDropdownMenuComponent['placement'] => {
4546
// Map center placements to generic top/bottom classes for styling
@@ -191,7 +192,12 @@ export class NzDropDownDirective implements AfterViewInit, OnChanges {
191192
overlayConfig.minWidth = triggerWidth;
192193
}
193194
/** open dropdown with animation **/
194-
this.positionStrategy.withPositions([POSITION_MAP[this.nzPlacement], ...listOfPositions]);
195+
const positions = [this.nzPlacement, ...listOfPositions].map(position => {
196+
return this.nzArrow
197+
? setConnectedPositionOffset(POSITION_MAP[position], TOOLTIP_OFFSET_MAP[position])
198+
: POSITION_MAP[position];
199+
});
200+
this.positionStrategy.withPositions(positions);
195201
/** reset portal if needed **/
196202
if (!this.portal || this.portal.templateRef !== this.nzDropdownMenu!.templateRef) {
197203
this.portal = new TemplatePortal(this.nzDropdownMenu!.templateRef, this.viewContainerRef);

components/dropdown/style/index.less

Lines changed: 6 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
.@{dropdown-prefix-cls} {
88
.reset-component();
99

10+
--antd-arrow-background-color: @dropdown-menu-bg;
11+
1012
position: absolute;
1113
top: -9999px;
1214
left: -9999px;
@@ -15,13 +17,10 @@
1517

1618
&::before {
1719
position: absolute;
18-
top: -@popover-distance + @popover-arrow-width;
19-
right: 0;
20-
bottom: -@popover-distance + @popover-arrow-width;
21-
left: -7px;
20+
inset-block: calc(@popover-arrow-width / 2 - @popover-distance);
2221
z-index: -9999;
2322
opacity: 0.0001;
24-
content: ' ';
23+
content: '';
2524
}
2625

2726
&-wrap {
@@ -48,72 +47,8 @@
4847
display: none;
4948
}
5049

51-
// Offset the popover to account for the dropdown arrow
52-
&-show-arrow&-placement-topLeft,
53-
&-show-arrow&-placement-top,
54-
&-show-arrow&-placement-topRight {
55-
padding-bottom: @popover-distance;
56-
}
57-
58-
&-show-arrow&-placement-bottomLeft,
59-
&-show-arrow&-placement-bottom,
60-
&-show-arrow&-placement-bottomRight {
61-
padding-top: @popover-distance;
62-
}
63-
64-
// Arrows
65-
// .popover-arrow is outer, .popover-arrow:after is inner
66-
67-
&-arrow {
68-
position: absolute;
69-
z-index: 1; // lift it up so the menu wouldn't cask shadow on it
70-
display: block;
71-
width: @popover-arrow-width;
72-
height: @popover-arrow-width;
73-
.roundedArrow(@popover-arrow-width, 5px, @popover-bg);
74-
}
75-
76-
&-placement-top > &-arrow,
77-
&-placement-topLeft > &-arrow,
78-
&-placement-topRight > &-arrow {
79-
bottom: @popover-arrow-width * sqrt((1 / 2)) + 2px;
80-
box-shadow: 3px 3px 7px -3px fade(@black, 10%);
81-
transform: rotate(45deg);
82-
}
83-
84-
&-placement-top > &-arrow {
85-
left: 50%;
86-
transform: translateX(-50%) rotate(45deg);
87-
}
88-
89-
&-placement-topLeft > &-arrow {
90-
left: 16px;
91-
}
92-
93-
&-placement-topRight > &-arrow {
94-
right: 16px;
95-
}
96-
97-
&-placement-bottom > &-arrow,
98-
&-placement-bottomLeft > &-arrow,
99-
&-placement-bottomRight > &-arrow {
100-
top: (@popover-arrow-width + 2px) * sqrt((1 / 2));
101-
box-shadow: 2px 2px 5px -2px fade(@black, 10%);
102-
transform: rotate(-135deg) translateY(-0.5px);
103-
}
104-
105-
&-placement-bottom > &-arrow {
106-
left: 50%;
107-
transform: translateX(-50%) rotate(-135deg) translateY(-0.5px);
108-
}
109-
110-
&-placement-bottomLeft > &-arrow {
111-
left: 16px;
112-
}
113-
114-
&-placement-bottomRight > &-arrow {
115-
right: 16px;
116-
}
50+
// Arrow Style
51+
.placementArrow(@popover-arrow-width, 4px, @arrow-border-radius, var(--antd-arrow-background-color), @popover-arrow-box-shadow);
11752

11853
&-menu {
11954
position: relative;

components/dropdown/style/patch.less

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88

99
.ant-dropdown {
1010
position: relative;
11-
top: 0;
12-
left: 0;
13-
width: 100%;
14-
margin-top: 6px;
15-
margin-bottom: 6px;
11+
inset: 0;
12+
13+
&:not(:has(.ant-dropdown-arrow)) {
14+
margin-block: calc(@popover-distance - @popover-arrow-width / 2)
15+
}
1616
}
1717

1818
.@{dropdown-prefix-cls} {

components/popconfirm/popconfirm.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -177,12 +177,10 @@ export class NzPopconfirmDirective extends NzTooltipBaseDirective {
177177
[nzNoAnimation]="noAnimation?.nzNoAnimation"
178178
[@zoomBigMotion]="'active'"
179179
>
180+
@if (nzPopconfirmShowArrow) {
181+
<div class="ant-popover-arrow"></div>
182+
}
180183
<div class="ant-popover-content">
181-
@if (nzPopconfirmShowArrow) {
182-
<div class="ant-popover-arrow">
183-
<span class="ant-popover-arrow-content"></span>
184-
</div>
185-
}
186184
<div class="ant-popover-inner">
187185
<div>
188186
<div class="ant-popover-inner-content">

components/popover/popover.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,8 @@ export class NzPopoverDirective extends NzTooltipBaseDirective {
109109
[nzNoAnimation]="noAnimation?.nzNoAnimation"
110110
[@zoomBigMotion]="'active'"
111111
>
112+
<div class="ant-popover-arrow"></div>
112113
<div class="ant-popover-content">
113-
<div class="ant-popover-arrow">
114-
<span class="ant-popover-arrow-content"></span>
115-
</div>
116114
<div class="ant-popover-inner" role="tooltip">
117115
<div>
118116
@if (nzTitle) {

0 commit comments

Comments
 (0)