Skip to content

Commit 1330c4b

Browse files
feat: Support unique box moving (#568)
* chore: init * chore: post info * chore: use hooks * chore: api update * chore: comment * chore: comment * chore: wrapper * chore: move to show * chore: update demo * chore: align it * chore: motion pending * docs: update demo * chore: demo update * chore: delay of context * chore: tmp of it * chore: get a hooks * chore: floating * chore: motion of bg * chore: flying * chore: unique it * test: init * test: init * test: add test case * chore: lint * Update src/context.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Update src/UniqueProvider/useTargetState.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * chore: of it * Update src/UniqueProvider/useTargetState.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * chore: of it * chore: fix ts * chore: fix ts --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent 867a20c commit 1330c4b

File tree

13 files changed

+935
-65
lines changed

13 files changed

+935
-65
lines changed

assets/index.less

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,54 @@
7373
opacity: 0;
7474
}
7575
}
76+
77+
// =============== Float BG ===============
78+
&-float-bg {
79+
position: absolute;
80+
z-index: 0;
81+
box-sizing: border-box;
82+
border: 1px solid red;
83+
background: green;
84+
85+
&-hidden {
86+
display: none;
87+
}
88+
89+
&-visible {
90+
transition: all 0.1s;
91+
}
92+
}
93+
94+
// Debug
95+
&-unique-controlled {
96+
border-color: rgba(0, 0, 0, 0.01) !important;
97+
background: transparent !important;
98+
z-index: 1;
99+
}
100+
101+
// Motion Content
102+
&-motion-content {
103+
// Fade motion
104+
&-fade-appear {
105+
opacity: 0;
106+
animation-duration: 0.3s;
107+
animation-fill-mode: both;
108+
animation-timing-function: cubic-bezier(0.55, 0, 0.55, 0.2);
109+
}
110+
111+
&-fade-appear&-fade-appear-active {
112+
animation-name: rcTriggerFadeIn;
113+
}
114+
115+
@keyframes rcTriggerFadeIn {
116+
0% {
117+
opacity: 0;
118+
}
119+
100% {
120+
opacity: 1;
121+
}
122+
}
123+
}
76124
}
77125

78126
@import './index/Mask';

docs/demos/two-buttons.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
title: Moving Popup
3+
nav:
4+
title: Demo
5+
path: /demo
6+
---
7+
8+
<code src="../examples/two-buttons.tsx"></code>

