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

fix(Drawer): use FooterButton props define confirmBtn and closeBtn are invalid #3191

Merged
merged 3 commits into from
Nov 15, 2024
Merged
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
108 changes: 66 additions & 42 deletions src/drawer/Drawer.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import React, { forwardRef, useState, useEffect, useImperativeHandle, useRef, useMemo } from 'react';
import React, { forwardRef, useState, useEffect, useImperativeHandle, useRef, useMemo, isValidElement } from 'react';
import classnames from 'classnames';
import isString from 'lodash/isString';
import isObject from 'lodash/isObject';
import isFunction from 'lodash/isFunction';

import { CSSTransition } from 'react-transition-group';
import { CloseIcon as TdCloseIcon } from 'tdesign-icons-react';

import { useLocaleReceiver } from '../locale/LocalReceiver';
import { TdDrawerProps, DrawerEventSource } from './type';
import { StyledProps } from '../common';
import Button from '../button';
import Button, { ButtonProps } from '../button';
import useConfig from '../hooks/useConfig';
import useGlobalIcon from '../hooks/useGlobalIcon';
import { drawerDefaultProps } from './defaultProps';
import useDrag from './hooks/useDrag';
import Portal from '../common/Portal';
import useLockStyle from './hooks/useLockStyle';
import useDefaultProps from '../hooks/useDefaultProps';
import parseTNode from '../_util/parseTNode';

