chore: merge feature into master

This commit is contained in:
afc163 2021-09-29 08:43:06 +08:00
commit d35576d675
24 changed files with 485 additions and 42 deletions

View File

@ -479,6 +479,43 @@ Array [
</span>
</span>
</div>,
<div
class="ant-divider ant-divider-horizontal"
role="separator"
/>,
<div
class="ant-avatar-group"
>
<span
class="ant-avatar ant-avatar-lg ant-avatar-circle ant-avatar-image"
>
<img
src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"
/>
</span>
<span
class="ant-avatar ant-avatar-lg ant-avatar-circle"
style="background-color:#f56a00"
>
<span
class="ant-avatar-string"
style="opacity:0"
>
K
</span>
</span>
<span
class="ant-avatar ant-avatar-lg ant-avatar-circle"
style="color:#f56a00;background-color:#fde3cf;cursor:pointer"
>
<span
class="ant-avatar-string"
style="opacity:0"
>
+2
</span>
</span>
</div>,
]
`;

View File

@ -49,6 +49,20 @@ const Demo = () => (
</Tooltip>
<Avatar style={{ backgroundColor: '#1890ff' }} icon={<AntDesignOutlined />} />
</Avatar.Group>
<Divider />
<Avatar.Group
maxCount={2}
maxPopoverTrigger="click"
size="large"
maxStyle={{ color: '#f56a00', backgroundColor: '#fde3cf', cursor: 'pointer' }}
>
<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />
<Avatar style={{ backgroundColor: '#f56a00' }}>K</Avatar>
<Tooltip title="Ant User" placement="top">
<Avatar style={{ backgroundColor: '#87d068' }} icon={<UserOutlined />} />
</Tooltip>
<Avatar style={{ backgroundColor: '#1890ff' }} icon={<AntDesignOutlined />} />
</Avatar.Group>
</>
);

View File

@ -15,6 +15,7 @@ export interface GroupProps {
maxCount?: number;
maxStyle?: React.CSSProperties;
maxPopoverPlacement?: 'top' | 'bottom';
maxPopoverTrigger?: 'hover' | 'focus' | 'click';
/*
* Size of avatar, options: `large`, `small`, `default`
* or a custom number size
@ -36,7 +37,7 @@ const Group: React.FC<GroupProps> = props => {
className,
);
const { children, maxPopoverPlacement = 'top' } = props;
const { children, maxPopoverPlacement = 'top', maxPopoverTrigger = 'hover' } = props;
const childrenWithProps = toArray(children).map((child, index) =>
cloneElement(child, {
key: `avatar-key-${index}`,
@ -51,7 +52,7 @@ const Group: React.FC<GroupProps> = props => {
<Popover
key="avatar-popover-key"
content={childrenHidden}
trigger="hover"
trigger={maxPopoverTrigger}
placement={maxPopoverPlacement}
overlayClassName={`${prefixCls}-popover`}
>

View File

@ -32,5 +32,6 @@ Avatars can be used to represent people or objects. It supports images, `Icon`s,
| --- | --- | --- | --- | --- |
| maxCount | Max avatars to show | number | - | |
| maxPopoverPlacement | The placement of excess avatar Popover | `top` \| `bottom` | `top` | |
| maxPopoverTrigger | Set the trigger of excess avatar Popover | `hover` \| `focus` \| `click` | `hover` | 4.17.0 |
| maxStyle | The style of excess avatar style | CSSProperties | - | |
| size | The size of the avatar | number \| `large` \| `small` \| `default` \| { xs: number, sm: number, ...} | `default` | 4.8.0 |

View File

@ -37,5 +37,6 @@ cover: https://gw.alipayobjects.com/zos/antfincdn/aBcnbw68hP/Avatar.svg
| --- | --- | --- | --- | --- |
| maxCount | 显示的最大头像个数 | number | - | |
| maxPopoverPlacement | 多余头像气泡弹出位置 | `top` \| `bottom` | `top` | |
| maxPopoverTrigger | 设置多余头像 Popover 的触发方式 | `hover` \| `focus` \| `click` | `hover` | 4.17.0 |
| maxStyle | 多余头像样式 | CSSProperties | - | |
| size | 设置头像的大小 | number \| `large` \| `small` \| `default` \| { xs: number, sm: number, ...} | `default` | 4.8.0 |

View File

@ -173,5 +173,33 @@ Array [
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista probare, quae sunt a te dicta? Refert tamen, quo modo.
</p>,
<div
class="ant-divider ant-divider-horizontal ant-divider-with-text ant-divider-with-text-left ant-divider-no-default-orientation-margin-left"
role="separator"
>
<span
class="ant-divider-inner-text"
style="margin-left:0"
>
Left Text with 0 orientationMargin
</span>
</div>,
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista probare, quae sunt a te dicta? Refert tamen, quo modo.
</p>,
<div
class="ant-divider ant-divider-horizontal ant-divider-with-text ant-divider-with-text-right ant-divider-no-default-orientation-margin-right"
role="separator"
>
<span
class="ant-divider-inner-text"
style="margin-right:50px"
>
Right Text with 50px orientationMargin
</span>
</div>,
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista probare, quae sunt a te dicta? Refert tamen, quo modo.
</p>,
]
`;

