merge master into Feature (#25262)

* feat: add successColor for Progress (#24655)

* feat: add successColor for Progress

* feat: update

* fix: update test

* remove snap

* feat: add test case

* refactor success

* feat: adjust styyle

* feat: add DevWarning

* feat: Support rowSelection.dirty (#24718)

* feat: Support rowSelection.dirty

* rename to reserveKeys

* preserveKeys will keep record also

* to preserveSelectedRowKeys

* feat: add ghost prop for collapse (#24734)

* feat: add ghost prop for collapse

* doc: version of collapse's ghost prop

* refactor: make ghost collapse's less code to a nested style

* chore: remove redundant codes in ghost collapse's less & doc

* doc: add a background wrapper for ghost collapse demo

* doc: dark-theme wrapper bg-color for ghost collapse demo

* test: update snapshot of ghost collapse

* doc: use softer bg-color on ghost collapse demo

* doc: remove disabled panel in ghost collapse demo

* feat: form instance support getFieldInstance (#24711)

* support getFieldInstance

* update doc

* fix lint

* move func

* move into hooks

* update ref logic

* fix lint

* rm only

* fix docs

* feat: dropdown support arrow (#23869)

* feat: dropdown support arrow prop

close #22758

* test: update snapshot

* fix: fix dropdown cls names

* test: update snapshot

* test: update snapshot

* doc: update demo

* test: update demo snapshot

* demo

* fix: snapshot

* chore: change the style of ghost collapse & demo modified (#24762)

* refactor: reduce content padding in ghost collapse

* doc: remove the wrapper outside ghost collapse

Designer want the demo differs from other demos

* refactor: remove redundant .less code in collapse

* feat: cascader dropdown-render prop (#24812)

* feat: cascader dropdown-render prop

* fix: update Cascader dropdownRender type annotation

* fix: set rc-cascader semver from ^ to ~

* docs: fix coding style in cascader/custom-dropdown

* feat: 🆕 support Drawer closeIcon (#24842)

* feat: 🆕 support Drawer closeIcon

close #19283
close #19153

* add test case

* update docs

* feat: 🆕 Cascader expandIcon (#24865)

* feat: cascader expandIcon

* fix: snap

* refactor: reduce CSS size (#24846)

* refactor: reduce button css size

* refactor: remove redundant button .less code

* feat: add Table onChange an action param (#24697)

* Working on tests

* created TableAction type

* changed TableActions to tuple

* removed chinese documentation line

* refactor TableActions

* fix documentation

* Moved action into extra param

* minor doc change

* feat: add closeIcon customize tag close (#24885)

* feat: add closeIcon customize tag close

* docs fix

* update snap

* fix: css name

* update snapshot

* snapshot

* feat: add radio `optionType` api to set radio option type (#24809)

* feat: radio component

* docs: update md

* fix: snap

* test components

* fix: use optionType

* fix name

* add warning

* fix

* feat: expand rate character (#24903)

* feat: expand rate character

* fix: demo

* fix: snap

* Update components/rate/index.zh-CN.md

Co-authored-by: 偏右 <afc163@gmail.com>

* fix

Co-authored-by: 偏右 <afc163@gmail.com>

* Refactor demo code box actions (#24887)

* refactor: refine the styling of actions part of demo code-box

* fix: lint style

* refactor: move Result children to end (#24945)

* feat: remove content max-width on dot-step (#24907)

* feat: add Skeleton-Image (#24805)

* feat: add Skeleton-Image

* feat: add docs

* fix: adjust skeleton

* feat: adjust Image Component

* feat: rebase

* feat: adjust style

* fix: lint

* feat: remove size

* feat: delete md

* feat: fix style

*  feat: Mentions support autoSize (#24961)

close #17746

* chore: replace textarea with rc-textarea (#24966)

* feat: update pagination@2.3.0 support onChange called when pageSize change (#24964)

* feat: update pagination@2.5.0 and add test case to relative component

* fix: lint

* delete

* feat: add test case for pagination

* adjust test case

* feat: Implement centered prop in Tabs (#24958)

* Implement centered in Tabs along with its tests and docs

* Fix build error

* Add Chinese translations and remove test case

Co-authored-by: Ashkan Pourghasem <ashkan.pourghasem@gmail.com>

* feat: Add modal style parameter (#24773)

* add some paramters in default.less

* Update components/style/themes/default.less

Co-authored-by: Amumu <yoyo837@hotmail.com>

* change parameter in compact.less

Co-authored-by: Crystal Gao <jinggao@ebay.com>
Co-authored-by: Amumu <yoyo837@hotmail.com>

* feat: export Tabs addIcon (#25006)

* feat: export Tabs addIcon

* update snapshot

* feat: showNow on timepicker and datetimepicker (#25032)

* feat: update rc-picker@1.7.1 and fix icons of month and quarter picker in DatePicker Component (#25035)

* feat: update rc-picker@1.7.1

* delete

* add

* feat: expand rate support props (#24993)

* docs: 📝 Add Form.Item hidden in doc (#25108)

close #25101

* fix: ⌨️ Improve Pagination accessibility issue (#25119)

* ⌨️ Improve Pagination a11y by fixing a W3C error

https://github.com/react-component/pagination/issues/280

* update snapshot

* 🆙 rc-pagination to 2.4.1

* feat: support triggerSubMenuAction for <Menu /> (#25127)

* feat(menu): add triggerSubMenuAction for Menu

* feat(menu): test cases

* chore: Adjust picker logic (#25135)

* chore: update rc-picker 1.10.0 (#25174)

* feat: table row check strictly (#24931)

* feat: add checkStrictly on Table.rowSelection

* fix: LGTM warnings

* test: table rowSelection.checkStrictly

* test: add cov [wip]

* refactor: tree.rowSelection.checkStrictly [wip]

* test: table.rowSelection.checkStrictly basic case

* feat: support rowKey on checkStrictly table

* feat: Table checkStrictly support getCheckboxProps

* docs: Table checkStrictly

* chore: typo

* chore: remove useless comment

* chore: update snapshot

* chore: update snapshot

* fix: fire selectAll on selection dropdown menu & changeRows incorrect in selectAll callback

* docs: typo

* chore

* chore

* fix: expand buttons of leaf rows in tree data are not hidden

* feat: Table warning about rowKey index parameter

* perf: only generate keyEntities when not checkStrictly

* refactor: remove useless parseCheckedKeys

* refactor: get derived selected & half selected keys from selectedRowKeys

* chore: remove env condition stmt

* chore: revert index usage & code formatting

* chore: rerun ci

* docs: table tree-data checkstrictly

* test: update snapshots

* refactor: use useMergedState hook

* chore: rerun ci

* chore: rerun ci 2

* chore: revert selection select all behavior

* refactor: refactor code based on feature

* chore: revert table code format

* chore: revert table code format

* fix: useMemo deps

* fix: useMemo deps

* fix: useMemo deps

* feat: support preserve (#25186)

* docs: add responsibly order for Col (#25139)

* feat: add type

* feat: add responsibly order cols

* feat: add docs

* feat: add test case

* fix test

Co-authored-by: 二货机器人 <smith3816@gmail.com>
Co-authored-by: 偏右 <afc163@gmail.com>
Co-authored-by: zoomdong <1344492820@qq.com>
Co-authored-by: 07akioni <07akioni2@gmail.com>
Co-authored-by: wendellhu <wendellhu95@gmail.com>
Co-authored-by: xrkffgg <xrkffgg@gmail.com>
Co-authored-by: Neto Braghetto <netow93@gmail.com>
Co-authored-by: Kermit Xuan <kermitlx@outlook.com>
Co-authored-by: Ashkan Pourghasem <64011067+ashkan-pm@users.noreply.github.com>
Co-authored-by: Ashkan Pourghasem <ashkan.pourghasem@gmail.com>
Co-authored-by: hicrystal <295247343@qq.com>
Co-authored-by: Crystal Gao <jinggao@ebay.com>
Co-authored-by: Amumu <yoyo837@hotmail.com>
Co-authored-by: Li Ming <armyiljfe@gmail.com>
This commit is contained in:
陈帅 2020-06-28 22:41:59 +08:00 committed by GitHub
parent 1ca1bb08a6
commit 523b74e3b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
154 changed files with 4863 additions and 1918 deletions

View File

@ -80,6 +80,46 @@ exports[`renders ./components/cascader/demo/change-on-select.md correctly 1`] =
</span>
`;
exports[`renders ./components/cascader/demo/custom-dropdown.md correctly 1`] = `
<span
class="ant-cascader-picker"
tabindex="0"
>
<span
class="ant-cascader-picker-label"
/>
<input
autocomplete="off"
class="ant-input ant-cascader-input "
placeholder="Please select"
readonly=""
tabindex="-1"
type="text"
value=""
/>
<span
aria-label="down"
class="anticon anticon-down ant-cascader-picker-arrow"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
`;
exports[`renders ./components/cascader/demo/custom-render.md correctly 1`] = `
<span
class="ant-cascader-picker"
@ -551,7 +591,7 @@ exports[`renders ./components/cascader/demo/size.md correctly 1`] = `
`;
exports[`renders ./components/cascader/demo/suffix.md correctly 1`] = `
<div>
Array [
<span
class="ant-cascader-picker"
tabindex="0"
@ -588,10 +628,11 @@ exports[`renders ./components/cascader/demo/suffix.md correctly 1`] = `
/>
</svg>
</span>
</span>
</span>,
<br />,
<br />,
<span
class="ant-cascader-picker"
style="margin-top:1rem"
tabindex="0"
>
<span
@ -611,6 +652,84 @@ exports[`renders ./components/cascader/demo/suffix.md correctly 1`] = `
>
ab
</span>
</span>
</div>
</span>,
<br />,
<br />,
<span
class="ant-cascader-picker"
tabindex="0"
>
<span
class="ant-cascader-picker-label"
/>
<input
autocomplete="off"
class="ant-input ant-cascader-input "
placeholder="Please select"
readonly=""
tabindex="-1"
type="text"
value=""
/>
<span
aria-label="down"
class="anticon anticon-down ant-cascader-picker-arrow"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>,
<br />,
<br />,
<span
class="ant-cascader-picker"
tabindex="0"
>
<span
class="ant-cascader-picker-label"
/>
<input
autocomplete="off"
class="ant-input ant-cascader-input "
placeholder="Please select"
readonly=""
tabindex="-1"
type="text"
value=""
/>
<span
aria-label="down"
class="anticon anticon-down ant-cascader-picker-arrow"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>,
]
`;

View File

@ -0,0 +1,72 @@
---
order: 12
title:
zh-CN: 扩展菜单
en-US: Custom dropdown
---
## zh-CN
使用 `dropdownRender` 对下拉菜单进行自由扩展。
## en-US
Customize the dropdown menu via `dropdownRender`.
```jsx
import { Cascader, Divider } from 'antd';
const options = [
{
value: 'zhejiang',
label: 'Zhejiang',
children: [
{
value: 'hangzhou',
label: 'Hangzhou',
children: [
{
value: 'xihu',
label: 'West Lake',
},
],
},
],
},
{
value: 'jiangsu',
label: 'Jiangsu',
children: [
{
value: 'nanjing',
label: 'Nanjing',
children: [
{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
},
],
},
],
},
];
function dropdownRender(menus) {
return (
<div>
{menus}
<Divider style={{ margin: 0 }} />
<div style={{ padding: 8 }}>The footer is not very short.</div>
</div>
);
}
ReactDOM.render(
<Cascader
options={options}
dropdownRender={dropdownRender}
placeholder="Please select"
/>,
mountNode,
);
```

View File

@ -2,17 +2,17 @@
order: 11
debug: true
title:
zh-CN: 后缀图标
en-US: Suffix
zh-CN: 自定义图标
en-US: Custom Icons
---
## zh-CN
省市区级联
通过 `suffixIcon` 自定义选择框后缀图标,通过 `expandIcon` 自定义次级菜单展开图标
## en-US
Cascade selection box for selecting province/city/district.
Use `suffixIcon` to customize the selection box suffix icon, and use `expandIcon` to customize the current item expand icon.
```jsx
import { Cascader } from 'antd';
@ -58,21 +58,28 @@ function onChange(value) {
}
ReactDOM.render(
<div>
<>
<Cascader
suffixIcon={<SmileOutlined />}
options={options}
onChange={onChange}
placeholder="Please select"
/>
<br />
<br />
<Cascader suffixIcon="ab" options={options} onChange={onChange} placeholder="Please select" />
<br />
<br />
<Cascader
suffixIcon="ab"
style={{ marginTop: '1rem' }}
expandIcon={<SmileOutlined />}
options={options}
onChange={onChange}
placeholder="Please select"
/>
</div>,
<br />
<br />
<Cascader expandIcon="ab" options={options} onChange={onChange} placeholder="Please select" />
</>,
mountNode,
);
```

View File

@ -30,6 +30,7 @@ Cascade selection box.
| disabled | whether disabled select | boolean | false | |
| displayRender | render function of displaying selected options | `(label, selectedOptions) => ReactNode` | `label => label.join(' / ')` | |
| expandTrigger | expand current item when click or hover, one of 'click' 'hover' | string | `click` | |
| expandIcon | customize the current item expand icon | ReactNode | - | 4.4.0 |
| fieldNames | custom field name for label and value and children | object | `{ label: 'label', value: 'value', children: 'children' }` | |
| getPopupContainer | Parent Node which the selector should be rendered to. Default to `body`. When position issues happen, try to modify it into scrollable content and position it relative.[example](https://codepen.io/afc163/pen/zEjNOy?editors=0010) | Function(triggerNode) | () => document.body | |
| loadData | To load option lazily, and it cannot work with `showSearch` | `(selectedOptions) => void` | - | |
@ -44,6 +45,7 @@ Cascade selection box.
| style | additional style | CSSProperties | - | |
| suffixIcon | The custom suffix icon | ReactNode | - | |
| value | selected value | string\[] \| number\[] | - | |
| dropdownRender | Customize dropdown content | `(menus: ReactNode) => ReactNode` | - | 4.4.0 |
| onChange | callback when finishing cascader select | `(value, selectedOptions) => void` | - | |
| onPopupVisibleChange | callback when popup shown or hidden | `(value) => void` | - | |

View File

@ -97,6 +97,7 @@ export interface CascaderProps {
loadData?: (selectedOptions?: CascaderOptionType[]) => void;
/** 次级菜单的展开方式,可选 'click' 和 'hover' */
expandTrigger?: CascaderExpandTrigger;
expandIcon?: React.ReactNode;
/** 当此项为 true 时,点选每级菜单选项值都会发生变化 */
changeOnSelect?: boolean;
/** 浮层可见变化时回调 */
@ -108,6 +109,7 @@ export interface CascaderProps {
/** use this after antd@3.7.0 */
fieldNames?: FieldNamesType;
suffixIcon?: React.ReactNode;
dropdownRender?: (menus: React.ReactNode) => React.ReactNode
}
export interface CascaderState {
@ -458,11 +460,14 @@ class Cascader extends React.Component<CascaderProps, CascaderState> {
allowClear,
showSearch = false,
suffixIcon,
expandIcon,
notFoundContent,
popupClassName,
bordered,
dropdownRender,
...otherProps
} = props;
const mergedSize = customizeSize || size;
const { value, inputFocused } = state;
@ -592,9 +597,11 @@ class Cascader extends React.Component<CascaderProps, CascaderState> {
</span>
);
let expandIcon = <RightOutlined />;
if (isRtlLayout) {
expandIcon = <LeftOutlined />;
let expandIconNode;
if (expandIcon) {
expandIconNode = expandIcon;
} else {
expandIconNode = isRtlLayout ? <LeftOutlined /> : <RightOutlined />;
}
const loadingIcon = (
@ -621,10 +628,11 @@ class Cascader extends React.Component<CascaderProps, CascaderState> {
onPopupVisibleChange={this.handlePopupVisibleChange}
onChange={this.handleChange}
dropdownMenuColumnStyle={dropdownMenuColumnStyle}
expandIcon={expandIcon}
expandIcon={expandIconNode}
loadingIcon={loadingIcon}
popupClassName={rcCascaderPopupClassName}
popupPlacement={this.getPopupPlacement(direction)}
dropdownRender={dropdownRender}
>
{input}
</RcCascader>

View File

@ -31,6 +31,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/UdS8y8xyZ/Cascader.svg
| disabled | 禁用 | boolean | false | |
| displayRender | 选择后展示的渲染函数 | `(label, selectedOptions) => ReactNode` | `label => label.join(' / ')` | |
| expandTrigger | 次级菜单的展开方式,可选 'click' 和 'hover' | string | `click` | |
| expandIcon | 自定义次级菜单展开图标 | ReactNode | - | 4.4.0 |
| fieldNames | 自定义 options 中 label name children 的字段 | object | `{ label: 'label', value: 'value', children: 'children' }` | |
| getPopupContainer | 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。[示例](https://codepen.io/afc163/pen/zEjNOy?editors=0010) | Function(triggerNode) | () => document.body | |
| loadData | 用于动态加载选项,无法与 `showSearch` 一起使用 | `(selectedOptions) => void` | - | |
@ -45,6 +46,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/UdS8y8xyZ/Cascader.svg
| style | 自定义样式 | CSSProperties | - | |
| suffixIcon | 自定义的选择框后缀图标 | ReactNode | - | |
| value | 指定选中项 | string\[] \| number\[] | - | |
| dropdownRender | 自定义下拉框内容 | `(menus: ReactNode) => ReactNode` | - | 4.4.0 |
| onChange | 选择完成后的回调 | `(value, selectedOptions) => void` | - | |
| onPopupVisibleChange | 显示/隐藏浮层的回调 | `(value) => void` | - | |

View File

@ -23,6 +23,7 @@ export interface CollapseProps {
prefixCls?: string;
expandIcon?: (panelProps: PanelProps) => React.ReactNode;
expandIconPosition?: ExpandIconPosition;
ghost?: boolean;
}
interface PanelProps {
@ -42,7 +43,7 @@ interface CollapseInterface extends React.FC<CollapseProps> {
const Collapse: CollapseInterface = props => {
const { getPrefixCls, direction } = React.useContext(ConfigContext);
const { prefixCls: customizePrefixCls, className = '', bordered } = props;
const { prefixCls: customizePrefixCls, className = '', bordered, ghost } = props;
const prefixCls = getPrefixCls('collapse', customizePrefixCls);
const getIconPosition = () => {
@ -72,6 +73,7 @@ const Collapse: CollapseInterface = props => {
[`${prefixCls}-borderless`]: !bordered,
[`${prefixCls}-icon-position-${iconPosition}`]: true,
[`${prefixCls}-rtl`]: direction === 'rtl',
[`${prefixCls}-ghost`]: !!ghost,
},
className,
);

View File

@ -718,6 +718,125 @@ exports[`renders ./components/collapse/demo/extra.md correctly 1`] = `
</div>
`;
exports[`renders ./components/collapse/demo/ghost.md correctly 1`] = `
<div
class="ant-collapse ant-collapse-icon-position-left ant-collapse-ghost"
>
<div
class="ant-collapse-item ant-collapse-item-active"
>
<div
aria-expanded="true"
class="ant-collapse-header"
role="button"
tabindex="0"
>
<span
aria-label="right"
class="anticon anticon-right ant-collapse-arrow"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="right"
fill="currentColor"
focusable="false"
height="1em"
style="-ms-transform:rotate(90deg);transform:rotate(90deg)"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"
/>
</svg>
</span>
This is panel header 1
</div>
<div
class="ant-collapse-content ant-collapse-content-active"
>
<div
class="ant-collapse-content-box"
>
<p>
A dog is a type of domesticated animal.
Known for its loyalty and faithfulness,
it can be found as a welcome guest in many households across the world.
</p>
</div>
</div>
</div>
<div
class="ant-collapse-item"
>
<div
aria-expanded="false"
class="ant-collapse-header"
role="button"
tabindex="0"
>
<span
aria-label="right"
class="anticon anticon-right ant-collapse-arrow"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="right"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"
/>
</svg>
</span>
This is panel header 2
</div>
</div>
<div
class="ant-collapse-item"
>
<div
aria-expanded="false"
class="ant-collapse-header"
role="button"
tabindex="0"
>
<span
aria-label="right"
class="anticon anticon-right ant-collapse-arrow"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="right"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"
/>
</svg>
</span>
This is panel header 3
</div>
</div>
</div>
`;
exports[`renders ./components/collapse/demo/mix.md correctly 1`] = `
<div
class="ant-collapse ant-collapse-icon-position-left"

View File

@ -0,0 +1,41 @@
---
order: 6
title:
zh-CN: 幽灵折叠面板
en-US: Ghost Collapse
---
## zh-CN
将折叠面板的背景变成透明。
## en-US
Making collapse's background to transparent.
```jsx
import { Collapse } from 'antd';
const { Panel } = Collapse;
const text = `
A dog is a type of domesticated animal.
Known for its loyalty and faithfulness,
it can be found as a welcome guest in many households across the world.
`;
ReactDOM.render(
<Collapse defaultActiveKey={['1']} ghost>
<Panel header="This is panel header 1" key="1">
<p>{text}</p>
</Panel>
<Panel header="This is panel header 2" key="2">
<p>{text}</p>
</Panel>
<Panel header="This is panel header 3" key="3">
<p>{text}</p>
</Panel>
</Collapse>,
mountNode,
);
```

View File

@ -27,6 +27,7 @@ A content area which can be collapsed and expanded.
| expandIcon | allow to customize collapse icon | (panelProps) => ReactNode | - | |
| expandIconPosition | Set expand icon position | `left` \| `right` | - | |
| destroyInactivePanel | Destroy Inactive Panel | boolean | false | |
| ghost | make the collapse borderless and its background transparent | boolean | false | 4.4.0 |
### Collapse.Panel

View File

@ -28,6 +28,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/IxH16B9RD/Collapse.svg
| expandIcon | 自定义切换图标 | (panelProps) => ReactNode | - | |
| expandIconPosition | 设置图标位置 | `left` \| `right` | - | |
| destroyInactivePanel | 销毁折叠隐藏的面板 | boolean | false | |
| ghost | 使折叠面板透明且无边框 | boolean | false | 4.4.0 |
### Collapse.Panel

View File

@ -125,6 +125,22 @@
padding-top: 4px;
}
&-ghost {
background-color: transparent;
border: 0;
> .@{collapse-prefix-cls}-item {
border-bottom: 0;
> .@{collapse-prefix-cls}-content {
background-color: transparent;
border-top: 0;
> .@{collapse-prefix-cls}-content-box {
padding-top: 12px;
padding-bottom: 12px;
}
}
}
}
& &-item-disabled > &-header {
&,
& > .arrow {

View File

@ -16901,9 +16901,11 @@ exports[`ConfigProvider components Pagination configProvider 1`] = `
class="config-pagination-prev config-pagination-disabled"
title="Previous Page"
>
<a
<button
class="config-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -16925,7 +16927,7 @@ exports[`ConfigProvider components Pagination configProvider 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="config-pagination-item config-pagination-item-0 config-pagination-disabled config-pagination-item-disabled"
@ -16941,9 +16943,11 @@ exports[`ConfigProvider components Pagination configProvider 1`] = `
class="config-pagination-next config-pagination-disabled"
title="Next Page"
>
<a
<button
class="config-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -16965,7 +16969,7 @@ exports[`ConfigProvider components Pagination configProvider 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="config-pagination-options"
@ -17048,9 +17052,11 @@ exports[`ConfigProvider components Pagination configProvider 1`] = `
class="config-pagination-prev config-pagination-disabled"
title="Previous Page"
>
<a
<button
class="config-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -17072,7 +17078,7 @@ exports[`ConfigProvider components Pagination configProvider 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="config-pagination-item config-pagination-item-0 config-pagination-disabled config-pagination-item-disabled"
@ -17088,9 +17094,11 @@ exports[`ConfigProvider components Pagination configProvider 1`] = `
class="config-pagination-next config-pagination-disabled"
title="Next Page"
>
<a
<button
class="config-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -17112,7 +17120,7 @@ exports[`ConfigProvider components Pagination configProvider 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="config-pagination-options"
@ -17200,9 +17208,11 @@ exports[`ConfigProvider components Pagination configProvider componentSize large
class="config-pagination-prev config-pagination-disabled"
title="Previous Page"
>
<a
<button
class="config-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -17224,7 +17234,7 @@ exports[`ConfigProvider components Pagination configProvider componentSize large
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="config-pagination-item config-pagination-item-0 config-pagination-disabled config-pagination-item-disabled"
@ -17240,9 +17250,11 @@ exports[`ConfigProvider components Pagination configProvider componentSize large
class="config-pagination-next config-pagination-disabled"
title="Next Page"
>
<a
<button
class="config-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -17264,7 +17276,7 @@ exports[`ConfigProvider components Pagination configProvider componentSize large
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="config-pagination-options"
@ -17347,9 +17359,11 @@ exports[`ConfigProvider components Pagination configProvider componentSize large
class="config-pagination-prev config-pagination-disabled"
title="Previous Page"
>
<a
<button
class="config-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -17371,7 +17385,7 @@ exports[`ConfigProvider components Pagination configProvider componentSize large
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="config-pagination-item config-pagination-item-0 config-pagination-disabled config-pagination-item-disabled"
@ -17387,9 +17401,11 @@ exports[`ConfigProvider components Pagination configProvider componentSize large
class="config-pagination-next config-pagination-disabled"
title="Next Page"
>
<a
<button
class="config-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -17411,7 +17427,7 @@ exports[`ConfigProvider components Pagination configProvider componentSize large
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="config-pagination-options"
@ -17499,9 +17515,11 @@ exports[`ConfigProvider components Pagination configProvider componentSize middl
class="config-pagination-prev config-pagination-disabled"
title="Previous Page"
>
<a
<button
class="config-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -17523,7 +17541,7 @@ exports[`ConfigProvider components Pagination configProvider componentSize middl
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="config-pagination-item config-pagination-item-0 config-pagination-disabled config-pagination-item-disabled"
@ -17539,9 +17557,11 @@ exports[`ConfigProvider components Pagination configProvider componentSize middl
class="config-pagination-next config-pagination-disabled"
title="Next Page"
>
<a
<button
class="config-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -17563,7 +17583,7 @@ exports[`ConfigProvider components Pagination configProvider componentSize middl
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="config-pagination-options"
@ -17646,9 +17666,11 @@ exports[`ConfigProvider components Pagination configProvider componentSize middl
class="config-pagination-prev config-pagination-disabled"
title="Previous Page"
>
<a
<button
class="config-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -17670,7 +17692,7 @@ exports[`ConfigProvider components Pagination configProvider componentSize middl
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="config-pagination-item config-pagination-item-0 config-pagination-disabled config-pagination-item-disabled"
@ -17686,9 +17708,11 @@ exports[`ConfigProvider components Pagination configProvider componentSize middl
class="config-pagination-next config-pagination-disabled"
title="Next Page"
>
<a
<button
class="config-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -17710,7 +17734,7 @@ exports[`ConfigProvider components Pagination configProvider componentSize middl
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="config-pagination-options"
@ -17798,9 +17822,11 @@ exports[`ConfigProvider components Pagination configProvider virtual and dropdow
class="ant-pagination-prev ant-pagination-disabled"
title="Previous Page"
>
<a
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -17822,7 +17848,7 @@ exports[`ConfigProvider components Pagination configProvider virtual and dropdow
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-item ant-pagination-item-0 ant-pagination-disabled ant-pagination-item-disabled"
@ -17838,9 +17864,11 @@ exports[`ConfigProvider components Pagination configProvider virtual and dropdow
class="ant-pagination-next ant-pagination-disabled"
title="Next Page"
>
<a
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -17862,7 +17890,7 @@ exports[`ConfigProvider components Pagination configProvider virtual and dropdow
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-options"
@ -17945,9 +17973,11 @@ exports[`ConfigProvider components Pagination configProvider virtual and dropdow
class="ant-pagination-prev ant-pagination-disabled"
title="Previous Page"
>
<a
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -17969,7 +17999,7 @@ exports[`ConfigProvider components Pagination configProvider virtual and dropdow
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-item ant-pagination-item-0 ant-pagination-disabled ant-pagination-item-disabled"
@ -17985,9 +18015,11 @@ exports[`ConfigProvider components Pagination configProvider virtual and dropdow
class="ant-pagination-next ant-pagination-disabled"
title="Next Page"
>
<a
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -18009,7 +18041,7 @@ exports[`ConfigProvider components Pagination configProvider virtual and dropdow
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-options"
@ -18097,9 +18129,11 @@ exports[`ConfigProvider components Pagination normal 1`] = `
class="ant-pagination-prev ant-pagination-disabled"
title="Previous Page"
>
<a
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -18121,7 +18155,7 @@ exports[`ConfigProvider components Pagination normal 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-item ant-pagination-item-0 ant-pagination-disabled ant-pagination-item-disabled"
@ -18137,9 +18171,11 @@ exports[`ConfigProvider components Pagination normal 1`] = `
class="ant-pagination-next ant-pagination-disabled"
title="Next Page"
>
<a
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -18161,7 +18197,7 @@ exports[`ConfigProvider components Pagination normal 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-options"
@ -18244,9 +18280,11 @@ exports[`ConfigProvider components Pagination normal 1`] = `
class="ant-pagination-prev ant-pagination-disabled"
title="Previous Page"
>
<a
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -18268,7 +18306,7 @@ exports[`ConfigProvider components Pagination normal 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-item ant-pagination-item-0 ant-pagination-disabled ant-pagination-item-disabled"
@ -18284,9 +18322,11 @@ exports[`ConfigProvider components Pagination normal 1`] = `
class="ant-pagination-next ant-pagination-disabled"
title="Next Page"
>
<a
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -18308,7 +18348,7 @@ exports[`ConfigProvider components Pagination normal 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-options"
@ -18396,9 +18436,11 @@ exports[`ConfigProvider components Pagination prefixCls 1`] = `
class="prefix-Pagination-prev prefix-Pagination-disabled"
title="Previous Page"
>
<a
<button
class="prefix-Pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -18420,7 +18462,7 @@ exports[`ConfigProvider components Pagination prefixCls 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="prefix-Pagination-item prefix-Pagination-item-0 prefix-Pagination-disabled prefix-Pagination-item-disabled"
@ -18436,9 +18478,11 @@ exports[`ConfigProvider components Pagination prefixCls 1`] = `
class="prefix-Pagination-next prefix-Pagination-disabled"
title="Next Page"
>
<a
<button
class="prefix-Pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -18460,7 +18504,7 @@ exports[`ConfigProvider components Pagination prefixCls 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="prefix-Pagination-options"
@ -18543,9 +18587,11 @@ exports[`ConfigProvider components Pagination prefixCls 1`] = `
class="prefix-Pagination-prev prefix-Pagination-disabled"
title="Previous Page"
>
<a
<button
class="prefix-Pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -18567,7 +18613,7 @@ exports[`ConfigProvider components Pagination prefixCls 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="prefix-Pagination-item prefix-Pagination-item-0 prefix-Pagination-disabled prefix-Pagination-item-disabled"
@ -18583,9 +18629,11 @@ exports[`ConfigProvider components Pagination prefixCls 1`] = `
class="prefix-Pagination-next prefix-Pagination-disabled"
title="Next Page"
>
<a
<button
class="prefix-Pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -18607,7 +18655,7 @@ exports[`ConfigProvider components Pagination prefixCls 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="prefix-Pagination-options"

View File

@ -24,7 +24,9 @@ exports[`MonthPicker and WeekPicker render MonthPicker 1`] = `
tabindex="-1"
type="button"
>
«
<span
class="ant-picker-super-prev-icon"
/>
</button>
<div
class="ant-picker-header-view"
@ -41,7 +43,9 @@ exports[`MonthPicker and WeekPicker render MonthPicker 1`] = `
tabindex="-1"
type="button"
>
»
<span
class="ant-picker-super-next-icon"
/>
</button>
</div>
<div

View File

@ -92,6 +92,7 @@ The following APIs are shared by DatePicker, YearPicker, MonthPicker, RangePicke
| onChange | a callback function, can be executed when the selected time is changing | function(date: moment, dateString: string) | - | |
| onOk | callback when click ok button | function() | - | |
| onPanelChange | Callback function for panel changing | function(value, mode) | - | |
| showNow | Whether to show 'Now' button on panel when `showTime` is set | boolean | - | 4.4.0 |
### YearPicker

View File

@ -94,6 +94,7 @@ import 'moment/locale/zh-cn';
| onChange | 时间发生变化的回调 | function(date: moment, dateString: string) | - | |
| onOk | 点击确定按钮的回调 | function() | - | |
| onPanelChange | 日期面板变化时的回调 | function(value, mode) | - | |
| showNow | 当设定了 `showTime` 的时候,面板是否显示“此刻”按钮 | boolean | - | 4.4.0 |
### YearPicker

View File

@ -125,4 +125,13 @@ describe('Drawer', () => {
);
expect(wrapper2.find('button.forceRender').length).toBe(1);
});
it('support closeIcon', () => {
const wrapper = render(
<Drawer visible closable closeIcon={<span>close</span>} width={400} getContainer={false}>
Here is content of Drawer
</Drawer>,
);
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -82,7 +82,7 @@ describe('Drawer', () => {
expect(wrapper.instance().state.visible).toBe(true);
});
it('destroyOnClose is true onClose', () => {
it('dom should be removed after close when destroyOnClose is true', () => {
const wrapper = mount(<DrawerEventTester destroyOnClose />);
wrapper.find('button.ant-btn').simulate('click');
expect(wrapper.find('.ant-drawer-wrapper-body').exists()).toBe(true);
@ -94,6 +94,18 @@ describe('Drawer', () => {
expect(wrapper.find('.ant-drawer-wrapper-body').exists()).toBe(false);
});
it('dom should be existed after close when destroyOnClose is false', () => {
const wrapper = mount(<DrawerEventTester />);
wrapper.find('button.ant-btn').simulate('click');
expect(wrapper.find('.ant-drawer-wrapper-body').exists()).toBe(true);
wrapper.setState({
visible: false,
});
wrapper.find('.ant-drawer-wrapper-body').simulate('transitionend');
expect(wrapper.find('.ant-drawer-wrapper-body').exists()).toBe(true);
});
it('no mask and no closable', () => {
const wrapper = mount(<DrawerEventTester destroyOnClose />);

View File

@ -558,3 +558,49 @@ exports[`Drawer style/drawerStyle/headerStyle/bodyStyle should work 1`] = `
</div>
</div>
`;
exports[`Drawer support closeIcon 1`] = `
<div
class=""
>
<div
class="ant-drawer ant-drawer-right"
tabindex="-1"
>
<div
class="ant-drawer-mask"
/>
<div
class="ant-drawer-content-wrapper"
style="transform:translateX(100%);-ms-transform:translateX(100%);width:400px"
>
<div
class="ant-drawer-content"
>
<div
class="ant-drawer-wrapper-body"
>
<div
class="ant-drawer-header-no-title"
>
<button
aria-label="Close"
class="ant-drawer-close"
style="--scroll-bar:0px"
>
<span>
close
</span>
</button>
</div>
<div
class="ant-drawer-body"
>
Here is content of Drawer
</div>
</div>
</div>
</div>
</div>
</div>
`;

View File

@ -21,6 +21,7 @@ A Drawer is a panel that is typically overlaid on top of a page and slides in fr
| Props | Description | Type | Default |
| --- | --- | --- | --- |
| closable | Whether a close (x) button is visible on top right of the Drawer dialog or not. | boolean | true |
| closeIcon | custom close icon | ReactNode | `<CloseOutlined />` |
| destroyOnClose | Whether to unmount child components on closing drawer or not. | boolean | false |
| forceRender | Prerender Drawer component forcely | boolean | false |
| getContainer | Return the mounted node for Drawer. | HTMLElement \| `() => HTMLElement` \| Selectors \| false | 'body' |

View File

@ -21,6 +21,7 @@ const PlacementTypes = tuple('top', 'right', 'bottom', 'left');
type placementType = typeof PlacementTypes[number];
export interface DrawerProps {
closable?: boolean;
closeIcon?: React.ReactNode;
destroyOnClose?: boolean;
forceRender?: boolean;
getContainer?: string | HTMLElement | getContainerFunc | false;
@ -195,7 +196,7 @@ class Drawer extends React.Component<DrawerProps & ConfigConsumerProps, IDrawerS
}
renderCloseIcon() {
const { closable, prefixCls, onClose } = this.props;
const { closable, closeIcon = <CloseOutlined />, prefixCls, onClose } = this.props;
return (
closable && (
// eslint-disable-next-line react/button-has-type
@ -209,7 +210,7 @@ class Drawer extends React.Component<DrawerProps & ConfigConsumerProps, IDrawerS
} as any
}
>
<CloseOutlined />
{closeIcon}
</button>
)
);
@ -283,6 +284,7 @@ class Drawer extends React.Component<DrawerProps & ConfigConsumerProps, IDrawerS
'zIndex',
'style',
'closable',
'closeIcon',
'destroyOnClose',
'drawerStyle',
'headerStyle',

View File

@ -20,6 +20,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/7z8NJQhFb/Drawer.svg
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| closable | 是否显示右上角的关闭按钮 | boolean | true |
| closeIcon | 自定义关闭图标 | ReactNode | `<CloseOutlined />` |
| destroyOnClose | 关闭时销毁 Drawer 里的子元素 | boolean | false |
| forceRender | 预渲染 Drawer 内元素 | boolean | false |
| getContainer | 指定 Drawer 挂载的 HTML 节点, false 为挂载在当前 dom | HTMLElement \| `() => HTMLElement` \| Selectors \| false | 'body' |

View File

@ -1,5 +1,59 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders ./components/dropdown/demo/arrow.md correctly 1`] = `
Array [
<button
class="ant-btn ant-dropdown-trigger"
type="button"
>
<span>
bottomLeft
</span>
</button>,
<button
class="ant-btn ant-dropdown-trigger"
type="button"
>
<span>
bottomCenter
</span>
</button>,
<button
class="ant-btn ant-dropdown-trigger"
type="button"
>
<span>
bottomRight
</span>
</button>,
<br />,
<button
class="ant-btn ant-dropdown-trigger"
type="button"
>
<span>
topLeft
</span>
</button>,
<button
class="ant-btn ant-dropdown-trigger"
type="button"
>
<span>
topCenter
</span>
</button>,
<button
class="ant-btn ant-dropdown-trigger"
type="button"
>
<span>
topRight
</span>
</button>,
]
`;
exports[`renders ./components/dropdown/demo/basic.md correctly 1`] = `
<a
class="ant-dropdown-link ant-dropdown-trigger"

View File

@ -0,0 +1,75 @@
---
order: 2
title:
zh-CN: 箭头
en-US: Arrow
---
## zh-CN
可以展示一个箭头。
## en-US
You could display an arrow.
```jsx
import { Menu, Dropdown, Button } from 'antd';
const menu = (
<Menu>
<Menu.Item>
<a target="_blank" rel="noopener noreferrer" href="http://www.alipay.com/">
1st menu item
</a>
</Menu.Item>
<Menu.Item>
<a target="_blank" rel="noopener noreferrer" href="http://www.taobao.com/">
2nd menu item
</a>
</Menu.Item>
<Menu.Item>
<a target="_blank" rel="noopener noreferrer" href="http://www.tmall.com/">
3rd menu item
</a>
</Menu.Item>
</Menu>
);
ReactDOM.render(
<>
<Dropdown overlay={menu} placement="bottomLeft" arrow>
<Button>bottomLeft</Button>
</Dropdown>
<Dropdown overlay={menu} placement="bottomCenter" arrow>
<Button>bottomCenter</Button>
</Dropdown>
<Dropdown overlay={menu} placement="bottomRight" arrow>
<Button>bottomRight</Button>
</Dropdown>
<br />
<Dropdown overlay={menu} placement="topLeft" arrow>
<Button>topLeft</Button>
</Dropdown>
<Dropdown overlay={menu} placement="topCenter" arrow>
<Button>topCenter</Button>
</Dropdown>
<Dropdown overlay={menu} placement="topRight" arrow>
<Button>topRight</Button>
</Dropdown>
</>,
mountNode,
);
```
```css
#components-dropdown-demo-arrow .ant-btn {
margin-right: 8px;
margin-bottom: 8px;
}
.ant-row-rtl #components-dropdown-demo-arrow .ant-btn {
margin-right: 0;
margin-bottom: 8px;
margin-left: 8px;
}
```

View File

@ -35,6 +35,7 @@ type Align = {
};
export interface DropDownProps {
arrow?: boolean;
trigger?: ('click' | 'hover' | 'contextMenu')[];
overlay: React.ReactElement | OverlayFunc;
onVisibleChange?: (visible: boolean) => void;
@ -130,6 +131,7 @@ const Dropdown: DropdownInterface = props => {
};
const {
arrow,
prefixCls: customizePrefixCls,
children,
trigger,
@ -160,6 +162,7 @@ const Dropdown: DropdownInterface = props => {
return (
<RcDropdown
arrow={arrow}
alignPoint={alignPoint}
{...props}
overlayClassName={overlayClassNameCustomized}

View File

@ -17,7 +17,8 @@ When there are more than a few options to choose from, you can wrap them in a `D
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| disabled | Whether the dropdown menu is disabled | boolean | false | |
| arrow | Whether the dropdown arrow should be visible | boolean | false | |
| disabled | Whether the dropdown menu is disabled | boolean | - | |
| getPopupContainer | To set the container of the dropdown menu. The default is to create a `div` element in `body`, but you can reset it to the scrolling area and make a relative reposition. [Example on CodePen](https://codepen.io/afc163/pen/zEjNOy?editors=0010). | Function(triggerNode) | `() => document.body` | |
| overlay | The dropdown menu | [Menu](/components/menu) \| () => Menu | - | |
| overlayClassName | Class name of the dropdown root element | string | - | |

View File

@ -21,7 +21,8 @@ cover: https://gw.alipayobjects.com/zos/alicdn/eedWN59yJ/Dropdown.svg
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| disabled | 菜单是否禁用 | boolean | false | |
| arrow | 下拉框箭头是否显示 | boolean | false | |
| disabled | 菜单是否禁用 | boolean | - | |
| getPopupContainer | 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。[示例](https://codepen.io/afc163/pen/zEjNOy?editors=0010) | Function(triggerNode) | `() => document.body` | |
| overlay | 菜单 | [Menu](/components/menu) \| () => Menu | - | |
| overlayClassName | 下拉根元素的类名称 | string | - | |

View File

@ -47,6 +47,76 @@
display: none;
}
// Offset the popover to account for the dropdown arrow
&-show-arrow&-placement-topCenter,
&-show-arrow&-placement-topLeft,
&-show-arrow&-placement-topRight {
padding-bottom: @popover-distance;
}
&-show-arrow&-placement-bottomCenter,
&-show-arrow&-placement-bottomLeft,
&-show-arrow&-placement-bottomRight {
padding-top: @popover-distance;
}
// Arrows
// .popover-arrow is outer, .popover-arrow:after is inner
&-arrow {
position: absolute;
z-index: 1; // lift it up so the menu wouldn't cask shadow on it
display: block;
width: sqrt(@popover-arrow-width * @popover-arrow-width * 2);
height: sqrt(@popover-arrow-width * @popover-arrow-width * 2);
background: transparent;
border-style: solid;
border-width: sqrt(@popover-arrow-width * @popover-arrow-width * 2) / 2;
transform: rotate(45deg);
}
&-placement-topCenter > &-arrow,
&-placement-topLeft > &-arrow,
&-placement-topRight > &-arrow {
bottom: @popover-distance - @popover-arrow-width + 2.2px;
border-top-color: transparent;
border-right-color: @popover-bg;
border-bottom-color: @popover-bg;
border-left-color: transparent;
box-shadow: 3px 3px 7px fade(@black, 7%);
}
&-placement-topCenter > &-arrow {
left: 50%;
transform: translateX(-50%) rotate(45deg);
}
&-placement-topLeft > &-arrow {
left: 16px;
}
&-placement-topRight > &-arrow {
right: 16px;
}
&-placement-bottomCenter > &-arrow,
&-placement-bottomLeft > &-arrow,
&-placement-bottomRight > &-arrow {
top: @popover-distance - @popover-arrow-width + 2px;
border-top-color: @popover-bg;
border-right-color: transparent;
border-bottom-color: transparent;
border-left-color: @popover-bg;
box-shadow: -2px -2px 5px fade(@black, 6%);
}
&-placement-bottomCenter > &-arrow {
left: 50%;
transform: translateX(-50%) rotate(45deg);
}
&-placement-bottomLeft > &-arrow {
left: 16px;
}
&-placement-bottomRight > &-arrow {
right: 16px;
}
&-menu {
position: relative;
margin: 0;

View File

@ -1,5 +1,4 @@
import * as React from 'react';
import omit from 'omit.js';
import classNames from 'classnames';
import FieldForm, { List } from 'rc-field-form';
import { FormProps as RcFormProps } from 'rc-field-form/lib/Form';
@ -8,7 +7,7 @@ import { ColProps } from '../grid/col';
import { ConfigContext, ConfigConsumerProps } from '../config-provider';
import { FormContext } from './context';
import { FormLabelAlign } from './interface';
import { useForm, FormInstance } from './util';
import useForm, { FormInstance } from './hooks/useForm';
import SizeContext, { SizeType, SizeContextProvider } from '../config-provider/SizeContext';
export type FormLayout = 'horizontal' | 'inline' | 'vertical';
@ -31,21 +30,24 @@ const InternalForm: React.ForwardRefRenderFunction<unknown, FormProps> = (props,
const contextSize = React.useContext(SizeContext);
const { getPrefixCls, direction }: ConfigConsumerProps = React.useContext(ConfigContext);
const { name } = props;
const {
prefixCls: customizePrefixCls,
className = '',
size = contextSize,
form,
colon,
name,
labelAlign,
labelCol,
wrapperCol,
prefixCls: customizePrefixCls,
hideRequiredMark,
className = '',
layout = 'horizontal',
size = contextSize,
scrollToFirstError,
onFinishFailed,
...restFormProps
} = props;
const prefixCls = getPrefixCls('form', customizePrefixCls);
const formClassName = classNames(
@ -59,20 +61,9 @@ const InternalForm: React.ForwardRefRenderFunction<unknown, FormProps> = (props,
className,
);
const formProps = omit(props, [
'prefixCls',
'className',
'layout',
'hideRequiredMark',
'wrapperCol',
'labelAlign',
'labelCol',
'colon',
'scrollToFirstError',
]);
const [wrapForm] = useForm(form);
wrapForm.__INTERNAL__.name = name;
const { __INTERNAL__ } = wrapForm;
__INTERNAL__.name = name;
const formContextValue = React.useMemo(
() => ({
@ -82,6 +73,7 @@ const InternalForm: React.ForwardRefRenderFunction<unknown, FormProps> = (props,
wrapperCol,
vertical: layout === 'vertical',
colon,
itemRef: __INTERNAL__.itemRef,
}),
[name, labelAlign, labelCol, wrapperCol, layout, colon],
);
@ -100,12 +92,10 @@ const InternalForm: React.ForwardRefRenderFunction<unknown, FormProps> = (props,
return (
<SizeContextProvider size={size}>
<FormContext.Provider
value={formContextValue}
>
<FormContext.Provider value={formContextValue}>
<FieldForm
id={name}
{...formProps}
{...restFormProps}
onFinishFailed={onInternalFinishFailed}
form={wrapForm}
className={formClassName}

View File

@ -5,6 +5,7 @@ import { Field, FormInstance } from 'rc-field-form';
import { FieldProps } from 'rc-field-form/lib/Field';
import FieldContext from 'rc-field-form/lib/FieldContext';
import { Meta, NamePath } from 'rc-field-form/lib/interface';
import { supportRef } from 'rc-util/lib/ref';
import omit from 'omit.js';
import Row from '../grid/row';
import { ConfigContext } from '../config-provider';
@ -13,8 +14,10 @@ import devWarning from '../_util/devWarning';
import FormItemLabel, { FormItemLabelProps } from './FormItemLabel';
import FormItemInput, { FormItemInputProps } from './FormItemInput';
import { FormContext, FormItemContext } from './context';
import { toArray, getFieldId, useFrameState } from './util';
import { toArray, getFieldId } from './util';
import { cloneElement, isValidElement } from '../_util/reactNode';
import useFrameState from './hooks/useFrameState';
import useItemRef from './hooks/useItemRef';
const ValidateStatuses = tuple('success', 'warning', 'error', 'validating', '');
export type ValidateStatus = typeof ValidateStatuses[number];
@ -46,6 +49,7 @@ export interface FormItemProps extends FormItemLabelProps, FormItemInputProps, R
hasFeedback?: boolean;
validateStatus?: ValidateStatus;
required?: boolean;
hidden?: boolean;
/** Auto passed by List render props. User should not use this. */
fieldKey?: React.Key | React.Key[];
@ -77,11 +81,12 @@ function FormItem(props: FormItemProps): React.ReactElement {
label,
trigger = 'onChange',
validateTrigger,
hidden,
...restProps
} = props;
const destroyRef = React.useRef(false);
const { getPrefixCls } = React.useContext(ConfigContext);
const formContext = React.useContext(FormContext);
const { name: formName } = React.useContext(FormContext);
const { updateItemErrors } = React.useContext(FormItemContext);
const [domErrorVisible, innerSetDomErrorVisible] = React.useState(!!help);
const prevValidateStatusRef = React.useRef<ValidateStatus | undefined>(validateStatus);
@ -97,7 +102,6 @@ function FormItem(props: FormItemProps): React.ReactElement {
}
}
const { name: formName } = formContext;
const hasName = hasValidName(name);
// Cache Field NamePath
@ -126,6 +130,9 @@ function FormItem(props: FormItemProps): React.ReactElement {
}
};
// ===================== Children Ref =====================
const getItemRef = useItemRef();
function renderLayout(
baseChildren: React.ReactNode,
fieldId?: string,
@ -179,6 +186,7 @@ function FormItem(props: FormItemProps): React.ReactElement {
[`${prefixCls}-item-has-error-leave`]:
!help && domErrorVisible && prevValidateStatusRef.current === 'error',
[`${prefixCls}-item-is-validating`]: mergedValidateStatus === 'validating',
[`${prefixCls}-hidden`]: hidden,
};
// ======================= Children =======================
@ -323,6 +331,10 @@ function FormItem(props: FormItemProps): React.ReactElement {
childProps.id = fieldId;
}
if (supportRef(children)) {
childProps.ref = getItemRef(mergedName, children);
}
// We should keep user origin event handler
const triggers = new Set<string>([
...toArray(trigger),

View File

@ -10,7 +10,7 @@ import CSSMotion from 'rc-animate/lib/CSSMotion';
import Col, { ColProps } from '../grid/col';
import { ValidateStatus } from './FormItem';
import { FormContext } from './context';
import { useCacheErrors } from './util';
import useCacheErrors from './hooks/useCacheErrors';
interface FormItemInputMiscProps {
prefixCls: string;

View File

@ -2196,6 +2196,84 @@ exports[`renders ./components/form/demo/normal-login.md correctly 1`] = `
</form>
`;
exports[`renders ./components/form/demo/ref-item.md correctly 1`] = `
<form
class="ant-form ant-form-horizontal"
>
<div
class="ant-row ant-form-item"
>
<div
class="ant-col ant-form-item-label"
>
<label
class=""
for="test"
title="test"
>
test
</label>
</div>
<div
class="ant-col ant-form-item-control"
>
<div
class="ant-form-item-control-input"
>
<div
class="ant-form-item-control-input-content"
>
<input
class="ant-input"
id="test"
type="text"
value=""
/>
</div>
</div>
</div>
</div>
<div
class="ant-row ant-form-item"
>
<div
class="ant-col ant-form-item-control"
>
<div
class="ant-form-item-control-input"
>
<div
class="ant-form-item-control-input-content"
>
<input
class="ant-input"
id="list_0"
type="text"
value="light"
/>
</div>
</div>
</div>
</div>
<button
class="ant-btn ant-btn-button"
type="button"
>
<span>
Focus Form.Item
</span>
</button>
<button
class="ant-btn"
type="button"
>
<span>
Focus Form.List
</span>
</button>
</form>
`;
exports[`renders ./components/form/demo/register.md correctly 1`] = `
<form
class="ant-form ant-form-horizontal"

View File

@ -1,5 +1,34 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Form Form item hidden 1`] = `
<form
class="ant-form ant-form-horizontal"
>
<div
class="ant-row ant-form-item ant-form-hidden"
>
<div
class="ant-col ant-form-item-control"
>
<div
class="ant-form-item-control-input"
>
<div
class="ant-form-item-control-input-content"
>
<input
class="ant-input"
id="light"
type="text"
value=""
/>
</div>
</div>
</div>
</div>
</form>
`;
exports[`Form Form.Item should support data-*、aria-* and custom attribute 1`] = `
<form
class="ant-form ant-form-horizontal"

View File

@ -597,4 +597,15 @@ describe('Form', () => {
expect(wrapper.find('input').prop('onBlur')).toBeTruthy();
});
it('Form item hidden', () => {
const wrapper = mount(
<Form>
<Form.Item name="light" hidden>
<Input />
</Form.Item>
</Form>,
);
expect(wrapper).toMatchRenderedSnapshot();
});
});

View File

@ -0,0 +1,91 @@
/* eslint-disable react/jsx-key */
import React from 'react';
import { mount } from 'enzyme';
import Form from '..';
import Input from '../../input';
import Button from '../../button';
describe('Form.Ref', () => {
const Test = ({
onRef,
show,
}: {
onRef: (node: React.ReactElement, originRef: React.RefObject<any>) => void;
show?: boolean;
}) => {
const [form] = Form.useForm();
const removeRef = React.useRef<any>();
const testRef = React.useRef<any>();
const listRef = React.useRef<any>();
return (
<Form form={form} initialValues={{ list: ['light'] }}>
{show && (
<Form.Item name="remove" label="remove">
<Input ref={removeRef} />
</Form.Item>
)}
<Form.Item name="test" label="test">
<Input ref={testRef} />
</Form.Item>
<Form.List name="list">
{fields =>
fields.map(field => (
<Form.Item {...field}>
<Input ref={listRef} />
</Form.Item>
))
}
</Form.List>
<Button
className="ref-item"
onClick={() => {
onRef(form.getFieldInstance('test'), testRef.current);
}}
>
Form.Item
</Button>
<Button
className="ref-list"
onClick={() => {
onRef(form.getFieldInstance(['list', 0]), listRef.current);
}}
>
Form.List
</Button>
<Button
className="ref-remove"
onClick={() => {
onRef(form.getFieldInstance('remove'), removeRef.current);
}}
>
Removed
</Button>
</Form>
);
};
it('should ref work', () => {
const onRef = jest.fn();
const wrapper = mount(<Test onRef={onRef} show />);
wrapper.find('.ref-item').last().simulate('click');
expect(onRef).toHaveBeenCalled();
expect(onRef.mock.calls[0][0]).toBe(onRef.mock.calls[0][1]);
onRef.mockReset();
wrapper.find('.ref-list').last().simulate('click');
expect(onRef).toHaveBeenCalled();
expect(onRef.mock.calls[0][0]).toBe(onRef.mock.calls[0][1]);
onRef.mockReset();
wrapper.setProps({ show: false });
wrapper.update();
wrapper.find('.ref-remove').last().simulate('click');
expect(onRef).toHaveBeenCalledWith(undefined, null);
});
});

View File

@ -16,11 +16,13 @@ export interface FormContextProps {
labelAlign?: FormLabelAlign;
labelCol?: ColProps;
wrapperCol?: ColProps;
itemRef: (name: (string | number)[]) => (node: React.ReactElement) => void;
}
export const FormContext = React.createContext<FormContextProps>({
labelAlign: 'right',
vertical: false,
itemRef: (() => {}) as any,
});
/**

View File

@ -0,0 +1,61 @@
---
order: 999999
title:
zh-CN: 引用字段
en-US: Ref item
debug: true
---
## zh-CN
请优先使用 `ref`
## en-US
Use `ref` first!
```jsx
import React from 'react';
import { Button, Form, Input } from 'antd';
const Demo = () => {
const [form] = Form.useForm();
const ref = React.useRef();
return (
<Form form={form} initialValues={{ list: ['light'] }}>
<Form.Item name="test" label="test">
<Input ref={ref} />
</Form.Item>
<Form.List name="list">
{fields =>
fields.map(field => (
<Form.Item key={field.key} {...field}>
<Input ref={ref} />
</Form.Item>
))
}
</Form.List>
<Button
type="button"
onClick={() => {
form.getFieldInstance('test').focus();
}}
>
Focus Form.Item
</Button>
<Button
onClick={() => {
form.getFieldInstance(['list', 0]).focus();
}}
>
Focus Form.List
</Button>
</Form>
);
};
ReactDOM.render(<Demo />, mountNode);
```

View File

@ -0,0 +1,48 @@
import * as React from 'react';
/**
* Always debounce error to avoid [error -> null -> error] blink
*/
export default function useCacheErrors(
errors: React.ReactNode[],
changeTrigger: (visible: boolean) => void,
directly: boolean,
): [boolean, React.ReactNode[]] {
const cacheRef = React.useRef({
errors,
visible: !!errors.length,
});
const [, forceUpdate] = React.useState({});
const update = () => {
const prevVisible = cacheRef.current.visible;
const newVisible = !!errors.length;
const prevErrors = cacheRef.current.errors;
cacheRef.current.errors = errors;
cacheRef.current.visible = newVisible;
if (prevVisible !== newVisible) {
changeTrigger(newVisible);
} else if (
prevErrors.length !== errors.length ||
prevErrors.some((prevErr, index) => prevErr !== errors[index])
) {
forceUpdate({});
}
};
React.useEffect(() => {
if (!directly) {
const timeout = setTimeout(update, 10);
return () => clearTimeout(timeout);
}
}, [errors]);
if (directly) {
update();
}
return [cacheRef.current.visible, cacheRef.current.errors];
}

View File

@ -0,0 +1,64 @@
import { useRef, useMemo } from 'react';
import { useForm as useRcForm, FormInstance as RcFormInstance } from 'rc-field-form';
import scrollIntoView from 'scroll-into-view-if-needed';
import { ScrollOptions, NamePath, InternalNamePath } from '../interface';
import { toArray, getFieldId } from '../util';
export interface FormInstance extends RcFormInstance {
scrollToField: (name: NamePath, options?: ScrollOptions) => void;
/** This is an internal usage. Do not use in your prod */
__INTERNAL__: {
/** No! Do not use this in your code! */
name?: string;
/** No! Do not use this in your code! */
itemRef: (name: InternalNamePath) => (node: React.ReactElement) => void;
};
getFieldInstance: (name: NamePath) => any;
}
function toNamePathStr(name: NamePath) {
const namePath = toArray(name);
return namePath.join('_');
}
export default function useForm(form?: FormInstance): [FormInstance] {
const [rcForm] = useRcForm();
const itemsRef = useRef<Record<string, React.ReactElement>>({});
const wrapForm: FormInstance = useMemo(
() =>
form || {
...rcForm,
__INTERNAL__: {
itemRef: (name: InternalNamePath) => (node: React.ReactElement) => {
const namePathStr = toNamePathStr(name);
if (node) {
itemsRef.current[namePathStr] = node;
} else {
delete itemsRef.current[namePathStr];
}
},
},
scrollToField: (name: string, options: ScrollOptions = {}) => {
const namePath = toArray(name);
const fieldId = getFieldId(namePath, wrapForm.__INTERNAL__.name);
const node: HTMLElement | null = fieldId ? document.getElementById(fieldId) : null;
if (node) {
scrollIntoView(node, {
scrollMode: 'if-needed',
block: 'nearest',
...options,
});
}
},
getFieldInstance: (name: string) => {
const namePathStr = toNamePathStr(name);
return itemsRef.current[namePathStr];
},
},
[form, rcForm],
);
return [wrapForm];
}

View File

@ -0,0 +1,48 @@
import * as React from 'react';
import { useRef } from 'react';
import raf from 'raf';
type Updater<ValueType> = (prev?: ValueType) => ValueType;
export default function useFrameState<ValueType>(
defaultValue: ValueType,
): [ValueType, (updater: Updater<ValueType>) => void] {
const [value, setValue] = React.useState(defaultValue);
const frameRef = useRef<number | null>(null);
const batchRef = useRef<Updater<ValueType>[]>([]);
const destroyRef = useRef(false);
React.useEffect(
() => () => {
destroyRef.current = true;
raf.cancel(frameRef.current!);
},
[],
);
function setFrameValue(updater: Updater<ValueType>) {
if (destroyRef.current) {
return;
}
if (frameRef.current === null) {
batchRef.current = [];
frameRef.current = raf(() => {
frameRef.current = null;
setValue(prevValue => {
let current = prevValue;
batchRef.current.forEach(func => {
current = func(current);
});
return current;
});
});
}
batchRef.current.push(updater);
}
return [value, setFrameValue];
}

View File

@ -0,0 +1,28 @@
import * as React from 'react';
import { composeRef } from 'rc-util/lib/ref';
import { FormContext } from '../context';
import { InternalNamePath } from '../interface';
export default function useItemRef() {
const { itemRef } = React.useContext(FormContext);
const cacheRef = React.useRef<{
name?: string;
originRef?: React.Ref<any>;
ref?: React.Ref<any>;
}>({});
function getRef(name: InternalNamePath, children: any) {
const childrenRef: React.Ref<React.ReactElement> =
children && typeof children === 'object' && children.ref;
const nameStr = name.join('_');
if (cacheRef.current.name !== nameStr || cacheRef.current.originRef !== childrenRef) {
cacheRef.current.name = nameStr;
cacheRef.current.originRef = childrenRef;
cacheRef.current.ref = composeRef(itemRef(name), childrenRef);
}
return cacheRef.current.ref;
}
return getRef;
}

View File

@ -29,6 +29,7 @@ High performance Form component with data scope management. Including data colle
| labelCol | label layout, like `<Col>` component. Set `span` `offset` value like `{span: 3, offset: 12}` or `sm: {span: 3, offset: 12}` | [object](/components/grid/#Col) | - | |
| layout | Form layout | `horizontal` \| `vertical` \| `inline` | `horizontal` | |
| name | Form name. Will be the prefix of Field `id` | string | - | |
| preserve | Keep field value even when field removed | boolean | true | 4.4.0 |
| scrollToFirstError | Auto scroll to first failed field when submit | boolean | false | |
| size | Set field component size (antd components only) | `small` \| `middle` \| `large` | - | |
| validateMessages | Validation prompt template, description [see below](#validateMessages) | [ValidateMessages](https://github.com/react-component/field-form/blob/master/src/utils/messages.ts) | - | |
@ -86,6 +87,7 @@ Form field component for data bidirectional binding, validation, layout, and so
| labelCol | The layout of label. You can set `span` `offset` to something like `{span: 3, offset: 12}` or `sm: {span: 3, offset: 12}` same as with `<Col>`. You can set `labelCol` on Form. If both exists, use Item first | [object](/components/grid/#Col) | - | |
| name | Field name, support array | [NamePath](#NamePath) | - | |
| normalize | Normalize value from component value before passing to Form instance | (value, prevValue, prevValues) => any | - | |
| preserve | Keep field value even when field removed | boolean | true | 4.4.0 |
| required | Display required style. It will be generated by the validation rule | boolean | false | |
| rules | Rules for field validation. Click [here](#components-form-demo-basic) to see an example | [Rule](#Rule)[] | - | |
| shouldUpdate | Custom field update logic. See [below](#shouldUpdate) | boolean \| (prevValue, curValue) => boolean | false | |
@ -95,6 +97,7 @@ Form field component for data bidirectional binding, validation, layout, and so
| validateTrigger | When to validate the value of children node | string \| string[] | `onChange` | |
| valuePropName | Props of children node, for example, the prop of Switch is 'checked'. This prop is an encapsulation of `getValueProps`, which will be invalid after customizing `getValueProps` | string | `value` | |
| wrapperCol | The layout for input controls, same as `labelCol`. You can set `wrapperCol` on Form. If both exists, use Item first | [object](/components/grid/#Col) | - | |
| hidden | whether to hide Form.Item (still collect and validate value) | boolean | false | |
After wrapped by `Form.Item` with `name` property, `value`(or other property defined by `valuePropName`) `onChange`(or other property defined by `trigger`) props will be added to form controls, the flow of form data will be handled by Form which will cause:
@ -185,21 +188,22 @@ Provide linkage between forms. If a sub form with `name` prop update, it will au
### FormInstance
| Name | Description | Type |
| --- | --- | --- |
| getFieldValue | Get the value by the field name | (name: [NamePath](#NamePath)) => any |
| getFieldsValue | Get values by a set of field names. Return according to the corresponding structure | (nameList?: [NamePath](#NamePath)[], filterFunc?: (meta: { touched: boolean, validating: boolean }) => boolean) => any |
| getFieldError | Get the error messages by the field name | (name: [NamePath](#NamePath)) => string[] |
| getFieldsError | Get the error messages by the fields name. Return as an array | (nameList?: [NamePath](#NamePath)[]) => FieldError[] |
| isFieldTouched | Check if a field has been operated | (name: [NamePath](#NamePath)) => boolean |
| isFieldsTouched | Check if fields have been operated. Check if all fields is touched when `allTouched` is `true` | (nameList?: [NamePath](#NamePath)[], allTouched?: boolean) => boolean |
| isFieldValidating | Check fields if is in validating | (name: [NamePath](#NamePath)) => boolean |
| resetFields | Reset fields to `initialValues` | (fields?: [NamePath](#NamePath)[]) => void |
| scrollToField | Scroll to field position | (name: [NamePath](#NamePath), options: [[ScrollOptions](https://github.com/stipsan/scroll-into-view-if-needed/tree/ece40bd9143f48caf4b99503425ecb16b0ad8249#options)]) => void |
| setFields | Set fields status | (fields: [FieldData](#FieldData)[]) => void |
| setFieldsValue | Set fields value | (values) => void |
| submit | Submit the form. It's same as click `submit` button | () => void |
| validateFields | Validate fields | (nameList?: [NamePath](#NamePath)[]) => Promise |
| Name | Description | Type | Version |
| --- | --- | --- | --- |
| getFieldInstance | Get field instance | (name: [NamePath](#NamePath)) => any | 4.4.0 |
| getFieldValue | Get the value by the field name | (name: [NamePath](#NamePath)) => any | |
| getFieldsValue | Get values by a set of field names. Return according to the corresponding structure | (nameList?: [NamePath](#NamePath)[], filterFunc?: (meta: { touched: boolean, validating: boolean }) => boolean) => any | |
| getFieldError | Get the error messages by the field name | (name: [NamePath](#NamePath)) => string[] | |
| getFieldsError | Get the error messages by the fields name. Return as an array | (nameList?: [NamePath](#NamePath)[]) => FieldError[] | |
| isFieldTouched | Check if a field has been operated | (name: [NamePath](#NamePath)) => boolean | |
| isFieldsTouched | Check if fields have been operated. Check if all fields is touched when `allTouched` is `true` | (nameList?: [NamePath](#NamePath)[], allTouched?: boolean) => boolean | |
| isFieldValidating | Check fields if is in validating | (name: [NamePath](#NamePath)) => boolean | |
| resetFields | Reset fields to `initialValues` | (fields?: [NamePath](#NamePath)[]) => void | |
| scrollToField | Scroll to field position | (name: [NamePath](#NamePath), options: [[ScrollOptions](https://github.com/stipsan/scroll-into-view-if-needed/tree/ece40bd9143f48caf4b99503425ecb16b0ad8249#options)]) => void | |
| setFields | Set fields status | (fields: [FieldData](#FieldData)[]) => void | |
| setFieldsValue | Set fields value | (values) => void | |
| submit | Submit the form. It's same as click `submit` button | () => void | |
| validateFields | Validate fields | (nameList?: [NamePath](#NamePath)[]) => Promise | |
#### validateFields return sample

View File

@ -30,6 +30,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/ORmcdeaoO/Form.svg
| labelCol | label 标签布局,同 `<Col>` 组件,设置 `span` `offset` 值,如 `{span: 3, offset: 12}``sm: {span: 3, offset: 12}` | [object](/components/grid/#Col) | - | |
| layout | 表单布局 | `horizontal` \| `vertical` \| `inline` | `horizontal` | |
| name | 表单名称,会作为表单字段 `id` 前缀使用 | string | - | |
| preserve | 当字段被删除时保留字段值 | boolean | true | 4.4.0 |
| scrollToFirstError | 提交失败自动滚动到第一个错误字段 | boolean | false | |
| size | 设置字段组件的尺寸(仅限 antd 组件) | `small` \| `middle` \| `large` | - | |
| validateMessages | 验证提示模板,说明[见下](#validateMessages) | [ValidateMessages](https://github.com/react-component/field-form/blob/master/src/utils/messages.ts) | - | |
@ -86,6 +87,7 @@ const validateMessages = {
| labelAlign | 标签文本对齐方式 | `left` \| `right` | `right` | |
| labelCol | `label` 标签布局,同 `<Col>` 组件,设置 `span` `offset` 值,如 `{span: 3, offset: 12}``sm: {span: 3, offset: 12}`。你可以通过 Form 的 `labelCol` 进行统一设置。当和 Form 同时设置时,以 Item 为准 | [object](/components/grid/#Col) | - | |
| name | 字段名,支持数组 | [NamePath](#NamePath) | - | |
| preserve | 当字段被删除时保留字段值 | boolean | true | 4.4.0 |
| normalize | 组件获取值后进行转换,再放入 Form 中 | (value, prevValue, prevValues) => any | - | |
| required | 必填样式设置。如不设置,则会根据校验规则自动生成 | boolean | false | |
| rules | 校验规则,设置字段的校验逻辑。点击[此处](#components-form-demo-basic)查看示例 | [Rule](#Rule)[] | - | |
@ -96,6 +98,7 @@ const validateMessages = {
| validateTrigger | 设置字段校验的时机 | string \| string[] | `onChange` | |
| valuePropName | 子节点的值的属性,如 Switch 的是 'checked'。该属性为 `getValueProps` 的封装,自定义 `getValueProps` 后会失效 | string | `value` | |
| wrapperCol | 需要为输入控件设置布局样式时,使用该属性,用法同 `labelCol`。你可以通过 Form 的 `wrapperCol` 进行统一设置。当和 Form 同时设置时,以 Item 为准。 | [object](/components/grid/#Col) | - | |
| hidden | 是否隐藏字段(依然会收集和校验字段) | boolean | false | |
被设置了 `name` 属性的 `Form.Item` 包装的控件,表单控件会自动添加 `value`(或 `valuePropName` 指定的其他属性) `onChange`(或 `trigger` 指定的其他属性),数据同步将被 Form 接管,这会导致以下结果:
@ -186,21 +189,22 @@ Form 通过增量更新方式,只更新被修改的字段相关组件以达到
### FormInstance
| 名称 | 说明 | 类型 |
| --- | --- | --- |
| getFieldValue | 获取对应字段名的值 | (name: [NamePath](#NamePath)) => any |
| getFieldsValue | 获取一组字段名对应的值,会按照对应结构返回 | (nameList?: [NamePath](#NamePath)[], filterFunc?: (meta: { touched: boolean, validating: boolean }) => boolean) => any |
| getFieldError | 获取对应字段名的错误信息 | (name: [NamePath](#NamePath)) => string[] |
| getFieldsError | 获取一组字段名对应的错误信息,返回为数组形式 | (nameList?: [NamePath](#NamePath)[]) => FieldError[] |
| isFieldTouched | 检查对应字段是否被用户操作过 | (name: [NamePath](#NamePath)) => boolean |
| isFieldsTouched | 检查一组字段是否被用户操作过,`allTouched` 为 `true` 时检查是否所有字段都被操作过 | (nameList?: [NamePath](#NamePath)[], allTouched?: boolean) => boolean |
| isFieldValidating | 检查一组字段是否正在校验 | (name: [NamePath](#NamePath)) => boolean |
| resetFields | 重置一组字段到 `initialValues` | (fields?: [NamePath](#NamePath)[]) => void |
| scrollToField | 滚动到对应字段位置 | (name: [NamePath](#NamePath), options: [[ScrollOptions](https://github.com/stipsan/scroll-into-view-if-needed/tree/ece40bd9143f48caf4b99503425ecb16b0ad8249#options)]) => void |
| setFields | 设置一组字段状态 | (fields: [FieldData](#FieldData)[]) => void |
| setFieldsValue | 设置表单的值 | (values) => void |
| submit | 提交表单,与点击 `submit` 按钮效果相同 | () => void |
| validateFields | 触发表单验证 | (nameList?: [NamePath](#NamePath)[]) => Promise |
| 名称 | 说明 | 类型 | 版本 |
| --- | --- | --- | --- |
| getFieldInstance | 获取对应字段示例 | (name: [NamePath](#NamePath)) => any | 4.4.0 |
| getFieldValue | 获取对应字段名的值 | (name: [NamePath](#NamePath)) => any | |
| getFieldsValue | 获取一组字段名对应的值,会按照对应结构返回 | (nameList?: [NamePath](#NamePath)[], filterFunc?: (meta: { touched: boolean, validating: boolean }) => boolean) => any | |
| getFieldError | 获取对应字段名的错误信息 | (name: [NamePath](#NamePath)) => string[] | |
| getFieldsError | 获取一组字段名对应的错误信息,返回为数组形式 | (nameList?: [NamePath](#NamePath)[]) => FieldError[] | |
| isFieldTouched | 检查对应字段是否被用户操作过 | (name: [NamePath](#NamePath)) => boolean | |
| isFieldsTouched | 检查一组字段是否被用户操作过,`allTouched` 为 `true` 时检查是否所有字段都被操作过 | (nameList?: [NamePath](#NamePath)[], allTouched?: boolean) => boolean | |
| isFieldValidating | 检查一组字段是否正在校验 | (name: [NamePath](#NamePath)) => boolean | |
| resetFields | 重置一组字段到 `initialValues` | (fields?: [NamePath](#NamePath)[]) => void | |
| scrollToField | 滚动到对应字段位置 | (name: [NamePath](#NamePath), options: [[ScrollOptions](https://github.com/stipsan/scroll-into-view-if-needed/tree/ece40bd9143f48caf4b99503425ecb16b0ad8249#options)]) => void | |
| setFields | 设置一组字段状态 | (fields: [FieldData](#FieldData)[]) => void | |
| setFieldsValue | 设置表单的值 | (values) => void | |
| submit | 提交表单,与点击 `submit` 按钮效果相同 | () => void | |
| validateFields | 触发表单验证 | (nameList?: [NamePath](#NamePath)[]) => Promise | |
#### validateFields 返回示例

View File

@ -1,3 +1,3 @@
export { Options as ScrollOptions } from 'scroll-into-view-if-needed';
export type FormLabelAlign = 'left' | 'right';
export { Store, StoreValue } from 'rc-field-form/lib/interface';
export { Store, StoreValue, NamePath, InternalNamePath } from 'rc-field-form/lib/interface';

View File

@ -66,6 +66,10 @@
margin-bottom: 0;
}
&-hidden {
display: none;
}
// ==============================================================
// = Label =
// ==============================================================

View File

@ -1,61 +1,4 @@
import * as React from 'react';
import raf from 'raf';
import { useForm as useRcForm, FormInstance as RcFormInstance } from 'rc-field-form';
import scrollIntoView from 'scroll-into-view-if-needed';
import { ScrollOptions } from './interface';
type InternalNamePath = (string | number)[];
/**
* Always debounce error to avoid [error -> null -> error] blink
*/
export function useCacheErrors(
errors: React.ReactNode[],
changeTrigger: (visible: boolean) => void,
directly: boolean,
): [boolean, React.ReactNode[]] {
const cacheRef = React.useRef({
errors,
visible: !!errors.length,
});
const [, forceUpdate] = React.useState({});
const update = (newErrors: React.ReactNode[]) => {
const prevVisible = cacheRef.current.visible;
const newVisible = !!newErrors.length;
const prevErrors = cacheRef.current.errors;
cacheRef.current.errors = newErrors;
cacheRef.current.visible = newVisible;
if (prevVisible !== newVisible) {
changeTrigger(newVisible);
} else if (
prevErrors.length !== newErrors.length ||
prevErrors.some((prevErr, index) => prevErr !== newErrors[index])
) {
forceUpdate({});
}
};
React.useEffect(() => {
if (!directly) {
const timeout = setTimeout(() => {
update(errors);
}, 10);
return () => {
clearTimeout(timeout);
};
}
}, [errors]);
if (directly) {
update(errors);
}
return [cacheRef.current.visible, cacheRef.current.errors];
}
import { InternalNamePath } from './interface';
export function toArray<T>(candidate?: T | T[] | false): T[] {
if (candidate === undefined || candidate === false) return [];
@ -69,83 +12,3 @@ export function getFieldId(namePath: InternalNamePath, formName?: string): strin
const mergedId = namePath.join('_');
return formName ? `${formName}_${mergedId}` : mergedId;
}
export interface FormInstance extends RcFormInstance {
scrollToField: (name: string | number | InternalNamePath, options?: ScrollOptions) => void;
__INTERNAL__: {
name?: string;
};
}
export function useForm(form?: FormInstance): [FormInstance] {
const [rcForm] = useRcForm();
const wrapForm: FormInstance = React.useMemo(
() =>
form || {
...rcForm,
__INTERNAL__: {},
scrollToField: (name: string, options: ScrollOptions = {}) => {
const namePath = toArray(name);
const fieldId = getFieldId(namePath, wrapForm.__INTERNAL__.name);
const node: HTMLElement | null = fieldId ? document.getElementById(fieldId) : null;
if (node) {
scrollIntoView(node, {
scrollMode: 'if-needed',
block: 'nearest',
...options,
});
}
},
},
[form, rcForm],
);
return [wrapForm];
}
type Updater<ValueType> = (prev?: ValueType) => ValueType;
export function useFrameState<ValueType>(
defaultValue: ValueType,
): [ValueType, (updater: Updater<ValueType>) => void] {
const [value, setValue] = React.useState(defaultValue);
const frameRef = React.useRef<number | null>(null);
const batchRef = React.useRef<Updater<ValueType>[]>([]);
const destroyRef = React.useRef(false);
React.useEffect(
() => () => {
destroyRef.current = true;
raf.cancel(frameRef.current!);
},
[],
);
function setFrameValue(updater: Updater<ValueType>) {
if (destroyRef.current) {
return;
}
if (frameRef.current === null) {
batchRef.current = [];
frameRef.current = raf(() => {
frameRef.current = null;
setValue(prevValue => {
let current = prevValue;
batchRef.current.forEach(func => {
current = func(current);
});
return current;
});
});
}
batchRef.current.push(updater);
}
return [value, setFrameValue];
}

View File

@ -410,30 +410,78 @@ Array [
`;
exports[`renders ./components/grid/demo/flex-order.md correctly 1`] = `
<div
class="ant-row"
>
Array [
<div
class="ant-col ant-col-6 ant-col-order-4"
class="ant-divider ant-divider-horizontal ant-divider-with-text ant-divider-with-text-left"
role="separator"
style="color:#333;font-weight:normal"
>
1 col-order-4
</div>
<span
class="ant-divider-inner-text"
>
Normal
</span>
</div>,
<div
class="ant-col ant-col-6 ant-col-order-3"
class="ant-row"
>
2 col-order-3
</div>
<div
class="ant-col ant-col-6 ant-col-order-4"
>
1 col-order-4
</div>
<div
class="ant-col ant-col-6 ant-col-order-3"
>
2 col-order-3
</div>
<div
class="ant-col ant-col-6 ant-col-order-2"
>
3 col-order-2
</div>
<div
class="ant-col ant-col-6 ant-col-order-1"
>
4 col-order-1
</div>
</div>,
<div
class="ant-col ant-col-6 ant-col-order-2"
class="ant-divider ant-divider-horizontal ant-divider-with-text ant-divider-with-text-left"
role="separator"
style="color:#333;font-weight:normal"
>
3 col-order-2
</div>
<span
class="ant-divider-inner-text"
>
Responsive
</span>
</div>,
<div
class="ant-col ant-col-6 ant-col-order-1"
class="ant-row"
>
4 col-order-1
</div>
</div>
<div
class="ant-col ant-col-6 ant-col-xs-order-1 ant-col-sm-order-2 ant-col-md-order-3 ant-col-lg-order-4"
>
1 col-order-esponsive
</div>
<div
class="ant-col ant-col-6 ant-col-xs-order-2 ant-col-sm-order-1 ant-col-md-order-4 ant-col-lg-order-3"
>
2 col-order-esponsive
</div>
<div
class="ant-col ant-col-6 ant-col-xs-order-3 ant-col-sm-order-4 ant-col-md-order-2 ant-col-lg-order-1"
>
3 col-order-esponsive
</div>
<div
class="ant-col ant-col-6 ant-col-xs-order-4 ant-col-sm-order-3 ant-col-md-order-1 ant-col-lg-order-2"
>
4 col-order-esponsive
</div>
</div>,
]
`;
exports[`renders ./components/grid/demo/flex-stretch.md correctly 1`] = `

View File

@ -14,23 +14,45 @@ title:
To change the element sort by order.
```jsx
import { Row, Col } from 'antd';
import { Row, Col, Divider } from 'antd';
ReactDOM.render(
<Row>
<Col span={6} order={4}>
1 col-order-4
</Col>
<Col span={6} order={3}>
2 col-order-3
</Col>
<Col span={6} order={2}>
3 col-order-2
</Col>
<Col span={6} order={1}>
4 col-order-1
</Col>
</Row>,
<>
<Divider orientation="left" style={{ color: '#333', fontWeight: 'normal' }}>
Normal
</Divider>
<Row>
<Col span={6} order={4}>
1 col-order-4
</Col>
<Col span={6} order={3}>
2 col-order-3
</Col>
<Col span={6} order={2}>
3 col-order-2
</Col>
<Col span={6} order={1}>
4 col-order-1
</Col>
</Row>
<Divider orientation="left" style={{ color: '#333', fontWeight: 'normal' }}>
Responsive
</Divider>
<Row>
<Col span={6} xs={{ order: 1 }} sm={{ order: 2 }} md={{ order: 3 }} lg={{ order: 4 }}>
1 col-order-esponsive
</Col>
<Col span={6} xs={{ order: 2 }} sm={{ order: 1 }} md={{ order: 4 }} lg={{ order: 3 }}>
2 col-order-esponsive
</Col>
<Col span={6} xs={{ order: 3 }} sm={{ order: 4 }} md={{ order: 2 }} lg={{ order: 1 }}>
3 col-order-esponsive
</Col>
<Col span={6} xs={{ order: 4 }} sm={{ order: 3 }} md={{ order: 1 }} lg={{ order: 2 }}>
4 col-order-esponsive
</Col>
</Row>
</>,
mountNode,
);
```

View File

@ -1,158 +0,0 @@
import * as React from 'react';
import ResizeObserver from 'rc-resize-observer';
import omit from 'omit.js';
import classNames from 'classnames';
import calculateNodeHeight from './calculateNodeHeight';
import raf from '../_util/raf';
import { TextAreaProps } from './TextArea';
const RESIZE_STATUS_NONE = 0;
const RESIZE_STATUS_RESIZING = 1;
const RESIZE_STATUS_RESIZED = 2;
export interface AutoSizeType {
minRows?: number;
maxRows?: number;
}
export interface TextAreaState {
textareaStyles?: React.CSSProperties;
/** We need add process style to disable scroll first and then add back to avoid unexpected scrollbar */
resizeStatus?:
| typeof RESIZE_STATUS_NONE
| typeof RESIZE_STATUS_RESIZING
| typeof RESIZE_STATUS_RESIZED;
}
class ResizableTextArea extends React.Component<TextAreaProps, TextAreaState> {
nextFrameActionId: number;
resizeFrameId: number;
constructor(props: TextAreaProps) {
super(props);
this.state = {
textareaStyles: {},
resizeStatus: RESIZE_STATUS_NONE,
};
}
textArea: HTMLTextAreaElement;
saveTextArea = (textArea: HTMLTextAreaElement) => {
this.textArea = textArea;
};
componentDidMount() {
this.resizeTextarea();
}
componentDidUpdate(prevProps: TextAreaProps) {
// Re-render with the new content then recalculate the height as required.
if (prevProps.value !== this.props.value) {
this.resizeTextarea();
}
}
handleResize = (size: { width: number; height: number }) => {
const { resizeStatus } = this.state;
const { autoSize, onResize } = this.props;
if (resizeStatus !== RESIZE_STATUS_NONE) {
return;
}
if (typeof onResize === 'function') {
onResize(size);
}
if (autoSize) {
this.resizeOnNextFrame();
}
};
resizeOnNextFrame = () => {
raf.cancel(this.nextFrameActionId);
this.nextFrameActionId = raf(this.resizeTextarea);
};
resizeTextarea = () => {
const { autoSize } = this.props;
if (!autoSize || !this.textArea) {
return;
}
const { minRows, maxRows } = autoSize as AutoSizeType;
const textareaStyles = calculateNodeHeight(this.textArea, false, minRows, maxRows);
this.setState({ textareaStyles, resizeStatus: RESIZE_STATUS_RESIZING }, () => {
raf.cancel(this.resizeFrameId);
this.resizeFrameId = raf(() => {
this.setState({ resizeStatus: RESIZE_STATUS_RESIZED }, () => {
this.resizeFrameId = raf(() => {
this.setState({ resizeStatus: RESIZE_STATUS_NONE });
this.fixFirefoxAutoScroll();
});
});
});
});
};
componentWillUnmount() {
raf.cancel(this.nextFrameActionId);
raf.cancel(this.resizeFrameId);
}
// https://github.com/ant-design/ant-design/issues/21870
fixFirefoxAutoScroll() {
try {
if (document.activeElement === this.textArea) {
const currentStart = this.textArea.selectionStart;
const currentEnd = this.textArea.selectionEnd;
this.textArea.setSelectionRange(currentStart, currentEnd);
}
} catch (e) {
// Fix error in Chrome:
// Failed to read the 'selectionStart' property from 'HTMLInputElement'
// http://stackoverflow.com/q/21177489/3040605
}
}
renderTextArea = () => {
const { prefixCls, autoSize, onResize, className, disabled } = this.props;
const { textareaStyles, resizeStatus } = this.state;
const otherProps = omit(this.props, [
'prefixCls',
'onPressEnter',
'autoSize',
'defaultValue',
'allowClear',
'onResize',
]);
const cls = classNames(prefixCls, className, {
[`${prefixCls}-disabled`]: disabled,
});
// Fix https://github.com/ant-design/ant-design/issues/6776
// Make sure it could be reset when using form.getFieldDecorator
if ('value' in otherProps) {
otherProps.value = otherProps.value || '';
}
const style = {
...this.props.style,
...textareaStyles,
...(resizeStatus === RESIZE_STATUS_RESIZING
? // React will warning when mix `overflow` & `overflowY`.
// We need to define this separately.
{ overflowX: 'hidden', overflowY: 'hidden' }
: null),
};
return (
<ResizeObserver onResize={this.handleResize} disabled={!(autoSize || onResize)}>
<textarea {...otherProps} className={cls} style={style} ref={this.saveTextArea} />
</ResizeObserver>
);
};
render() {
return this.renderTextArea();
}
}
export default ResizableTextArea;

View File

@ -1,17 +1,12 @@
import * as React from 'react';
import RcTextArea, { TextAreaProps as RcTextAreaProps, ResizableTextArea } from 'rc-textarea';
import omit from 'omit.js';
import ClearableLabeledInput from './ClearableLabeledInput';
import ResizableTextArea, { AutoSizeType } from './ResizableTextArea';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import { fixControlledValue, resolveOnChange } from './Input';
export type HTMLTextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
export interface TextAreaProps extends HTMLTextareaProps {
prefixCls?: string;
autoSize?: boolean | AutoSizeType;
onPressEnter?: React.KeyboardEventHandler<HTMLTextAreaElement>;
export interface TextAreaProps extends RcTextAreaProps {
allowClear?: boolean;
onResize?: (size: { width: number; height: number }) => void;
}
export interface TextAreaState {
@ -54,8 +49,8 @@ class TextArea extends React.Component<TextAreaProps, TextAreaState> {
this.resizableTextArea.textArea.blur();
}
saveTextArea = (resizableTextArea: ResizableTextArea) => {
this.resizableTextArea = resizableTextArea;
saveTextArea = (textarea: RcTextArea) => {
this.resizableTextArea = textarea?.resizableTextArea;
};
saveClearableInput = (clearableInput: ClearableLabeledInput) => {
@ -63,25 +58,12 @@ class TextArea extends React.Component<TextAreaProps, TextAreaState> {
};
handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
this.setValue(e.target.value, () => {
this.resizableTextArea.resizeTextarea();
});
this.setValue(e.target.value);
resolveOnChange(this.resizableTextArea.textArea, e, this.props.onChange);
};
handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
const { onPressEnter, onKeyDown } = this.props;
if (e.keyCode === 13 && onPressEnter) {
onPressEnter(e);
}
if (onKeyDown) {
onKeyDown(e);
}
};
handleReset = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
this.setValue('', () => {
this.resizableTextArea.renderTextArea();
this.focus();
});
resolveOnChange(this.resizableTextArea.textArea, e, this.props.onChange);
@ -89,10 +71,9 @@ class TextArea extends React.Component<TextAreaProps, TextAreaState> {
renderTextArea = (prefixCls: string) => {
return (
<ResizableTextArea
{...this.props}
<RcTextArea
{...omit(this.props, ['allowClear'])}
prefixCls={prefixCls}
onKeyDown={this.handleKeyDown}
onChange={this.handleChange}
ref={this.saveTextArea}
/>

View File

@ -252,6 +252,7 @@ Array [
style="width:100px"
>
<textarea
class="rc-textarea"
rows="1"
/>
</div>,

View File

@ -1,9 +1,8 @@
import React from 'react';
import { mount } from 'enzyme';
// eslint-disable-next-line import/no-unresolved
import RcTextArea from 'rc-textarea';
import Input from '..';
import focusTest from '../../../tests/shared/focusTest';
import calculateNodeHeight, { calculateNodeStyling } from '../calculateNodeHeight';
import { sleep } from '../../../tests/utils';
const { TextArea } = Input;
@ -78,56 +77,14 @@ describe('TextArea', () => {
expect(wrapper.render()).toMatchSnapshot();
});
it('calculateNodeStyling works correctly', () => {
const wrapper = document.createElement('textarea');
wrapper.id = 'test';
wrapper.wrap = 'wrap';
calculateNodeStyling(wrapper, true);
const value = calculateNodeStyling(wrapper, true);
expect(value).toEqual({
borderSize: 2,
boxSizing: 'border-box',
paddingSize: 4,
sizingStyle:
'letter-spacing:normal;line-height:normal;padding-top:2px;padding-bottom:2px;font-family:-webkit-small-control;font-weight:;font-size:;font-variant:;text-rendering:auto;text-transform:none;width:;text-indent:0;padding-left:2px;padding-right:2px;border-width:1px;box-sizing:border-box',
});
});
it('boxSizing === "border-box"', () => {
const wrapper = document.createElement('textarea');
wrapper.style.boxSizing = 'border-box';
const { height } = calculateNodeHeight(wrapper);
expect(height).toBe(2);
});
it('boxSizing === "content-box"', () => {
const wrapper = document.createElement('textarea');
wrapper.style.boxSizing = 'content-box';
const { height } = calculateNodeHeight(wrapper);
expect(height).toBe(-4);
});
it('minRows or maxRows is not null', () => {
const wrapper = document.createElement('textarea');
expect(calculateNodeHeight(wrapper, 1, 1)).toEqual({
height: 2,
maxHeight: 9007199254740991,
minHeight: 2,
overflowY: undefined,
});
wrapper.style.boxSizing = 'content-box';
expect(calculateNodeHeight(wrapper, 1, 1)).toEqual({
height: -4,
maxHeight: 9007199254740991,
minHeight: -4,
overflowY: undefined,
});
});
it('when prop value not in this.props, resizeTextarea should be called', () => {
it('when prop value not in this.props, resizeTextarea should be called', async () => {
const wrapper = mount(<TextArea aria-label="textarea" />);
const resizeTextarea = jest.spyOn(wrapper.instance().resizableTextArea, 'resizeTextarea');
wrapper.find('textarea').simulate('change', 'test');
wrapper.find('textarea').simulate('change', {
target: {
value: 'test',
},
});
expect(resizeTextarea).toHaveBeenCalled();
});
@ -137,7 +94,7 @@ describe('TextArea', () => {
const wrapper = mount(
<TextArea onPressEnter={onPressEnter} onKeyDown={onKeyDown} aria-label="textarea" />,
);
wrapper.instance().handleKeyDown({ keyCode: 13 });
wrapper.find(RcTextArea).instance().handleKeyDown({ keyCode: 13 });
expect(onPressEnter).toHaveBeenCalled();
expect(onKeyDown).toHaveBeenCalled();
});

View File

@ -1,156 +0,0 @@
// Thanks to https://github.com/andreypopp/react-textarea-autosize/
/**
* calculateNodeHeight(uiTextNode, useCache = false)
*/
const HIDDEN_TEXTAREA_STYLE = `
min-height:0 !important;
max-height:none !important;
height:0 !important;
visibility:hidden !important;
overflow:hidden !important;
position:absolute !important;
z-index:-1000 !important;
top:0 !important;
right:0 !important
`;
const SIZING_STYLE = [
'letter-spacing',
'line-height',
'padding-top',
'padding-bottom',
'font-family',
'font-weight',
'font-size',
'font-variant',
'text-rendering',
'text-transform',
'width',
'text-indent',
'padding-left',
'padding-right',
'border-width',
'box-sizing',
];
export interface NodeType {
sizingStyle: string;
paddingSize: number;
borderSize: number;
boxSizing: string;
}
const computedStyleCache: { [key: string]: NodeType } = {};
let hiddenTextarea: HTMLTextAreaElement;
export function calculateNodeStyling(node: HTMLElement, useCache = false) {
const nodeRef = (node.getAttribute('id') ||
node.getAttribute('data-reactid') ||
node.getAttribute('name')) as string;
if (useCache && computedStyleCache[nodeRef]) {
return computedStyleCache[nodeRef];
}
const style = window.getComputedStyle(node);
const boxSizing =
style.getPropertyValue('box-sizing') ||
style.getPropertyValue('-moz-box-sizing') ||
style.getPropertyValue('-webkit-box-sizing');
const paddingSize =
parseFloat(style.getPropertyValue('padding-bottom')) +
parseFloat(style.getPropertyValue('padding-top'));
const borderSize =
parseFloat(style.getPropertyValue('border-bottom-width')) +
parseFloat(style.getPropertyValue('border-top-width'));
const sizingStyle = SIZING_STYLE.map(name => `${name}:${style.getPropertyValue(name)}`).join(';');
const nodeInfo: NodeType = {
sizingStyle,
paddingSize,
borderSize,
boxSizing,
};
if (useCache && nodeRef) {
computedStyleCache[nodeRef] = nodeInfo;
}
return nodeInfo;
}
export default function calculateNodeHeight(
uiTextNode: HTMLTextAreaElement,
useCache = false,
minRows: number | null = null,
maxRows: number | null = null,
) {
if (!hiddenTextarea) {
hiddenTextarea = document.createElement('textarea');
hiddenTextarea.setAttribute('tab-index', '-1');
hiddenTextarea.setAttribute('aria-hidden', 'true');
document.body.appendChild(hiddenTextarea);
}
// Fix wrap="off" issue
// https://github.com/ant-design/ant-design/issues/6577
if (uiTextNode.getAttribute('wrap')) {
hiddenTextarea.setAttribute('wrap', uiTextNode.getAttribute('wrap') as string);
} else {
hiddenTextarea.removeAttribute('wrap');
}
// Copy all CSS properties that have an impact on the height of the content in
// the textbox
const { paddingSize, borderSize, boxSizing, sizingStyle } = calculateNodeStyling(
uiTextNode,
useCache,
);
// Need to have the overflow attribute to hide the scrollbar otherwise
// text-lines will not calculated properly as the shadow will technically be
// narrower for content
hiddenTextarea.setAttribute('style', `${sizingStyle};${HIDDEN_TEXTAREA_STYLE}`);
hiddenTextarea.value = uiTextNode.value || uiTextNode.placeholder || '';
let minHeight = Number.MIN_SAFE_INTEGER;
let maxHeight = Number.MAX_SAFE_INTEGER;
let height = hiddenTextarea.scrollHeight;
let overflowY: any;
if (boxSizing === 'border-box') {
// border-box: add border, since height = content + padding + border
height += borderSize;
} else if (boxSizing === 'content-box') {
// remove padding, since height = content
height -= paddingSize;
}
if (minRows !== null || maxRows !== null) {
// measure height of a textarea with a single row
hiddenTextarea.value = ' ';
const singleRowHeight = hiddenTextarea.scrollHeight - paddingSize;
if (minRows !== null) {
minHeight = singleRowHeight * minRows;
if (boxSizing === 'border-box') {
minHeight = minHeight + paddingSize + borderSize;
}
height = Math.max(minHeight, height);
}
if (maxRows !== null) {
maxHeight = singleRowHeight * maxRows;
if (boxSizing === 'border-box') {
maxHeight = maxHeight + paddingSize + borderSize;
}
overflowY = height > maxHeight ? '' : 'hidden';
height = Math.min(maxHeight, height);
}
}
return { height, minHeight, maxHeight, overflowY };
}

View File

@ -2142,9 +2142,11 @@ exports[`renders ./components/list/demo/vertical.md correctly 1`] = `
class="ant-pagination-prev ant-pagination-disabled"
title="Previous Page"
>
<a
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -2166,7 +2168,7 @@ exports[`renders ./components/list/demo/vertical.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-item ant-pagination-item-1 ant-pagination-item-active"
@ -2267,8 +2269,10 @@ exports[`renders ./components/list/demo/vertical.md correctly 1`] = `
tabindex="0"
title="Next Page"
>
<a
<button
class="ant-pagination-item-link"
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -2290,7 +2294,7 @@ exports[`renders ./components/list/demo/vertical.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
</ul>
</div>

View File

@ -38,9 +38,11 @@ exports[`List.pagination renders pagination correctly 1`] = `
class="ant-pagination-prev ant-pagination-disabled"
title="Previous Page"
>
<a
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -62,7 +64,7 @@ exports[`List.pagination renders pagination correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-item ant-pagination-item-1 ant-pagination-item-active"
@ -88,8 +90,10 @@ exports[`List.pagination renders pagination correctly 1`] = `
tabindex="0"
title="Next Page"
>
<a
<button
class="ant-pagination-item-link"
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -111,7 +115,7 @@ exports[`List.pagination renders pagination correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
</ul>
</div>
@ -128,9 +132,11 @@ exports[`List.pagination should change page size work 1`] = `
class="ant-pagination-prev ant-pagination-disabled"
title="Previous Page"
>
<a
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -152,7 +158,7 @@ exports[`List.pagination should change page size work 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-item ant-pagination-item-1 ant-pagination-item-active"
@ -168,9 +174,11 @@ exports[`List.pagination should change page size work 1`] = `
class="ant-pagination-next ant-pagination-disabled"
title="Next Page"
>
<a
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -192,7 +200,7 @@ exports[`List.pagination should change page size work 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-options"
@ -270,9 +278,11 @@ exports[`List.pagination should change page size work 2`] = `
class="ant-pagination-prev ant-pagination-disabled"
title="Previous Page"
>
<a
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -294,7 +304,7 @@ exports[`List.pagination should change page size work 2`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-item ant-pagination-item-1 ant-pagination-item-active"
@ -310,9 +320,11 @@ exports[`List.pagination should change page size work 2`] = `
class="ant-pagination-next ant-pagination-disabled"
title="Next Page"
>
<a
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -334,7 +346,7 @@ exports[`List.pagination should change page size work 2`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-options"
@ -536,8 +548,10 @@ exports[`List.pagination should default work 1`] = `
tabindex="0"
title="Previous Page"
>
<a
<button
class="ant-pagination-item-link"
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -559,7 +573,7 @@ exports[`List.pagination should default work 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-item ant-pagination-item-1"
@ -584,9 +598,11 @@ exports[`List.pagination should default work 1`] = `
class="ant-pagination-next ant-pagination-disabled"
title="Next Page"
>
<a
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -608,7 +624,7 @@ exports[`List.pagination should default work 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-options"

View File

@ -136,6 +136,7 @@ describe('List.pagination', () => {
expect(wrapper.find('Pagination').first().render()).toMatchSnapshot();
});
// https://github.com/ant-design/ant-design/issues/24913
// https://github.com/ant-design/ant-design/issues/24501
it('should onChange called when pageSize change', () => {
const handlePaginationChange = jest.fn();

View File

@ -105,11 +105,6 @@ function List<T>({
return (page: number, pageSize: number) => {
setPaginationCurrent(page);
setPaginationSize(pageSize);
if (eventName === 'onShowSizeChange') {
if (pagination) {
pagination?.onChange?.(page, pageSize);
}
}
if (pagination && (pagination as any)[eventName]) {
(pagination as any)[eventName](page, pageSize);
}

View File

@ -6,6 +6,19 @@ exports[`renders ./components/mentions/demo/async.md correctly 1`] = `
style="width:100%"
>
<textarea
class="rc-textarea"
rows="1"
/>
</div>
`;
exports[`renders ./components/mentions/demo/autoSize.md correctly 1`] = `
<div
class="ant-mentions"
style="width:100%"
>
<textarea
class="rc-textarea"
rows="1"
/>
</div>
@ -17,6 +30,7 @@ exports[`renders ./components/mentions/demo/basic.md correctly 1`] = `
style="width:100%"
>
<textarea
class="rc-textarea"
rows="1"
>
@afc163
@ -55,6 +69,7 @@ exports[`renders ./components/mentions/demo/form.md correctly 1`] = `
class="ant-mentions"
>
<textarea
class="rc-textarea"
id="coders"
rows="1"
/>
@ -90,6 +105,7 @@ exports[`renders ./components/mentions/demo/form.md correctly 1`] = `
class="ant-mentions"
>
<textarea
class="rc-textarea"
id="bio"
placeholder="You can use @ to ref user here"
rows="3"
@ -141,6 +157,7 @@ exports[`renders ./components/mentions/demo/placement.md correctly 1`] = `
style="width:100%"
>
<textarea
class="rc-textarea"
rows="1"
/>
</div>
@ -152,6 +169,7 @@ exports[`renders ./components/mentions/demo/prefix.md correctly 1`] = `
style="width:100%"
>
<textarea
class="rc-textarea"
placeholder="input @ to mention people, # to mention tag"
rows="1"
/>
@ -168,6 +186,7 @@ exports[`renders ./components/mentions/demo/readonly.md correctly 1`] = `
style="width:100%"
>
<textarea
class="rc-textarea rc-textarea-disabled"
disabled=""
placeholder="this is disabled Mentions"
rows="1"
@ -179,6 +198,7 @@ exports[`renders ./components/mentions/demo/readonly.md correctly 1`] = `
style="width:100%"
>
<textarea
class="rc-textarea"
placeholder="this is readOnly Mentions"
readonly=""
rows="1"

View File

@ -5,6 +5,7 @@ exports[`Mentions rtl render component should be rendered correctly in RTL direc
class="ant-mentions ant-mentions-rtl"
>
<textarea
class="rc-textarea"
rows="1"
/>
</div>

View File

@ -0,0 +1,29 @@
---
order: 6
title:
zh-CN: 自动大小
en-US: autoSize
---
## zh-CN
自适应内容高度。
## en-US
Height autoSize.
```jsx
import { Mentions } from 'antd';
const { Option } = Mentions;
ReactDOM.render(
<Mentions autoSize style={{ width: '100%' }}>
<Option value="afc163">afc163</Option>
<Option value="zombieJ">zombieJ</Option>
<Option value="yesmeck">yesmeck</Option>
</Mentions>,
mountNode,
);
```

View File

@ -7,7 +7,7 @@ title:
## zh-CN
基本使用
基本使用
## en-US

View File

@ -38,6 +38,8 @@ When need to mention someone or something.
| onFocus | Trigger when mentions get focus | () => void | |
| onBlur | Trigger when mentions lose focus | () => void | |
| getPopupContainer | Set the mount HTML node for suggestions | () => HTMLElement | |
| autoSize | Textarea height autosize feature, can be set to `true\|false` or an object `{ minRows: 2, maxRows: 6 }` | boolean \| object | false |
| onResize | The callback function that is triggered when textarea resize | function({ width, height }) | |
### Mention methods

View File

@ -39,6 +39,8 @@ cover: https://gw.alipayobjects.com/zos/alicdn/jPE-itMFM/Mentions.svg
| onFocus | 获得焦点时触发 | () => void | |
| onBlur | 失去焦点时触发 | () => void | |
| getPopupContainer | 指定建议框挂载的 HTML 节点 | () => HTMLElement | |
| autoSize | 自适应内容高度,可设置为 `true|false` 或对象:`{ minRows: 2, maxRows: 6 }`。 | boolean\|object | false |
| onResize | resize 回调 | function({ width, height }) | |
### Mentions 方法

View File

@ -332,7 +332,7 @@ describe('Menu', () => {
);
});
it('vertical', () => {
it('vertical with hover(default)', () => {
const wrapper = mount(
<Menu mode="vertical">
<SubMenu key="1" title="submenu1">
@ -354,7 +354,29 @@ describe('Menu', () => {
);
});
it('horizontal', () => {
it('vertical with click', () => {
const wrapper = mount(
<Menu mode="vertical" triggerSubMenuAction="click">
<SubMenu key="1" title="submenu1">
<Menu.Item key="submenu1">Option 1</Menu.Item>
<Menu.Item key="submenu2">Option 2</Menu.Item>
</SubMenu>
<Menu.Item key="2">menu2</Menu.Item>
</Menu>,
);
expect(wrapper.find('.ant-menu-sub').length).toBe(0);
toggleMenu(wrapper, 0, 'click');
expect(wrapper.find('.ant-menu-sub').hostNodes().length).toBe(1);
expect(wrapper.find('.ant-menu-sub').hostNodes().at(0).hasClass('ant-menu-hidden')).not.toBe(
true,
);
toggleMenu(wrapper, 0, 'click');
expect(wrapper.find('.ant-menu-sub').hostNodes().at(0).hasClass('ant-menu-hidden')).toBe(
true,
);
});
it('horizontal with hover(default)', () => {
jest.useFakeTimers();
const wrapper = mount(
<Menu mode="horizontal">
@ -376,6 +398,29 @@ describe('Menu', () => {
true,
);
});
it('horizontal with click', () => {
jest.useFakeTimers();
const wrapper = mount(
<Menu mode="horizontal" triggerSubMenuAction="click">
<SubMenu key="1" title="submenu1">
<Menu.Item key="submenu1">Option 1</Menu.Item>
<Menu.Item key="submenu2">Option 2</Menu.Item>
</SubMenu>
<Menu.Item key="2">menu2</Menu.Item>
</Menu>,
);
expect(wrapper.find('.ant-menu-sub').length).toBe(0);
toggleMenu(wrapper, 0, 'click');
expect(wrapper.find('.ant-menu-sub').hostNodes().length).toBe(1);
expect(wrapper.find('.ant-menu-sub').hostNodes().at(0).hasClass('ant-menu-hidden')).not.toBe(
true,
);
toggleMenu(wrapper, 0, 'click');
expect(wrapper.find('.ant-menu-sub').hostNodes().at(0).hasClass('ant-menu-hidden')).toBe(
true,
);
});
});
it('inline title', () => {

View File

@ -45,6 +45,7 @@ More layouts with navigation: [Layout](/components/layout).
| theme | Color theme of the menu | `light` \| `dark` | `light` | |
| onClick | Called when a menu item is clicked | function({ item, key, keyPath, domEvent }) | - | |
| onDeselect | Called when a menu item is deselected (multiple mode only) | function({ item, key, keyPath, selectedKeys, domEvent }) | - | |
| triggerSubMenuAction | Which action can trigger submenu open/close | `hover` \| `click` | `hover` | |
| onOpenChange | Called when sub-menus are opened or closed | function(openKeys: string\[]) | noop | |
| onSelect | Called when a menu item is selected | function({ item, key, keyPath, selectedKeys, domEvent }) | none | |
| overflowedIndicator | Customized icon when menu is collapsed | ReactNode | - | |

View File

@ -42,6 +42,7 @@ export interface MenuProps {
onOpenChange?: (openKeys: string[]) => void;
onSelect?: (param: SelectParam) => void;
onDeselect?: (param: SelectParam) => void;
triggerSubMenuAction?: 'hover' | 'click';
onClick?: (param: ClickParam) => void;
style?: React.CSSProperties;
openAnimation?: string;

View File

@ -46,6 +46,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/3XZcjGpvK/Menu.svg
| theme | 主题颜色 | `light` \| `dark` | `light` | |
| onClick | 点击 MenuItem 调用此函数 | function({ item, key, keyPath, domEvent }) | - | |
| onDeselect | 取消选中时调用,仅在 multiple 生效 | function({ item, key, keyPath, selectedKeys, domEvent }) | - | |
| triggerSubMenuAction | SubMenu 展开/关闭的触发行为 | `hover` \| `click` | `hover` | |
| onOpenChange | SubMenu 展开/关闭的回调 | function(openKeys: string\[]) | noop | |
| onSelect | 被选中时调用 | function({ item, key, keyPath, selectedKeys, domEvent }) | -   | |
| overflowedIndicator | 自定义 Menu 折叠时的图标 | ReactNode | - | |

View File

@ -20,7 +20,7 @@ When requiring users to interact with the application, but without jumping to a
| cancelText | Text of the Cancel button | string\|ReactNode | `Cancel` |
| centered | Centered Modal | boolean | false |
| closable | Whether a close (x) button is visible on top right of the modal dialog or not | boolean | true |
| closeIcon | custom close icon | ReactNode | - |
| closeIcon | custom close icon | ReactNode | `<CloseOutlined />` |
| confirmLoading | Whether to apply loading visual effect for OK button or not | boolean | false |
| destroyOnClose | Whether to unmount child components on onClose | boolean | false |
| footer | Footer content, set as `footer={null}` when you don't need default buttons | string\|ReactNode | OK and Cancel buttons |

View File

@ -23,7 +23,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/3StSdUlSH/Modal.svg
| cancelText | 取消按钮文字 | string\|ReactNode | 取消 |
| centered | 垂直居中展示 Modal | boolean | false |
| closable | 是否显示右上角的关闭按钮 | boolean | true |
| closeIcon | 自定义关闭图标 | ReactNode | - |
| closeIcon | 自定义关闭图标 | ReactNode | `<CloseOutlined />` |
| confirmLoading | 确定按钮 loading | boolean | false |
| destroyOnClose | 关闭时销毁 Modal 里的子元素 | boolean | false |
| footer | 底部内容,当不需要默认底部按钮时,可以设为 `footer={null}` | string\|ReactNode | 确定取消按钮 |

View File

@ -27,8 +27,8 @@
margin: 0;
color: @modal-heading-color;
font-weight: 500;
font-size: @font-size-lg;
line-height: 22px;
font-size: @modal-header-title-font-size;
line-height: @modal-header-title-line-height;
word-wrap: break-word;
}
@ -81,7 +81,7 @@
padding: @modal-header-padding;
color: @text-color;
background: @modal-header-bg;
border-bottom: @border-width-base @border-style-base @modal-header-border-color-split;
border-bottom: @modal-header-border-width @modal-header-border-style @modal-header-border-color-split;
border-radius: @border-radius-base @border-radius-base 0 0;
}
@ -96,7 +96,7 @@
padding: @modal-footer-padding-vertical @modal-footer-padding-horizontal;
text-align: right;
background: @modal-footer-bg;
border-top: @border-width-base @border-style-base @modal-footer-border-color-split;
border-top: @modal-footer-border-width @modal-footer-border-style @modal-footer-border-color-split;
border-radius: 0 0 @border-radius-base @border-radius-base;
button + button {

View File

@ -67,22 +67,23 @@ const Pagination: React.FC<PaginationProps> = ({
const prefixCls = getPrefixCls('pagination', customizePrefixCls);
const getIconsProps = () => {
const ellipsis = <span className={`${prefixCls}-item-ellipsis`}></span>;
let prevIcon = (
<a className={`${prefixCls}-item-link`}>
<button className={`${prefixCls}-item-link`} type="button" tabIndex={-1}>
<LeftOutlined />
</a>
</button>
);
let nextIcon = (
<a className={`${prefixCls}-item-link`}>
<button className={`${prefixCls}-item-link`} type="button" tabIndex={-1}>
<RightOutlined />
</a>
</button>
);
let jumpPrevIcon = (
<a className={`${prefixCls}-item-link`}>
{/* You can use transition effects in the container :) */}
<div className={`${prefixCls}-item-container`}>
<DoubleLeftOutlined className={`${prefixCls}-item-link-icon`} />
<span className={`${prefixCls}-item-ellipsis`}></span>
{ellipsis}
</div>
</a>
);
@ -91,21 +92,14 @@ const Pagination: React.FC<PaginationProps> = ({
{/* You can use transition effects in the container :) */}
<div className={`${prefixCls}-item-container`}>
<DoubleRightOutlined className={`${prefixCls}-item-link-icon`} />
<span className={`${prefixCls}-item-ellipsis`}></span>
{ellipsis}
</div>
</a>
);
// change arrows direction in right-to-left direction
if (direction === 'rtl') {
let temp: any;
temp = prevIcon;
prevIcon = nextIcon;
nextIcon = temp;
temp = jumpPrevIcon;
jumpPrevIcon = jumpNextIcon;
jumpNextIcon = temp;
[prevIcon, nextIcon] = [nextIcon, prevIcon];
[jumpPrevIcon, jumpNextIcon] = [jumpNextIcon, jumpPrevIcon];
}
return {
prevIcon,

View File

@ -16,9 +16,11 @@ exports[`renders ./components/pagination/demo/all.md correctly 1`] = `
class="ant-pagination-prev ant-pagination-disabled"
title="Previous Page"
>
<a
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -40,7 +42,7 @@ exports[`renders ./components/pagination/demo/all.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-item ant-pagination-item-1 ant-pagination-item-active"
@ -141,8 +143,10 @@ exports[`renders ./components/pagination/demo/all.md correctly 1`] = `
tabindex="0"
title="Next Page"
>
<a
<button
class="ant-pagination-item-link"
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -164,7 +168,7 @@ exports[`renders ./components/pagination/demo/all.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-options"
@ -251,9 +255,11 @@ exports[`renders ./components/pagination/demo/basic.md correctly 1`] = `
class="ant-pagination-prev ant-pagination-disabled"
title="Previous Page"
>
<a
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -275,7 +281,7 @@ exports[`renders ./components/pagination/demo/basic.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-item ant-pagination-item-1 ant-pagination-item-active"
@ -328,8 +334,10 @@ exports[`renders ./components/pagination/demo/basic.md correctly 1`] = `
tabindex="0"
title="Next Page"
>
<a
<button
class="ant-pagination-item-link"
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -351,7 +359,7 @@ exports[`renders ./components/pagination/demo/basic.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
</ul>
`;
@ -368,8 +376,10 @@ exports[`renders ./components/pagination/demo/changer.md correctly 1`] = `
tabindex="0"
title="Previous Page"
>
<a
<button
class="ant-pagination-item-link"
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -391,7 +401,7 @@ exports[`renders ./components/pagination/demo/changer.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-item ant-pagination-item-1"
@ -492,8 +502,10 @@ exports[`renders ./components/pagination/demo/changer.md correctly 1`] = `
tabindex="0"
title="Next Page"
>
<a
<button
class="ant-pagination-item-link"
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -515,7 +527,7 @@ exports[`renders ./components/pagination/demo/changer.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-options"
@ -591,8 +603,10 @@ exports[`renders ./components/pagination/demo/changer.md correctly 1`] = `
tabindex="0"
title="Previous Page"
>
<a
<button
class="ant-pagination-item-link"
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -614,7 +628,7 @@ exports[`renders ./components/pagination/demo/changer.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-item ant-pagination-item-1"
@ -715,8 +729,10 @@ exports[`renders ./components/pagination/demo/changer.md correctly 1`] = `
tabindex="0"
title="Next Page"
>
<a
<button
class="ant-pagination-item-link"
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -738,7 +754,7 @@ exports[`renders ./components/pagination/demo/changer.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-options"
@ -819,8 +835,10 @@ exports[`renders ./components/pagination/demo/controlled.md correctly 1`] = `
tabindex="0"
title="Previous Page"
>
<a
<button
class="ant-pagination-item-link"
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -842,7 +860,7 @@ exports[`renders ./components/pagination/demo/controlled.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-item ant-pagination-item-1"
@ -895,8 +913,10 @@ exports[`renders ./components/pagination/demo/controlled.md correctly 1`] = `
tabindex="0"
title="Next Page"
>
<a
<button
class="ant-pagination-item-link"
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -918,7 +938,7 @@ exports[`renders ./components/pagination/demo/controlled.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
</ul>
`;
@ -1119,8 +1139,10 @@ exports[`renders ./components/pagination/demo/jump.md correctly 1`] = `
tabindex="0"
title="Previous Page"
>
<a
<button
class="ant-pagination-item-link"
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -1142,7 +1164,7 @@ exports[`renders ./components/pagination/demo/jump.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-item ant-pagination-item-1"
@ -1243,8 +1265,10 @@ exports[`renders ./components/pagination/demo/jump.md correctly 1`] = `
tabindex="0"
title="Next Page"
>
<a
<button
class="ant-pagination-item-link"
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -1266,7 +1290,7 @@ exports[`renders ./components/pagination/demo/jump.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-options"
@ -1351,8 +1375,10 @@ exports[`renders ./components/pagination/demo/jump.md correctly 1`] = `
tabindex="0"
title="Previous Page"
>
<a
<button
class="ant-pagination-item-link"
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -1374,7 +1400,7 @@ exports[`renders ./components/pagination/demo/jump.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-item ant-pagination-item-1"
@ -1475,8 +1501,10 @@ exports[`renders ./components/pagination/demo/jump.md correctly 1`] = `
tabindex="0"
title="Next Page"
>
<a
<button
class="ant-pagination-item-link"
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -1498,7 +1526,7 @@ exports[`renders ./components/pagination/demo/jump.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-options"
@ -1589,9 +1617,11 @@ exports[`renders ./components/pagination/demo/mini.md correctly 1`] = `
class="ant-pagination-prev ant-pagination-disabled"
title="Previous Page"
>
<a
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -1613,7 +1643,7 @@ exports[`renders ./components/pagination/demo/mini.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-item ant-pagination-item-1 ant-pagination-item-active"
@ -1666,8 +1696,10 @@ exports[`renders ./components/pagination/demo/mini.md correctly 1`] = `
tabindex="0"
title="Next Page"
>
<a
<button
class="ant-pagination-item-link"
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -1689,7 +1721,7 @@ exports[`renders ./components/pagination/demo/mini.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
</ul>
<ul
@ -1701,9 +1733,11 @@ exports[`renders ./components/pagination/demo/mini.md correctly 1`] = `
class="ant-pagination-prev ant-pagination-disabled"
title="Previous Page"
>
<a
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -1725,7 +1759,7 @@ exports[`renders ./components/pagination/demo/mini.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-item ant-pagination-item-1 ant-pagination-item-active"
@ -1778,8 +1812,10 @@ exports[`renders ./components/pagination/demo/mini.md correctly 1`] = `
tabindex="0"
title="Next Page"
>
<a
<button
class="ant-pagination-item-link"
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -1801,7 +1837,7 @@ exports[`renders ./components/pagination/demo/mini.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-options"
@ -1889,9 +1925,11 @@ exports[`renders ./components/pagination/demo/mini.md correctly 1`] = `
class="ant-pagination-prev ant-pagination-disabled"
title="Previous Page"
>
<a
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -1913,7 +1951,7 @@ exports[`renders ./components/pagination/demo/mini.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-item ant-pagination-item-1 ant-pagination-item-active"
@ -1966,8 +2004,10 @@ exports[`renders ./components/pagination/demo/mini.md correctly 1`] = `
tabindex="0"
title="Next Page"
>
<a
<button
class="ant-pagination-item-link"
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -1989,7 +2029,7 @@ exports[`renders ./components/pagination/demo/mini.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
</ul>
</div>
@ -2006,8 +2046,10 @@ exports[`renders ./components/pagination/demo/more.md correctly 1`] = `
tabindex="0"
title="Previous Page"
>
<a
<button
class="ant-pagination-item-link"
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -2029,7 +2071,7 @@ exports[`renders ./components/pagination/demo/more.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-item ant-pagination-item-1"
@ -2178,8 +2220,10 @@ exports[`renders ./components/pagination/demo/more.md correctly 1`] = `
tabindex="0"
title="Next Page"
>
<a
<button
class="ant-pagination-item-link"
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -2201,7 +2245,7 @@ exports[`renders ./components/pagination/demo/more.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-options"
@ -2278,8 +2322,10 @@ exports[`renders ./components/pagination/demo/simple.md correctly 1`] = `
tabindex="0"
title="Previous Page"
>
<a
<button
class="ant-pagination-item-link"
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -2301,7 +2347,7 @@ exports[`renders ./components/pagination/demo/simple.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-simple-pager"
@ -2325,8 +2371,10 @@ exports[`renders ./components/pagination/demo/simple.md correctly 1`] = `
tabindex="0"
title="Next Page"
>
<a
<button
class="ant-pagination-item-link"
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -2348,7 +2396,7 @@ exports[`renders ./components/pagination/demo/simple.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
</ul>
`;
@ -2369,9 +2417,11 @@ exports[`renders ./components/pagination/demo/total.md correctly 1`] = `
class="ant-pagination-prev ant-pagination-disabled"
title="Previous Page"
>
<a
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -2393,7 +2443,7 @@ exports[`renders ./components/pagination/demo/total.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-item ant-pagination-item-1 ant-pagination-item-active"
@ -2446,8 +2496,10 @@ exports[`renders ./components/pagination/demo/total.md correctly 1`] = `
tabindex="0"
title="Next Page"
>
<a
<button
class="ant-pagination-item-link"
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -2469,7 +2521,7 @@ exports[`renders ./components/pagination/demo/total.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-options"
@ -2549,9 +2601,11 @@ exports[`renders ./components/pagination/demo/total.md correctly 1`] = `
class="ant-pagination-prev ant-pagination-disabled"
title="Previous Page"
>
<a
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -2573,7 +2627,7 @@ exports[`renders ./components/pagination/demo/total.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-item ant-pagination-item-1 ant-pagination-item-active"
@ -2626,8 +2680,10 @@ exports[`renders ./components/pagination/demo/total.md correctly 1`] = `
tabindex="0"
title="Next Page"
>
<a
<button
class="ant-pagination-item-link"
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -2649,7 +2705,7 @@ exports[`renders ./components/pagination/demo/total.md correctly 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-options"

View File

@ -10,9 +10,11 @@ exports[`Pagination rtl render component should be rendered correctly in RTL dir
class="ant-pagination-prev ant-pagination-disabled"
title="Previous Page"
>
<a
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -34,7 +36,7 @@ exports[`Pagination rtl render component should be rendered correctly in RTL dir
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-item ant-pagination-item-0 ant-pagination-disabled ant-pagination-item-disabled"
@ -50,9 +52,11 @@ exports[`Pagination rtl render component should be rendered correctly in RTL dir
class="ant-pagination-next ant-pagination-disabled"
title="Next Page"
>
<a
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -74,7 +78,7 @@ exports[`Pagination rtl render component should be rendered correctly in RTL dir
/>
</svg>
</span>
</a>
</button>
</li>
</ul>
`;
@ -89,9 +93,11 @@ exports[`Pagination should be rendered correctly in RTL 1`] = `
class="ant-pagination-prev ant-pagination-disabled"
title="Previous Page"
>
<a
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="right"
@ -113,7 +119,7 @@ exports[`Pagination should be rendered correctly in RTL 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
<li
class="ant-pagination-item ant-pagination-item-1 ant-pagination-item-active"
@ -166,8 +172,10 @@ exports[`Pagination should be rendered correctly in RTL 1`] = `
tabindex="0"
title="Next Page"
>
<a
<button
class="ant-pagination-item-link"
tabindex="-1"
type="button"
>
<span
aria-label="left"
@ -189,7 +197,7 @@ exports[`Pagination should be rendered correctly in RTL 1`] = `
/>
</svg>
</span>
</a>
</button>
</li>
</ul>
`;

View File

@ -29,21 +29,30 @@ describe('Pagination', () => {
return originalElement;
}
const wrapper = mount(<Pagination defaultCurrent={1} total={50} itemRender={itemRender} />);
expect(
wrapper
.find('button')
.at(0)
.props().disabled,
).toBe(true);
expect(wrapper.find('button').at(0).props().disabled).toBe(true);
});
it('should autometically be small when size is not specified', async () => {
const wrapper = mount(<Pagination responsive />);
expect(
wrapper
.find('ul')
.at(0)
.hasClass('mini'),
).toBe(true);
expect(wrapper.find('ul').at(0).hasClass('mini')).toBe(true);
});
// https://github.com/ant-design/ant-design/issues/24913
// https://github.com/ant-design/ant-design/issues/24501
it('should onChange called when pageSize change', () => {
const onChange = jest.fn();
const onShowSizeChange = jest.fn();
const wrapper = mount(
<Pagination
defaultCurrent={1}
total={500}
onChange={onChange}
onShowSizeChange={onShowSizeChange}
/>,
);
wrapper.find('.ant-select-selector').simulate('mousedown');
expect(wrapper.find('.ant-select-item-option').length).toBe(4);
wrapper.find('.ant-select-item-option').at(1).simulate('click');
expect(onChange).toHaveBeenCalledWith(1, 20);
});
});

View File

@ -165,17 +165,18 @@
&-next {
outline: 0;
a {
button {
color: @text-color;
user-select: none;
}
&:hover a {
&:hover button {
border-color: @primary-5;
}
.@{pagination-prefix-cls}-item-link {
display: block;
width: 100%;
height: 100%;
font-size: 12px;
text-align: center;
@ -198,7 +199,6 @@
&:hover,
&:focus {
cursor: not-allowed;
a,
.@{pagination-prefix-cls}-item-link {
color: @disabled-color;
border-color: @border-color-base;
@ -372,27 +372,18 @@
}
.@{pagination-prefix-cls}-item-link {
&,
&:hover,
&:focus {
color: @text-color-secondary;
background: @disabled-bg;
border-color: @border-color-base;
cursor: not-allowed;
}
color: @disabled-color;
background: @disabled-bg;
border-color: @border-color-base;
cursor: not-allowed;
}
.@{pagination-prefix-cls}-jump-prev,
.@{pagination-prefix-cls}-jump-next {
&:focus,
&:hover {
.@{pagination-prefix-cls}-item-link-icon {
opacity: 0;
}
.@{pagination-prefix-cls}-item-ellipsis {
opacity: 1;
}
}
.@{pagination-prefix-cls}-item-link-icon {
opacity: 0;
}
.@{pagination-prefix-cls}-item-ellipsis {
opacity: 1;
}
}
}

View File

@ -10,8 +10,11 @@ interface CircleProps extends ProgressProps {
progressStatus: string;
}
function getPercentage({ percent, successPercent }: CircleProps) {
function getPercentage({ percent, success, successPercent }: CircleProps) {
const ptg = validProgress(percent);
if (success && 'progress' in success) {
successPercent = success.progress;
}
if (!successPercent) {
return ptg;
}
@ -20,8 +23,11 @@ function getPercentage({ percent, successPercent }: CircleProps) {
return [successPercent, validProgress(ptg - successPtg)];
}
function getStrokeColor({ successPercent, strokeColor }: CircleProps) {
function getStrokeColor({ success, strokeColor, successPercent }: CircleProps) {
const color = strokeColor || null;
if (success && 'progress' in success) {
successPercent = success.progress;
}
if (!successPercent) {
return color;
}

View File

@ -61,14 +61,15 @@ const Line: React.FC<LineProps> = props => {
const {
prefixCls,
percent,
successPercent,
strokeWidth,
size,
strokeColor,
strokeLinecap,
children,
trailColor,
success,
} = props;
let backgroundProps;
if (strokeColor && typeof strokeColor !== 'string') {
backgroundProps = handleGradient(strokeColor);
@ -77,23 +78,45 @@ const Line: React.FC<LineProps> = props => {
background: strokeColor,
};
}
let trailStyle;
if (trailColor && typeof trailColor === 'string') {
trailStyle = {
backgroundColor: trailColor,
};
}
let successColor;
if (success && 'strokeColor' in success) {
successColor = success.strokeColor;
}
let successStyle;
if (successColor && typeof successColor === 'string') {
successStyle = {
backgroundColor: successColor,
};
}
const percentStyle = {
width: `${validProgress(percent)}%`,
height: strokeWidth || (size === 'small' ? 6 : 8),
borderRadius: strokeLinecap === 'square' ? 0 : '',
...backgroundProps,
};
const successPercentStyle = {
let { successPercent } = props;
if (success && 'progress' in success) {
successPercent = success.progress;
}
let successPercentStyle = {
width: `${validProgress(successPercent)}%`,
height: strokeWidth || (size === 'small' ? 6 : 8),
borderRadius: strokeLinecap === 'square' ? 0 : '',
};
if (successStyle) {
successPercentStyle = { ...successPercentStyle, ...successStyle };
}
const successSegment =
successPercent !== undefined ? (
<div className={`${prefixCls}-success-bg`} style={successPercentStyle} />

View File

@ -500,6 +500,35 @@ exports[`Progress render strokeColor 3`] = `
</Progress>
`;
exports[`Progress render successColor progress 1`] = `
<div
class="ant-progress ant-progress-line ant-progress-status-normal ant-progress-show-info ant-progress-default"
>
<div
class="ant-progress-outer"
>
<div
class="ant-progress-inner"
>
<div
class="ant-progress-bg"
style="width: 60%; height: 8px;"
/>
<div
class="ant-progress-success-bg"
style="width: 30%; height: 8px; background-color: rgb(255, 255, 255);"
/>
</div>
</div>
<span
class="ant-progress-text"
title="60%"
>
60%
</span>
</div>
`;
exports[`Progress render trailColor progress 1`] = `
<div
class="ant-progress ant-progress-line ant-progress-status-normal ant-progress-show-info ant-progress-default"

View File

@ -10,13 +10,13 @@ describe('Progress', () => {
rtlTest(Progress);
it('successPercent should decide the progress status when it exists', () => {
const wrapper = mount(<Progress percent={100} successPercent={50} />);
const wrapper = mount(<Progress percent={100} success={{ progress: 50 }} />);
expect(wrapper.find('.ant-progress-status-success')).toHaveLength(0);
wrapper.setProps({ percent: 50, successPercent: 100 });
wrapper.setProps({ percent: 50, success: { progress: 100 } });
expect(wrapper.find('.ant-progress-status-success')).toHaveLength(1);
wrapper.setProps({ percent: 100, successPercent: 0 });
wrapper.setProps({ percent: 100, success: { progress: 0 } });
expect(wrapper.find('.ant-progress-status-success')).toHaveLength(0);
});
@ -36,7 +36,7 @@ describe('Progress', () => {
});
it('render negative successPercent', () => {
const wrapper = mount(<Progress percent={50} successPercent={-20} />);
const wrapper = mount(<Progress percent={50} success={{ progress: -20 }} />);
expect(wrapper.render()).toMatchSnapshot();
});
@ -44,7 +44,7 @@ describe('Progress', () => {
const wrapper = mount(
<Progress
percent={50}
successPercent={10}
success={{ progress: 10 }}
format={(percent, successPercent) => `${percent} ${successPercent}`}
/>,
);
@ -81,6 +81,13 @@ describe('Progress', () => {
expect(wrapper.render()).toMatchSnapshot();
});
it('render successColor progress', () => {
const wrapper = mount(
<Progress percent={60} success={{ progress: 30, strokeColor: '#ffffff' }} />,
);
expect(wrapper.render()).toMatchSnapshot();
});
it('render dashboard zero gapDegree', () => {
const wrapper = mount(<Progress type="dashboard" gapDegree={0} />);
expect(wrapper.render()).toMatchSnapshot();

View File

@ -19,15 +19,15 @@ import { Tooltip, Progress } from 'antd';
ReactDOM.render(
<>
<Tooltip title="3 done / 3 in progress / 4 to do">
<Progress percent={60} successPercent={30} />
<Progress percent={60} success={{ progress: 30 }} />
</Tooltip>
<Tooltip title="3 done / 3 in progress / 4 to do">
<Progress percent={60} successPercent={30} type="circle" />
<Progress percent={60} success={{ progress: 30 }} type="circle" />
</Tooltip>
<Tooltip title="3 done / 3 in progress / 4 to do">
<Progress percent={60} successPercent={30} type="dashboard" />
<Progress percent={60} success={{ progress: 30 }} type="dashboard" />
</Tooltip>
</>,
mountNode,

View File

@ -27,8 +27,8 @@ Properties that shared by all types.
| status | to set the status of the Progress, options: `success` `exception` `normal` `active`(line only) | string | - |
| strokeLinecap | to set the style of the progress linecap | `round` \| `square` | `round` |
| strokeColor | color of progress bar | string | - |
| successPercent | segmented success percent | number | 0 |
| trailColor | color of unfilled part | string | - |
| success | configs of successfully progress bar | { progress: number, strokeColor: string } | - |
### `type="line"`

View File

@ -28,8 +28,8 @@ cover: https://gw.alipayobjects.com/zos/alicdn/xqsDu4ZyR/Progress.svg
| status | 状态,可选:`success` `exception` `normal` `active`(仅限 line) | string | - |
| strokeLinecap | - | `round` \| `square` | `round` |
| strokeColor | 进度条的色彩 | string | - |
| successPercent | 已完成的分段百分比 | number | 0 |
| trailColor | 未完成的分段的颜色 | string | - |
| success | 成功进度条相关配置 | { progress: number, strokeColor: string } | - |
### `type="line"`

View File

@ -8,6 +8,7 @@ import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import { tuple } from '../_util/type';
import devWarning from '../_util/devWarning';
import Line from './Line';
import Circle from './Circle';
import Steps from './Steps';
@ -20,12 +21,17 @@ export type ProgressSize = 'default' | 'small';
export type StringGradients = { [percentage: string]: string };
type FromToGradients = { from: string; to: string };
export type ProgressGradient = { direction?: string } & (StringGradients | FromToGradients);
export interface SuccessProps {
progress?: number;
strokeColor?: string;
}
export interface ProgressProps {
prefixCls?: string;
className?: string;
type?: ProgressType;
percent?: number;
successPercent?: number;
format?: (percent?: number, successPercent?: number) => React.ReactNode;
status?: typeof ProgressStatuses[number];
showInfo?: boolean;
@ -34,11 +40,14 @@ export interface ProgressProps {
strokeColor?: string | ProgressGradient;
trailColor?: string;
width?: number;
success?: SuccessProps;
style?: React.CSSProperties;
gapDegree?: number;
gapPosition?: 'top' | 'bottom' | 'left' | 'right';
size?: ProgressSize;
steps?: number;
/** @deprecated Use `success` instead */
successPercent?: number;
}
export default class Progress extends React.Component<ProgressProps> {
@ -54,7 +63,11 @@ export default class Progress extends React.Component<ProgressProps> {
};
getPercentNumber() {
const { successPercent, percent = 0 } = this.props;
const { percent = 0, success } = this.props;
let { successPercent } = this.props;
if (success && 'progress' in success) {
successPercent = success.progress;
}
return parseInt(
successPercent !== undefined ? successPercent.toString() : percent.toString(),
10,
@ -70,7 +83,11 @@ export default class Progress extends React.Component<ProgressProps> {
}
renderProcessInfo(prefixCls: string, progressStatus: typeof ProgressStatuses[number]) {
const { showInfo, format, type, percent, successPercent } = this.props;
const { showInfo, format, type, percent, success } = this.props;
let { successPercent } = this.props;
if (success && 'progress' in success) {
successPercent = success.progress;
}
if (!showInfo) return null;
let text;
@ -105,6 +122,13 @@ export default class Progress extends React.Component<ProgressProps> {
const prefixCls = getPrefixCls('progress', customizePrefixCls);
const progressStatus = this.getProgressStatus();
const progressInfo = this.renderProcessInfo(prefixCls, progressStatus);
devWarning(
'successPercent' in props,
'Progress',
'`successPercent` is deprecated. Please use `success` instead.',
);
let progress;
// Render progress shape
if (type === 'line') {
@ -148,7 +172,6 @@ export default class Progress extends React.Component<ProgressProps> {
'status',
'format',
'trailColor',
'successPercent',
'strokeWidth',
'width',
'gapDegree',
@ -157,6 +180,8 @@ export default class Progress extends React.Component<ProgressProps> {
'strokeLinecap',
'percent',
'steps',
'success',
'successPercent',
])}
className={classString}
>

View File

@ -1110,6 +1110,7 @@ Array [
</span>
</label>
</div>,
<br />,
<div
class="ant-radio-group ant-radio-group-outline"
>
@ -1153,13 +1154,14 @@ Array [
</span>
</label>
<label
class="ant-radio-wrapper"
class="ant-radio-wrapper ant-radio-wrapper-disabled"
>
<span
class="ant-radio"
class="ant-radio ant-radio-disabled"
>
<input
class="ant-radio-input"
disabled=""
type="radio"
value="Orange"
/>
@ -1172,23 +1174,25 @@ Array [
</span>
</label>
</div>,
<br />,
<br />,
<div
class="ant-radio-group ant-radio-group-outline"
>
<label
class="ant-radio-wrapper ant-radio-wrapper-checked"
class="ant-radio-button-wrapper ant-radio-button-wrapper-checked"
>
<span
class="ant-radio ant-radio-checked"
class="ant-radio-button ant-radio-button-checked"
>
<input
checked=""
class="ant-radio-input"
class="ant-radio-button-input"
type="radio"
value="Apple"
/>
<span
class="ant-radio-inner"
class="ant-radio-button-inner"
/>
</span>
<span>
@ -1196,18 +1200,18 @@ Array [
</span>
</label>
<label
class="ant-radio-wrapper"
class="ant-radio-button-wrapper"
>
<span
class="ant-radio"
class="ant-radio-button"
>
<input
class="ant-radio-input"
class="ant-radio-button-input"
type="radio"
value="Pear"
/>
<span
class="ant-radio-inner"
class="ant-radio-button-inner"
/>
</span>
<span>
@ -1215,18 +1219,83 @@ Array [
</span>
</label>
<label
class="ant-radio-wrapper"
class="ant-radio-button-wrapper"
>
<span
class="ant-radio"
class="ant-radio-button"
>
<input
class="ant-radio-input"
class="ant-radio-button-input"
type="radio"
value="Orange"
/>
<span
class="ant-radio-inner"
class="ant-radio-button-inner"
/>
</span>
<span>
Orange
</span>
</label>
</div>,
<br />,
<br />,
<div
class="ant-radio-group ant-radio-group-solid"
>
<label
class="ant-radio-button-wrapper ant-radio-button-wrapper-checked"
>
<span
class="ant-radio-button ant-radio-button-checked"
>
<input
checked=""
class="ant-radio-button-input"
type="radio"
value="Apple"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
Apple
</span>
</label>
<label
class="ant-radio-button-wrapper"
>
<span
class="ant-radio-button"
>
<input
class="ant-radio-button-input"
type="radio"
value="Pear"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
Pear
</span>
</label>
<label
class="ant-radio-button-wrapper ant-radio-button-wrapper-disabled"
>
<span
class="ant-radio-button ant-radio-button-disabled"
>
<input
class="ant-radio-button-input"
disabled=""
type="radio"
value="Orange"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>

View File

@ -7,11 +7,11 @@ title:
## zh-CN
通过配置 `options` 参数来渲染单选框。
通过配置 `options` 参数来渲染单选框。也可通过 `optionType` 参数来设置 Radio 类型。
## en-US
Render radios by configuring `options`.
Render radios by configuring `options`. Radio type can also be set through the `optionType` parameter.
```jsx
import { Radio } from 'antd';
@ -25,7 +25,7 @@ const options = [
const optionsWithDisabled = [
{ label: 'Apple', value: 'Apple' },
{ label: 'Pear', value: 'Pear' },
{ label: 'Orange', value: 'Orange', disabled: false },
{ label: 'Orange', value: 'Orange', disabled: true },
];
class App extends React.Component {
@ -33,6 +33,7 @@ class App extends React.Component {
value1: 'Apple',
value2: 'Apple',
value3: 'Apple',
value4: 'Apple',
};
onChange1 = e => {
@ -56,16 +57,36 @@ class App extends React.Component {
});
};
onChange4 = e => {
console.log('radio4 checked', e.target.value);
this.setState({
value4: e.target.value,
});
};
render() {
const { value1, value2, value3 } = this.state;
const { value1, value2, value3, value4 } = this.state;
return (
<>
<Radio.Group options={plainOptions} onChange={this.onChange1} value={value1} />
<Radio.Group options={options} onChange={this.onChange2} value={value2} />
<br />
<Radio.Group options={optionsWithDisabled} onChange={this.onChange2} value={value2} />
<br />
<br />
<Radio.Group
options={optionsWithDisabled}
options={options}
onChange={this.onChange3}
value={value3}
optionType="button"
/>
<br />
<br />
<Radio.Group
options={optionsWithDisabled}
onChange={this.onChange4}
value={value4}
optionType="button"
buttonStyle="solid"
/>
</>
);

View File

@ -43,6 +43,7 @@ const RadioGroup: React.FC<RadioGroupProps> = props => {
prefixCls: customizePrefixCls,
className = '',
options,
optionType,
buttonStyle,
disabled,
children,
@ -57,13 +58,14 @@ const RadioGroup: React.FC<RadioGroupProps> = props => {
let childrenToRender = children;
// 如果存在 options, 优先使用
if (options && options.length > 0) {
const optionsPrefixCls = optionType === 'button' ? `${prefixCls}-button` : prefixCls;
childrenToRender = options.map(option => {
if (typeof option === 'string') {
// 此处类型自动推导为 string
return (
<Radio
key={option}
prefixCls={prefixCls}
prefixCls={optionsPrefixCls}
disabled={disabled}
value={option}
checked={value === option}
@ -76,7 +78,7 @@ const RadioGroup: React.FC<RadioGroupProps> = props => {
return (
<Radio
key={`radio-group-value-options-${option.value}`}
prefixCls={prefixCls}
prefixCls={optionsPrefixCls}
disabled={option.disabled || disabled}
value={option.value}
checked={value === option.value}

View File

@ -28,16 +28,17 @@ Radio.
Radio group can wrap a group of `Radio`
| Property | Description | Type | Default |
| --- | --- | --- | --- |
| defaultValue | Default selected value | any | |
| disabled | Disable all radio buttons | boolean | false |
| name | The `name` property of all `input[type="radio"]` children | string | |
| options | set children optional | string\[] \| Array&lt;{ label: string value: string disabled?: boolean }> | |
| size | size for radio button style | `large` \| `middle` \| `small` | |
| value | Used for setting the currently selected value. | any | |
| onChange | The callback function that is triggered when the state changes. | Function(e:Event) | |
| buttonStyle | style type of radio button | `outline` \| `solid` | `outline` |
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| defaultValue | Default selected value | any | | |
| disabled | Disable all radio buttons | boolean | false | |
| name | The `name` property of all `input[type="radio"]` children | string | | |
| options | set children optional | string\[] \| Array&lt;{ label: string value: string disabled?: boolean }> | | |
| size | size for radio button style | `large` \| `middle` \| `small` | | |
| value | Used for setting the currently selected value. | any | | |
| onChange | The callback function that is triggered when the state changes. | Function(e:Event) | | |
| optionType | set Radio optionType | `default` \| `button` | `default` | 4.4.0 |
| buttonStyle | style type of radio button | `outline` \| `solid` | `outline` | |
## Methods

View File

@ -29,16 +29,17 @@ cover: https://gw.alipayobjects.com/zos/alicdn/8cYb5seNB/Radio.svg
单选框组合,用于包裹一组 `Radio`
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| defaultValue | 默认选中的值 | any | |
| disabled | 禁选所有子单选器 | boolean | false |
| name | RadioGroup 下所有 `input[type="radio"]``name` 属性 | string | |
| options | 以配置形式设置子元素 | string\[] \| Array&lt;{ label: string value: string disabled?: boolean }> | |
| size | 大小,只对按钮样式生效 | `large` \| `middle` \| `small` | - |
| value | 用于设置当前选中的值 | any | |
| onChange | 选项变化时的回调函数 | Function(e:Event) | |
| buttonStyle | RadioButton 的风格样式,目前有描边和填色两种风格 | `outline` \| `solid` | `outline` |
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| defaultValue | 默认选中的值 | any | | |
| disabled | 禁选所有子单选器 | boolean | false | | |
| name | RadioGroup 下所有 `input[type="radio"]``name` 属性 | string | | |
| options | 以配置形式设置子元素 | string\[] \| Array&lt;{ label: string value: string disabled?: boolean }> | | |
| size | 大小,只对按钮样式生效 | `large` \| `middle` \| `small` | - | |
| value | 用于设置当前选中的值 | any | | |
| onChange | 选项变化时的回调函数 | Function(e:Event) | | |
| optionType | 用于设置 Radio `options` 类型 | `default` \| `button` | `default` | 4.4.0 |
| buttonStyle | RadioButton 的风格样式,目前有描边和填色两种风格 | `outline` \| `solid` | `outline` | |
## 方法

View File

@ -4,6 +4,7 @@ import { AbstractCheckboxProps } from '../checkbox/Checkbox';
import { SizeType } from '../config-provider/SizeContext';
export type RadioGroupButtonStyle = 'outline' | 'solid';
export type RadioGroupOptionType = 'default' | 'button';
export interface RadioGroupProps extends AbstractCheckboxGroupProps {
defaultValue?: any;
@ -15,6 +16,7 @@ export interface RadioGroupProps extends AbstractCheckboxGroupProps {
name?: string;
children?: React.ReactNode;
id?: string;
optionType?: RadioGroupOptionType;
buttonStyle?: RadioGroupButtonStyle;
}

View File

@ -5,6 +5,7 @@ import { RadioProps, RadioChangeEvent } from './interface';
import { ConfigContext } from '../config-provider';
import RadioGroupContext from './context';
import { composeRef } from '../_util/ref';
import devWarning from '../_util/devWarning';
const InternalRadio: React.ForwardRefRenderFunction<unknown, RadioProps> = (props, ref) => {
const context = React.useContext(RadioGroupContext);
@ -12,6 +13,10 @@ const InternalRadio: React.ForwardRefRenderFunction<unknown, RadioProps> = (prop
const innerRef = React.useRef<HTMLElement>();
const mergedRef = composeRef(ref, innerRef);
React.useEffect(() => {
devWarning(!('optionType' in props), 'Radio', '`optionType` is only support in Radio.Group.');
}, []);
const onChange = (e: RadioChangeEvent) => {
if (props.onChange) {
props.onChange(e);

View File

@ -310,7 +310,7 @@ exports[`renders ./components/rate/demo/basic.md correctly 1`] = `
`;
exports[`renders ./components/rate/demo/character.md correctly 1`] = `
<div>
Array [
<ul
class="ant-rate"
role="radiogroup"
@ -616,8 +616,8 @@ exports[`renders ./components/rate/demo/character.md correctly 1`] = `
</div>
</div>
</li>
</ul>
<br />
</ul>,
<br />,
<ul
class="ant-rate"
role="radiogroup"
@ -734,8 +734,8 @@ exports[`renders ./components/rate/demo/character.md correctly 1`] = `
</div>
</div>
</li>
</ul>
<br />
</ul>,
<br />,
<ul
class="ant-rate"
role="radiogroup"
@ -851,12 +851,440 @@ exports[`renders ./components/rate/demo/character.md correctly 1`] = `
</div>
</div>
</li>
</ul>
</div>
</ul>,
]
`;
exports[`renders ./components/rate/demo/character-function.md correctly 1`] = `
Array [
<ul
class="ant-rate"
role="radiogroup"
tabindex="0"
>
<li
class="ant-rate-star ant-rate-star-full"
>
<div
aria-checked="true"
aria-posinset="1"
aria-setsize="5"
role="radio"
tabindex="0"
>
<div
class="ant-rate-star-first"
>
1
</div>
<div
class="ant-rate-star-second"
>
1
</div>
</div>
</li>
<li
class="ant-rate-star ant-rate-star-full"
>
<div
aria-checked="true"
aria-posinset="2"
aria-setsize="5"
role="radio"
tabindex="0"
>
<div
class="ant-rate-star-first"
>
2
</div>
<div
class="ant-rate-star-second"
>
2
</div>
</div>
</li>
<li
class="ant-rate-star ant-rate-star-zero"
>
<div
aria-checked="false"
aria-posinset="3"
aria-setsize="5"
role="radio"
tabindex="0"
>
<div
class="ant-rate-star-first"
>
3
</div>
<div
class="ant-rate-star-second"
>
3
</div>
</div>
</li>
<li
class="ant-rate-star ant-rate-star-zero"
>
<div
aria-checked="false"
aria-posinset="4"
aria-setsize="5"
role="radio"
tabindex="0"
>
<div
class="ant-rate-star-first"
>
4
</div>
<div
class="ant-rate-star-second"
>
4
</div>
</div>
</li>
<li
class="ant-rate-star ant-rate-star-zero"
>
<div
aria-checked="false"
aria-posinset="5"
aria-setsize="5"
role="radio"
tabindex="0"
>
<div
class="ant-rate-star-first"
>
5
</div>
<div
class="ant-rate-star-second"
>
5
</div>
</div>
</li>
</ul>,
<br />,
<ul
class="ant-rate"
role="radiogroup"
tabindex="0"
>
<li
class="ant-rate-star ant-rate-star-full"
>
<div
aria-checked="true"
aria-posinset="1"
aria-setsize="5"
role="radio"
tabindex="0"
>
<div
class="ant-rate-star-first"
>
<span
aria-label="frown"
class="anticon anticon-frown"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="frown"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M288 421a48 48 0 1096 0 48 48 0 10-96 0zm352 0a48 48 0 1096 0 48 48 0 10-96 0zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm263 711c-34.2 34.2-74 61-118.3 79.8C611 874.2 562.3 884 512 884c-50.3 0-99-9.8-144.8-29.2A370.4 370.4 0 01248.9 775c-34.2-34.2-61-74-79.8-118.3C149.8 611 140 562.3 140 512s9.8-99 29.2-144.8A370.4 370.4 0 01249 248.9c34.2-34.2 74-61 118.3-79.8C413 149.8 461.7 140 512 140c50.3 0 99 9.8 144.8 29.2A370.4 370.4 0 01775.1 249c34.2 34.2 61 74 79.8 118.3C874.2 413 884 461.7 884 512s-9.8 99-29.2 144.8A368.89 368.89 0 01775 775zM512 533c-85.5 0-155.6 67.3-160 151.6a8 8 0 008 8.4h48.1c4.2 0 7.8-3.2 8.1-7.4C420 636.1 461.5 597 512 597s92.1 39.1 95.8 88.6c.3 4.2 3.9 7.4 8.1 7.4H664a8 8 0 008-8.4C667.6 600.3 597.5 533 512 533z"
/>
</svg>
</span>
</div>
<div
class="ant-rate-star-second"
>
<span
aria-label="frown"
class="anticon anticon-frown"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="frown"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M288 421a48 48 0 1096 0 48 48 0 10-96 0zm352 0a48 48 0 1096 0 48 48 0 10-96 0zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm263 711c-34.2 34.2-74 61-118.3 79.8C611 874.2 562.3 884 512 884c-50.3 0-99-9.8-144.8-29.2A370.4 370.4 0 01248.9 775c-34.2-34.2-61-74-79.8-118.3C149.8 611 140 562.3 140 512s9.8-99 29.2-144.8A370.4 370.4 0 01249 248.9c34.2-34.2 74-61 118.3-79.8C413 149.8 461.7 140 512 140c50.3 0 99 9.8 144.8 29.2A370.4 370.4 0 01775.1 249c34.2 34.2 61 74 79.8 118.3C874.2 413 884 461.7 884 512s-9.8 99-29.2 144.8A368.89 368.89 0 01775 775zM512 533c-85.5 0-155.6 67.3-160 151.6a8 8 0 008 8.4h48.1c4.2 0 7.8-3.2 8.1-7.4C420 636.1 461.5 597 512 597s92.1 39.1 95.8 88.6c.3 4.2 3.9 7.4 8.1 7.4H664a8 8 0 008-8.4C667.6 600.3 597.5 533 512 533z"
/>
</svg>
</span>
</div>
</div>
</li>
<li
class="ant-rate-star ant-rate-star-full"
>
<div
aria-checked="true"
aria-posinset="2"
aria-setsize="5"
role="radio"
tabindex="0"
>
<div
class="ant-rate-star-first"
>
<span
aria-label="frown"
class="anticon anticon-frown"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="frown"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M288 421a48 48 0 1096 0 48 48 0 10-96 0zm352 0a48 48 0 1096 0 48 48 0 10-96 0zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm263 711c-34.2 34.2-74 61-118.3 79.8C611 874.2 562.3 884 512 884c-50.3 0-99-9.8-144.8-29.2A370.4 370.4 0 01248.9 775c-34.2-34.2-61-74-79.8-118.3C149.8 611 140 562.3 140 512s9.8-99 29.2-144.8A370.4 370.4 0 01249 248.9c34.2-34.2 74-61 118.3-79.8C413 149.8 461.7 140 512 140c50.3 0 99 9.8 144.8 29.2A370.4 370.4 0 01775.1 249c34.2 34.2 61 74 79.8 118.3C874.2 413 884 461.7 884 512s-9.8 99-29.2 144.8A368.89 368.89 0 01775 775zM512 533c-85.5 0-155.6 67.3-160 151.6a8 8 0 008 8.4h48.1c4.2 0 7.8-3.2 8.1-7.4C420 636.1 461.5 597 512 597s92.1 39.1 95.8 88.6c.3 4.2 3.9 7.4 8.1 7.4H664a8 8 0 008-8.4C667.6 600.3 597.5 533 512 533z"
/>
</svg>
</span>
</div>
<div
class="ant-rate-star-second"
>
<span
aria-label="frown"
class="anticon anticon-frown"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="frown"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M288 421a48 48 0 1096 0 48 48 0 10-96 0zm352 0a48 48 0 1096 0 48 48 0 10-96 0zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm263 711c-34.2 34.2-74 61-118.3 79.8C611 874.2 562.3 884 512 884c-50.3 0-99-9.8-144.8-29.2A370.4 370.4 0 01248.9 775c-34.2-34.2-61-74-79.8-118.3C149.8 611 140 562.3 140 512s9.8-99 29.2-144.8A370.4 370.4 0 01249 248.9c34.2-34.2 74-61 118.3-79.8C413 149.8 461.7 140 512 140c50.3 0 99 9.8 144.8 29.2A370.4 370.4 0 01775.1 249c34.2 34.2 61 74 79.8 118.3C874.2 413 884 461.7 884 512s-9.8 99-29.2 144.8A368.89 368.89 0 01775 775zM512 533c-85.5 0-155.6 67.3-160 151.6a8 8 0 008 8.4h48.1c4.2 0 7.8-3.2 8.1-7.4C420 636.1 461.5 597 512 597s92.1 39.1 95.8 88.6c.3 4.2 3.9 7.4 8.1 7.4H664a8 8 0 008-8.4C667.6 600.3 597.5 533 512 533z"
/>
</svg>
</span>
</div>
</div>
</li>
<li
class="ant-rate-star ant-rate-star-full"
>
<div
aria-checked="true"
aria-posinset="3"
aria-setsize="5"
role="radio"
tabindex="0"
>
<div
class="ant-rate-star-first"
>
<span
aria-label="meh"
class="anticon anticon-meh"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="meh"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M288 421a48 48 0 1096 0 48 48 0 10-96 0zm352 0a48 48 0 1096 0 48 48 0 10-96 0zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm263 711c-34.2 34.2-74 61-118.3 79.8C611 874.2 562.3 884 512 884c-50.3 0-99-9.8-144.8-29.2A370.4 370.4 0 01248.9 775c-34.2-34.2-61-74-79.8-118.3C149.8 611 140 562.3 140 512s9.8-99 29.2-144.8A370.4 370.4 0 01249 248.9c34.2-34.2 74-61 118.3-79.8C413 149.8 461.7 140 512 140c50.3 0 99 9.8 144.8 29.2A370.4 370.4 0 01775.1 249c34.2 34.2 61 74 79.8 118.3C874.2 413 884 461.7 884 512s-9.8 99-29.2 144.8A368.89 368.89 0 01775 775zM664 565H360c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h304c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z"
/>
</svg>
</span>
</div>
<div
class="ant-rate-star-second"
>
<span
aria-label="meh"
class="anticon anticon-meh"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="meh"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M288 421a48 48 0 1096 0 48 48 0 10-96 0zm352 0a48 48 0 1096 0 48 48 0 10-96 0zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm263 711c-34.2 34.2-74 61-118.3 79.8C611 874.2 562.3 884 512 884c-50.3 0-99-9.8-144.8-29.2A370.4 370.4 0 01248.9 775c-34.2-34.2-61-74-79.8-118.3C149.8 611 140 562.3 140 512s9.8-99 29.2-144.8A370.4 370.4 0 01249 248.9c34.2-34.2 74-61 118.3-79.8C413 149.8 461.7 140 512 140c50.3 0 99 9.8 144.8 29.2A370.4 370.4 0 01775.1 249c34.2 34.2 61 74 79.8 118.3C874.2 413 884 461.7 884 512s-9.8 99-29.2 144.8A368.89 368.89 0 01775 775zM664 565H360c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h304c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z"
/>
</svg>
</span>
</div>
</div>
</li>
<li
class="ant-rate-star ant-rate-star-zero"
>
<div
aria-checked="false"
aria-posinset="4"
aria-setsize="5"
role="radio"
tabindex="0"
>
<div
class="ant-rate-star-first"
>
<span
aria-label="smile"
class="anticon anticon-smile"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="smile"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M288 421a48 48 0 1096 0 48 48 0 10-96 0zm352 0a48 48 0 1096 0 48 48 0 10-96 0zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm263 711c-34.2 34.2-74 61-118.3 79.8C611 874.2 562.3 884 512 884c-50.3 0-99-9.8-144.8-29.2A370.4 370.4 0 01248.9 775c-34.2-34.2-61-74-79.8-118.3C149.8 611 140 562.3 140 512s9.8-99 29.2-144.8A370.4 370.4 0 01249 248.9c34.2-34.2 74-61 118.3-79.8C413 149.8 461.7 140 512 140c50.3 0 99 9.8 144.8 29.2A370.4 370.4 0 01775.1 249c34.2 34.2 61 74 79.8 118.3C874.2 413 884 461.7 884 512s-9.8 99-29.2 144.8A368.89 368.89 0 01775 775zM664 533h-48.1c-4.2 0-7.8 3.2-8.1 7.4C604 589.9 562.5 629 512 629s-92.1-39.1-95.8-88.6c-.3-4.2-3.9-7.4-8.1-7.4H360a8 8 0 00-8 8.4c4.4 84.3 74.5 151.6 160 151.6s155.6-67.3 160-151.6a8 8 0 00-8-8.4z"
/>
</svg>
</span>
</div>
<div
class="ant-rate-star-second"
>
<span
aria-label="smile"
class="anticon anticon-smile"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="smile"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M288 421a48 48 0 1096 0 48 48 0 10-96 0zm352 0a48 48 0 1096 0 48 48 0 10-96 0zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm263 711c-34.2 34.2-74 61-118.3 79.8C611 874.2 562.3 884 512 884c-50.3 0-99-9.8-144.8-29.2A370.4 370.4 0 01248.9 775c-34.2-34.2-61-74-79.8-118.3C149.8 611 140 562.3 140 512s9.8-99 29.2-144.8A370.4 370.4 0 01249 248.9c34.2-34.2 74-61 118.3-79.8C413 149.8 461.7 140 512 140c50.3 0 99 9.8 144.8 29.2A370.4 370.4 0 01775.1 249c34.2 34.2 61 74 79.8 118.3C874.2 413 884 461.7 884 512s-9.8 99-29.2 144.8A368.89 368.89 0 01775 775zM664 533h-48.1c-4.2 0-7.8 3.2-8.1 7.4C604 589.9 562.5 629 512 629s-92.1-39.1-95.8-88.6c-.3-4.2-3.9-7.4-8.1-7.4H360a8 8 0 00-8 8.4c4.4 84.3 74.5 151.6 160 151.6s155.6-67.3 160-151.6a8 8 0 00-8-8.4z"
/>
</svg>
</span>
</div>
</div>
</li>
<li
class="ant-rate-star ant-rate-star-zero"
>
<div
aria-checked="false"
aria-posinset="5"
aria-setsize="5"
role="radio"
tabindex="0"
>
<div
class="ant-rate-star-first"
>
<span
aria-label="smile"
class="anticon anticon-smile"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="smile"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M288 421a48 48 0 1096 0 48 48 0 10-96 0zm352 0a48 48 0 1096 0 48 48 0 10-96 0zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm263 711c-34.2 34.2-74 61-118.3 79.8C611 874.2 562.3 884 512 884c-50.3 0-99-9.8-144.8-29.2A370.4 370.4 0 01248.9 775c-34.2-34.2-61-74-79.8-118.3C149.8 611 140 562.3 140 512s9.8-99 29.2-144.8A370.4 370.4 0 01249 248.9c34.2-34.2 74-61 118.3-79.8C413 149.8 461.7 140 512 140c50.3 0 99 9.8 144.8 29.2A370.4 370.4 0 01775.1 249c34.2 34.2 61 74 79.8 118.3C874.2 413 884 461.7 884 512s-9.8 99-29.2 144.8A368.89 368.89 0 01775 775zM664 533h-48.1c-4.2 0-7.8 3.2-8.1 7.4C604 589.9 562.5 629 512 629s-92.1-39.1-95.8-88.6c-.3-4.2-3.9-7.4-8.1-7.4H360a8 8 0 00-8 8.4c4.4 84.3 74.5 151.6 160 151.6s155.6-67.3 160-151.6a8 8 0 00-8-8.4z"
/>
</svg>
</span>
</div>
<div
class="ant-rate-star-second"
>
<span
aria-label="smile"
class="anticon anticon-smile"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="smile"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M288 421a48 48 0 1096 0 48 48 0 10-96 0zm352 0a48 48 0 1096 0 48 48 0 10-96 0zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm263 711c-34.2 34.2-74 61-118.3 79.8C611 874.2 562.3 884 512 884c-50.3 0-99-9.8-144.8-29.2A370.4 370.4 0 01248.9 775c-34.2-34.2-61-74-79.8-118.3C149.8 611 140 562.3 140 512s9.8-99 29.2-144.8A370.4 370.4 0 01249 248.9c34.2-34.2 74-61 118.3-79.8C413 149.8 461.7 140 512 140c50.3 0 99 9.8 144.8 29.2A370.4 370.4 0 01775.1 249c34.2 34.2 61 74 79.8 118.3C874.2 413 884 461.7 884 512s-9.8 99-29.2 144.8A368.89 368.89 0 01775 775zM664 533h-48.1c-4.2 0-7.8 3.2-8.1 7.4C604 589.9 562.5 629 512 629s-92.1-39.1-95.8-88.6c-.3-4.2-3.9-7.4-8.1-7.4H360a8 8 0 00-8 8.4c4.4 84.3 74.5 151.6 160 151.6s155.6-67.3 160-151.6a8 8 0 00-8-8.4z"
/>
</svg>
</span>
</div>
</div>
</li>
</ul>,
]
`;
exports[`renders ./components/rate/demo/clear.md correctly 1`] = `
<div>
Array [
<ul
class="ant-rate"
role="radiogroup"
@ -1162,13 +1590,13 @@ exports[`renders ./components/rate/demo/clear.md correctly 1`] = `
</div>
</div>
</li>
</ul>
</ul>,
<span
class="ant-rate-text"
>
allowClear: true
</span>
<br />
</span>,
<br />,
<ul
class="ant-rate"
role="radiogroup"
@ -1474,13 +1902,13 @@ exports[`renders ./components/rate/demo/clear.md correctly 1`] = `
</div>
</div>
</li>
</ul>
</ul>,
<span
class="ant-rate-text"
>
allowClear: false
</span>
</div>
</span>,
]
`;
exports[`renders ./components/rate/demo/disabled.md correctly 1`] = `
@ -1497,7 +1925,7 @@ exports[`renders ./components/rate/demo/disabled.md correctly 1`] = `
aria-posinset="1"
aria-setsize="5"
role="radio"
tabindex="0"
tabindex="-1"
>
<div
class="ant-rate-star-first"
@ -1557,7 +1985,7 @@ exports[`renders ./components/rate/demo/disabled.md correctly 1`] = `
aria-posinset="2"
aria-setsize="5"
role="radio"
tabindex="0"
tabindex="-1"
>
<div
class="ant-rate-star-first"
@ -1617,7 +2045,7 @@ exports[`renders ./components/rate/demo/disabled.md correctly 1`] = `
aria-posinset="3"
aria-setsize="5"
role="radio"
tabindex="0"
tabindex="-1"
>
<div
class="ant-rate-star-first"
@ -1677,7 +2105,7 @@ exports[`renders ./components/rate/demo/disabled.md correctly 1`] = `
aria-posinset="4"
aria-setsize="5"
role="radio"
tabindex="0"
tabindex="-1"
>
<div
class="ant-rate-star-first"
@ -1737,7 +2165,7 @@ exports[`renders ./components/rate/demo/disabled.md correctly 1`] = `
aria-posinset="5"
aria-setsize="5"
role="radio"
tabindex="0"
tabindex="-1"
>
<div
class="ant-rate-star-first"

View File

@ -0,0 +1,46 @@
---
order: 6
title:
zh-CN: 自定义字符
en-US: Customize character
---
## zh-CN
可以使用 `(RateProps) => ReactNode` 的方式自定义每一个字符。
## en-US
Can customize each character using `(RateProps) => ReactNode`.
```jsx
import { Rate } from 'antd';
import { FrownOutlined, MehOutlined, SmileOutlined } from '@ant-design/icons';
const customIcons = {
1: <FrownOutlined />,
2: <FrownOutlined />,
3: <MehOutlined />,
4: <SmileOutlined />,
5: <SmileOutlined />,
};
ReactDOM.render(
<>
<Rate
defaultValue={2}
character={({ index }) => {
return index + 1;
}}
/>
<br />
<Rate
defaultValue={3}
character={({ index }) => {
return customIcons[index + 1];
}}
/>
</>,
mountNode,
);
```

View File

@ -18,13 +18,13 @@ import { Rate } from 'antd';
import { HeartOutlined } from '@ant-design/icons';
ReactDOM.render(
<div>
<>
<Rate character={<HeartOutlined />} allowHalf />
<br />
<Rate character="A" allowHalf style={{ fontSize: 36 }} />
<br />
<Rate character="好" allowHalf />
</div>,
</>,
mountNode,
);
```

View File

@ -17,13 +17,13 @@ Support set allow to clear star when click again.
import { Rate } from 'antd';
ReactDOM.render(
<div>
<>
<Rate defaultValue={3} />
<span className="ant-rate-text">allowClear: true</span>
<br />
<Rate allowClear={false} defaultValue={3} />
<span className="ant-rate-text">allowClear: false</span>
</div>,
</>,
mountNode,
);
```

View File

@ -14,24 +14,24 @@ Rate component.
## API
| Property | Description | type | Default |
| --- | --- | --- | --- |
| allowClear | whether to allow clear when click again | boolean | true |
| allowHalf | whether to allow semi selection | boolean | false |
| autoFocus | get focus when component mounted | boolean | false |
| character | custom character of rate | ReactNode | [`<StarFilled />`](/components/icon/) |
| className | custom class name of rate | string | |
| count | star count | number | 5 |
| defaultValue | default value | number | 0 |
| disabled | read only, unable to interact | boolean | false |
| style | custom style object of rate | CSSProperties | |
| tooltips | Customize tooltip by each character | string\[] | |
| value | current value | number | |
| onBlur | callback when component lose focus | Function() | |
| onChange | callback when select value | Function(value: number) | |
| onFocus | callback when component get focus | Function() | |
| onHoverChange | callback when hover item | Function(value: number) | |
| onKeyDown | callback when keydown on component | Function(event) | |
| Property | Description | type | Default | Version |
| --- | --- | --- | --- | --- |
| allowClear | whether to allow clear when click again | boolean | true | |
| allowHalf | whether to allow semi selection | boolean | false | |
| autoFocus | get focus when component mounted | boolean | false | |
| character | custom character of rate | ReactNode \| (RateProps) => ReactNode | [`<StarFilled />`](/components/icon/) | Function(): 4.4.0 |
| className | custom class name of rate | string | | |
| count | star count | number | 5 | |
| defaultValue | default value | number | 0 | |
| disabled | read only, unable to interact | boolean | false | |
| style | custom style object of rate | CSSProperties | | |
| tooltips | Customize tooltip by each character | string\[] | | |
| value | current value | number | | |
| onBlur | callback when component lose focus | Function() | | |
| onChange | callback when select value | Function(value: number) | | |
| onFocus | callback when component get focus | Function() | | |
| onHoverChange | callback when hover item | Function(value: number) | | |
| onKeyDown | callback when keydown on component | Function(event) | | |
## Methods

View File

@ -15,24 +15,24 @@ cover: https://gw.alipayobjects.com/zos/alicdn/R5uiIWmxe/Rate.svg
## API
| 属性 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| allowClear | 是否允许再次点击后清除 | boolean | true |
| allowHalf | 是否允许半选 | boolean | false |
| autoFocus | 自动获取焦点 | boolean | false |
| character | 自定义字符 | ReactNode | [`<StarFilled />`](/components/icon/) |
| className | 自定义样式类名 | string | |
| count | star 总数 | number | 5 |
| defaultValue | 默认值 | number | 0 |
| disabled | 只读,无法进行交互 | boolean | false |
| style | 自定义样式对象 | CSSProperties | |
| tooltips | 自定义每项的提示信息 | string\[] | |
| value | 当前数,受控值 | number | |
| onBlur | 失去焦点时的回调 | Function() | |
| onChange | 选择时的回调 | Function(value: number) | |
| onFocus | 获取焦点时的回调 | Function() | |
| onHoverChange | 鼠标经过时数值变化的回调 | Function(value: number) | |
| onKeyDown | 按键回调 | Function(event) | |
| 属性 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| allowClear | 是否允许再次点击后清除 | boolean | true | |
| allowHalf | 是否允许半选 | boolean | false | |
| autoFocus | 自动获取焦点 | boolean | false | |
| character | 自定义字符 | ReactNode \| (RateProps) => ReactNode | [`<StarFilled />`](/components/icon/) | Function(): 4.4.0 |
| className | 自定义样式类名 | string | | |
| count | star 总数 | number | 5 | |
| defaultValue | 默认值 | number | 0 | |
| disabled | 只读,无法进行交互 | boolean | false | |
| style | 自定义样式对象 | CSSProperties | | |
| tooltips | 自定义每项的提示信息 | string\[] | | |
| value | 当前数,受控值 | number | | |
| onBlur | 失去焦点时的回调 | Function() | | |
| onChange | 选择时的回调 | Function(value: number) | | |
| onFocus | 获取焦点时的回调 | Function() | | |
| onHoverChange | 鼠标经过时数值变化的回调 | Function(value: number) | | |
| onKeyDown | 按键回调 | Function(event) | | |
## 方法

View File

@ -1108,6 +1108,26 @@ exports[`renders ./components/result/demo/error.md correctly 1`] = `
>
Please check and modify the following information before resubmitting.
</div>
<div
class="ant-result-extra"
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Go Console
</span>
</button>
<button
class="ant-btn"
type="button"
>
<span>
Buy Again
</span>
</button>
</div>
<div
class="ant-result-content"
>
@ -1190,26 +1210,6 @@ exports[`renders ./components/result/demo/error.md correctly 1`] = `
</div>
</div>
</div>
<div
class="ant-result-extra"
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Go Console
</span>
</button>
<button
class="ant-btn"
type="button"
>
<span>
Buy Again
</span>
</button>
</div>
</div>
`;

View File

@ -104,8 +104,8 @@ const Result: ResultType = props => (
{renderIcon(prefixCls, props)}
<div className={`${prefixCls}-title`}>{title}</div>
{subTitle && <div className={`${prefixCls}-subtitle`}>{subTitle}</div>}
{children && <div className={`${prefixCls}-content`}>{children}</div>}
{renderExtra(prefixCls, props)}
{children && <div className={`${prefixCls}-content`}>{children}</div>}
</div>
);
}}

View File

@ -741,7 +741,7 @@ exports[`renders ./components/select/demo/custom-tag-render.md correctly 1`] = `
gold
<span
aria-label="close"
class="anticon anticon-close"
class="anticon anticon-close ant-tag-close-icon"
role="img"
tabindex="-1"
>
@ -770,7 +770,7 @@ exports[`renders ./components/select/demo/custom-tag-render.md correctly 1`] = `
cyan
<span
aria-label="close"
class="anticon anticon-close"
class="anticon anticon-close ant-tag-close-icon"
role="img"
tabindex="-1"
>

Some files were not shown because too many files have changed in this diff Show More