Skip to content

Commit

Permalink
fix(Drawer): use FooterButton props define confirmBtn and `closeB…
Browse files Browse the repository at this point in the history
…tn` are invalid (#3191)

* fix(Drawer): use `FooterButton` parm  define `confirmBtn` and `closeBtn` are invalid

* chore: remove unexpected strings

* chore: remove test toMatchSnapshot
  • Loading branch information
RSS1102 authored Nov 15, 2024
1 parent d125788 commit 2c28a90
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 46 deletions.
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

0 comments on commit 2c28a90

Please sign in to comment.