mirror of
https://github.com/ant-design/ant-design.git
synced 2025-01-18 06:03:38 +08:00
Merge pull request #27488 from ant-design/chore-conflict-feature
chore: merge feature into master
This commit is contained in:
commit
2a4027bbd9
@ -599,6 +599,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"
|
||||
>
|
||||
<span
|
||||
class="ant-avatar-string"
|
||||
style="opacity:0"
|
||||
>
|
||||
+2
|
||||
</span>
|
||||
</span>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
@ -760,6 +797,18 @@ Array [
|
||||
src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"
|
||||
/>
|
||||
</span>,
|
||||
<span
|
||||
class="ant-avatar ant-avatar-circle ant-avatar-image"
|
||||
>
|
||||
<div
|
||||
class="ant-image"
|
||||
>
|
||||
<img
|
||||
class="ant-image-img"
|
||||
src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"
|
||||
/>
|
||||
</div>
|
||||
</span>,
|
||||
<span
|
||||
class="ant-avatar ant-avatar-circle"
|
||||
style="color:#f56a00;background-color:#fde3cf"
|
||||
|
@ -8,6 +8,8 @@ import { composeRef } from '../_util/ref';
|
||||
import { Breakpoint, responsiveArray, ScreenSizeMap } from '../_util/responsiveObserve';
|
||||
import useBreakpoint from '../grid/hooks/useBreakpoint';
|
||||
|
||||
export type AvatarSize = 'large' | 'small' | 'default' | number | ScreenSizeMap;
|
||||
|
||||
export interface AvatarProps {
|
||||
/** Shape of avatar, options:`circle`, `square` */
|
||||
shape?: 'circle' | 'square';
|
||||
@ -15,7 +17,7 @@ export interface AvatarProps {
|
||||
* Size of avatar, options: `large`, `small`, `default`
|
||||
* or a custom number size
|
||||
* */
|
||||
size?: 'large' | 'small' | 'default' | number | ScreenSizeMap;
|
||||
size?: AvatarSize;
|
||||
gap?: number;
|
||||
/** Src of image avatar */
|
||||
src?: string;
|
||||
@ -128,12 +130,14 @@ const InternalAvatar: React.ForwardRefRenderFunction<unknown, AvatarProps> = (pr
|
||||
[`${prefixCls}-sm`]: size === 'small',
|
||||
});
|
||||
|
||||
const hasImageElement = React.isValidElement(src);
|
||||
|
||||
const classString = classNames(
|
||||
prefixCls,
|
||||
sizeCls,
|
||||
{
|
||||
[`${prefixCls}-${shape}`]: shape,
|
||||
[`${prefixCls}-image`]: src && isImgExist,
|
||||
[`${prefixCls}-image`]: hasImageElement || (src && isImgExist),
|
||||
[`${prefixCls}-icon`]: icon,
|
||||
},
|
||||
className,
|
||||
@ -150,10 +154,12 @@ const InternalAvatar: React.ForwardRefRenderFunction<unknown, AvatarProps> = (pr
|
||||
: {};
|
||||
|
||||
let childrenToRender;
|
||||
if (src && isImgExist) {
|
||||
if (typeof src === 'string' && isImgExist) {
|
||||
childrenToRender = (
|
||||
<img src={src} draggable={draggable} srcSet={srcSet} onError={handleImgLoadError} alt={alt} />
|
||||
);
|
||||
} else if (hasImageElement) {
|
||||
childrenToRender = src;
|
||||
} else if (icon) {
|
||||
childrenToRender = icon;
|
||||
} else if (mounted || scale !== 1) {
|
||||
|
@ -38,6 +38,19 @@ class Demo extends React.Component {
|
||||
</Tooltip>
|
||||
<Avatar style={{ backgroundColor: '#1890ff' }} icon={<AntDesignOutlined />} />
|
||||
</Avatar.Group>
|
||||
<Divider />
|
||||
<Avatar.Group
|
||||
maxCount={2}
|
||||
size="large"
|
||||
maxStyle={{ color: '#f56a00', backgroundColor: '#fde3cf' }}
|
||||
>
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ title:
|
||||
Image, Icon and letter are supported, and the latter two kinds of avatar can have custom colors and background colors.
|
||||
|
||||
```tsx
|
||||
import { Avatar } from 'antd';
|
||||
import { Avatar, Image } from 'antd';
|
||||
import { UserOutlined } from '@ant-design/icons';
|
||||
|
||||
ReactDOM.render(
|
||||
@ -23,6 +23,9 @@ ReactDOM.render(
|
||||
<Avatar>U</Avatar>
|
||||
<Avatar size={40}>USER</Avatar>
|
||||
<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />
|
||||
<Avatar
|
||||
src={<Image src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />}
|
||||
/>
|
||||
<Avatar style={{ color: '#f56a00', backgroundColor: '#fde3cf' }}>U</Avatar>
|
||||
<Avatar style={{ backgroundColor: '#87d068' }} icon={<UserOutlined />} />
|
||||
</>,
|
||||
|
@ -3,7 +3,7 @@ import classNames from 'classnames';
|
||||
import toArray from 'rc-util/lib/Children/toArray';
|
||||
import { cloneElement } from '../_util/reactNode';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
import Avatar from './avatar';
|
||||
import Avatar, { AvatarSize } from './avatar';
|
||||
import Popover from '../popover';
|
||||
|
||||
export interface GroupProps {
|
||||
@ -14,12 +14,19 @@ export interface GroupProps {
|
||||
maxCount?: number;
|
||||
maxStyle?: React.CSSProperties;
|
||||
maxPopoverPlacement?: 'top' | 'bottom';
|
||||
/*
|
||||
* Size of avatar, options: `large`, `small`, `default`
|
||||
* or a custom number size
|
||||
* */
|
||||
size?: AvatarSize;
|
||||
}
|
||||
|
||||
const Group: React.FC<GroupProps> = props => {
|
||||
const { getPrefixCls, direction } = React.useContext(ConfigContext);
|
||||
const { prefixCls: customizePrefixCls, className = '', maxCount, maxStyle } = props;
|
||||
const { prefixCls: customizePrefixCls, className = '', maxCount, maxStyle, size } = props;
|
||||
|
||||
const prefixCls = getPrefixCls('avatar-group', customizePrefixCls);
|
||||
|
||||
const cls = classNames(
|
||||
prefixCls,
|
||||
{
|
||||
@ -31,6 +38,7 @@ const Group: React.FC<GroupProps> = props => {
|
||||
const { children, maxPopoverPlacement = 'top' } = props;
|
||||
const childrenWithProps = toArray(children).map((child, index) => {
|
||||
return cloneElement(child, {
|
||||
size,
|
||||
key: `avatar-key-${index}`,
|
||||
});
|
||||
});
|
||||
@ -47,7 +55,7 @@ const Group: React.FC<GroupProps> = props => {
|
||||
placement={maxPopoverPlacement}
|
||||
overlayClassName={`${prefixCls}-popover`}
|
||||
>
|
||||
<Avatar style={maxStyle}>{`+${numOfChildren - maxCount}`}</Avatar>
|
||||
<Avatar style={maxStyle} size={size}>{`+${numOfChildren - maxCount}`}</Avatar>
|
||||
</Popover>,
|
||||
);
|
||||
return (
|
||||
@ -58,7 +66,7 @@ const Group: React.FC<GroupProps> = props => {
|
||||
}
|
||||
return (
|
||||
<div className={cls} style={props.style}>
|
||||
{children}
|
||||
{childrenWithProps}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -18,7 +18,7 @@ Avatars can be used to represent people or objects. It supports images, `Icon`s,
|
||||
| icon | Custom icon type for an icon avatar | ReactNode | - | |
|
||||
| shape | The shape of avatar | `circle` \| `square` | `circle` | |
|
||||
| size | The size of the avatar | number \| `large` \| `small` \| `default` \| `{ xs: number, sm: number, ...}` | `default` | 4.7.0 |
|
||||
| src | The address of the image for an image avatar | string | - | |
|
||||
| src | The address of the image for an image avatar or image element | string \| ReactNode | - | |
|
||||
| srcSet | A list of sources to use for different screen resolutions | string | - | |
|
||||
| onError | Handler when img load error, return false to prevent default fallback behavior | () => boolean | - | |
|
||||
|
||||
@ -31,3 +31,4 @@ 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` | |
|
||||
| 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 |
|
||||
|
@ -23,7 +23,7 @@ cover: https://gw.alipayobjects.com/zos/antfincdn/aBcnbw68hP/Avatar.svg
|
||||
| icon | 设置头像的自定义图标 | ReactNode | - | |
|
||||
| shape | 指定头像的形状 | `circle` \| `square` | `circle` | |
|
||||
| size | 设置头像的大小 | number \| `large` \| `small` \| `default` \| `{ xs: number, sm: number, ...}` | `default` | 4.7.0 |
|
||||
| src | 图片类头像的资源地址 | string | - | |
|
||||
| src | 图片类头像的资源地址或者图片元素 | string \| ReactNode | - | |
|
||||
| srcSet | 设置图片类头像响应式资源地址 | string | - | |
|
||||
| onError | 图片加载失败的事件,返回 false 会关闭组件默认的 fallback 行为 | () => boolean | - | |
|
||||
|
||||
@ -36,3 +36,4 @@ cover: https://gw.alipayobjects.com/zos/antfincdn/aBcnbw68hP/Avatar.svg
|
||||
| maxCount | 显示的最大头像个数 | number | - | |
|
||||
| maxPopoverPlacement | 多余头像气泡弹出位置 | `top` \| `bottom` | `top` | |
|
||||
| maxStyle | 多余头像样式 | CSSProperties | - | |
|
||||
| size | 设置头像的大小 | number \| `large` \| `small` \| `default` \| { xs: number, sm: number, ...} | `default` | 4.8.0 |
|
||||
|
@ -35,11 +35,10 @@
|
||||
.iconfont-mixin();
|
||||
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
top: ceil(@padding-sm + (@font-size-base * @line-height-base - @font-size-base) / 2);
|
||||
left: @collapse-header-arrow-left;
|
||||
display: inline-block;
|
||||
font-size: @font-size-sm;
|
||||
transform: translateY(-50%);
|
||||
|
||||
& svg {
|
||||
transition: transform 0.24s;
|
||||
|
@ -14643,7 +14643,7 @@ exports[`ConfigProvider components Input configProvider componentSize large 1`]
|
||||
</span>
|
||||
</span>
|
||||
<textarea
|
||||
class="config-input"
|
||||
class="config-input config-input-lg"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
@ -0,0 +1,272 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ConfigProvider.Form form requiredMark set requiredMark optional 1`] = `
|
||||
<ConfigProvider
|
||||
form={
|
||||
Object {
|
||||
"requiredMark": "optional",
|
||||
}
|
||||
}
|
||||
>
|
||||
<LocaleReceiver
|
||||
componentName="global"
|
||||
>
|
||||
<SizeContextProvider>
|
||||
<LocaleProvider
|
||||
_ANT_MARK__="internalMark"
|
||||
locale={Object {}}
|
||||
>
|
||||
<ForwardRef(InternalForm)
|
||||
initialValues={
|
||||
Object {
|
||||
"age": 18,
|
||||
}
|
||||
}
|
||||
>
|
||||
<SizeContextProvider>
|
||||
<ForwardRef(Form)
|
||||
className="ant-form ant-form-horizontal"
|
||||
form={
|
||||
Object {
|
||||
"__INTERNAL__": Object {
|
||||
"itemRef": [Function],
|
||||
"name": undefined,
|
||||
},
|
||||
"getFieldError": [Function],
|
||||
"getFieldInstance": [Function],
|
||||
"getFieldValue": [Function],
|
||||
"getFieldsError": [Function],
|
||||
"getFieldsValue": [Function],
|
||||
"getInternalHooks": [Function],
|
||||
"isFieldTouched": [Function],
|
||||
"isFieldValidating": [Function],
|
||||
"isFieldsTouched": [Function],
|
||||
"isFieldsValidating": [Function],
|
||||
"resetFields": [Function],
|
||||
"scrollToField": [Function],
|
||||
"setFields": [Function],
|
||||
"setFieldsValue": [Function],
|
||||
"submit": [Function],
|
||||
"validateFields": [Function],
|
||||
}
|
||||
}
|
||||
initialValues={
|
||||
Object {
|
||||
"age": 18,
|
||||
}
|
||||
}
|
||||
onFinishFailed={[Function]}
|
||||
>
|
||||
<form
|
||||
className="ant-form ant-form-horizontal"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<FormItem
|
||||
label="年龄"
|
||||
name="age"
|
||||
rules={
|
||||
Array [
|
||||
Object {
|
||||
"len": 17,
|
||||
"type": "number",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<WrapperField
|
||||
label="年龄"
|
||||
messageVariables={
|
||||
Object {
|
||||
"label": "年龄",
|
||||
}
|
||||
}
|
||||
name="age"
|
||||
onReset={[Function]}
|
||||
rules={
|
||||
Array [
|
||||
Object {
|
||||
"len": 17,
|
||||
"type": "number",
|
||||
},
|
||||
]
|
||||
}
|
||||
trigger="onChange"
|
||||
validateTrigger="onChange"
|
||||
>
|
||||
<Field
|
||||
fieldContext={
|
||||
Object {
|
||||
"__INTERNAL__": Object {
|
||||
"itemRef": [Function],
|
||||
"name": undefined,
|
||||
},
|
||||
"getFieldError": [Function],
|
||||
"getFieldInstance": [Function],
|
||||
"getFieldValue": [Function],
|
||||
"getFieldsError": [Function],
|
||||
"getFieldsValue": [Function],
|
||||
"getInternalHooks": [Function],
|
||||
"isFieldTouched": [Function],
|
||||
"isFieldValidating": [Function],
|
||||
"isFieldsTouched": [Function],
|
||||
"isFieldsValidating": [Function],
|
||||
"resetFields": [Function],
|
||||
"scrollToField": [Function],
|
||||
"setFields": [Function],
|
||||
"setFieldsValue": [Function],
|
||||
"submit": [Function],
|
||||
"validateFields": [Function],
|
||||
"validateTrigger": "onChange",
|
||||
}
|
||||
}
|
||||
key="_age"
|
||||
label="年龄"
|
||||
messageVariables={
|
||||
Object {
|
||||
"label": "年龄",
|
||||
}
|
||||
}
|
||||
name={
|
||||
Array [
|
||||
"age",
|
||||
]
|
||||
}
|
||||
onReset={[Function]}
|
||||
rules={
|
||||
Array [
|
||||
Object {
|
||||
"len": 17,
|
||||
"type": "number",
|
||||
},
|
||||
]
|
||||
}
|
||||
trigger="onChange"
|
||||
validateTrigger="onChange"
|
||||
valuePropName="value"
|
||||
>
|
||||
<Row
|
||||
className="ant-form-item"
|
||||
key="row"
|
||||
>
|
||||
<div
|
||||
className="ant-row ant-form-item"
|
||||
style={Object {}}
|
||||
>
|
||||
<FormItemLabel
|
||||
htmlFor="age"
|
||||
label="年龄"
|
||||
name="age"
|
||||
prefixCls="ant-form"
|
||||
required={false}
|
||||
requiredMark="optional"
|
||||
rules={
|
||||
Array [
|
||||
Object {
|
||||
"len": 17,
|
||||
"type": "number",
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<Col
|
||||
className="ant-form-item-label"
|
||||
>
|
||||
<div
|
||||
className="ant-col ant-form-item-label"
|
||||
style={Object {}}
|
||||
>
|
||||
<label
|
||||
className="ant-form-item-required-mark-optional"
|
||||
htmlFor="age"
|
||||
title="年龄"
|
||||
>
|
||||
年龄
|
||||
<span
|
||||
className="ant-form-item-optional"
|
||||
>
|
||||
(optional)
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</Col>
|
||||
</FormItemLabel>
|
||||
<FormItemInput
|
||||
errors={Array []}
|
||||
label="年龄"
|
||||
name={
|
||||
Array [
|
||||
"age",
|
||||
]
|
||||
}
|
||||
onDomErrorVisibleChange={[Function]}
|
||||
prefixCls="ant-form"
|
||||
rules={
|
||||
Array [
|
||||
Object {
|
||||
"len": 17,
|
||||
"type": "number",
|
||||
},
|
||||
]
|
||||
}
|
||||
status=""
|
||||
touched={false}
|
||||
validateStatus=""
|
||||
validating={false}
|
||||
>
|
||||
<Col
|
||||
className="ant-form-item-control"
|
||||
>
|
||||
<div
|
||||
className="ant-col ant-form-item-control"
|
||||
style={Object {}}
|
||||
>
|
||||
<div
|
||||
className="ant-form-item-control-input"
|
||||
>
|
||||
<div
|
||||
className="ant-form-item-control-input-content"
|
||||
>
|
||||
<Component
|
||||
update={1}
|
||||
value={18}
|
||||
>
|
||||
<input
|
||||
id="age"
|
||||
onChange={[Function]}
|
||||
value={18}
|
||||
/>
|
||||
</Component>
|
||||
</div>
|
||||
</div>
|
||||
<ErrorList
|
||||
errors={Array []}
|
||||
onDomErrorVisibleChange={[Function]}
|
||||
>
|
||||
<CSSMotion
|
||||
motionAppear={true}
|
||||
motionDeadline={500}
|
||||
motionName="show-help"
|
||||
onLeaveEnd={[Function]}
|
||||
removeOnLeave={true}
|
||||
visible={false}
|
||||
>
|
||||
<DomWrapper />
|
||||
</CSSMotion>
|
||||
</ErrorList>
|
||||
</div>
|
||||
</Col>
|
||||
</FormItemInput>
|
||||
</div>
|
||||
</Row>
|
||||
</Field>
|
||||
</WrapperField>
|
||||
</FormItem>
|
||||
</form>
|
||||
</ForwardRef(Form)>
|
||||
</SizeContextProvider>
|
||||
</ForwardRef(InternalForm)>
|
||||
</LocaleProvider>
|
||||
</SizeContextProvider>
|
||||
</LocaleReceiver>
|
||||
</ConfigProvider>
|
||||
`;
|
@ -5,7 +5,7 @@ import ConfigProvider from '..';
|
||||
import zhCN from '../../locale/zh_CN';
|
||||
import Form from '../../form';
|
||||
|
||||
describe('ConfigProvider.Form.Locale', () => {
|
||||
describe('ConfigProvider.Form', () => {
|
||||
describe('form validateMessages', () => {
|
||||
const wrapperComponent = ({ validateMessages }) => {
|
||||
const formRef = React.createRef();
|
||||
@ -75,4 +75,20 @@ describe('ConfigProvider.Form.Locale', () => {
|
||||
expect(wrapper.find('.ant-form-item-explain').last().text()).toEqual('年龄必须等于17');
|
||||
});
|
||||
});
|
||||
|
||||
describe('form requiredMark', () => {
|
||||
it('set requiredMark optional', async () => {
|
||||
const wrapper = mount(
|
||||
<ConfigProvider form={{ requiredMark: 'optional' }}>
|
||||
<Form initialValues={{ age: 18 }}>
|
||||
<Form.Item name="age" label="年龄" rules={[{ type: 'number', len: 17 }]}>
|
||||
<input />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</ConfigProvider>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
@ -2,6 +2,7 @@ import * as React from 'react';
|
||||
import defaultRenderEmpty, { RenderEmptyHandler } from './renderEmpty';
|
||||
import { Locale } from '../locale-provider';
|
||||
import { SizeType } from './SizeContext';
|
||||
import { RequiredMark } from '../form/Form';
|
||||
|
||||
export interface CSPConfig {
|
||||
nonce?: string;
|
||||
@ -28,6 +29,9 @@ export interface ConfigConsumerProps {
|
||||
};
|
||||
virtual?: boolean;
|
||||
dropdownMatchSelectWidth?: boolean;
|
||||
form?: {
|
||||
requiredMark?: RequiredMark;
|
||||
};
|
||||
}
|
||||
|
||||
export const ConfigContext = React.createContext<ConfigConsumerProps>({
|
||||
|
@ -49,6 +49,9 @@ const FormSizeDemo = () => {
|
||||
<div className="example">
|
||||
<Input.Search allowClear />
|
||||
</div>
|
||||
<div className="example">
|
||||
<Input.TextArea allowClear />
|
||||
</div>
|
||||
<div className="example">
|
||||
<Select defaultValue="demo" options={[{ value: 'demo' }]} />
|
||||
</div>
|
||||
|
@ -43,7 +43,7 @@ Some components use dynamic style to support wave effect. You can config `csp` p
|
||||
| csp | Set [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) config | { nonce: string } | - | |
|
||||
| direction | Set direction of layout. See [demo](#components-config-provider-demo-direction) | `ltr` \| `rtl` | `ltr` | |
|
||||
| dropdownMatchSelectWidth | Determine whether the dropdown menu and the select input are the same width. Default set `min-width` same as input. Will ignore when value less than select width. `false` will disable virtual scroll | boolean \| number | - | 4.3.0 |
|
||||
| form | Set Form common props | { validateMessages?: [ValidateMessages](/components/form/#validateMessages) } | - | |
|
||||
| form | Set Form common props | { validateMessages?: [ValidateMessages](/components/form/#validateMessages), requiredMark?: boolean \| `optional` } | - | requiredMark: 4.8.0 |
|
||||
| getPopupContainer | To set the container of the popup element. The default is to create a `div` element in `body` | function(triggerNode) | () => document.body | |
|
||||
| getTargetContainer | Config Affix, Anchor scroll target container | () => HTMLElement | () => window | 4.2.0 |
|
||||
| input | Set Input common props | { autoComplete?: string } | - | 4.2.0 |
|
||||
|
@ -11,6 +11,7 @@ import { ConfigConsumer, ConfigContext, CSPConfig, ConfigConsumerProps } from '.
|
||||
import { SizeType, SizeContextProvider } from './SizeContext';
|
||||
import message from '../message';
|
||||
import notification from '../notification';
|
||||
import { RequiredMark } from '../form/Form';
|
||||
|
||||
export { RenderEmptyHandler, ConfigContext, ConfigConsumer, CSPConfig, ConfigConsumerProps };
|
||||
|
||||
@ -36,6 +37,7 @@ export interface ConfigProviderProps {
|
||||
autoInsertSpaceInButton?: boolean;
|
||||
form?: {
|
||||
validateMessages?: ValidateMessages;
|
||||
requiredMark?: RequiredMark;
|
||||
};
|
||||
input?: {
|
||||
autoComplete?: string;
|
||||
@ -130,8 +132,11 @@ const ConfigProvider: React.FC<ConfigProviderProps> & {
|
||||
config.input = input;
|
||||
}
|
||||
|
||||
let childNode = children;
|
||||
if (form) {
|
||||
config.form = form;
|
||||
}
|
||||
|
||||
let childNode = children;
|
||||
// Additional Form provider
|
||||
let validateMessages: ValidateMessages = {};
|
||||
|
||||
|
@ -44,7 +44,7 @@ export default () => (
|
||||
| csp | 设置 [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) 配置 | { nonce: string } | - | |
|
||||
| direction | 设置文本展示方向。 [示例](#components-config-provider-demo-direction) | `ltr` \| `rtl` | `ltr` | |
|
||||
| dropdownMatchSelectWidth | 下拉菜单和选择器同宽。默认将设置 `min-width`,当值小于选择框宽度时会被忽略。`false` 时会关闭虚拟滚动 | boolean \| number | - | 4.3.0 |
|
||||
| form | 设置 Form 组件的通用属性 | { validateMessages?: [ValidateMessages](/components/form/#validateMessages) } | - | |
|
||||
| form | 设置 Form 组件的通用属性 | { validateMessages?: [ValidateMessages](/components/form/#validateMessages), requiredMark?: boolean \| `optional` } | - | requiredMark: 4.8.0 |
|
||||
| getPopupContainer | 弹出框(Select, Tooltip, Menu 等等)渲染父节点,默认渲染到 body 上。 | function(triggerNode) | () => document.body | |
|
||||
| getTargetContainer | 配置 Affix、Anchor 滚动监听容器。 | () => HTMLElement | () => window | 4.2.0 |
|
||||
| input | 设置 Input 组件的通用属性 | { autoComplete?: string } | - | 4.2.0 |
|
||||
|
@ -5,7 +5,7 @@ import FieldForm, { List } from 'rc-field-form';
|
||||
import { FormProps as RcFormProps } from 'rc-field-form/lib/Form';
|
||||
import { ValidateErrorEntity } from 'rc-field-form/lib/interface';
|
||||
import { ColProps } from '../grid/col';
|
||||
import { ConfigContext, ConfigConsumerProps } from '../config-provider';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
import { FormContext, FormContextProps } from './context';
|
||||
import { FormLabelAlign } from './interface';
|
||||
import useForm, { FormInstance } from './hooks/useForm';
|
||||
@ -32,7 +32,7 @@ export interface FormProps<Values = any> extends Omit<RcFormProps<Values>, 'form
|
||||
|
||||
const InternalForm: React.ForwardRefRenderFunction<unknown, FormProps> = (props, ref) => {
|
||||
const contextSize = React.useContext(SizeContext);
|
||||
const { getPrefixCls, direction }: ConfigConsumerProps = React.useContext(ConfigContext);
|
||||
const { getPrefixCls, direction, form: contextForm } = React.useContext(ConfigContext);
|
||||
|
||||
const { name } = props;
|
||||
|
||||
@ -58,12 +58,16 @@ const InternalForm: React.ForwardRefRenderFunction<unknown, FormProps> = (props,
|
||||
return requiredMark;
|
||||
}
|
||||
|
||||
if (contextForm && contextForm.requiredMark !== undefined) {
|
||||
return contextForm.requiredMark;
|
||||
}
|
||||
|
||||
if (hideRequiredMark) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, [hideRequiredMark, requiredMark]);
|
||||
}, [hideRequiredMark, requiredMark, contextForm]);
|
||||
|
||||
const prefixCls = getPrefixCls('form', customizePrefixCls);
|
||||
|
||||
|
@ -2,6 +2,7 @@ import { createContext, Context } from 'react';
|
||||
|
||||
export interface RowContextState {
|
||||
gutter?: [number, number];
|
||||
wrap?: boolean;
|
||||
}
|
||||
|
||||
const RowContext: Context<RowContextState> = createContext({});
|
||||
|
@ -554,6 +554,26 @@ Array [
|
||||
0 1 300px
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-row ant-row-no-wrap"
|
||||
>
|
||||
<div
|
||||
class="ant-col"
|
||||
style="flex:none"
|
||||
>
|
||||
<div
|
||||
style="padding:0 16px"
|
||||
>
|
||||
none
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-col"
|
||||
style="flex:auto;min-width:0"
|
||||
>
|
||||
auto with no-wrap
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
|
@ -47,7 +47,7 @@ function parseFlex(flex: FlexType): string {
|
||||
|
||||
const Col = React.forwardRef<HTMLDivElement, ColProps>((props, ref) => {
|
||||
const { getPrefixCls, direction } = React.useContext(ConfigContext);
|
||||
const { gutter } = React.useContext(RowContext);
|
||||
const { gutter, wrap } = React.useContext(RowContext);
|
||||
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
@ -122,6 +122,12 @@ const Col = React.forwardRef<HTMLDivElement, ColProps>((props, ref) => {
|
||||
}
|
||||
if (flex) {
|
||||
mergedStyle.flex = parseFlex(flex);
|
||||
|
||||
// Hack for Firefox to avoid size issue
|
||||
// https://github.com/ant-design/ant-design/pull/20023#issuecomment-564389553
|
||||
if (flex === 'auto' && wrap === false && !mergedStyle.minWidth) {
|
||||
mergedStyle.minWidth = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -33,6 +33,13 @@ ReactDOM.render(
|
||||
<Col flex="1 1 200px">1 1 200px</Col>
|
||||
<Col flex="0 1 300px">0 1 300px</Col>
|
||||
</Row>
|
||||
|
||||
<Row wrap={false}>
|
||||
<Col flex="none">
|
||||
<div style={{ padding: '0 16px' }}>none</div>
|
||||
</Col>
|
||||
<Col flex="auto">auto with no-wrap</Col>
|
||||
</Row>
|
||||
</>,
|
||||
mountNode,
|
||||
);
|
||||
|
@ -92,6 +92,7 @@ If the Ant Design grid layout component does not meet your needs, you can use th
|
||||
| align | Vertical alignment | `top` \| `middle` \| `bottom` | `top` | | |
|
||||
| gutter | Spacing between grids, could be a number or a object like { xs: 8, sm: 16, md: 24}. Or you can use array to make horizontal and vertical spacing work at the same time `[horizontal, vertical]` | number \| object \| array | 0 | | |
|
||||
| justify | Horizontal arrangement | `start` \| `end` \| `center` \| `space-around` \| `space-between` | `start` | | |
|
||||
| wrap | Auto wrap line | boolean | true | 4.8.0 |
|
||||
|
||||
### Col
|
||||
|
||||
|
@ -91,6 +91,7 @@ Ant Design 的布局组件若不能满足你的需求,你也可以直接使用
|
||||
| align | 垂直对齐方式 | `top` \| `middle` \| `bottom` | `top` | |
|
||||
| gutter | 栅格间隔,可以写成像素值或支持响应式的对象写法来设置水平间隔 { xs: 8, sm: 16, md: 24}。或者使用数组形式同时设置 `[水平间距, 垂直间距]` | number \| object \| array | 0 | |
|
||||
| justify | 水平排列方式 | `start` \| `end` \| `center` \| `space-around` \| `space-between` | `start` | |
|
||||
| wrap | 是否自动换行 | boolean | true | 4.8.0 |
|
||||
|
||||
### Col
|
||||
|
||||
|
@ -18,6 +18,7 @@ export interface RowProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
align?: typeof RowAligns[number];
|
||||
justify?: typeof RowJustify[number];
|
||||
prefixCls?: string;
|
||||
wrap?: boolean;
|
||||
}
|
||||
|
||||
const Row = React.forwardRef<HTMLDivElement, RowProps>((props, ref) => {
|
||||
@ -29,6 +30,7 @@ const Row = React.forwardRef<HTMLDivElement, RowProps>((props, ref) => {
|
||||
style,
|
||||
children,
|
||||
gutter = 0,
|
||||
wrap,
|
||||
...others
|
||||
} = props;
|
||||
|
||||
@ -83,6 +85,7 @@ const Row = React.forwardRef<HTMLDivElement, RowProps>((props, ref) => {
|
||||
const classes = classNames(
|
||||
prefixCls,
|
||||
{
|
||||
[`${prefixCls}-no-wrap`]: wrap === false,
|
||||
[`${prefixCls}-${justify}`]: justify,
|
||||
[`${prefixCls}-${align}`]: align,
|
||||
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||
@ -106,7 +109,7 @@ const Row = React.forwardRef<HTMLDivElement, RowProps>((props, ref) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<RowContext.Provider value={{ gutter: gutters }}>
|
||||
<RowContext.Provider value={{ gutter: gutters, wrap }}>
|
||||
<div {...others} className={classes} style={rowStyle} ref={ref}>
|
||||
{children}
|
||||
</div>
|
||||
|
@ -11,6 +11,11 @@
|
||||
&::after {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
// No wrap of flex
|
||||
&-no-wrap {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
// x轴原点
|
||||
|
@ -31,6 +31,7 @@ Previewable image.
|
||||
{
|
||||
visible?: boolean;
|
||||
onVisibleChange?: (visible, prevVisible) => void;
|
||||
getContainer?: string | HTMLElement | (() => HTMLElement); // V4.8.0
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -32,6 +32,7 @@ cover: https://gw.alipayobjects.com/zos/antfincdn/D1dXz9PZqa/image.svg
|
||||
{
|
||||
visible?: boolean;
|
||||
onVisibleChange?: (visible, prevVisible) => void;
|
||||
getContainer: string | HTMLElement | (() => HTMLElement); // V4.8.0
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -5,12 +5,14 @@ import classNames from 'classnames';
|
||||
import ClearableLabeledInput from './ClearableLabeledInput';
|
||||
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
|
||||
import { fixControlledValue, resolveOnChange } from './Input';
|
||||
import SizeContext, { SizeType } from '../config-provider/SizeContext';
|
||||
|
||||
export interface TextAreaProps extends RcTextAreaProps {
|
||||
allowClear?: boolean;
|
||||
bordered?: boolean;
|
||||
showCount?: boolean;
|
||||
maxLength?: number;
|
||||
size?: SizeType;
|
||||
}
|
||||
|
||||
export interface TextAreaState {
|
||||
@ -76,15 +78,17 @@ class TextArea extends React.Component<TextAreaProps, TextAreaState> {
|
||||
resolveOnChange(this.resizableTextArea.textArea, e, this.props.onChange);
|
||||
};
|
||||
|
||||
renderTextArea = (prefixCls: string, bordered: boolean) => {
|
||||
const { showCount, className, style } = this.props;
|
||||
renderTextArea = (prefixCls: string, bordered: boolean, size?: SizeType) => {
|
||||
const { showCount, className, style, size: customizeSize } = this.props;
|
||||
|
||||
return (
|
||||
<RcTextArea
|
||||
{...omit(this.props, ['allowClear', 'bordered', 'showCount'])}
|
||||
{...omit(this.props, ['allowClear', 'bordered', 'showCount', 'size'])}
|
||||
className={classNames({
|
||||
[`${prefixCls}-borderless`]: !bordered,
|
||||
[className!]: className && !showCount,
|
||||
[`${prefixCls}-sm`]: size === 'small' || customizeSize === 'small',
|
||||
[`${prefixCls}-lg`]: size === 'large' || customizeSize === 'large',
|
||||
})}
|
||||
style={showCount ? null : style}
|
||||
prefixCls={prefixCls}
|
||||
@ -112,14 +116,14 @@ class TextArea extends React.Component<TextAreaProps, TextAreaState> {
|
||||
value = hasMaxLength ? value.slice(0, maxLength) : value;
|
||||
|
||||
// TextArea
|
||||
let textareaNode = (
|
||||
const textareaNode = (size?: SizeType) => (
|
||||
<ClearableLabeledInput
|
||||
{...this.props}
|
||||
prefixCls={prefixCls}
|
||||
direction={direction}
|
||||
inputType="text"
|
||||
value={value}
|
||||
element={this.renderTextArea(prefixCls, bordered)}
|
||||
element={this.renderTextArea(prefixCls, bordered, size)}
|
||||
handleReset={this.handleReset}
|
||||
ref={this.saveClearableInput}
|
||||
triggerFocus={this.focus}
|
||||
@ -132,25 +136,29 @@ class TextArea extends React.Component<TextAreaProps, TextAreaState> {
|
||||
const valueLength = [...value].length;
|
||||
const dataCount = `${valueLength}${hasMaxLength ? ` / ${maxLength}` : ''}`;
|
||||
|
||||
textareaNode = (
|
||||
<div
|
||||
className={classNames(
|
||||
`${prefixCls}-textarea`,
|
||||
{
|
||||
[`${prefixCls}-textarea-rtl`]: direction === 'rtl',
|
||||
},
|
||||
`${prefixCls}-textarea-show-count`,
|
||||
className,
|
||||
return (
|
||||
<SizeContext.Consumer>
|
||||
{(size?: SizeType) => (
|
||||
<div
|
||||
className={classNames(
|
||||
`${prefixCls}-textarea`,
|
||||
{
|
||||
[`${prefixCls}-textarea-rtl`]: direction === 'rtl',
|
||||
},
|
||||
`${prefixCls}-textarea-show-count`,
|
||||
className,
|
||||
)}
|
||||
style={style}
|
||||
data-count={dataCount}
|
||||
>
|
||||
{textareaNode(size)}
|
||||
</div>
|
||||
)}
|
||||
style={style}
|
||||
data-count={dataCount}
|
||||
>
|
||||
{textareaNode}
|
||||
</div>
|
||||
</SizeContext.Consumer>
|
||||
);
|
||||
}
|
||||
|
||||
return textareaNode;
|
||||
return <SizeContext.Consumer>{textareaNode}</SizeContext.Consumer>;
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -263,3 +263,9 @@ exports[`TextArea should support maxLength 1`] = `
|
||||
maxlength="10"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`TextArea should support size 1`] = `
|
||||
<textarea
|
||||
class="ant-input ant-input-lg"
|
||||
/>
|
||||
`;
|
||||
|
@ -155,6 +155,12 @@ describe('TextArea', () => {
|
||||
expect(wrapper.find('.ant-input').props().style.background).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should support size', async () => {
|
||||
const wrapper = mount(<TextArea size="large" />);
|
||||
expect(wrapper.find('textarea').hasClass('ant-input-lg')).toBe(true);
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('TextArea allowClear', () => {
|
||||
|
@ -3,6 +3,7 @@ import classNames from 'classnames';
|
||||
import Dialog, { ModalFuncProps } from './Modal';
|
||||
import ActionButton from './ActionButton';
|
||||
import devWarning from '../_util/devWarning';
|
||||
import ConfigProvider from '../config-provider';
|
||||
|
||||
interface ConfirmDialogProps extends ModalFuncProps {
|
||||
afterClose?: () => void;
|
||||
@ -31,6 +32,7 @@ const ConfirmDialog = (props: ConfirmDialogProps) => {
|
||||
direction,
|
||||
prefixCls,
|
||||
rootPrefixCls,
|
||||
bodyStyle,
|
||||
modalRender,
|
||||
} = props;
|
||||
|
||||
@ -97,13 +99,15 @@ const ConfirmDialog = (props: ConfirmDialogProps) => {
|
||||
modalRender={modalRender}
|
||||
>
|
||||
<div className={`${contentPrefixCls}-body-wrapper`}>
|
||||
<div className={`${contentPrefixCls}-body`}>
|
||||
{icon}
|
||||
{props.title === undefined ? null : (
|
||||
<span className={`${contentPrefixCls}-title`}>{props.title}</span>
|
||||
)}
|
||||
<div className={`${contentPrefixCls}-content`}>{props.content}</div>
|
||||
</div>
|
||||
<ConfigProvider prefixCls={rootPrefixCls}>
|
||||
<div className={`${contentPrefixCls}-body`} style={bodyStyle}>
|
||||
{icon}
|
||||
{props.title === undefined ? null : (
|
||||
<span className={`${contentPrefixCls}-title`}>{props.title}</span>
|
||||
)}
|
||||
<div className={`${contentPrefixCls}-content`}>{props.content}</div>
|
||||
</div>
|
||||
</ConfigProvider>
|
||||
<div className={`${contentPrefixCls}-btns`}>
|
||||
{cancelButton}
|
||||
<ActionButton
|
||||
|
@ -115,6 +115,7 @@ export interface ModalFuncProps {
|
||||
transitionName?: string;
|
||||
maskTransitionName?: string;
|
||||
direction?: string;
|
||||
bodyStyle?: React.CSSProperties;
|
||||
modalRender?: (node: React.ReactNode) => React.ReactNode;
|
||||
}
|
||||
|
||||
|
@ -244,7 +244,7 @@ describe('Modal.confirm triggers callbacks correctly', () => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('could be update', () => {
|
||||
it('could be update by new config', () => {
|
||||
jest.useFakeTimers();
|
||||
['info', 'success', 'warning', 'error'].forEach(type => {
|
||||
const instance = Modal[type]({
|
||||
@ -273,6 +273,49 @@ describe('Modal.confirm triggers callbacks correctly', () => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('could be update by call function', () => {
|
||||
jest.useFakeTimers();
|
||||
['info', 'success', 'warning', 'error'].forEach(type => {
|
||||
const instance = Modal[type]({
|
||||
title: 'title',
|
||||
okButtonProps: {
|
||||
loading: true,
|
||||
style: {
|
||||
color: 'red',
|
||||
},
|
||||
},
|
||||
});
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
|
||||
expect($$('.ant-modal-confirm-title')[0].innerHTML).toBe('title');
|
||||
expect($$('.ant-modal-confirm-btns .ant-btn-primary')[0].classList).toContain(
|
||||
'ant-btn-loading',
|
||||
);
|
||||
expect($$('.ant-modal-confirm-btns .ant-btn-primary')[0].style.color).toBe('red');
|
||||
instance.update(prevConfig => ({
|
||||
...prevConfig,
|
||||
okButtonProps: {
|
||||
...prevConfig.okButtonProps,
|
||||
loading: false,
|
||||
},
|
||||
}));
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
|
||||
expect($$('.ant-modal-confirm-title')[0].innerHTML).toBe('title');
|
||||
expect($$('.ant-modal-confirm-btns .ant-btn-primary')[0].classList).not.toContain(
|
||||
'ant-btn-loading',
|
||||
);
|
||||
expect($$('.ant-modal-confirm-btns .ant-btn-primary')[0].style.color).toBe('red');
|
||||
instance.destroy();
|
||||
jest.runAllTimers();
|
||||
});
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('could be destroy', () => {
|
||||
jest.useFakeTimers();
|
||||
['info', 'success', 'warning', 'error'].forEach(type => {
|
||||
|
@ -14,11 +14,13 @@ function getRootPrefixCls() {
|
||||
return defaultRootPrefixCls;
|
||||
}
|
||||
|
||||
type ConfigUpdate = ModalFuncProps | ((prevConfig: ModalFuncProps) => ModalFuncProps);
|
||||
|
||||
export type ModalFunc = (
|
||||
props: ModalFuncProps,
|
||||
) => {
|
||||
destroy: () => void;
|
||||
update: (newConfig: ModalFuncProps) => void;
|
||||
update: (configUpdate: ConfigUpdate) => void;
|
||||
};
|
||||
|
||||
export interface ModalStaticFunctions {
|
||||
@ -84,11 +86,15 @@ export default function confirm(config: ModalFuncProps) {
|
||||
render(currentConfig);
|
||||
}
|
||||
|
||||
function update(newConfig: ModalFuncProps) {
|
||||
currentConfig = {
|
||||
...currentConfig,
|
||||
...newConfig,
|
||||
};
|
||||
function update(configUpdate: ConfigUpdate) {
|
||||
if (typeof configUpdate === 'function') {
|
||||
currentConfig = configUpdate(currentConfig);
|
||||
} else {
|
||||
currentConfig = {
|
||||
...currentConfig,
|
||||
...configUpdate,
|
||||
};
|
||||
}
|
||||
render(currentConfig);
|
||||
}
|
||||
|
||||
|
@ -65,6 +65,7 @@ The items listed above are all functions, expecting a settings object as paramet
|
||||
| Property | Description | Type | Default | Version |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| autoFocusButton | Specify which button to autofocus | null \| `ok` \| `cancel` | `ok` | |
|
||||
| bodyStyle | Body style for modal body element. Such as height, padding etc | CSSProperties | | 4.8.0 |
|
||||
| cancelButtonProps | The cancel button props | [ButtonProps](/components/button/#API) | - | |
|
||||
| cancelText | Text of the Cancel button with Modal.confirm | string | `Cancel` | |
|
||||
| centered | Centered Modal | boolean | false | |
|
||||
@ -96,6 +97,12 @@ modal.update({
|
||||
content: 'Updated content',
|
||||
});
|
||||
|
||||
// on 4.8.0 or above, you can pass a function to update modal
|
||||
modal.update(prevConfig => ({
|
||||
...prevConfig,
|
||||
title: `${prevConfig.title} (New)`,
|
||||
}));
|
||||
|
||||
modal.destroy();
|
||||
```
|
||||
|
||||
|
@ -68,6 +68,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/3StSdUlSH/Modal.svg
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| autoFocusButton | 指定自动获得焦点的按钮 | null \| `ok` \| `cancel` | `ok` | |
|
||||
| bodyStyle | Modal body 样式 | CSSProperties | | 4.8.0 |
|
||||
| cancelButtonProps | cancel 按钮 props | [ButtonProps](/components/button/#API) | - | |
|
||||
| cancelText | 设置 Modal.confirm 取消按钮文字 | string | `取消` | |
|
||||
| centered | 垂直居中展示 Modal | boolean | false | |
|
||||
@ -99,6 +100,12 @@ modal.update({
|
||||
content: '修改的内容',
|
||||
});
|
||||
|
||||
// 在 4.8.0 或更高版本中,可以通过传入函数的方式更新弹窗
|
||||
modal.update(prevConfig => ({
|
||||
...prevConfig,
|
||||
title: `${prevConfig.title}(新)`,
|
||||
}));
|
||||
|
||||
modal.destroy();
|
||||
```
|
||||
|
||||
|
@ -3,6 +3,7 @@ import classNames from 'classnames';
|
||||
|
||||
import { ConfigConsumerProps } from '../config-provider';
|
||||
import { withConfigConsumer } from '../config-provider/context';
|
||||
import Skeleton from '../skeleton';
|
||||
import StatisticNumber from './Number';
|
||||
import Countdown from './Countdown';
|
||||
import { valueType, FormatConfig } from './utils';
|
||||
@ -21,6 +22,7 @@ export interface StatisticProps extends FormatConfig {
|
||||
title?: React.ReactNode;
|
||||
prefix?: React.ReactNode;
|
||||
suffix?: React.ReactNode;
|
||||
loading?: boolean;
|
||||
onMouseEnter?: React.MouseEventHandler<HTMLDivElement>;
|
||||
onMouseLeave?: React.MouseEventHandler<HTMLDivElement>;
|
||||
}
|
||||
@ -36,6 +38,7 @@ const Statistic: React.FC<StatisticProps & ConfigConsumerProps> = props => {
|
||||
valueRender,
|
||||
prefix,
|
||||
suffix,
|
||||
loading,
|
||||
direction,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
@ -51,11 +54,13 @@ const Statistic: React.FC<StatisticProps & ConfigConsumerProps> = props => {
|
||||
return (
|
||||
<div className={cls} style={style} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
|
||||
{title && <div className={`${prefixCls}-title`}>{title}</div>}
|
||||
<div style={valueStyle} className={`${prefixCls}-content`}>
|
||||
{prefix && <span className={`${prefixCls}-content-prefix`}>{prefix}</span>}
|
||||
{valueRender ? valueRender(valueNode) : valueNode}
|
||||
{suffix && <span className={`${prefixCls}-content-suffix`}>{suffix}</span>}
|
||||
</div>
|
||||
<Skeleton paragraph={false} loading={loading}>
|
||||
<div style={valueStyle} className={`${prefixCls}-content`}>
|
||||
{prefix && <span className={`${prefixCls}-content-prefix`}>{prefix}</span>}
|
||||
{valueRender ? valueRender(valueNode) : valueNode}
|
||||
{suffix && <span className={`${prefixCls}-content-suffix`}>{suffix}</span>}
|
||||
</div>
|
||||
</Skeleton>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -63,6 +68,7 @@ const Statistic: React.FC<StatisticProps & ConfigConsumerProps> = props => {
|
||||
Statistic.defaultProps = {
|
||||
decimalSeparator: '.',
|
||||
groupSeparator: ',',
|
||||
loading: false,
|
||||
};
|
||||
|
||||
const WrapperStatistic = withConfigConsumer<StatisticProps>({
|
||||
|
@ -73,6 +73,31 @@ exports[`renders ./components/statistic/demo/basic.md correctly 1`] = `
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ant-col ant-col-12"
|
||||
style="padding-left:8px;padding-right:8px"
|
||||
>
|
||||
<div
|
||||
class="ant-statistic"
|
||||
>
|
||||
<div
|
||||
class="ant-statistic-title"
|
||||
>
|
||||
Active Users
|
||||
</div>
|
||||
<div
|
||||
class="ant-skeleton"
|
||||
>
|
||||
<div
|
||||
class="ant-skeleton-content"
|
||||
>
|
||||
<h3
|
||||
class="ant-skeleton-title"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
@ -50,6 +50,18 @@ describe('Statistic', () => {
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('loading with skeleton', async () => {
|
||||
let loading = false;
|
||||
const wrapper = mount(<Statistic title="Active Users" value={112112} loading={loading} />);
|
||||
expect(wrapper.find('.ant-skeleton')).toHaveLength(0);
|
||||
expect(wrapper.find('.ant-statistic-content')).toHaveLength(1);
|
||||
|
||||
loading = true;
|
||||
wrapper.setProps({ loading });
|
||||
expect(wrapper.find('.ant-skeleton')).toHaveLength(1);
|
||||
expect(wrapper.find('.ant-statistic-content')).toHaveLength(0);
|
||||
});
|
||||
|
||||
describe('Countdown', () => {
|
||||
it('render correctly', () => {
|
||||
const now = moment().add(2, 'd').add(11, 'h').add(28, 'm').add(9, 's').add(3, 'ms');
|
||||
|
@ -27,6 +27,9 @@ ReactDOM.render(
|
||||
Recharge
|
||||
</Button>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Statistic title="Active Users" value={112893} loading />
|
||||
</Col>
|
||||
</Row>,
|
||||
mountNode,
|
||||
);
|
||||
|
@ -21,6 +21,7 @@ Display statistic number.
|
||||
| decimalSeparator | The decimal separator | string | `.` | |
|
||||
| formatter | Customize value display logic | (value) => ReactNode | - | |
|
||||
| groupSeparator | Group separator | string | `,` | |
|
||||
| loading | Loading status of Statistic | boolean | false | 4.8.0 |
|
||||
| precision | The precision of input value | number | - | |
|
||||
| prefix | The prefix node of value | ReactNode | - | |
|
||||
| suffix | The suffix node of value | ReactNode | - | |
|
||||
|
@ -22,6 +22,7 @@ cover: https://gw.alipayobjects.com/zos/antfincdn/rcBNhLBrKbE/Statistic.svg
|
||||
| decimalSeparator | 设置小数点 | string | `.` | |
|
||||
| formatter | 自定义数值展示 | (value) => ReactNode | - | |
|
||||
| groupSeparator | 设置千分位标识符 | string | `,` | |
|
||||
| loading | 数值是否加载中 | boolean | false | 4.8.0 |
|
||||
| precision | 数值精度 | number | - | |
|
||||
| prefix | 设置数值的前缀 | ReactNode | - | |
|
||||
| suffix | 设置数值的后缀 | ReactNode | - | |
|
||||
|
@ -1,2 +1,5 @@
|
||||
import '../../style/index.less';
|
||||
import './index.less';
|
||||
|
||||
// style dependencies
|
||||
import '../../skeleton/style';
|
||||
|
@ -126,7 +126,7 @@
|
||||
"rc-drawer": "~4.1.0",
|
||||
"rc-dropdown": "~3.2.0",
|
||||
"rc-field-form": "~1.13.0",
|
||||
"rc-image": "~3.2.1",
|
||||
"rc-image": "~4.0.0",
|
||||
"rc-input-number": "~6.1.0",
|
||||
"rc-mentions": "~1.5.0",
|
||||
"rc-menu": "~8.8.2",
|
||||
@ -141,7 +141,7 @@
|
||||
"rc-slider": "~9.5.2",
|
||||
"rc-steps": "~4.1.0",
|
||||
"rc-switch": "~3.2.0",
|
||||
"rc-table": "~7.10.0",
|
||||
"rc-table": "~7.11.0",
|
||||
"rc-tabs": "~11.7.0",
|
||||
"rc-textarea": "~0.3.0",
|
||||
"rc-tooltip": "~5.0.0",
|
||||
|
@ -30,8 +30,7 @@ const LIST_CN: Recommend[] = [
|
||||
},
|
||||
{
|
||||
title: '第十五届 D2 前端技术论坛 - 无界',
|
||||
description:
|
||||
'前端热爱,技术无界,第十五届 D2 前端技术论坛,我们云端相聚!',
|
||||
description: '前端热爱,技术无界,第十五届 D2 前端技术论坛,我们云端相聚!',
|
||||
img: 'https://img.alicdn.com/tfs/TB1R39KnSR26e4jSZFEXXbwuXXa-1960-768.png',
|
||||
href: 'http://d2forum.alibaba-inc.com/',
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user