View File

@ -37,6 +37,20 @@ ReactDOM.render(
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista
probare, quae sunt a te dicta? Refert tamen, quo modo.
</p>
<Divider orientation="left" orientationMargin="0">
Left Text with 0 orientationMargin
</Divider>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista
probare, quae sunt a te dicta? Refert tamen, quo modo.
</p>
<Divider orientation="right" orientationMargin={50}>
Right Text with 50px orientationMargin
</Divider>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nonne merninisti licere mihi ista
probare, quae sunt a te dicta? Refert tamen, quo modo.
</p>
</>,
mountNode,
);

View File

@ -16,9 +16,11 @@ A divider line separates different content.
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| children | The wrapped title | ReactNode | - | |
| className | The className of container | string | - | |
| dashed | Whether line is dashed | boolean | false | |
| orientation | The position of title inside divider | `left` \| `right` \| `center` | `center` | |
| orientationMargin | The margin-left/right between the title and its closest border, while the `orientation` must be `left` or `right` | string \| number | - | |
| plain | Divider text show as plain style | boolean | true | 4.2.0 |
| style | The style object of container | CSSProperties | - | |
| type | The direction type of divider | `horizontal` \| `vertical` | `horizontal` | |

View File

@ -6,6 +6,7 @@ export interface DividerProps {
prefixCls?: string;
type?: 'horizontal' | 'vertical';
orientation?: 'left' | 'right' | 'center';
orientationMargin?: string | number;
className?: string;
children?: React.ReactNode;
dashed?: boolean;
@ -20,6 +21,7 @@ const Divider: React.FC<DividerProps> = props => (
prefixCls: customizePrefixCls,
type = 'horizontal',
orientation = 'center',
orientationMargin,
className,
children,
dashed,
@ -29,6 +31,8 @@ const Divider: React.FC<DividerProps> = props => (
const prefixCls = getPrefixCls('divider', customizePrefixCls);
const orientationPrefix = orientation.length > 0 ? `-${orientation}` : orientation;
const hasChildren = !!children;
const hasCustomMarginLeft = orientation === 'left' && orientationMargin != null;
const hasCustomMarginRight = orientation === 'right' && orientationMargin != null;
const classString = classNames(
prefixCls,
`${prefixCls}-${type}`,
@ -38,12 +42,24 @@ const Divider: React.FC<DividerProps> = props => (
[`${prefixCls}-dashed`]: !!dashed,
[`${prefixCls}-plain`]: !!plain,
[`${prefixCls}-rtl`]: direction === 'rtl',
[`${prefixCls}-no-default-orientation-margin-left`]: hasCustomMarginLeft,
[`${prefixCls}-no-default-orientation-margin-right`]: hasCustomMarginRight,
},
className,
);
const innerStyle = {
...(hasCustomMarginLeft && { marginLeft: orientationMargin }),
...(hasCustomMarginRight && { marginRight: orientationMargin }),
};
return (
<div className={classString} {...restProps} role="separator">
{children && <span className={`${prefixCls}-inner-text`}>{children}</span>}
{children && (
<span className={`${prefixCls}-inner-text`} style={innerStyle}>
{children}
</span>
)}
</div>
);
}}

View File

@ -17,9 +17,11 @@ cover: https://gw.alipayobjects.com/zos/alicdn/5swjECahe/Divider.svg
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| children | 嵌套的标题 | ReactNode | - | |
| className | 分割线样式类 | string | - | |
| dashed | 是否虚线 | boolean | false | |
| orientation | 分割线标题的位置 | `left` \| `right` \| `center` | `center` | |
| orientationMargin | 标题和最近 left/right 边框之间的距离,去除了分割线,同时 `orientation` 必须为 `left``right` | string \| number | - | |
| plain | 文字是否显示为普通正文样式 | boolean | false | 4.2.0 |
| style | 分割线样式对象 | CSSProperties | - | |
| type | 水平还是垂直类型 | `horizontal` \| `vertical` | `horizontal` | |

View File

@ -103,6 +103,30 @@
font-weight: normal;
font-size: @font-size-base;
}
&-horizontal&-with-text-left&-no-default-orientation-margin-left {
&::before {
width: 0;
}
&::after {
width: 100%;
}
.ant-divider-inner-text {
padding-left: 0;
}
}
&-horizontal&-with-text-right&-no-default-orientation-margin-right {
&::before {
width: 100%;
}
&::after {
width: 0;
}
.ant-divider-inner-text {
padding-right: 0;
}
}
}
@import './rtl';

