mirror of
https://github.com/ant-design/ant-design.git
synced 2025-01-18 14:13:37 +08:00
chore: merge feature into master
This commit is contained in:
commit
d35576d675
@ -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>,
|
||||
]
|
||||
`;
|
||||
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
||||
|
@ -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`}
|
||||
>
|
||||
|
@ -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 |
|
||||
|
@ -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 |
|
||||
|
@ -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>,
|
||||
]
|
||||
`;
|
||||
|
@ -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,
|
||||
);
|
||||
|
@ -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` | |
|
||||
|
@ -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>
|
||||
);
|
||||
}}
|
||||
|
@ -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` | |
|
||||
|
@ -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';
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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"
|
||||
>
|
||||
|
@ -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();
|
||||
|
@ -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>
|
||||
|
@ -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 | <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<`icon`\|`text`> | \[`icon`] | |
|
||||
| enterIcon | Custom "enter" icon in the edit field (passing `null` removes the icon) | ReactNode | `<EnterOutlined />` | 4.17.0 |
|
||||
|
||||
### ellipsis
|
||||
|
||||
|
@ -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<`icon`\|`text`> | \[`icon`] | |
|
||||
| enterIcon | 在编辑段中自定义“enter”图标(传递“null”将删除图标) | ReactNode | `<EnterOutlined />` | 4.17.0 |
|
||||
|
||||
### ellipsis
|
||||
|
||||
|
@ -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 }}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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}
|
||||
|
@ -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<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 |
|
||||
|
@ -41,7 +41,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/QaeBt_ZMg/Upload.svg
|
||||
| openFileDialogOnClick | 点击打开文件对话框 | boolean | true | |
|
||||
| previewFile | 自定义文件预览逻辑 | (file: File \| Blob) => Promise<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 |
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user