export const CloseTriggerType: { [key: string]: DrawerEventSource } = {
CLICK_OVERLAY: 'overlay',
Expand All @@ -25,6 +30,12 @@ export const CloseTriggerType: { [key: string]: DrawerEventSource } = {
export interface DrawerProps extends TdDrawerProps, StyledProps {}

const Drawer = forwardRef<HTMLDivElement, DrawerProps>((originalProps, ref) => {
// 国际化文本初始化
const [local, t] = useLocaleReceiver('drawer');
const { CloseIcon } = useGlobalIcon({ CloseIcon: TdCloseIcon });
const confirmText = t(local.confirm);
const cancelText = t(local.cancel);

const props = useDefaultProps<DrawerProps>(originalProps, drawerDefaultProps);
const {
className,
Expand All @@ -49,28 +60,22 @@ const Drawer = forwardRef<HTMLDivElement, DrawerProps>((originalProps, ref) => {
body,
footer,
closeBtn,
cancelBtn,
confirmBtn,
cancelBtn = cancelText,
confirmBtn = confirmText,
zIndex,
destroyOnClose,
sizeDraggable,
forceRender,
} = props;

// 国际化文本初始化
const [local, t] = useLocaleReceiver('drawer');
const { CloseIcon } = useGlobalIcon({ CloseIcon: TdCloseIcon });
const size = propsSize ?? local.size;
const confirmText = t(local.confirm);
const cancelText = t(local.cancel);

const { classPrefix } = useConfig();
const maskRef = useRef<HTMLDivElement>();
const containerRef = useRef<HTMLDivElement>();
const drawerWrapperRef = useRef<HTMLElement>(); // 即最终的 attach dom,默认为 document.body
const prefixCls = `${classPrefix}-drawer`;

const closeIcon = React.isValidElement(closeBtn) ? closeBtn : <CloseIcon />;
const closeIcon = isValidElement(closeBtn) ? closeBtn : <CloseIcon />;
const { dragSizeValue, enableDrag, draggableLineStyles } = useDrag(placement, sizeDraggable, onSizeDragEnd);
const [animationStart, setAnimationStart] = useState(visible);

Expand Down Expand Up @@ -119,37 +124,57 @@ const Drawer = forwardRef<HTMLDivElement, DrawerProps>((originalProps, ref) => {
[visible, placement, sizeValue, animationStart],
);

function getFooter(): React.ReactNode {
if (footer !== true) return footer;

const defaultCancelBtn = (
<Button theme="default" onClick={onCancelClick} className={`${prefixCls}__cancel`}>
{cancelBtn && typeof cancelBtn === 'string' ? cancelBtn : cancelText}
</Button>
);

const defaultConfirmBtn = (
<Button theme="primary" onClick={onConfirmClick} className={`${prefixCls}__confirm`}>
{confirmBtn && typeof confirmBtn === 'string' ? confirmBtn : confirmText}
</Button>
);

const renderCancelBtn = cancelBtn && React.isValidElement(cancelBtn) ? cancelBtn : defaultCancelBtn;
const renderConfirmBtn = confirmBtn && React.isValidElement(confirmBtn) ? confirmBtn : defaultConfirmBtn;

const footerStyle = {
display: 'flex',
justifyContent: placement === 'right' ? 'flex-start' : 'flex-end',
const renderDrawerButton = (btn: DrawerProps['cancelBtn'], defaultProps: ButtonProps) => {
let result = null;

if (isString(btn)) {
result = <Button {...defaultProps}>{btn}</Button>;
} else if (isValidElement(btn)) {
result = btn;
} else if (isObject(btn)) {
result = <Button {...defaultProps} {...(btn as {})} />;
} else if (isFunction(btn)) {
result = btn();
}

return result;
};

const renderFooter = () => {
const defaultFooter = () => {
const renderCancelBtn = renderDrawerButton(cancelBtn, {
theme: 'default',
onClick: (e: React.MouseEvent<HTMLButtonElement>) => onCancelClick?.(e),
className: `${prefixCls}__cancel`,
});
const renderConfirmBtn = renderDrawerButton(confirmBtn, {
theme: 'primary',
onClick: (e: React.MouseEvent<HTMLButtonElement>) => onConfirmClick?.(e),
className: `${prefixCls}__confirm`,
});

const footerStyle = {
display: 'flex',
justifyContent: placement === 'right' ? 'flex-start' : 'flex-end',
};

return (
<div style={footerStyle}>
{placement === 'right' ? (
<>
{renderConfirmBtn} {renderCancelBtn}
</>
) : (
<>
{renderCancelBtn} {renderConfirmBtn}
</>
)}
</div>
);
};

return (
<div style={footerStyle}>
{placement === 'right' && renderConfirmBtn}
{renderCancelBtn}
{placement !== 'right' && renderConfirmBtn}
</div>
);
}
return <div className={`${prefixCls}__footer`}>{parseTNode(footer, null, defaultFooter())}</div>;
};

const renderOverlay = showOverlay && (
<CSSTransition in={visible} timeout={200} classNames={`${prefixCls}-fade`} nodeRef={maskRef}>
Expand All @@ -163,7 +188,6 @@ const Drawer = forwardRef<HTMLDivElement, DrawerProps>((originalProps, ref) => {
);
const renderHeader = header && <div className={`${prefixCls}__header`}>{header}</div>;
const renderBody = <div className={`${prefixCls}__body`}>{body || children}</div>;
const renderFooter = footer && <div className={`${prefixCls}__footer`}>{getFooter()}</div>;

return (
<CSSTransition
Expand Down Expand Up @@ -195,7 +219,7 @@ const Drawer = forwardRef<HTMLDivElement, DrawerProps>((originalProps, ref) => {
{renderCloseBtn}
{renderHeader}
{renderBody}
{renderFooter}
{!!footer && renderFooter()}
{sizeDraggable && <div style={draggableLineStyles} onMouseDown={enableDrag}></div>}
</div>
</div>
Expand Down
44 changes: 40 additions & 4 deletions src/drawer/__tests__/drawer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,54 @@ describe('test Drawer', () => {
fireEvent.click(getByText('Open'));
expect(document.querySelector('.t-drawer__mask')).not.toBeInTheDocument();
});
test('Drawer header and footer', () => {
test('Drawer header and footer custom', () => {
const { getByText } = render(<DrawerDemo header={<div>自定义头部</div>} footer={<div>自定义底部</div>} />);
fireEvent.click(getByText('Open'));
expect(getByText('自定义头部').parentElement).toHaveClass('t-drawer__header');
expect(getByText('自定义底部').parentElement).toHaveClass('t-drawer__footer');
});
test('Drawer cancelBtn and confirmBtn', () => {
test('Drawer cancelBtn and confirmBtn custom', () => {
const { getByText } = render(<DrawerDemo cancelBtn={<div>cancelBtn</div>} confirmBtn={<div>confirmBtn</div>} />);
fireEvent.click(getByText('Open'));
expect(getByText('cancelBtn').parentElement.parentElement).toHaveClass('t-drawer__footer');
expect(getByText('confirmBtn').parentElement.parentElement).toHaveClass('t-drawer__footer');

const cancelBtn = getByText('cancelBtn');
const confirmBtn = getByText('confirmBtn');

expect(cancelBtn.parentElement.parentElement).toHaveClass('t-drawer__footer');
expect(confirmBtn.parentElement.parentElement).toHaveClass('t-drawer__footer');
});

test('Drawer cancelBtn and confirmBtn props', () => {
const { getByText } = render(
<DrawerDemo
confirmBtn={{
content: '确认按钮',
theme: 'success',
}}
cancelBtn={{
content: '取消按钮',
theme: 'danger',
}}
/>,
);
fireEvent.click(getByText('Open'));

const confirmBtn = getByText('确认按钮');
const cancelBtn = getByText('取消按钮');

// 是否有这两个元素
expect(cancelBtn).toBeInTheDocument();
expect(confirmBtn).toBeInTheDocument();

expect(confirmBtn.parentElement).toHaveClass('t-drawer__confirm');
expect(cancelBtn.parentElement).toHaveClass('t-drawer__cancel');
expect(confirmBtn.parentElement.parentElement.parentElement).toHaveClass('t-drawer__footer');
expect(cancelBtn.parentElement.parentElement.parentElement).toHaveClass('t-drawer__footer');

expect(confirmBtn.parentElement).toHaveClass(`t-button--theme-success`);
expect(cancelBtn.parentElement).toHaveClass(`t-button--theme-danger`);
});

test('Drawer mode push', () => {
const { getByText } = render(<DrawerDemo attach="body" mode="push" />);
fireEvent.click(getByText('Open'));
Expand Down
Loading