Skip to content
Open
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
28 changes: 21 additions & 7 deletions packages/components/dialog/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import useDialogDrag from './hooks/useDialogDrag';
import useDialogEsc from './hooks/useDialogEsc';
import useDialogPosition from './hooks/useDialogPosition';
import useLockStyle from './hooks/useLockStyle';

import type { StyledProps } from '../common';
import type { DialogInstance, TdDialogProps } from './type';

Expand All @@ -27,15 +28,17 @@ export interface DialogProps extends TdDialogProps, StyledProps {
const Dialog = forwardRef<DialogInstance, DialogProps>((originalProps, ref) => {
const props = useDefaultProps<DialogProps>(originalProps, dialogDefaultProps);
const { children, ...restProps } = props;
const { classPrefix } = useConfig();

const { classPrefix } = useConfig();
const componentCls = `${classPrefix}-dialog`;

const wrapRef = useRef<HTMLDivElement>(null);
const maskRef = useRef<HTMLDivElement>(null);
const contentClickRef = useRef(false);
const dialogCardRef = useRef<HTMLDivElement>(null);
const dialogPosition = useRef(null);
const portalRef = useRef(null);

const [state, setState] = useSetState<DialogProps>({ isPlugin: false, ...restProps });
const [local] = useLocaleReceiver('dialog');

Expand Down Expand Up @@ -72,14 +75,17 @@ const Dialog = forwardRef<DialogInstance, DialogProps>((originalProps, ref) => {
...restState
} = state;

const isModeless = mode === 'modeless';
const isFullScreen = mode === 'full-screen';

const dialogAttach = useAttach('dialog', attach);

useLockStyle({ preventScrollThrough, visible, mode, showInAttachedElement });
useDialogEsc(visible, wrapRef);
useDialogPosition(visible, dialogCardRef);
useDialogDrag({
dialogCardRef,
canDraggable: draggable && mode === 'modeless',
canDraggable: draggable && isModeless,
});

useDeepEffect(() => {
Expand Down Expand Up @@ -113,6 +119,8 @@ const Dialog = forwardRef<DialogInstance, DialogProps>((originalProps, ref) => {
}

const onMaskClick = (e: React.MouseEvent<HTMLDivElement>) => {
if (isModeless) return;

if (showOverlay && (closeOnOverlayClick ?? local.closeOnOverlayClick)) {
// 判断点击事件初次点击是否为内容区域
if (contentClickRef.current) {
Expand Down Expand Up @@ -189,6 +197,7 @@ const Dialog = forwardRef<DialogInstance, DialogProps>((originalProps, ref) => {
</CSSTransition>
) : null;
};

return (
<CSSTransition
in={visible}
Expand All @@ -208,6 +217,7 @@ const Dialog = forwardRef<DialogInstance, DialogProps>((originalProps, ref) => {
className={classNames(className, `${componentCls}__ctx`, `${componentCls}__${mode}`, {
[`${componentCls}__ctx--fixed`]: !showInAttachedElement,
[`${componentCls}__ctx--absolute`]: showInAttachedElement,
[`${componentCls}__ctx--modeless`]: isModeless,
})}
style={{ zIndex, display: 'none' }}
onKeyDown={handleKeyDown}
Expand All @@ -217,11 +227,14 @@ const Dialog = forwardRef<DialogInstance, DialogProps>((originalProps, ref) => {
<div className={`${componentCls}__wrap`}>
<div
ref={dialogPosition}
className={classNames(`${componentCls}__position`, {
[`${componentCls}--top`]: !!props.top || props.placement === 'top',
[`${componentCls}--center`]: props.placement === 'center' && !props.top,
})}
style={{ paddingTop: pxCompat(props.top) }}
className={classNames(
isFullScreen ? `${componentCls}__position_fullscreen` : `${componentCls}__position`,
{
[`${componentCls}--top`]: !isFullScreen && (!!props.top || props.placement === 'top'),
[`${componentCls}--center`]: !isFullScreen && props.placement === 'center' && !props.top,
},
)}
style={{ paddingTop: isFullScreen ? undefined : pxCompat(props.top) }}
onClick={onMaskClick}
>
<CSSTransition
Expand All @@ -236,6 +249,7 @@ const Dialog = forwardRef<DialogInstance, DialogProps>((originalProps, ref) => {
<DialogCard
ref={dialogCardRef}
{...restState}
mode={mode}
className={dialogClassName}
style={{ ...style, width: pxCompat(width || style?.width) }}
onConfirm={onConfirm}
Expand Down
64 changes: 51 additions & 13 deletions packages/components/dialog/DialogCard.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
import React, { forwardRef, isValidElement } from 'react';
import classNames from 'classnames';
import { isString, isObject, isFunction } from 'lodash-es';
import {
CheckCircleFilledIcon as TdCheckCircleFilledIcon,
CloseIcon as TdCloseIcon,
InfoCircleFilledIcon as TdInfoCircleFilledIcon,
CheckCircleFilledIcon as TdCheckCircleFilledIcon,
} from 'tdesign-icons-react';
import Button, { ButtonProps } from '../button';
import { TdDialogCardProps } from './type';
import { StyledProps } from '../common';
import classNames from 'classnames';
import { isFunction, isObject, isString } from 'lodash-es';

import parseTNode from '../_util/parseTNode';
import Button, { type ButtonProps } from '../button';
import useConfig from '../hooks/useConfig';
import useDefaultProps from '../hooks/useDefaultProps';
import useGlobalIcon from '../hooks/useGlobalIcon';
import { useLocaleReceiver } from '../locale/LocalReceiver';
import { dialogCardDefaultProps } from './defaultProps';
import useDefaultProps from '../hooks/useDefaultProps';

import type { StyledProps } from '../common';
import type { TdDialogCardProps, TdDialogProps } from './type';

export interface DialogCardProps extends TdDialogCardProps, StyledProps, React.HTMLAttributes<HTMLDivElement> {
children?: React.ReactNode;
mode?: TdDialogProps['mode'];
}

const renderDialogButton = (btn: DialogCardProps['cancelBtn'], defaultProps: ButtonProps) => {
Expand All @@ -28,7 +31,7 @@ const renderDialogButton = (btn: DialogCardProps['cancelBtn'], defaultProps: But
} else if (isValidElement(btn)) {
result = btn;
} else if (isObject(btn)) {
result = <Button {...defaultProps} {...(btn as {})} />;
result = <Button {...defaultProps} {...(btn as ButtonProps)} />;
} else if (isFunction(btn)) {
result = btn();
}
Expand Down Expand Up @@ -62,9 +65,12 @@ const DialogCard = forwardRef<HTMLDivElement, DialogCardProps>((props, ref) => {
cancelBtn = cancelText,
confirmBtn = confirmText,
confirmLoading,
mode,
...otherProps
} = useDefaultProps<DialogCardProps>(props, dialogCardDefaultProps);

const isFullScreen = mode === 'full-screen';

const renderHeaderContent = () => {
const iconMap = {
info: <InfoCircleFilledIcon className={`${classPrefix}-is-info`} />,
Expand Down Expand Up @@ -92,7 +98,9 @@ const DialogCard = forwardRef<HTMLDivElement, DialogCardProps>((props, ref) => {

return (
<span
className={`${componentCls}__close`}
className={classNames(`${componentCls}__close`, {
[`${componentCls}__close--fullscreen`]: isFullScreen,
})}
style={{
marginLeft: 'auto',
}}
Expand All @@ -104,7 +112,11 @@ const DialogCard = forwardRef<HTMLDivElement, DialogCardProps>((props, ref) => {
};

const renderHeader = () => (
<div className={classNames(`${componentCls}__header`)}>
<div
className={classNames(`${componentCls}__header`, {
[`${componentCls}__header--fullscreen`]: isFullScreen,
})}
>
{renderHeaderContent()}
{renderCloseBtn()}
</div>
Expand Down Expand Up @@ -132,13 +144,39 @@ const DialogCard = forwardRef<HTMLDivElement, DialogCardProps>((props, ref) => {
);
};

return <div className={`${componentCls}__footer`}>{parseTNode(footer, null, defaultFooter())}</div>;
return (
<div
className={classNames(`${componentCls}__footer`, {
[`${componentCls}__footer--fullscreen`]: isFullScreen,
})}
>
{parseTNode(footer, null, defaultFooter())}
</div>
);
};

return (
<div ref={ref} {...otherProps} className={classNames(componentCls, `${componentCls}--default`, className)}>
<div
ref={ref}
{...otherProps}
className={classNames(
componentCls,
`${componentCls}--default`,
{
[`${componentCls}__fullscreen`]: isFullScreen,
},
className,
)}
>
{!!header && renderHeader()}
<div className={`${componentCls}__body`}>{body || children}</div>
<div
className={classNames(`${componentCls}__body`, {
[`${componentCls}__body--fullscreen`]: isFullScreen && !!footer,
[`${componentCls}__body--fullscreen--without-footer`]: isFullScreen && !footer,
})}
>
{body || children}
</div>
{!!footer && renderFooter()}
</div>
);
Expand Down
110 changes: 23 additions & 87 deletions packages/components/dialog/_example/modal.tsx
Original file line number Diff line number Diff line change
@@ -1,104 +1,40 @@
import React, { useState } from 'react';
import { Dialog, DialogCard, Button, Select } from 'tdesign-react';
import { Button, Dialog, Space } from 'tdesign-react';

export default function NotModalExample() {
const [visible, setVisible] = useState(false);
const [visible1, setVisible1] = useState(false);
const [visible2, setVisible2] = useState(false);
const [value, setValue] = useState('');
const [modelessAndDraggable, setModelessAndDraggable] = useState(false);
const [modeless, setModeless] = useState(false);
const [modal, setModal] = useState(false);

const handleClick = () => {
setVisible(true);
};
const handleClose = () => {
setVisible(false);
};
const handleClose1 = () => {
setVisible1(false);
};
const handleClose2 = () => {
setVisible2(false);
};
const onChange = (value: string) => {
setValue(value);
};
return (
<>
<Button theme="primary" onClick={handleClick} style={{ marginRight: 16 }}>
模态对话框
</Button>
<Button
theme="primary"
onClick={() => {
setVisible1(true);
}}
style={{ marginRight: 16 }}
>
非模态对话框
</Button>
<Button
theme="primary"
onClick={() => {
setVisible2(true);
}}
style={{ marginRight: 16 }}
>
非模态对话框2
</Button>
<Space>
<Button theme="primary" variant="outline" onClick={() => setModelessAndDraggable(true)}>
非模态对话框(可拖拽)
</Button>
<Button theme="primary" onClick={() => setModeless(true)}>
非模态对话框(不可拖拽)
</Button>
<Button theme="primary" onClick={() => setModal(true)}>
普通对话框(不可拖拽)
</Button>
</Space>

<Dialog
width={800}
header="模态对话框"
visible={visible}
onClose={handleClose}
onCloseBtnClick={() => {
console.log('on click close btn');
}}
onOpened={() => {
console.log('dialog is open');
}}
>
<p>This is a dialog</p>
</Dialog>
<Dialog
mode="modeless"
header="非模态对话框"
draggable={true}
visible={visible1}
onClose={handleClose1}
onOpened={() => {
console.log('dialog is open');
}}
header="非模态对话框(可拖拽)"
draggable
visible={modelessAndDraggable}
onClose={() => setModelessAndDraggable(false)}
>
<Select
value={value}
onChange={onChange}
style={{ width: '40%' }}
clearable
options={[
{ label: '架构云', value: '1' },
{ label: '大数据', value: '2' },
{ label: '区块链', value: '3' },
{ label: '物联网', value: '4', disabled: true },
{ label: '人工智能', value: '5' },
]}
></Select>
<p>This is a dialog</p>
</Dialog>
<Dialog
mode="modeless"
header="非模态对话框2"
draggable={true}
visible={visible2}
onClose={handleClose2}
onOpened={() => {
console.log('dialog is open');
}}
>
<Dialog mode="modeless" header="非模态对话框(不可拖拽)" visible={modeless} onClose={() => setModeless(false)}>
<p>This is a dialog</p>
</Dialog>
<DialogCard header="普通对话框">
<Dialog header="普通对话框(不可拖拽)" visible={modal} onClose={() => setModal(false)}>
<p>This is a dialog</p>
</DialogCard>
</Dialog>
</>
);
}
10 changes: 2 additions & 8 deletions packages/components/dialog/_usage/props.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@
"value": "modeless"
},
{
"label": "normal",
"value": "normal"
"label": "full-screen",
"value": "full-screen"
}
]
},
Expand Down Expand Up @@ -119,11 +119,5 @@
"value": "success"
}
]
},
{
"name": "visible",
"type": "Boolean",
"defaultValue": false,
"options": []
}
]
Loading
Loading