mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-24 02:59:58 +08:00
chore: next merge master
This commit is contained in:
commit
3ffd0d8a46
@ -7,7 +7,7 @@ import { convertLegacyProps } from '../button/button';
|
||||
export interface ActionButtonProps {
|
||||
type?: LegacyButtonType;
|
||||
actionFn?: (...args: any[]) => any | PromiseLike<any>;
|
||||
close: Function;
|
||||
close?: Function;
|
||||
autoFocus?: boolean;
|
||||
prefixCls: string;
|
||||
buttonProps?: ButtonProps;
|
||||
@ -24,6 +24,10 @@ const ActionButton: React.FC<ActionButtonProps> = props => {
|
||||
const clickedRef = React.useRef<boolean>(false);
|
||||
const ref = React.useRef<any>();
|
||||
const [loading, setLoading] = useState<ButtonProps['loading']>(false);
|
||||
const { close } = props;
|
||||
const onInternalClose = (...args: any[]) => {
|
||||
close?.(...args);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
let timeoutId: any;
|
||||
@ -39,7 +43,6 @@ const ActionButton: React.FC<ActionButtonProps> = props => {
|
||||
}, []);
|
||||
|
||||
const handlePromiseOnOk = (returnValueOfOnOk?: PromiseLike<any>) => {
|
||||
const { close } = props;
|
||||
if (!isThenable(returnValueOfOnOk)) {
|
||||
return;
|
||||
}
|
||||
@ -47,7 +50,7 @@ const ActionButton: React.FC<ActionButtonProps> = props => {
|
||||
returnValueOfOnOk!.then(
|
||||
(...args: any[]) => {
|
||||
setLoading(false, true);
|
||||
close(...args);
|
||||
onInternalClose(...args);
|
||||
clickedRef.current = false;
|
||||
},
|
||||
(e: Error) => {
|
||||
@ -62,13 +65,13 @@ const ActionButton: React.FC<ActionButtonProps> = props => {
|
||||
};
|
||||
|
||||
const onClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
const { actionFn, close } = props;
|
||||
const { actionFn } = props;
|
||||
if (clickedRef.current) {
|
||||
return;
|
||||
}
|
||||
clickedRef.current = true;
|
||||
if (!actionFn) {
|
||||
close();
|
||||
onInternalClose();
|
||||
return;
|
||||
}
|
||||
let returnValueOfOnOk;
|
||||
@ -76,7 +79,7 @@ const ActionButton: React.FC<ActionButtonProps> = props => {
|
||||
returnValueOfOnOk = actionFn(e);
|
||||
if (props.quitOnNullishReturnValue && !isThenable(returnValueOfOnOk)) {
|
||||
clickedRef.current = false;
|
||||
close(e);
|
||||
onInternalClose(e);
|
||||
return;
|
||||
}
|
||||
} else if (actionFn.length) {
|
||||
@ -86,7 +89,7 @@ const ActionButton: React.FC<ActionButtonProps> = props => {
|
||||
} else {
|
||||
returnValueOfOnOk = actionFn();
|
||||
if (!returnValueOfOnOk) {
|
||||
close();
|
||||
onInternalClose();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import type { TransferLocale as TransferLocaleForEmpty } from '../empty';
|
||||
import type { ModalLocale } from '../modal/locale';
|
||||
import { changeConfirmLocale } from '../modal/locale';
|
||||
import type { PaginationLocale } from '../pagination/Pagination';
|
||||
import type { PopconfirmLocale } from '../popconfirm';
|
||||
import type { PopconfirmLocale } from '../popconfirm/PurePanel';
|
||||
import type { TableLocale } from '../table/interface';
|
||||
import type { TransferLocale } from '../transfer';
|
||||
import type { UploadLocale } from '../upload/interface';
|
||||
|
81
components/popconfirm/PurePanel.tsx
Normal file
81
components/popconfirm/PurePanel.tsx
Normal file
@ -0,0 +1,81 @@
|
||||
import * as React from 'react';
|
||||
import type { PopconfirmProps } from '.';
|
||||
import Button from '../button';
|
||||
import { convertLegacyProps } from '../button/button';
|
||||
import ActionButton from '../_util/ActionButton';
|
||||
import LocaleReceiver from '../locale-provider/LocaleReceiver';
|
||||
import defaultLocale from '../locale/default';
|
||||
import { getRenderPropValue } from '../_util/getRenderPropValue';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
|
||||
export interface PopconfirmLocale {
|
||||
okText: string;
|
||||
cancelText: string;
|
||||
}
|
||||
|
||||
export interface OverlayProps
|
||||
extends Pick<
|
||||
PopconfirmProps,
|
||||
| 'icon'
|
||||
| 'okButtonProps'
|
||||
| 'cancelButtonProps'
|
||||
| 'cancelText'
|
||||
| 'okText'
|
||||
| 'okType'
|
||||
| 'showCancel'
|
||||
| 'title'
|
||||
> {
|
||||
prefixCls: string;
|
||||
close?: Function;
|
||||
onConfirm?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
onCancel?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
}
|
||||
|
||||
export function Overlay(props: OverlayProps) {
|
||||
const {
|
||||
prefixCls,
|
||||
okButtonProps,
|
||||
cancelButtonProps,
|
||||
title,
|
||||
cancelText,
|
||||
okText,
|
||||
okType,
|
||||
icon,
|
||||
showCancel = true,
|
||||
close,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
} = props;
|
||||
|
||||
const { getPrefixCls } = React.useContext(ConfigContext);
|
||||
|
||||
return (
|
||||
<LocaleReceiver componentName="Popconfirm" defaultLocale={defaultLocale.Popconfirm}>
|
||||
{(popconfirmLocale: PopconfirmLocale) => (
|
||||
<div className={`${prefixCls}-inner-content`}>
|
||||
<div className={`${prefixCls}-message`}>
|
||||
{icon}
|
||||
<div className={`${prefixCls}-message-title`}>{getRenderPropValue(title)}</div>
|
||||
</div>
|
||||
<div className={`${prefixCls}-buttons`}>
|
||||
{showCancel && (
|
||||
<Button onClick={onCancel} size="small" {...cancelButtonProps}>
|
||||
{cancelText || popconfirmLocale.cancelText}
|
||||
</Button>
|
||||
)}
|
||||
<ActionButton
|
||||
buttonProps={{ size: 'small', ...convertLegacyProps(okType), ...okButtonProps }}
|
||||
actionFn={onConfirm}
|
||||
close={close}
|
||||
prefixCls={getPrefixCls('btn')}
|
||||
quitOnNullishReturnValue
|
||||
emitEvent
|
||||
>
|
||||
{okText || popconfirmLocale.okText}
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</LocaleReceiver>
|
||||
);
|
||||
}
|
@ -3,19 +3,15 @@ import classNames from 'classnames';
|
||||
import useMergedState from 'rc-util/lib/hooks/useMergedState';
|
||||
import KeyCode from 'rc-util/lib/KeyCode';
|
||||
import * as React from 'react';
|
||||
import Button from '../button';
|
||||
import type { ButtonProps, LegacyButtonType } from '../button/button';
|
||||
import { convertLegacyProps } from '../button/button';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
import LocaleReceiver from '../locale-provider/LocaleReceiver';
|
||||
import defaultLocale from '../locale/default';
|
||||
import Popover from '../popover';
|
||||
import genPurePanel from '../_util/PurePanel';
|
||||
import type { AbstractTooltipProps } from '../tooltip';
|
||||
import ActionButton from '../_util/ActionButton';
|
||||
import type { RenderFunction } from '../_util/getRenderPropValue';
|
||||
import { getRenderPropValue } from '../_util/getRenderPropValue';
|
||||
import { cloneElement } from '../_util/reactNode';
|
||||
import { Overlay } from './PurePanel';
|
||||
|
||||
import usePopconfirmStyle from './style';
|
||||
|
||||
export interface PopconfirmProps extends AbstractTooltipProps {
|
||||
@ -40,11 +36,6 @@ export interface PopconfirmState {
|
||||
visible?: boolean;
|
||||
}
|
||||
|
||||
export interface PopconfirmLocale {
|
||||
okText: string;
|
||||
cancelText: string;
|
||||
}
|
||||
|
||||
const Popconfirm = React.forwardRef<unknown, PopconfirmProps>((props, ref) => {
|
||||
const { getPrefixCls } = React.useContext(ConfigContext);
|
||||
const [visible, setVisible] = useMergedState(false, {
|
||||
@ -87,44 +78,6 @@ const Popconfirm = React.forwardRef<unknown, PopconfirmProps>((props, ref) => {
|
||||
settingVisible(value);
|
||||
};
|
||||
|
||||
const renderOverlay = (prefixCls: string, popconfirmLocale: PopconfirmLocale) => {
|
||||
const {
|
||||
okButtonProps,
|
||||
cancelButtonProps,
|
||||
title,
|
||||
cancelText,
|
||||
okText,
|
||||
okType,
|
||||
icon,
|
||||
showCancel = true,
|
||||
} = props;
|
||||
return (
|
||||
<div className={`${prefixCls}-inner-content`}>
|
||||
<div className={`${prefixCls}-message`}>
|
||||
{icon}
|
||||
<div className={`${prefixCls}-message-title`}>{getRenderPropValue(title)}</div>
|
||||
</div>
|
||||
<div className={`${prefixCls}-buttons`}>
|
||||
{showCancel && (
|
||||
<Button onClick={onCancel} size="small" {...cancelButtonProps}>
|
||||
{cancelText || popconfirmLocale.cancelText}
|
||||
</Button>
|
||||
)}
|
||||
<ActionButton
|
||||
buttonProps={{ size: 'small', ...convertLegacyProps(okType), ...okButtonProps }}
|
||||
actionFn={onConfirm}
|
||||
close={close}
|
||||
prefixCls={getPrefixCls('btn')}
|
||||
quitOnNullishReturnValue
|
||||
emitEvent
|
||||
>
|
||||
{okText || popconfirmLocale.okText}
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
placement,
|
||||
@ -137,19 +90,21 @@ const Popconfirm = React.forwardRef<unknown, PopconfirmProps>((props, ref) => {
|
||||
|
||||
const [wrapSSR] = usePopconfirmStyle(prefixCls);
|
||||
|
||||
const overlay = (
|
||||
<LocaleReceiver componentName="Popconfirm" defaultLocale={defaultLocale.Popconfirm}>
|
||||
{(popconfirmLocale: PopconfirmLocale) => renderOverlay(prefixCls, popconfirmLocale)}
|
||||
</LocaleReceiver>
|
||||
);
|
||||
|
||||
return wrapSSR(
|
||||
<Popover
|
||||
{...restProps}
|
||||
placement={placement}
|
||||
onVisibleChange={onVisibleChange}
|
||||
visible={visible}
|
||||
_overlay={overlay}
|
||||
_overlay={
|
||||
<Overlay
|
||||
{...props}
|
||||
prefixCls={prefixCls}
|
||||
close={close}
|
||||
onConfirm={onConfirm}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
}
|
||||
overlayClassName={overlayClassNames}
|
||||
ref={ref as any}
|
||||
data-popover-inject
|
||||
|
@ -59,7 +59,7 @@ Select component to select value from options.
|
||||
| removeIcon | The custom remove icon | ReactNode | - | |
|
||||
| searchValue | The current input "search" text | string | - | |
|
||||
| showArrow | Whether to show the drop-down arrow | boolean | true(for single select), false(for multiple select) | |
|
||||
| showSearch | Whether show search input in single mode | boolean | false | |
|
||||
| showSearch | Whether select is searchable | boolean | single: false, multple: true | |
|
||||
| size | Size of Select input | `large` \| `middle` \| `small` | `middle` | |
|
||||
| status | Set validation status | 'error' \| 'warning' | - | 4.19.0 |
|
||||
| suffixIcon | The custom suffix icon | ReactNode | - | |
|
||||
@ -107,7 +107,7 @@ Select component to select value from options.
|
||||
|
||||
## FAQ
|
||||
|
||||
### Why sometime search will show 2 same option when in `tag` mode?
|
||||
### Why sometime search will show 2 same option when in `tags` mode?
|
||||
|
||||
It's caused by option with different `label` and `value`. You can use `optionFilterProp="label"` to change filter logic instead.
|
||||
|
||||
|
@ -60,7 +60,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/_0XzgOis7/Select.svg
|
||||
| removeIcon | 自定义的多选框清除图标 | ReactNode | - | |
|
||||
| searchValue | 控制搜索文本 | string | - | |
|
||||
| showArrow | 是否显示下拉小箭头 | boolean | 单选为 true,多选为 false | |
|
||||
| showSearch | 使单选模式可搜索 | boolean | false | |
|
||||
| showSearch | 配置是否可搜索 | boolean | 单选为 false,多选为 true | |
|
||||
| size | 选择框大小 | `large` \| `middle` \| `small` | `middle` | |
|
||||
| status | 设置校验状态 | 'error' \| 'warning' | - | 4.19.0 |
|
||||
| suffixIcon | 自定义的选择框后缀图标 | ReactNode | - | |
|
||||
@ -108,7 +108,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/_0XzgOis7/Select.svg
|
||||
|
||||
## FAQ
|
||||
|
||||
### `tag` 模式下为何搜索有时会出现两个相同选项?
|
||||
### `mode="tags"` 模式下为何搜索有时会出现两个相同选项?
|
||||
|
||||
这一般是 `options` 中的 `label` 和 `value` 不同导致的,你可以通过 `optionFilterProp="label"` 将过滤设置为展示值以避免这种情况。
|
||||
|
||||
|
@ -1,6 +1,3 @@
|
||||
// import '../../style/index.less';
|
||||
// import './index.less';
|
||||
|
||||
// deps-lint-skip-all
|
||||
import type { CSSInterpolation, CSSObject } from '@ant-design/cssinjs';
|
||||
import { Keyframes } from '@ant-design/cssinjs';
|
||||
@ -163,6 +160,21 @@ export const genBaseStyle = (prefixCls: string, token: TreeToken): CSSObject =>
|
||||
color: 'inherit',
|
||||
fontWeight: 500,
|
||||
},
|
||||
|
||||
'&-draggable': {
|
||||
[`${treeCls}-draggable-icon`]: {
|
||||
width: treeTitleHeight,
|
||||
lineHeight: `${treeTitleHeight}px`,
|
||||
textAlign: 'center',
|
||||
visibility: 'visible',
|
||||
opacity: 0.2,
|
||||
transition: `opacity ${token.motionDurationSlow}`,
|
||||
|
||||
[`${treeNodeCls}:hover &`]: {
|
||||
opacity: 0.45,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// >>> Indent
|
||||
@ -178,15 +190,7 @@ export const genBaseStyle = (prefixCls: string, token: TreeToken): CSSObject =>
|
||||
|
||||
// >>> Drag Handler
|
||||
[`${treeCls}-draggable-icon`]: {
|
||||
width: treeTitleHeight,
|
||||
lineHeight: `${treeTitleHeight}px`,
|
||||
textAlign: 'center',
|
||||
opacity: 0.2,
|
||||
transition: `opacity ${token.motionDurationSlow}`,
|
||||
|
||||
[`${treeNodeCls}:hover &`]: {
|
||||
opacity: 0.45,
|
||||
},
|
||||
visibility: 'hidden',
|
||||
},
|
||||
|
||||
// >>> Switcher
|
||||
|
@ -928,6 +928,35 @@ describe('Upload List', () => {
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('upload svg file with <foreignObject> should not have CORS error', async () => {
|
||||
const mockFile = new File(
|
||||
[
|
||||
'<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"><foreignObject x="20" y="20" width="160" height="160"><div xmlns="http://www.w3.org/1999/xhtml">Test</div></foreignObject></svg>',
|
||||
],
|
||||
'bar.svg',
|
||||
{ type: 'image/svg+xml' },
|
||||
);
|
||||
|
||||
const previewFunc = jest.fn(previewImage);
|
||||
|
||||
const { unmount } = render(
|
||||
<Upload
|
||||
fileList={[{ originFileObj: mockFile }]}
|
||||
previewFile={previewFunc}
|
||||
locale={{ uploading: 'uploading' }}
|
||||
listType="picture-card"
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(previewFunc).toHaveBeenCalled();
|
||||
});
|
||||
await previewFunc(mockFile).then(dataUrl => {
|
||||
expect(dataUrl).toEqual('data:image/png;base64,');
|
||||
});
|
||||
unmount();
|
||||
});
|
||||
|
||||
it("upload non image file shouldn't be converted to the base64", async () => {
|
||||
const mockFile = new File([''], 'foo.7z', {
|
||||
type: 'application/x-7z-compressed',
|
||||
|
@ -110,6 +110,15 @@ export function previewImage(file: File | Blob): Promise<string> {
|
||||
|
||||
resolve(dataURL);
|
||||
};
|
||||
img.src = window.URL.createObjectURL(file);
|
||||
img.crossOrigin = 'anonymous';
|
||||
if (file.type.startsWith('image/svg+xml')) {
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener('load', () => {
|
||||
if (reader.result) img.src = reader.result as string;
|
||||
});
|
||||
reader.readAsDataURL(file);
|
||||
} else {
|
||||
img.src = window.URL.createObjectURL(file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user