Skip to content

feat: Support mobile config #528

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,10 @@ module.exports = {
'react/sort-comp': 0,
'jsx-a11y/label-has-for': 0,
'jsx-a11y/label-has-associated-control': 0,
'@typescript-eslint/consistent-indexed-object-style': 0,
'@typescript-eslint/no-parameter-properties': 0,
'@typescript-eslint/ban-types': 0,
'@typescript-eslint/type-annotation-spacing': 0,
'@typescript-eslint/no-throw-literal': 0,
},
};
42 changes: 36 additions & 6 deletions assets/index/Mobile.less
Original file line number Diff line number Diff line change
@@ -1,25 +1,55 @@
.@{triggerPrefixCls} {
&-mobile {
transition: all 0.3s;
position: fixed;
left: 0;
right: 0;
bottom: 0;
top: auto;

&-fade {
&-appear,
&-enter {
&-start {
transform: translateY(100%);
// Motion
&.raise {
&-enter,
&-appear {
transform: translateY(100%);

&-active {
transition: all 0.3s;
transform: translateY(0);
}
}

&-leave {
transform: translateY(0);

&-active {
transition: all 0.3s;
transform: translateY(100%);
}
}
}

// Mask
&-mask {
&.fade {
&-enter,
&-appear {
opacity: 0;

&-active {
transition: all 0.3s;
opacity: 1;
}
}

&-leave {
opacity: 1;

&-active {
transition: all 0.3s;
opacity: 0;
}
}
}
}
}
}
8 changes: 8 additions & 0 deletions docs/demos/mobile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: Mobile
nav:
title: Demo
path: /demo
---

<code src="../examples/mobile.tsx"></code>
68 changes: 68 additions & 0 deletions docs/examples/mobile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import Trigger from '@rc-component/trigger';
import React from 'react';
import '../../assets/index.less';

const builtinPlacements = {
left: {
points: ['cr', 'cl'],
},
right: {
points: ['cl', 'cr'],
},
top: {
points: ['bc', 'tc'],
},
bottom: {
points: ['tc', 'bc'],
},
topLeft: {
points: ['bl', 'tl'],
},
topRight: {
points: ['br', 'tr'],
},
bottomRight: {
points: ['tr', 'br'],
},
bottomLeft: {
points: ['tl', 'bl'],
},
};

const Test = () => {
const [open1, setOpen1] = React.useState(false);

return (
<div style={{ margin: 200 }}>
<div>
<Trigger
popupPlacement="top"
action={['hover']}
builtinPlacements={builtinPlacements}
popupVisible={open1}
onOpenChange={setOpen1}
popup={
<div
style={{
background: '#FFF',
boxShadow: '0 0 3px red',
padding: 12,
}}
>
<h2>Hello World</h2>
</div>
}
mobile={{
mask: true,
motion: { motionName: 'raise' },
maskMotion: { motionName: 'fade' },
}}
>
<span>Click Me</span>
</Trigger>
</div>
</div>
);
};

export default Test;
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"react": "^18.0.0",
"react-dom": "^18.0.0",
"regenerator-runtime": "^0.14.0",
"typescript": "^5.1.6"
"typescript": "~5.1.6"
},
"dependencies": {
"@rc-component/motion": "^1.1.4",
Expand Down
10 changes: 9 additions & 1 deletion src/Popup/Mask.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export interface MaskProps {

// Motion
motion?: CSSMotionProps;

mobile?: boolean;
}

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

mask,
motion,

mobile,
} = props;

if (!mask) {
Expand All @@ -32,7 +36,11 @@ export default function Mask(props: MaskProps) {
{({ className }) => (
<div
style={{ zIndex }}
className={classNames(`${prefixCls}-mask`, className)}
className={classNames(
`${prefixCls}-mask`,
mobile && `${prefixCls}-mobile-mask`,
className,
)}
/>
)}
</CSSMotion>
Expand Down
59 changes: 48 additions & 11 deletions src/Popup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ import Arrow from './Arrow';
import Mask from './Mask';
import PopupContent from './PopupContent';

export interface MobileConfig {
mask?: boolean;
/** Set popup motion. You can ref `rc-motion` for more info. */
motion?: CSSMotionProps;
/** Set mask motion. You can ref `rc-motion` for more info. */
maskMotion?: CSSMotionProps;
}

export interface PopupProps {
prefixCls: string;
className?: string;
Expand Down Expand Up @@ -63,6 +71,9 @@ export interface PopupProps {
stretch?: string;
targetWidth?: number;
targetHeight?: number;

// Mobile
mobile?: MobileConfig;
}

const Popup = React.forwardRef<HTMLDivElement, PopupProps>((props, ref) => {
Expand Down Expand Up @@ -95,6 +106,9 @@ const Popup = React.forwardRef<HTMLDivElement, PopupProps>((props, ref) => {
motion,
maskMotion,

// Mobile
mobile,

// Portal
forceRender,
getPopupContainer,
Expand Down Expand Up @@ -126,6 +140,24 @@ const Popup = React.forwardRef<HTMLDivElement, PopupProps>((props, ref) => {
// We can not remove holder only when motion finished.
const isNodeVisible = open || keepDom;

// ========================= Mobile =========================
const isMobile = !!mobile;

// ========================== Mask ==========================
const [mergedMask, mergedMaskMotion, mergedPopupMotion] = React.useMemo<
[
mask: boolean,
maskMotion: CSSMotionProps | undefined,
popupMotion: CSSMotionProps | undefined,
]
>(() => {
if (mobile) {
return [mobile.mask, mobile.maskMotion, mobile.motion];
}

return [mask, maskMotion, motion];
}, [mobile]);

// ======================= Container ========================
const getPopupContainerNeedParams = getPopupContainer?.length > 0;

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

const offsetStyle: React.CSSProperties = {
left: '-1000vw',
top: '-1000vh',
right: AUTO,
bottom: AUTO,
};
const offsetStyle: React.CSSProperties = isMobile
? {}
: {
left: '-1000vw',
top: '-1000vh',
right: AUTO,
bottom: AUTO,
};

// Set align style
if (ready || !open) {
if (!isMobile && (ready || !open)) {
const { points } = align;
const dynamicInset =
align.dynamicInset || (align as any)._experimental?.dynamicInset;
Expand Down Expand Up @@ -209,8 +243,9 @@ const Popup = React.forwardRef<HTMLDivElement, PopupProps>((props, ref) => {
prefixCls={prefixCls}
open={open}
zIndex={zIndex}
mask={mask}
motion={maskMotion}
mask={mergedMask}
motion={mergedMaskMotion}
mobile={isMobile}
/>
<ResizeObserver onResize={onAlign} disabled={!open}>
{(resizeObserverRef) => {
Expand All @@ -222,7 +257,7 @@ const Popup = React.forwardRef<HTMLDivElement, PopupProps>((props, ref) => {
removeOnLeave={false}
forceRender={forceRender}
leavedClassName={`${prefixCls}-hidden`}
{...motion}
{...mergedPopupMotion}
onAppearPrepare={onPrepare}
onEnterPrepare={onPrepare}
visible={open}
Expand All @@ -235,7 +270,9 @@ const Popup = React.forwardRef<HTMLDivElement, PopupProps>((props, ref) => {
{ className: motionClassName, style: motionStyle },
motionRef,
) => {
const cls = classNames(prefixCls, motionClassName, className);
const cls = classNames(prefixCls, motionClassName, className, {
[`${prefixCls}-mobile`]: isMobile,
});

return (
<div
Expand Down
23 changes: 10 additions & 13 deletions src/hooks/useAction.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,34 @@
import * as React from 'react';
import type { ActionType } from '../interface';

type ActionTypes = ActionType | ActionType[];
type InternalActionType = ActionType | 'touch';

type ActionTypes = InternalActionType | InternalActionType[];

function toArray<T>(val?: T | T[]) {
return val ? (Array.isArray(val) ? val : [val]) : [];
}

export default function useAction(
mobile: boolean,
action: ActionTypes,
showAction?: ActionTypes,
hideAction?: ActionTypes,
): [showAction: Set<ActionType>, hideAction: Set<ActionType>] {
): [showAction: Set<InternalActionType>, hideAction: Set<InternalActionType>] {
return React.useMemo(() => {
const mergedShowAction = toArray(showAction ?? action);
const mergedHideAction = toArray(hideAction ?? action);

const showActionSet = new Set(mergedShowAction);
const hideActionSet = new Set(mergedHideAction);

if (mobile) {
if (showActionSet.has('hover')) {
showActionSet.delete('hover');
showActionSet.add('click');
}
if (showActionSet.has('hover') && !showActionSet.has('click')) {
showActionSet.add('touch');
}

if (hideActionSet.has('hover')) {
hideActionSet.delete('hover');
hideActionSet.add('click');
}
if (hideActionSet.has('hover') && !hideActionSet.has('click')) {
hideActionSet.add('touch');
}

return [showActionSet, hideActionSet];
}, [mobile, action, showAction, hideAction]);
}, [action, showAction, hideAction]);
}
Loading
Loading