Skip to content
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

feat(popup): popup对齐vue mobile #449

Merged
merged 10 commits into from
Aug 21, 2024
2 changes: 1 addition & 1 deletion site/mobile/mobile.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default {
{
title: 'Popup 弹出层',
name: 'popup',
component: () => import('tdesign-mobile-react/popup/_example/base.jsx'),
component: () => import('tdesign-mobile-react/popup/_example/index.tsx'),
},
{
title: 'Progress 进度条',
Expand Down
2 changes: 1 addition & 1 deletion site/web/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

import '@common/style/mobile/index.less';
// import '@common/style/mobile/index.less';

import 'tdesign-site-components';
import 'tdesign-site-components/lib/styles/style.css';
Expand Down
2 changes: 1 addition & 1 deletion src/_common
Submodule _common updated 0 files
156 changes: 97 additions & 59 deletions src/popup/Popup.tsx
Original file line number Diff line number Diff line change
@@ -1,92 +1,130 @@
import React, { FC, useState } from 'react';
import React, { forwardRef, useMemo, useRef, useState } from 'react';
import classnames from 'classnames';
import { useSpring, animated } from 'react-spring';
import { CloseIcon } from 'tdesign-icons-react';
import { CSSTransition } from 'react-transition-group';
import { CSSTransitionClassNames } from 'react-transition-group/CSSTransition';
import Overlay from '../overlay';
import useDefault from '../_util/useDefault';
import withNativeProps, { NativeProps } from '../_util/withNativeProps';
import { TdPopupProps } from './type';
import useConfig from '../_util/useConfig';
import { popupDefaultProps } from './defaultProps';
import useDefaultProps from '../hooks/useDefaultProps';
import parseTNode from '../_util/parseTNode';
import { usePrefixClass } from '../hooks/useClass';
import Portal from '../common/Portal';

export interface PopupProps extends TdPopupProps, NativeProps {}

enum PopupSourceEnum {
OVERLAY = 'overlay',
CLOSEBTN = 'close-btn',
}

enum PlacementEnum {
TOP = 'top',
BOTTOM = 'bottom',
LEFT = 'left',
RIGHT = 'right',
CENTER = 'center',
}
const Popup = forwardRef<HTMLDivElement, PopupProps>((props) => {
const {
children,
placement,
showOverlay,
visible,
defaultVisible,
zIndex,
overlayProps,
preventScrollThrough,
attach,
destroyOnClose,
closeBtn,
closeOnOverlayClick,
onClose,
onClosed,
onOpen,
onOpened,
onVisibleChange,
} = useDefaultProps(props, popupDefaultProps);

const Popup: FC<PopupProps> = (props) => {
const { children, placement, showOverlay, visible, defaultVisible, zIndex, overlayProps, onVisibleChange } = props;
const duration = 300;

const { classPrefix } = useConfig();
const name = usePrefixClass('popup');

const name = `${classPrefix}-popup`;
const contentRef = useRef<HTMLDivElement>(null);

const [show, setShow] = useDefault<boolean, any>(visible, defaultVisible, onVisibleChange);

const [active, setActive] = useState(show);

const handleOverlayClick = () => {
const handleOverlayClick = (e) => {
if (!closeOnOverlayClick) return;
onClose?.(e);
setShow(false, PopupSourceEnum.OVERLAY);
};

const { progress, opacity } = useSpring({
progress: show ? 0 : 100,
opacity: show ? 1 : 0,
onStart: () => {
setActive(true);
},
onRest: () => {
setActive(show);
},
});

const contentStyle = {
transform: progress.to((p) => {
if (placement === PlacementEnum.BOTTOM) {
return `translateY(${p}%)`;
}
if (placement === PlacementEnum.TOP) {
return `translateY(-${p}%)`;
}
if (placement === PlacementEnum.LEFT) {
return `translateX(-${p}%)`;
}
if (placement === PlacementEnum.RIGHT) {
return `translateX(${p}%)`;
}
const handleCloseClick = (e) => {
onClose?.(e);
setShow(false, PopupSourceEnum.CLOSEBTN);
};

const contentStyle = useMemo<React.CSSProperties>(
() => ({
zIndex,
display: active ? null : 'none',
animationFillMode: 'forwards',
}),
opacity: opacity.to((o) => {
if (placement === PlacementEnum.CENTER) {
return o;
}
[zIndex, active],
);

const classNames = useMemo<CSSTransitionClassNames>(
() => ({
enterActive: placement === 'center' ? 'fade-zoom-enter-active' : `slide-${placement}-enter-active`,
exitActive: placement === 'center' ? 'fade-zoom-leave-active' : `slide-${placement}-leave-active`,
}),
};
[placement],
);

const rootStyles = {
zIndex,
display: active ? 'block' : 'none',
};
const closeBtnNode = !closeBtn ? null : (
<div className={`${name}__close`} onClick={handleCloseClick}>
{parseTNode(closeBtn, props, <CloseIcon size={24} />)}
</div>
);

return withNativeProps(
props,
<div className={`${name}`} style={rootStyles}>
{showOverlay && <Overlay visible={show} onClick={handleOverlayClick} {...overlayProps} />}
<animated.div className={classnames([`${name}--content`, `${name}--content-${placement}`])} style={contentStyle}>
{active && children}
</animated.div>
</div>,
const node = (
<>
<Overlay
visible={show && showOverlay}
onClick={handleOverlayClick}
preventScrollThrough={preventScrollThrough}
duration={duration}
{...overlayProps}
/>
<CSSTransition
in={show}
timeout={duration}
nodeRef={contentRef}
unmountOnExit={destroyOnClose}
classNames={classNames}
onEnter={() => {
onOpen?.();
setActive(true);
}}
onEntered={() => {
onOpened?.();
}}
onExited={() => {
onClosed?.();
setActive(false);
}}
>
{withNativeProps(
props,
<div ref={contentRef} className={classnames([name, `${name}--${placement}`])} style={contentStyle}>
{closeBtnNode}
{parseTNode(children)}
</div>,
)}
</CSSTransition>
</>
);
};
return <Portal attach={attach}>{node}</Portal>;
});

Popup.displayName = 'Popup';
Popup.defaultProps = popupDefaultProps;

export default Popup;
65 changes: 0 additions & 65 deletions src/popup/_example/base.jsx

This file was deleted.

48 changes: 48 additions & 0 deletions src/popup/_example/base.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React, { useState } from 'react';
import Button from '../../button';
import Popup from '..';

const placementList = [
{ value: 'top', text: '顶部弹出' },
{ value: 'left', text: '左侧弹出' },
{ value: 'center', text: '中间弹出' },
{ value: 'bottom', text: '底部弹出' },
{ value: 'right', text: '右侧弹出' },
] as const;

type PlacementValue = (typeof placementList)[number]['value'];

export default function Base() {
const [visible, setVisible] = useState(false);
const [placement, setPlacement] = useState<PlacementValue>('top');
const onClick = (value) => {
setPlacement(value);
setVisible(true);
};
const handleVisibleChange = (visible) => {
setVisible(visible);
};
return (
<div style={{ padding: '0 16px' }}>
{placementList.map((item) => (
<Button
key={item.value}
block={true}
variant="outline"
theme="primary"
size="large"
onClick={() => onClick(item.value)}
style={{ margin: '16px 0' }}
>
{item.text}
</Button>
))}
<Popup
visible={visible}
placement={placement}
style={{ padding: '100px' }}
onVisibleChange={handleVisibleChange}
></Popup>
</div>
);
}
35 changes: 35 additions & 0 deletions src/popup/_example/custom-close.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React, { useState } from 'react';
import { CloseCircleIcon } from 'tdesign-icons-react';
import { Popup, Button } from 'tdesign-mobile-react';

export default function CustomClose() {
const [visible, setVisible] = useState(false);

const handleVisibleChange = (visible) => {
setVisible(visible);
};

const onHide = () => setVisible(false);

return (
<>
<Button variant="outline" block={true} theme="primary" size="large" onClick={() => setVisible(true)}>
居中弹出层-带自定义关闭按钮
</Button>

<Popup
visible={visible}
onVisibleChange={handleVisibleChange}
placement="center"
style={{ width: '240px', height: '240px' }}
>
<CloseCircleIcon
className="design-mobile-popup-demo__custom-close close-btn"
size={32}
color="#fff"
onClick={onHide}
/>
</Popup>
</>
);
}
Loading
Loading