docs/examples/two-buttons.tsx

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import Trigger, { UniqueProvider } from '@rc-component/trigger';
2+
import React, { useState } from 'react';
3+
import '../../assets/index.less';
4+
5+
const LEAVE_DELAY = 0.2;
6+
7+
const builtinPlacements = {
8+
left: {
9+
points: ['cr', 'cl'],
10+
offset: [-10, 0],
11+
},
12+
right: {
13+
points: ['cl', 'cr'],
14+
offset: [10, 0],
15+
},
16+
top: {
17+
points: ['bc', 'tc'],
18+
offset: [0, -10],
19+
},
20+
bottom: {
21+
points: ['tc', 'bc'],
22+
offset: [0, 10],
23+
},
24+
};
25+
26+
const MovingPopupDemo = () => {
27+
const [useUniqueProvider, setUseUniqueProvider] = useState(true);
28+
const [triggerControl, setTriggerControl] = useState('none'); // 'button1', 'button2', 'none'
29+
30+
const getVisible = (name: string) => {
31+
if (triggerControl === 'none') {
32+
return undefined;
33+
}
34+
if (triggerControl === name) {
35+
return true;
36+
}
37+
return false;
38+
};
39+
40+
const content = (
41+
<div style={{ margin: 100 }}>
42+
<div style={{ display: 'flex', gap: 20 }}>
43+
<Trigger
44+
mouseLeaveDelay={LEAVE_DELAY}
45+
action={['hover']}
46+
popupPlacement="top"
47+
builtinPlacements={builtinPlacements}
48+
popupVisible={getVisible('button1')}
49+
popupMotion={{
50+
motionName: 'rc-trigger-popup-zoom',
51+
}}
52+
popup={<div>这是左侧按钮的提示信息</div>}
53+
popupStyle={{
54+
border: '1px solid #ccc',
55+
padding: 10,
56+
background: 'white',
57+
boxSizing: 'border-box',
58+
}}
59+
unique
60+
>
61+
<button type="button">左侧按钮</button>
62+
</Trigger>
63+
64+
<Trigger
65+
mouseLeaveDelay={LEAVE_DELAY}
66+
action={['hover']}
67+
popupPlacement="top"
68+
builtinPlacements={builtinPlacements}
69+
popupVisible={getVisible('button2')}
70+
popupMotion={{
71+
motionName: 'rc-trigger-popup-zoom',
72+
}}
73+
popup={<div>This is the tooltip for the right button</div>}
74+
popupStyle={{
75+
border: '1px solid #ccc',
76+
padding: 10,
77+
background: 'white',
78+
boxSizing: 'border-box',
79+
}}
80+
unique
81+
>
82+
<button type="button">Right Button</button>
83+
</Trigger>
84+
</div>
85+
86+
<div style={{ marginBottom: 20 }}>
87+
<label>
88+
<input
89+
type="checkbox"
90+
checked={useUniqueProvider}
91+
onChange={(e) => setUseUniqueProvider(e.target.checked)}
92+
/>
93+
使用 UniqueProvider
94+
</label>
95+
</div>
96+
97+
<div style={{ marginBottom: 20 }}>
98+
<div>Trigger 控制:</div>
99+
<label>
100+
<input
101+
type="radio"
102+
name="triggerControl"
103+
value="button1"
104+
checked={triggerControl === 'button1'}
105+
onChange={(e) => setTriggerControl(e.target.value)}
106+
/>
107+
Button 1 显示 Trigger
108+
</label>
109+
<label style={{ marginLeft: 20 }}>
110+
<input
111+
type="radio"
112+
name="triggerControl"
113+
value="button2"
114+
checked={triggerControl === 'button2'}
115+
onChange={(e) => setTriggerControl(e.target.value)}
116+
/>
117+
Button 2 显示 Trigger
118+
</label>
119+
<label style={{ marginLeft: 20 }}>
120+
<input
121+
type="radio"
122+
name="triggerControl"
123+
value="none"
124+
checked={triggerControl === 'none'}
125+
onChange={(e) => setTriggerControl(e.target.value)}
126+
/>
127+
都不受控 (Hover 控制)
128+
</label>
129+
</div>
130+
</div>
131+
);
132+
133+
return useUniqueProvider ? (
134+
<UniqueProvider>{content}</UniqueProvider>
135+
) : (
136+
content
137+
);
138+
};
139+
140+
export default MovingPopupDemo;

src/Popup/index.tsx

Lines changed: 38 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import classNames from 'classnames';
22
import type { CSSMotionProps } from '@rc-component/motion';
33
import CSSMotion from '@rc-component/motion';
4-
import ResizeObserver from '@rc-component/resize-observer';
4+
import ResizeObserver, {
5+
type ResizeObserverProps,
6+
} from '@rc-component/resize-observer';
57
import useLayoutEffect from '@rc-component/util/lib/hooks/useLayoutEffect';
68
import { composeRef } from '@rc-component/util/lib/ref';
79
import * as React from 'react';
@@ -10,6 +12,8 @@ import type { AlignType, ArrowPos, ArrowTypeOuter } from '../interface';
1012
import Arrow from './Arrow';
1113
import Mask from './Mask';
1214
import PopupContent from './PopupContent';
15+
import useOffsetStyle from '../hooks/useOffsetStyle';
16+
import { useEvent } from '@rc-component/util';
1317

