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/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import App from './App';
import '../style/mobile/index.less';

import '../../src/_common/style/mobile/_reset.less';
import '../../src/_common/style/mobile/index.less';
// import '../../src/_common/style/mobile/index.less';

ReactDOM.render(
<React.StrictMode>
Expand Down
4 changes: 2 additions & 2 deletions site/mobile/mobile.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,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 Expand Up @@ -216,6 +216,6 @@ export default {
title: 'Result 结果',
name: 'result',
component: () => import('tdesign-mobile-react/result/_example/index.tsx'),
}
},
],
};
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
22 changes: 22 additions & 0 deletions src/_util/renderTNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ReactNode } from 'react';
import { TNode } from '../common';

interface JSXRenderContext<T = Record<string, any>> {
defaultNode?: ReactNode;
params?: T;
wrap?: (node: ReactNode) => ReactNode;
}

export const renderTNode = <T = any>(node: TNode<T>, options: JSXRenderContext<T>): ReactNode => {
const wrap = options.wrap ?? ((node) => node);
if (typeof node === 'function') {
return wrap(node(options.params));
}
if (node === true) {
return wrap(options.defaultNode);
}
if (!node) {
return null;
}
return wrap(node);
};
15 changes: 15 additions & 0 deletions src/_util/renderToContainer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { createPortal } from 'react-dom';
import { ReactElement, ReactPortal } from 'react';
import isFunction from 'lodash/isFunction';
import isString from 'lodash/isString';
import { resolveContainer } from './getContainer';
import { canUseDom } from './canUseDom';
import { AttachNode } from '../common';

export type GetContainer = HTMLElement | (() => HTMLElement) | null;

Expand All @@ -12,3 +15,15 @@ export function renderToContainer(getContainer: GetContainer, node: ReactElement
}
return node;
}

export function getAttach(node: AttachNode): HTMLElement {
const attachNode = isFunction(node) ? node() : node;

if (isString(attachNode)) {
return document.querySelector(attachNode);
}
if (attachNode instanceof HTMLElement) {
return attachNode;
}
return document.body;
}
4 changes: 3 additions & 1 deletion src/button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import { TdButtonProps } from './type';
import noop from '../_util/noop';
import { buttonDefaultProps } from './defaultProps';

export interface ButtonProps extends TdButtonProps, Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'content'> {}
export interface ButtonProps
hkaikai marked this conversation as resolved.
Show resolved Hide resolved
extends TdButtonProps,
Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'content' | 'children'> {}