View File

@ -41,6 +41,8 @@ interface EditConfig {
onEnd?: () => void;
maxLength?: number;
autoSize?: boolean | AutoSizeType;
triggerType?: ('icon' | 'text')[];
enterIcon?: React.ReactNode;
}
export interface EllipsisConfig {
@ -372,12 +374,12 @@ class Base extends React.Component<InternalBlockProps, BaseState> {
const { editable } = this.props;
if (!editable) return;
const { icon, tooltip } = editable as EditConfig;
const { icon, tooltip, triggerType = ['icon'] } = editable as EditConfig;
const title = toArray(tooltip)[0] || this.editStr;
const ariaLabel = typeof title === 'string' ? title : '';
return (
return triggerType.indexOf('icon') !== -1 ? (
<Tooltip key="edit" title={tooltip === false ? '' : title}>
<TransButton
ref={this.setEditRef}
@ -388,7 +390,7 @@ class Base extends React.Component<InternalBlockProps, BaseState> {
{icon || <EditOutlined role="button" />}
</TransButton>
</Tooltip>
);
) : null;
}
renderCopy() {
@ -427,7 +429,7 @@ class Base extends React.Component<InternalBlockProps, BaseState> {
renderEditInput() {
const { children, className, style } = this.props;
const { direction } = this.context;
const { maxLength, autoSize, onEnd } = this.getEditable();
const { maxLength, autoSize, onEnd, enterIcon } = this.getEditable();
return (
<Editable
value={typeof children === 'string' ? children : ''}
@ -440,6 +442,7 @@ class Base extends React.Component<InternalBlockProps, BaseState> {
direction={direction}
maxLength={maxLength}
autoSize={autoSize}
enterIcon={enterIcon}
/>
);
}
@ -455,6 +458,7 @@ class Base extends React.Component<InternalBlockProps, BaseState> {
const { component, children, className, type, disabled, style, ...restProps } = this.props;
const { direction } = this.context;
const { rows, suffix, tooltip } = this.getEllipsis();
const { triggerType = ['icon'] } = this.getEditable() as EditConfig;
const prefixCls = this.getPrefixCls();
@ -549,6 +553,7 @@ class Base extends React.Component<InternalBlockProps, BaseState> {
component={component}
ref={this.contentRef}
direction={direction}
onClick={triggerType.indexOf('text') !== -1 ? this.onEditClick : () => {}}
{...textProps}
>
{textNode}

View File

@ -5,6 +5,7 @@ import EnterOutlined from '@ant-design/icons/EnterOutlined';
import { AutoSizeType } from 'rc-textarea/lib/ResizableTextArea';
import TextArea from '../input/TextArea';
import { DirectionType } from '../config-provider';
import { cloneElement } from '../_util/reactNode';
interface EditableProps {
prefixCls?: string;
@ -18,6 +19,7 @@ interface EditableProps {
direction?: DirectionType;
maxLength?: number;
autoSize?: boolean | AutoSizeType;
enterIcon?: React.ReactNode;
}
const Editable: React.FC<EditableProps> = ({
@ -32,6 +34,7 @@ const Editable: React.FC<EditableProps> = ({
onSave,
onCancel,
onEnd,
enterIcon = <EnterOutlined />,
}) => {
const ref = React.useRef<any>();
@ -129,7 +132,9 @@ const Editable: React.FC<EditableProps> = ({
aria-label={ariaLabel}
autoSize={autoSize}
/>
<EnterOutlined className={`${prefixCls}-edit-content-confirm`} />
{enterIcon !== null
? cloneElement(enterIcon, { className: `${prefixCls}-edit-content-confirm` })
: null}
</div>
);
};

View File

@ -436,6 +436,165 @@ Array [
</span>
</div>
</div>,
"Trigger edit with: ",
<div
class="ant-radio-group ant-radio-group-outline"
>
<label
class="ant-radio-wrapper ant-radio-wrapper-checked"
>
<span
class="ant-radio ant-radio-checked"
>
<input
checked=""
class="ant-radio-input"
type="radio"
value="icon"
/>
<span
class="ant-radio-inner"
/>
</span>
<span>
icon
</span>
</label>
<label
class="ant-radio-wrapper"
>
<span
class="ant-radio"
>
<input
class="ant-radio-input"
type="radio"
value="text"
/>
<span
class="ant-radio-inner"
/>
</span>
<span>
text
</span>
</label>
<label
class="ant-radio-wrapper"
>
<span
class="ant-radio"
>
<input
class="ant-radio-input"
type="radio"
value="both"
/>
<span
class="ant-radio-inner"
/>
</span>
<span>
both
</span>
</label>
</div>,
<div
class="ant-typography"
>
Text or icon as trigger - click to start editing.
<div
aria-label="click to edit text"
class="ant-typography-edit"
role="button"
style="border:0;background:transparent;padding:0;line-height:inherit;display:inline-block"
tabindex="0"
>
<span
aria-label="edit"
class="anticon anticon-edit"
role="button"
>
<svg
aria-hidden="true"
data-icon="edit"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M257.7 752c2 0 4-.2 6-.5L431.9 722c2-.4 3.9-1.3 5.3-2.8l423.9-423.9a9.96 9.96 0 000-14.1L694.9 114.9c-1.9-1.9-4.4-2.9-7.1-2.9s-5.2 1-7.1 2.9L256.8 538.8c-1.5 1.5-2.4 3.3-2.8 5.3l-29.5 168.2a33.5 33.5 0 009.4 29.8c6.6 6.4 14.9 9.9 23.8 9.9zm67.4-174.4L687.8 215l73.3 73.3-362.7 362.6-88.9 15.7 15.6-89zM880 836H144c-17.7 0-32 14.3-32 32v36c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-36c0-17.7-14.3-32-32-32z"
/>
</svg>
</span>
</div>
</div>,
<div
class="ant-typography"
>
Editable text with a custom enter icon in edit field.
<div
aria-label="click to edit text"
class="ant-typography-edit"
role="button"
style="border:0;background:transparent;padding:0;line-height:inherit;display:inline-block"
tabindex="0"
>
<span
aria-label="highlight"
class="anticon anticon-highlight"
role="img"
>
<svg
aria-hidden="true"
data-icon="highlight"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M957.6 507.4L603.2 158.2a7.9 7.9 0 00-11.2 0L353.3 393.4a8.03 8.03 0 00-.1 11.3l.1.1 40 39.4-117.2 115.3a8.03 8.03 0 00-.1 11.3l.1.1 39.5 38.9-189.1 187H72.1c-4.4 0-8.1 3.6-8.1 8V860c0 4.4 3.6 8 8 8h344.9c2.1 0 4.1-.8 5.6-2.3l76.1-75.6 40.4 39.8a7.9 7.9 0 0011.2 0l117.1-115.6 40.1 39.5a7.9 7.9 0 0011.2 0l238.7-235.2c3.4-3 3.4-8 .3-11.2zM389.8 796.2H229.6l134.4-133 80.1 78.9-54.3 54.1zm154.8-62.1L373.2 565.2l68.6-67.6 171.4 168.9-68.6 67.6zM713.1 658L450.3 399.1 597.6 254l262.8 259-147.3 145z"
/>
</svg>
</span>
</div>
</div>,
<div
class="ant-typography"
>
Editable text with no enter icon in edit field.
<div
aria-label="click to edit text"
class="ant-typography-edit"
role="button"
style="border:0;background:transparent;padding:0;line-height:inherit;display:inline-block"
tabindex="0"
>
<span
aria-label="highlight"
class="anticon anticon-highlight"
role="img"
>
<svg
aria-hidden="true"
data-icon="highlight"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M957.6 507.4L603.2 158.2a7.9 7.9 0 00-11.2 0L353.3 393.4a8.03 8.03 0 00-.1 11.3l.1.1 40 39.4-117.2 115.3a8.03 8.03 0 00-.1 11.3l.1.1 39.5 38.9-189.1 187H72.1c-4.4 0-8.1 3.6-8.1 8V860c0 4.4 3.6 8 8 8h344.9c2.1 0 4.1-.8 5.6-2.3l76.1-75.6 40.4 39.8a7.9 7.9 0 0011.2 0l117.1-115.6 40.1 39.5a7.9 7.9 0 0011.2 0l238.7-235.2c3.4-3 3.4-8 .3-11.2zM389.8 796.2H229.6l134.4-133 80.1 78.9-54.3 54.1zm154.8-62.1L373.2 565.2l68.6-67.6 171.4 168.9-68.6 67.6zM713.1 658L450.3 399.1 597.6 254l262.8 259-147.3 145z"
/>
</svg>
</span>
</div>
</div>,
<div
class="ant-typography"
>

View File

@ -1,6 +1,6 @@
import React from 'react';
import { mount } from 'enzyme';
import { SmileOutlined, LikeOutlined, HighlightOutlined } from '@ant-design/icons';
import { SmileOutlined, LikeOutlined, HighlightOutlined, CheckOutlined } from '@ant-design/icons';
import KeyCode from 'rc-util/lib/KeyCode';
import { resetWarned } from 'rc-util/lib/warning';
import { spyElementPrototype } from 'rc-util/lib/test/domHook';
@ -395,7 +395,11 @@ describe('Typography', () => {
});
describe('editable', () => {
function testStep({ name = '', icon, tooltip } = {}, submitFunc, expectFunc) {
function testStep(
{ name = '', icon, tooltip, triggerType, enterIcon } = {},
submitFunc,
expectFunc,
) {
it(name, () => {
jest.useFakeTimers();
const onStart = jest.fn();
@ -406,7 +410,7 @@ describe('Typography', () => {
const wrapper = mount(
<Paragraph
editable={{ onChange, onStart, icon, tooltip }}
editable={{ onChange, onStart, icon, tooltip, triggerType, enterIcon }}
className={className}
style={style}
>
@ -414,28 +418,48 @@ describe('Typography', () => {
</Paragraph>,
);
if (icon) {
expect(wrapper.find('.anticon-highlight').length).toBeTruthy();
} else {
expect(wrapper.find('.anticon-edit').length).toBeTruthy();
if (triggerType === undefined || triggerType.indexOf('icon') !== -1) {
if (icon) {
expect(wrapper.find('.anticon-highlight').length).toBeTruthy();
} else {
expect(wrapper.find('.anticon-edit').length).toBeTruthy();
}
if (triggerType === undefined || triggerType.indexOf('text') === -1) {
wrapper.simulate('click');
expect(onStart).not.toHaveBeenCalled();
}
wrapper.find('.ant-typography-edit').first().simulate('mouseenter');
jest.runAllTimers();
wrapper.update();
if (tooltip === undefined || tooltip === true) {
expect(wrapper.find('.ant-tooltip-inner').text()).toBe('Edit');
} else if (tooltip === false) {
expect(wrapper.find('.ant-tooltip-inner').length).toBeFalsy();
} else {
expect(wrapper.find('.ant-tooltip-inner').text()).toBe(tooltip);
}
wrapper.find('.ant-typography-edit').first().simulate('click');
expect(onStart).toHaveBeenCalled();
if (triggerType !== undefined && triggerType.indexOf('text') !== -1) {
wrapper.find('textarea').simulate('keyDown', { keyCode: KeyCode.ESC });
wrapper.find('textarea').simulate('keyUp', { keyCode: KeyCode.ESC });
expect(onChange).not.toHaveBeenCalled();
}
}
wrapper.find('.ant-typography-edit').first().simulate('mouseenter');
jest.runAllTimers();
wrapper.update();
if (tooltip === undefined || tooltip === true) {
expect(wrapper.find('.ant-tooltip-inner').text()).toBe('Edit');
} else if (tooltip === false) {
expect(wrapper.find('.ant-tooltip-inner').length).toBeFalsy();
} else {
expect(wrapper.find('.ant-tooltip-inner').text()).toBe(tooltip);
if (triggerType !== undefined && triggerType.indexOf('text') !== -1) {
if (triggerType.indexOf('icon') === -1) {
expect(wrapper.find('.anticon-highlight').length).toBeFalsy();
expect(wrapper.find('.anticon-edit').length).toBeFalsy();
}
wrapper.simulate('click');
expect(onStart).toHaveBeenCalled();
}
wrapper.find('.ant-typography-edit').first().simulate('click');
expect(onStart).toHaveBeenCalled();
// Should have className
const props = wrapper.find('div').first().props();
expect(props.style).toEqual(style);
@ -445,6 +469,18 @@ describe('Typography', () => {
target: { value: 'Bamboo' },
});
if (enterIcon === undefined) {
expect(
wrapper.find('span.ant-typography-edit-content-confirm').first().props().className,
).toContain('anticon-enter');
} else if (enterIcon === null) {
expect(wrapper.find('span.ant-typography-edit-content-confirm').length).toBe(0);
} else {
expect(
wrapper.find('span.ant-typography-edit-content-confirm').first().props().className,
).not.toContain('anticon-enter');
}
if (submitFunc) {
submitFunc(wrapper);
} else {
@ -492,6 +528,13 @@ describe('Typography', () => {
testStep({ name: 'customize edit show tooltip', tooltip: true });
testStep({ name: 'customize edit hide tooltip', tooltip: false });
testStep({ name: 'customize edit tooltip text', tooltip: 'click to edit text' });
testStep({ name: 'enter icon - default', enterIcon: undefined });
testStep({ name: 'enter icon - null', enterIcon: null });
testStep({ name: 'enter icon - custom', enterIcon: <CheckOutlined /> });
testStep({ name: 'trigger by icon', triggerType: ['icon'] });
testStep({ name: 'trigger by text', triggerType: ['text'] });
testStep({ name: 'trigger by both icon and text', triggerType: ['icon', 'text'] });
it('should trigger onEnd when type Enter', () => {
const onEnd = jest.fn();

View File

@ -15,19 +15,48 @@ Provide additional interactive capacity of editable and copyable.
```jsx
import React, { useState } from 'react';
import { Typography } from 'antd';
import { HighlightOutlined, SmileOutlined, SmileFilled } from '@ant-design/icons';
import { Checkbox, Radio, Typography } from 'antd';
import { CheckOutlined, HighlightOutlined, SmileOutlined, SmileFilled } from '@ant-design/icons';
const { Paragraph } = Typography;
const Demo = () => {
const [editableStr, setEditableStr] = useState('This is an editable text.');
const [customIconStr, setCustomIconStr] = useState('Custom Edit icon and replace tooltip text.');
const [clickTriggerStr, setClickTriggerStr] = useState(
'Text or icon as trigger - click to start editing.',
);
const [chooseTrigger, setChooseTrigger] = useState('icon');
const [customEnterIconStr, setCustomEnterIconStr] = useState(
'Editable text with a custom enter icon in edit field.',
);
const [noEnterIconStr, setNoEnterIconStr] = useState(
'Editable text with no enter icon in edit field.',
);
const [hideTooltipStr, setHideTooltipStr] = useState('Hide Edit tooltip.');
const [lengthLimitedStr, setLengthLimitedStr] = useState(
'This is an editable text with limited length.',
);
const radioToState = input => {
switch (input) {
case 'text':
return ['text'];
case 'both':
return ['icon', 'text'];
case 'icon':
default:
return ['icon'];
}
};
const stateToRadio = () => {
if (chooseTrigger.indexOf('text') !== -1) {
return chooseTrigger.indexOf('icon') !== -1 ? 'both' : 'text';
}
return 'icon';
};
return (
<>
<Paragraph editable={{ onChange: setEditableStr }}>{editableStr}</Paragraph>
@ -40,6 +69,44 @@ const Demo = () => {
>
{customIconStr}
</Paragraph>
Trigger edit with:{' '}
<Radio.Group
onChange={e => setChooseTrigger(radioToState(e.target.value))}
value={stateToRadio()}
>
<Radio value="icon">icon</Radio>
<Radio value="text">text</Radio>
<Radio value="both">both</Radio>
</Radio.Group>
<Paragraph
editable={{
tooltip: 'click to edit text',
onChange: setClickTriggerStr,
triggerType: chooseTrigger,
}}
>
{clickTriggerStr}
</Paragraph>
<Paragraph
editable={{
icon: <HighlightOutlined />,
tooltip: 'click to edit text',
onChange: setCustomEnterIconStr,
enterIcon: <CheckOutlined />,
}}
>
{customEnterIconStr}
</Paragraph>
<Paragraph
editable={{
icon: <HighlightOutlined />,
tooltip: 'click to edit text',
onChange: setNoEnterIconStr,
enterIcon: null,
}}
>
{noEnterIconStr}
</Paragraph>
<Paragraph editable={{ tooltip: false, onChange: setHideTooltipStr }}>
{hideTooltipStr}
</Paragraph>

View File

@ -95,6 +95,8 @@ Basic text writing, including headings, body text, lists, and more.
onChange: function(string),
onCancel: function,
onEnd: function,
triggerType: ('icon' | 'text')[],
enterIcon: ReactNode,
}
| Property | Description | Type | Default | Version |
@ -104,10 +106,12 @@ Basic text writing, including headings, body text, lists, and more.
| icon | Custom editable icon | ReactNode | &lt;EditOutlined /> | 4.6.0 |
| maxLength | `maxLength` attribute of textarea | number | - | 4.4.0 |
| tooltip | Custom tooltip text, hide when it is false | boolean \| ReactNode | `Edit` | 4.6.0 |
| onCancel | Called when type ESC to exit editable state | function | - | |
| onChange | Called when input at textarea | function(event) | - | |
| onEnd | Called when type ENTER to exit editable state | function | - | 4.14.0 |
| onStart | Called when enter editable state | function | - | |
| onChange | Called when input at textarea | function(event) | - | |
| onCancel | Called when type ESC to exit editable state | function | - | |
| onEnd | Called when type ENTER to exit editable state | function | - | 4.14.0 |
| triggerType | Edit mode trigger - icon, text or both (not specifying icon as trigger hides it) | Array&lt;`icon`\|`text`> | \[`icon`] | |
| enterIcon | Custom "enter" icon in the edit field (passing `null` removes the icon) | ReactNode | `<EnterOutlined />` | 4.17.0 |
### ellipsis

View File

@ -96,6 +96,8 @@ cover: https://gw.alipayobjects.com/zos/alicdn/GOM1KQ24O/Typography.svg
onChange: function(string),
onCancel: function,
onEnd: function,
triggerType: ('icon' | 'text')[],
enterIcon: ReactNode,
}
| 参数 | 说明 | 类型 | 默认值 | 版本 |
@ -109,6 +111,8 @@ cover: https://gw.alipayobjects.com/zos/alicdn/GOM1KQ24O/Typography.svg
| onChange | 文本域编辑时触发 | function(event) | - | |
| onEnd | 按 ENTER 结束编辑状态时触发 | function | - | 4.14.0 |
| onStart | 进入编辑中状态时触发 | function | - | |
| triggerType | Edit mode trigger - icon, text or both (not specifying icon as trigger hides it) | Array&lt;`icon`\|`text`> | \[`icon`] | |
| enterIcon | 在编辑段中自定义“enter”图标传递“null”将删除图标 | ReactNode | `<EnterOutlined />` | 4.17.0 |
### ellipsis

View File

@ -174,13 +174,13 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
let clone;
try {
clone = (new File([originFileObj], originFileObj.name, {
clone = new File([originFileObj], originFileObj.name, {
type: originFileObj.type,
}) as any) as UploadFile;
}) as any as UploadFile;
} catch (e) {
clone = (new Blob([originFileObj], {
clone = new Blob([originFileObj], {
type: originFileObj.type,
}) as any) as UploadFile;
}) as any as UploadFile;
clone.name = originFileObj.name;
clone.lastModifiedDate = new Date();
clone.lastModified = new Date().getTime();
@ -326,7 +326,14 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
showUploadList ? (
<LocaleReceiver componentName="Upload" defaultLocale={defaultLocale.Upload}>
{(locale: UploadLocale) => {
const { showRemoveIcon, showPreviewIcon, showDownloadIcon, removeIcon, downloadIcon } =
const {
showRemoveIcon,
showPreviewIcon,
showDownloadIcon,
removeIcon,
previewIcon,
downloadIcon,
} =
typeof showUploadList === 'boolean' ? ({} as ShowUploadListInterface) : showUploadList;
return (
<UploadList
@ -340,6 +347,7 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
showPreviewIcon={showPreviewIcon}
showDownloadIcon={showDownloadIcon}
removeIcon={removeIcon}
previewIcon={previewIcon}
downloadIcon={downloadIcon}
iconRender={iconRender}
locale={{ ...locale, ...propLocale }}

View File

@ -30,6 +30,7 @@ export interface ListItemProps {
showPreviewIcon?: boolean;
removeIcon?: React.ReactNode | ((file: UploadFile) => React.ReactNode);
downloadIcon?: React.ReactNode | ((file: UploadFile) => React.ReactNode);
previewIcon?: React.ReactNode | ((file: UploadFile) => React.ReactNode);
iconRender: (file: UploadFile) => React.ReactNode;
actionIconRender: (
customIcon: React.ReactNode,
@ -62,6 +63,7 @@ const ListItem = React.forwardRef(
showPreviewIcon,
showRemoveIcon,
showDownloadIcon,
previewIcon: customPreviewIcon,
removeIcon: customRemoveIcon,
downloadIcon: customDownloadIcon,
onPreview,
@ -206,7 +208,9 @@ const ListItem = React.forwardRef(
onClick={e => onPreview(file, e)}
title={locale.previewFile}
>
<EyeOutlined />
{typeof customPreviewIcon === 'function'
? customPreviewIcon(file)
: customPreviewIcon || <EyeOutlined />}
</a>
) : null;

View File

@ -38,6 +38,7 @@ const InternalUploadList: React.ForwardRefRenderFunction<unknown, UploadListProp
showRemoveIcon,
showDownloadIcon,
removeIcon,
previewIcon,
downloadIcon,
progress,
appendAction,
@ -210,6 +211,7 @@ const InternalUploadList: React.ForwardRefRenderFunction<unknown, UploadListProp
showRemoveIcon={showRemoveIcon}
showDownloadIcon={showDownloadIcon}
removeIcon={removeIcon}
previewIcon={previewIcon}
downloadIcon={downloadIcon}
iconRender={internalIconRender}
actionIconRender={actionIconRender}

View File

@ -40,7 +40,7 @@ Uploading is the process of publishing information (web pages, text, pictures, v
| openFileDialogOnClick | Click open file dialog | boolean | true | |
| previewFile | Customize preview file logic | (file: File \| Blob) => Promise&lt;dataURL: string> | - | |
| progress | Custom progress bar | [ProgressProps](/components/progress/#API) (support `type="line"` only) | { strokeWidth: 2, showInfo: false } | 4.3.0 |
| showUploadList | Whether to show default upload list, could be an object to specify `showPreviewIcon`, `showRemoveIcon`, `showDownloadIcon`, `removeIcon` and `downloadIcon` individually | boolean \| { showPreviewIcon?: boolean, showDownloadIcon?: boolean, showRemoveIcon?: boolean, removeIcon?: ReactNode \| (file: UploadFile) => ReactNode, downloadIcon?: ReactNode \| (file: UploadFile) => ReactNode } | true | function: 4.7.0 |
| showUploadList | Whether to show default upload list, could be an object to specify `showPreviewIcon`, `showRemoveIcon`, `showDownloadIcon`, `removeIcon` and `downloadIcon` individually | boolean \| { showPreviewIcon?: boolean, showDownloadIcon?: boolean, showRemoveIcon?: boolean, previewIcon?: ReactNode \| (file: UploadFile) => ReactNode, removeIcon?: ReactNode \| (file: UploadFile) => ReactNode, downloadIcon?: ReactNode \| (file: UploadFile) => ReactNode } | true | function: 4.7.0 |
| withCredentials | The ajax upload with cookie sent | boolean | false | |
| onChange | A callback function, can be executed when uploading state is changing, see [onChange](#onChange) | function | - | |
| onDrop | A callback function executed when files are dragged and dropped into upload area | (event: React.DragEvent) => void | - | 4.16.0 |

View File

@ -41,7 +41,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/QaeBt_ZMg/Upload.svg
| openFileDialogOnClick | 点击打开文件对话框 | boolean | true | |
| previewFile | 自定义文件预览逻辑 | (file: File \| Blob) => Promise&lt;dataURL: string> | - | |
| progress | 自定义进度条样式 | [ProgressProps](/components/progress/#API)(仅支持 `type="line"` | { strokeWidth: 2, showInfo: false } | 4.3.0 |
| showUploadList | 是否展示文件列表, 可设为一个对象,用于单独设定 `showPreviewIcon`, `showRemoveIcon`, `showDownloadIcon`, `removeIcon``downloadIcon` | boolean \| { showPreviewIcon?: boolean, showRemoveIcon?: boolean, showDownloadIcon?: boolean, removeIcon?: ReactNode \| (file: UploadFile) => ReactNode, downloadIcon?: ReactNode \| (file: UploadFile) => ReactNode } | true | function: 4.7.0 |
| showUploadList | 是否展示文件列表, 可设为一个对象,用于单独设定 `showPreviewIcon`, `showRemoveIcon`, `showDownloadIcon`, `removeIcon``downloadIcon` | boolean \| { showPreviewIcon?: boolean, showRemoveIcon?: boolean, showDownloadIcon?: boolean, previewIcon?: ReactNode \| (file: UploadFile) => ReactNode, removeIcon?: ReactNode \| (file: UploadFile) => ReactNode, downloadIcon?: ReactNode \| (file: UploadFile) => ReactNode } | true | function: 4.7.0 |
| withCredentials | 上传请求时是否携带 cookie | boolean | false | |
| onChange | 上传文件改变时的状态,详见 [onChange](#onChange) | function | - | |
| onDrop | 当文件被拖入上传区域时执行的回调功能 | (event: React.DragEvent) => void | - | 4.16.0 |

View File

@ -52,6 +52,7 @@ export interface ShowUploadListInterface {
showDownloadIcon?: boolean;
removeIcon?: React.ReactNode | ((file: UploadFile) => React.ReactNode);
downloadIcon?: React.ReactNode | ((file: UploadFile) => React.ReactNode);
previewIcon?: React.ReactNode | ((file: UploadFile) => React.ReactNode);
}
export interface UploadLocale {
@ -145,6 +146,7 @@ export interface UploadListProps<T = any> {
showPreviewIcon?: boolean;
removeIcon?: React.ReactNode | ((file: UploadFile) => React.ReactNode);
downloadIcon?: React.ReactNode | ((file: UploadFile) => React.ReactNode);
previewIcon?: React.ReactNode | ((file: UploadFile) => React.ReactNode);
locale: UploadLocale;
previewFile?: PreviewFileHandler;
iconRender?: (file: UploadFile<T>, listType?: UploadListType) => React.ReactNode;