Skip to content

Commit a24e4f7

Browse files
committed
chore: mobile support
1 parent 80b0f9e commit a24e4f7

File tree

7 files changed

+133
-41
lines changed

7 files changed

+133
-41
lines changed

assets/index/Mobile.less

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,55 @@
11
.@{triggerPrefixCls} {
22
&-mobile {
3-
transition: all 0.3s;
43
position: fixed;
54
left: 0;
65
right: 0;
76
bottom: 0;
87
top: auto;
98

10-
&-fade {
11-
&-appear,
12-
&-enter {
13-
&-start {
14-
transform: translateY(100%);
9+
// Motion
10+
&.raise {
11+
&-enter,
12+
&-appear {
13+
transform: translateY(100%);
14+
15+
&-active {
16+
transition: all 0.3s;
17+
transform: translateY(0);
1518
}
1619
}
1720

1821
&-leave {
22+
transform: translateY(0);
23+
1924
&-active {
25+
transition: all 0.3s;
2026
transform: translateY(100%);
2127
}
2228
}
2329
}
30+
31+
// Mask
32+
&-mask {
33+
&.fade {
34+
&-enter,
35+
&-appear {
36+
opacity: 0;
37+
38+
&-active {
39+
transition: all 0.3s;
40+
opacity: 1;
41+
}
42+
}
43+
44+
&-leave {
45+
opacity: 1;
46+
47+
&-active {
48+
transition: all 0.3s;
49+
opacity: 0;
50+
}
51+
}
52+
}
53+
}
2454
}
2555
}

docs/examples/mobile.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,15 @@ const Test = () => {
4242
popupVisible={open1}
4343
onOpenChange={setOpen1}
4444
popup={
45-
<div>
45+
<div style={{ background: '#FFF', padding: 12 }}>
4646
<h2>Hello World</h2>
4747
</div>
4848
}
49-
mobile
49+
mobile={{
50+
mask: true,
51+
motion: { motionName: 'raise' },
52+
maskMotion: { motionName: 'fade' },
53+
}}
5054
>
5155
<span>Click Me</span>
5256
</Trigger>

src/Popup/Mask.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ export interface MaskProps {
1111

1212
// Motion
1313
motion?: CSSMotionProps;
14+
15+
mobile?: boolean;
1416
}
1517

1618
export default function Mask(props: MaskProps) {
@@ -21,6 +23,8 @@ export default function Mask(props: MaskProps) {
2123

2224
mask,
2325
motion,
26+
27+
mobile,
2428
} = props;
2529

2630
if (!mask) {
@@ -32,7 +36,11 @@ export default function Mask(props: MaskProps) {
3236
{({ className }) => (
3337
<div
3438
style={{ zIndex }}
35-
className={classNames(`${prefixCls}-mask`, className)}
39+
className={classNames(
40+
`${prefixCls}-mask`,
41+
mobile && `${prefixCls}-mobile-mask`,
42+
className,
43+
)}
3644
/>
3745
)}
3846
</CSSMotion>

src/Popup/index.tsx

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ import Arrow from './Arrow';
1111
import Mask from './Mask';
1212
import PopupContent from './PopupContent';
1313

14+
export interface MobileConfig {
15+
mask?: boolean;
16+
/** Set popup motion. You can ref `rc-motion` for more info. */
17+
motion?: CSSMotionProps;
18+
/** Set mask motion. You can ref `rc-motion` for more info. */
19+
maskMotion?: CSSMotionProps;
20+
}
21+
1422
export interface PopupProps {
1523
prefixCls: string;
1624
className?: string;
@@ -63,6 +71,9 @@ export interface PopupProps {
6371
stretch?: string;
6472
targetWidth?: number;
6573
targetHeight?: number;
74+
75+
// Mobile
76+
mobile?: MobileConfig;
6677
}
6778

6879
const Popup = React.forwardRef<HTMLDivElement, PopupProps>((props, ref) => {
@@ -95,6 +106,9 @@ const Popup = React.forwardRef<HTMLDivElement, PopupProps>((props, ref) => {
95106
motion,
96107
maskMotion,
97108

109+
// Mobile
110+
mobile,
111+
98112
// Portal
99113
forceRender,
100114
getPopupContainer,
@@ -126,6 +140,24 @@ const Popup = React.forwardRef<HTMLDivElement, PopupProps>((props, ref) => {
126140
// We can not remove holder only when motion finished.
127141
const isNodeVisible = open || keepDom;
128142

143+
// ========================= Mobile =========================
144+
const isMobile = !!mobile;
145+
146+
// ========================== Mask ==========================
147+
const [mergedMask, mergedMaskMotion, mergedPopupMotion] = React.useMemo<
148+
[
149+
mask: boolean,
150+
maskMotion: CSSMotionProps | undefined,
151+
popupMotion: CSSMotionProps | undefined,
152+
]
153+
>(() => {
154+
if (mobile) {
155+
return [mobile.mask, mobile.maskMotion, mobile.motion];
156+
}
157+
158+
return [mask, maskMotion, motion];
159+
}, [mobile]);
160+
129161
// ======================= Container ========================
130162
const getPopupContainerNeedParams = getPopupContainer?.length > 0;
131163

@@ -148,15 +180,17 @@ const Popup = React.forwardRef<HTMLDivElement, PopupProps>((props, ref) => {
148180
// >>>>> Offset
149181
const AUTO = 'auto' as const;
150182

151-
const offsetStyle: React.CSSProperties = {
152-
left: '-1000vw',
153-
top: '-1000vh',
154-
right: AUTO,
155-
bottom: AUTO,
156-
};
183+
const offsetStyle: React.CSSProperties = isMobile
184+
? {}
185+
: {
186+
left: '-1000vw',
187+
top: '-1000vh',
188+
right: AUTO,
189+
bottom: AUTO,
190+
};
157191

158192
// Set align style
159-
if (ready || !open) {
193+
if (!isMobile && (ready || !open)) {
160194
const { points } = align;
161195
const dynamicInset =
162196
align.dynamicInset || (align as any)._experimental?.dynamicInset;
@@ -209,8 +243,9 @@ const Popup = React.forwardRef<HTMLDivElement, PopupProps>((props, ref) => {
209243
prefixCls={prefixCls}
210244
open={open}
211245
zIndex={zIndex}
212-
mask={mask}
213-
motion={maskMotion}
246+
mask={mergedMask}
247+
motion={mergedMaskMotion}
248+
mobile={isMobile}
214249
/>
215250
<ResizeObserver onResize={onAlign} disabled={!open}>
216251
{(resizeObserverRef) => {
@@ -222,7 +257,7 @@ const Popup = React.forwardRef<HTMLDivElement, PopupProps>((props, ref) => {
222257
removeOnLeave={false}
223258
forceRender={forceRender}
224259
leavedClassName={`${prefixCls}-hidden`}
225-
{...motion}
260+
{...mergedPopupMotion}
226261
onAppearPrepare={onPrepare}
227262
onEnterPrepare={onPrepare}
228263
visible={open}
@@ -235,7 +270,9 @@ const Popup = React.forwardRef<HTMLDivElement, PopupProps>((props, ref) => {
235270
{ className: motionClassName, style: motionStyle },
236271
motionRef,
237272
) => {
238-
const cls = classNames(prefixCls, motionClassName, className);
273+
const cls = classNames(prefixCls, motionClassName, className, {
274+
[`${prefixCls}-mobile`]: isMobile,
275+
});
239276

240277
return (
241278
<div

src/hooks/useAlign.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ export default function useAlign(
9595
builtinPlacements: any,
9696
popupAlign?: AlignType,
9797
onPopupAlign?: TriggerProps['onPopupAlign'],
98+
mobile?: boolean,
9899
): [
99100
ready: boolean,
100101
offsetX: number,
@@ -109,15 +110,25 @@ export default function useAlign(
109110
onAlign: VoidFunction,
110111
] {
111112
const [offsetInfo, setOffsetInfo] = React.useState<{
113+
/** Align finished */
112114
ready: boolean;
115+
/** Offset Left of current Left */
113116
offsetX: number;
117+
/** Offset Top of current Top */
114118
offsetY: number;
119+
/** Offset Right of current Left */
115120
offsetR: number;
121+
/** Offset Bottom of current Top */
116122
offsetB: number;
123+
/** Arrow X offset related with popup */
117124
arrowX: number;
125+
/** Arrow Y offset related with popup */
118126
arrowY: number;
127+
/** Scale X of popup */
119128
scaleX: number;
129+
/** Scale Y of popup */
120130
scaleY: number;
131+
/** Calculated align info */
121132
align: AlignType;
122133
}>({
123134
ready: false,
@@ -134,7 +145,7 @@ export default function useAlign(
134145
const alignCountRef = React.useRef(0);
135146

136147
const scrollerList = React.useMemo(() => {
137-
if (!popupEle) {
148+
if (!popupEle || mobile) {
138149
return [];
139150
}
140151

@@ -161,7 +172,7 @@ export default function useAlign(
161172

162173
// ========================= Align =========================
163174
const onAlign = useEvent(() => {
164-
if (popupEle && target && open) {
175+
if (popupEle && target && open && !mobile) {
165176
const popupElement = popupEle;
166177

167178
const doc = popupElement.ownerDocument;
@@ -659,10 +670,14 @@ export default function useAlign(
659670
const targetTop = targetRect.y;
660671
const targetBottom = targetTop + targetHeight;
661672

673+
/** Max left of the popup and target element */
662674
const maxLeft = Math.max(popupLeft, targetLeft);
675+
/** Min right of the popup and target element */
663676
const minRight = Math.min(popupRight, targetRight);
664677

678+
/** The center X of popup & target cross area */
665679
const xCenter = (maxLeft + minRight) / 2;
680+
/** Arrow X of popup offset */
666681
const nextArrowX = xCenter - popupLeft;
667682

668683
const maxTop = Math.max(popupTop, targetTop);

src/index.tsx

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@ import { getShadowRoot } from '@rc-component/util/lib/Dom/shadow';
77
import useEvent from '@rc-component/util/lib/hooks/useEvent';
88
import useId from '@rc-component/util/lib/hooks/useId';
99
import useLayoutEffect from '@rc-component/util/lib/hooks/useLayoutEffect';
10-
import isMobile from '@rc-component/util/lib/isMobile';
1110
import * as React from 'react';
12-
import Popup from './Popup';
11+
import Popup, { MobileConfig } from './Popup';
1312
import TriggerWrapper from './TriggerWrapper';
1413
import type { TriggerContextProps } from './context';
1514
import TriggerContext from './context';
@@ -121,9 +120,12 @@ export interface TriggerProps {
121120
getTriggerDOMNode?: (node: React.ReactInstance) => HTMLElement;
122121

123122
// // ========================== Mobile ==========================
124-
// /** @private Bump fixed position at bottom in mobile.
125-
// * This is internal usage currently, do not use in your prod */
126-
// mobile?: MobileConfig;
123+
/**
124+
* @private Bump fixed position at bottom in mobile.
125+
* Will replace the config of root props.
126+
* This is internal usage currently, do not use in your prod.
127+
*/
128+
mobile?: MobileConfig;
127129
}
128130

129131
export function generateTrigger(
@@ -190,17 +192,15 @@ export function generateTrigger(
190192

191193
// Private
192194
getTriggerDOMNode,
195+
mobile,
193196

194197
...restProps
195198
} = props;
196199

197200
const mergedAutoDestroy = autoDestroy || false;
198201

199202
// =========================== Mobile ===========================
200-
const [mobile, setMobile] = React.useState(false);
201-
useLayoutEffect(() => {
202-
setMobile(isMobile());
203-
}, []);
203+
const isMobile = !!mobile;
204204

205205
// ========================== Context ===========================
206206
const subPopupElements = React.useRef<Record<string, HTMLElement>>({});
@@ -377,10 +377,11 @@ export function generateTrigger(
377377
builtinPlacements,
378378
popupAlign,
379379
onPopupAlign,
380+
isMobile,
380381
);
381382

382383
const [showActions, hideActions] = useAction(
383-
mobile,
384+
isMobile,
384385
action,
385386
showAction,
386387
hideAction,
@@ -670,7 +671,10 @@ export function generateTrigger(
670671
ref={setPopupRef}
671672
prefixCls={prefixCls}
672673
popup={popup}
673-
className={classNames(popupClassName, alignedClassName)}
674+
className={classNames(
675+
popupClassName,
676+
!isMobile && alignedClassName,
677+
)}
674678
style={popupStyle}
675679
target={targetEle}
676680
onMouseEnter={onPopupMouseEnter}
@@ -711,6 +715,8 @@ export function generateTrigger(
711715
stretch={stretch}
712716
targetWidth={targetWidth / scaleX}
713717
targetHeight={targetHeight / scaleY}
718+
// Mobile
719+
mobile={mobile}
714720
/>
715721
</TriggerContext.Provider>
716722
</>

src/interface.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,3 @@ export interface Point {
118118
export interface CommonEventHandler {
119119
remove: () => void;
120120
}
121-
122-
export interface MobileConfig {
123-
/** Set popup motion. You can ref `rc-motion` for more info. */
124-
popupMotion?: CSSMotionProps;
125-
popupClassName?: string;
126-
popupStyle?: React.CSSProperties;
127-
popupRender?: (originNode: React.ReactNode) => React.ReactNode;
128-
}

0 commit comments

Comments
 (0)