const Button = forwardRef((props: ButtonProps, ref: React.Ref<HTMLButtonElement>) => {
const {
Expand Down
2 changes: 1 addition & 1 deletion src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ReactElement, ReactNode, CSSProperties, FormEvent, DragEvent, Synthetic
// TElement 表示 API 只接受传入组件
export type TElement<T = undefined> = T extends undefined ? ReactElement : (props: T) => ReactElement;
// 1. TNode = ReactNode; 2. TNode<T> = (props: T) => ReactNode
export type TNode<T = undefined> = T extends undefined ? ReactNode : (props: T) => ReactNode;
export type TNode<T = undefined> = T extends undefined ? ReactNode | (() => ReactNode) : ReactNode | ((props: T) => ReactNode);
anlyyao marked this conversation as resolved.
Show resolved Hide resolved

export type AttachNodeReturnValue = HTMLElement | Element | Document;
export type AttachNode = CSSSelector | ((triggerNode?: HTMLElement) => AttachNodeReturnValue);
Expand Down
2 changes: 2 additions & 0 deletions src/overlay/Overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface OverlayProps extends NativeProps {
afterClose?: () => void;
anlyyao marked this conversation as resolved.
Show resolved Hide resolved
stopPropagation?: PropagationEvent[];
children?: React.ReactNode;
duration?: number;
}

const opacityRecord = {
Expand Down Expand Up @@ -62,6 +63,7 @@ const Overlay: FC<OverlayProps> = (props) => {
tension: 200,
friction: 30,
clamp: true,
duration: props.duration,
},
onStart: () => {
setActive(true);
Expand Down
101 changes: 78 additions & 23 deletions src/popup/Popup.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import React, { FC, useState } from 'react';
import classnames from 'classnames';
import { useSpring, animated } from 'react-spring';
import { CloseIcon } from 'tdesign-icons-react';
import Overlay from '../overlay';
import useDefault from '../_util/useDefault';
import { PropagationEvent } from '../_util/withStopPropagation';
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 { renderToContainer, getAttach } from '../_util/renderToContainer';
import { renderTNode } from '../_util/renderTNode';
anlyyao marked this conversation as resolved.
Show resolved Hide resolved

export interface PopupProps extends TdPopupProps, NativeProps {}

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

enum PlacementEnum {
Expand All @@ -24,28 +28,66 @@ enum PlacementEnum {
}

const Popup: FC<PopupProps> = (props) => {
anlyyao marked this conversation as resolved.
Show resolved Hide resolved
const { children, placement, showOverlay, visible, defaultVisible, zIndex, overlayProps, onVisibleChange } = props;
const {
children,
placement,
showOverlay,
visible,
defaultVisible,
zIndex,
overlayProps,
preventScrollThrough,
attach,
destroyOnClose,
closeBtn,
closeOnOverlayClick,
onClose,
onClosed,
onOpen,
onOpened,
onVisibleChange,
} = useDefaultProps(props, popupDefaultProps);

const { classPrefix } = useConfig();

const name = `${classPrefix}-popup`;

const duration = 300;

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 handleCloseClick = (e) => {
onClose?.(e);
setShow(false, PopupSourceEnum.CLOSEBTN);
};

const { progress, opacity } = useSpring({
progress: show ? 0 : 100,
opacity: show ? 1 : 0,
config: {
duration,
},
onStart: () => {
if (show) {
onOpen?.();
}
setActive(true);
},
onRest: () => {
setActive(show);
if (show) {
onOpened?.();
} else {
onClosed?.();
}
},
});

Expand All @@ -63,39 +105,52 @@ const Popup: FC<PopupProps> = (props) => {
if (placement === PlacementEnum.RIGHT) {
return `translateX(${p}%)`;
}
if (placement === PlacementEnum.CENTER) {
return `scale(${1 - p / 100}) translate3d(-50%, -50%, 0)`;
}
}),
opacity: opacity.to((o) => {
if (placement === PlacementEnum.CENTER) {
return o;
}
}),
};

const rootStyles = {
zIndex,
display: active ? 'block' : 'none',
display: active ? null : 'none',
transition: 'none',
transformOrigin: '0 0',
};

return withNativeProps(
props,
<div className={`${name}`} style={rootStyles}>
{showOverlay && (
<Overlay
visible={show}
onOverlayClick={handleOverlayClick}
disableBodyScroll={false}
stopPropagation={[PropagationEvent.CLICK, PropagationEvent.SCROLL]}
{...overlayProps}
/>
const closeBtnNode = renderTNode(closeBtn, {
defaultNode: <CloseIcon size={24} />,
params: props,
wrap: (node) => (
<div className={`${name}__close`} onClick={handleCloseClick}>
{node}
</div>
),
});
let node = (
<>
<Overlay
visible={show && showOverlay}
onOverlayClick={handleOverlayClick}
disableBodyScroll={preventScrollThrough}
duration={duration}
{...overlayProps}
/>
{withNativeProps(
props,
<animated.div className={classnames([name, `${name}--${placement}`])} style={contentStyle}>
anlyyao marked this conversation as resolved.
Show resolved Hide resolved
{closeBtnNode}
{children}
</animated.div>,
)}
<animated.div className={classnames([`${name}--content`, `${name}--content-${placement}`])} style={contentStyle}>
{active && children}
</animated.div>
</div>,
</>
);
node = attach ? renderToContainer(getAttach(attach), node) : node;
return (!destroyOnClose || active) && node;
};

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

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

This file was deleted.

Loading
Loading