1418
export interface MobileConfig {
1519
mask?: boolean;
@@ -58,6 +62,8 @@ export interface PopupProps {
5862
autoDestroy?: boolean;
5963
portal: React.ComponentType<any>;
6064

65+
children?: React.ReactElement;
66+
6167
// Align
6268
ready: boolean;
6369
offsetX: number;
@@ -72,6 +78,9 @@ export interface PopupProps {
7278
targetWidth?: number;
7379
targetHeight?: number;
7480

81+
// Resize
82+
onResize?: ResizeObserverProps['onResize'];
83+
7584
// Mobile
7685
mobile?: MobileConfig;
7786
}
@@ -114,6 +123,7 @@ const Popup = React.forwardRef<HTMLDivElement, PopupProps>((props, ref) => {
114123
getPopupContainer,
115124
autoDestroy,
116125
portal: Portal,
126+
children,
117127

118128
zIndex,
119129

@@ -130,12 +140,15 @@ const Popup = React.forwardRef<HTMLDivElement, PopupProps>((props, ref) => {
130140
onAlign,
131141
onPrepare,
132142

143+
// Resize
144+
onResize,
145+
133146
stretch,
134147
targetWidth,
135148
targetHeight,
136149
} = props;
137150

138-
const childNode = typeof popup === 'function' ? popup() : popup;
151+
const popupContent = typeof popup === 'function' ? popup() : popup;
139152

140153
// We can not remove holder only when motion finished.
141154
const isNodeVisible = open || keepDom;
@@ -172,48 +185,31 @@ const Popup = React.forwardRef<HTMLDivElement, PopupProps>((props, ref) => {
172185
}
173186
}, [show, getPopupContainerNeedParams, target]);
174187

188+
// ========================= Resize =========================
189+
const onInternalResize: ResizeObserverProps['onResize'] = useEvent(
190+
(size, ele) => {
191+
onResize?.(size, ele);
192+
onAlign();
193+
},
194+
);
195+
196+
// ========================= Styles =========================
197+
const offsetStyle = useOffsetStyle(
198+
isMobile,
199+
ready,
200+
open,
201+
align,
202+
offsetR,
203+
offsetB,
204+
offsetX,
205+
offsetY,
206+
);
207+
175208
// ========================= Render =========================
176209
if (!show) {
177210
return null;
178211
}
179212

180-
// >>>>> Offset
181-
const AUTO = 'auto' as const;
182-
183-
const offsetStyle: React.CSSProperties = isMobile
184-
? {}
185-
: {
186-
left: '-1000vw',
187-
top: '-1000vh',
188-
right: AUTO,
189-
bottom: AUTO,
190-
};
191-
192-
// Set align style
193-
if (!isMobile && (ready || !open)) {
194-
const { points } = align;
195-
const dynamicInset =
196-
align.dynamicInset || (align as any)._experimental?.dynamicInset;
197-
const alignRight = dynamicInset && points[0][1] === 'r';
198-
const alignBottom = dynamicInset && points[0][0] === 'b';
199-
200-
if (alignRight) {
201-
offsetStyle.right = offsetR;
202-
offsetStyle.left = AUTO;
203-
} else {
204-
offsetStyle.left = offsetX;
205-
offsetStyle.right = AUTO;
206-
}
207-
208-
if (alignBottom) {
209-
offsetStyle.bottom = offsetB;
210-
offsetStyle.top = AUTO;
211-
} else {
212-
offsetStyle.top = offsetY;
213-
offsetStyle.bottom = AUTO;
214-
}
215-
}
216-
217213
// >>>>> Misc
218214
const miscStyle: React.CSSProperties = {};
219215
if (stretch) {
@@ -247,7 +243,7 @@ const Popup = React.forwardRef<HTMLDivElement, PopupProps>((props, ref) => {
247243
motion={mergedMaskMotion}
248244
mobile={isMobile}
249245
/>
250-
<ResizeObserver onResize={onAlign} disabled={!open}>
246+
<ResizeObserver onResize={onInternalResize} disabled={!open}>
251247
{(resizeObserverRef) => {
252248
return (
253249
<CSSMotion
@@ -305,7 +301,7 @@ const Popup = React.forwardRef<HTMLDivElement, PopupProps>((props, ref) => {
305301
/>
306302
)}
307303
<PopupContent cache={!open && !fresh}>
308-
{childNode}
304+
{popupContent}
309305
</PopupContent>
310306
</div>
311307
);
@@ -314,6 +310,7 @@ const Popup = React.forwardRef<HTMLDivElement, PopupProps>((props, ref) => {
314310
);
315311
}}
316312
</ResizeObserver>
313+
{children}
317314
</Portal>
318315
);
319316
});

0 commit comments

Comments
 (0)