Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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