chore: Merge master branch and fix conflicts

This commit is contained in:
诸岳 2020-04-26 13:34:11 +08:00
commit f955620601
104 changed files with 1457 additions and 1238 deletions

View File

@ -1,7 +1,3 @@
const commonGlobals = {
gtag: true,
};
module.exports = {
extends: [
'airbnb',
@ -38,7 +34,6 @@ module.exports = {
{
files: ['*.md'],
globals: {
...commonGlobals,
React: true,
ReactDOM: true,
mountNode: true,
@ -124,5 +119,7 @@ module.exports = {
'unicorn/expiring-todo-comments': 2,
'unicorn/no-abusive-eslint-disable': 2,
},
globals: commonGlobals,
globals: {
gtag: true,
},
};

View File

@ -15,6 +15,30 @@ timeline: true
---
## 4.1.5
`2020-04-25`
- 🐞 Fix Button.Group align style. [#23590](https://github.com/ant-design/ant-design/pull/23590)
- 🐞 Fix Select cannot trigger open by clicking arrow icon. [#23448](https://github.com/ant-design/ant-design/pull/23448)
- 🐞 Fix Form fields shake when `@form-item-margin-bottom` is customize and switching the validing info. [#23436](https://github.com/ant-design/ant-design/pull/23436) [@yoyo837](https://github.com/yoyo837)
- 🐞 Fix the first Divider render differently with others. [#23438](https://github.com/ant-design/ant-design/pull/23438)
- 🐞 Fix nest ConfigProvider missing `prefixCls` value. [#23423](https://github.com/ant-design/ant-design/pull/23423)
- 🐞 Fix Carousel tabbed Radio/Checkbox to non-active slide. [#23380](https://github.com/ant-design/ant-design/pull/23380)
- 🐞 Fix Tree with virtual scroll frozen by quick `loadData`. [#23581](https://github.com/ant-design/ant-design/pull/23581)
- 🐞 Fix Steps style in IE11 when direction is vertical. [#23561](https://github.com/ant-design/ant-design/pull/23561) [@AdrianoRuberto](https://github.com/AdrianoRuberto)
- 🐞 Fix Input.Search height affected by `suffix` and `react key` warning. [#23527](https://github.com/ant-design/ant-design/pull/23527)
- 🐞 Fix Menu behavior when hover on submenu gap. [#23511](https://github.com/ant-design/ant-design/pull/23511)
- 🐞 Fix Tree custom icon missing when node is loading data. [#23494](https://github.com/ant-design/ant-design/pull/23494)
- RTL
- 🐞 Fix Alert RTL style when set both `showIcon` and `closable`. [#23526](https://github.com/ant-design/ant-design/pull/23526)
- 🐞 Fix Button RTL style when loading. [#23399](https://github.com/ant-design/ant-design/pull/23399)
- 🐞 Fix Collapse that icon position is incorrect in RTL. [#23445](https://github.com/ant-design/ant-design/pull/23445)
- 🐞 Fix Select group label style in RTL. [#23404](https://github.com/ant-design/ant-design/pull/23404)
- 🐞 Fix Statistic RTL style. [#23397](https://github.com/ant-design/ant-design/pull/23397)
- TypeScript
- 🐞 Fix type definition of `selections` for Table. [#23462](https://github.com/ant-design/ant-design/pull/23462) [@xiaoxintang](https://github.com/xiaoxintang)
## 4.1.4
`2020-04-18`

View File

@ -15,6 +15,30 @@ timeline: true
---
## 4.1.5
`2020-04-25`
- 🐞 修复 Button.Group 中按钮没有对齐的问题。[#23590](https://github.com/ant-design/ant-design/pull/23590)
- 🐞 修复 Select 箭头图标点击无法触发下拉的问题。[#23448](https://github.com/ant-design/ant-design/pull/23448)
- 🐞 修复 Form 自定义 `@form-item-margin-bottom` 变量时表单校验抖动的问题。[#23436](https://github.com/ant-design/ant-design/pull/23436) [@yoyo837](https://github.com/yoyo837)
- 🐞 修复第一个 Divider 渲染时样式不一致的问题。[#23438](https://github.com/ant-design/ant-design/pull/23438)
- 🐞 修复嵌套 ConfigProvider 会丢失 `prefixCls` 值的问题。[#23423](https://github.com/ant-design/ant-design/pull/23423)
- 🐞 修复 Carousel 键盘切换到非活跃 slide 上的 Radio/Checkbox 的问题。[#23380](https://github.com/ant-design/ant-design/pull/23380)
- 🐞 修复 Tree 使用虚拟滚动时会因为 `loadData` 更新过快而锁死的问题。[#23581](https://github.com/ant-design/ant-design/pull/23581)
- 🐞 修复 Steps 组件竖直展示时在 IE11 下样式错误的问题。[#23561](https://github.com/ant-design/ant-design/pull/23561) [@AdrianoRuberto](https://github.com/AdrianoRuberto)
- 🐞 修复 Input.Search 高度被 `suffix` 撑高的问题和报 `react key` 重复警告的问题。[#23527](https://github.com/ant-design/ant-design/pull/23527)
- 🐞 修复 Menu 鼠标移到缝隙处子菜单会消失的问题。[#23511](https://github.com/ant-design/ant-design/pull/23511)
- 🐞 修复 Tree 自定义图标在加载状态下消失的问题。[#23494](https://github.com/ant-design/ant-design/pull/23494)
- RTL
- 🐞 修复 Alert 在 `showIcon``closable` 都存在时的 RTL 样式问题。[#23526](https://github.com/ant-design/ant-design/pull/23526)
- 🐞 修复 Button 在 RTL 下 loading 样式不正确的问题。[#23399](https://github.com/ant-design/ant-design/pull/23399)
- 🐞 修复 Collapse 在 RTL 下切换图标位置不正确的问题。[#23445](https://github.com/ant-design/ant-design/pull/23445)
- 🐞 修复 Select 分组名称的 RTL 样式问题。[#23404](https://github.com/ant-design/ant-design/pull/23404)
- 🐞 修复 Statistic 的 RTL 样式不正确的问题。[#23397](https://github.com/ant-design/ant-design/pull/23397)
- TypeScript
- 🐞 修复 Table 的 `selections` 类型定义。[#23462](https://github.com/ant-design/ant-design/pull/23462) [@xiaoxintang](https://github.com/xiaoxintang)
## 4.1.4
`2020-04-18`

View File

@ -33,7 +33,7 @@ Uma solução empresarial de design e biblioteca UI para React.
## 🖥 Suporte aos ambientes
- Navegadores modernos e Internet Explorer 11+ (com [polyfills](https://ant.design/docs/react/getting-started#Compatibility))
- Navegadores modernos e Internet Explorer 11 (com [polyfills](https://ant.design/docs/react/getting-started#Compatibility))
- Renderização no lado do servidor (server-side)
- [Electron](https://www.electronjs.org/)

View File

@ -33,7 +33,7 @@
## 🖥 支持环境
- 现代浏览器和 IE11 及以上
- 现代浏览器和 IE11(需要 [polyfills](https://ant.design/docs/react/getting-started-cn#兼容性)
- 支持服务端渲染。
- [Electron](https://www.electronjs.org/)

View File

@ -14,7 +14,7 @@ An enterprise-class UI design language and React UI library.
[![david deps][david-image]][david-url] [![david devDeps][david-dev-image]][david-dev-url] [![Total alerts][lgtm-image]][lgtm-url] [![FOSSA Status][fossa-image]][fossa-url] [![Issues need help][help-wanted-image]][help-wanted-url]
[![Follow Twitter][twitter-image]][twitter-url] [![Gitter][gitter-english-image]][gitter-english-url] [![Gitter][gitter-chinese-image]][gitter-chinese-url]
[![Follow Twitter][twitter-image]][twitter-url] [![Gitter][gitter-english-image]][gitter-english-url] [![Gitter][gitter-chinese-image]][gitter-chinese-url] [![[SemVer stability]][semver-stability-image]][semver-stability-url]
[npm-image]: http://img.shields.io/npm/v/antd.svg?style=flat-square
[npm-url]: http://npmjs.org/package/antd
@ -38,10 +38,12 @@ An enterprise-class UI design language and React UI library.
[help-wanted-url]: https://github.com/ant-design/ant-design/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22
[twitter-image]: https://img.shields.io/twitter/follow/AntDesignUI.svg?label=Ant%20Design&style=social
[twitter-url]: https://twitter.com/AntDesignUI
[gitter-english-image]: https://img.shields.io/gitter/room/ant-design/ant-design-english.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjEyMzUiIGhlaWdodD0iNjUwIiB2aWV3Qm94PSIwIDAgNzQxMCAzOTAwIj4NCjxyZWN0IHdpZHRoPSI3NDEwIiBoZWlnaHQ9IjM5MDAiIGZpbGw9IiNiMjIyMzQiLz4NCjxwYXRoIGQ9Ik0wLDQ1MEg3NDEwbTAsNjAwSDBtMCw2MDBINzQxMG0wLDYwMEgwbTAsNjAwSDc0MTBtMCw2MDBIMCIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjMwMCIvPg0KPHJlY3Qgd2lkdGg9IjI5NjQiIGhlaWdodD0iMjEwMCIgZmlsbD0iIzNjM2I2ZSIvPg0KPGcgZmlsbD0iI2ZmZiI%2BDQo8ZyBpZD0iczE4Ij4NCjxnIGlkPSJzOSI%2BDQo8ZyBpZD0iczUiPg0KPGcgaWQ9InM0Ij4NCjxwYXRoIGlkPSJzIiBkPSJNMjQ3LDkwIDMxNy41MzQyMzAsMzA3LjA4MjAzOSAxMzIuODczMjE4LDE3Mi45MTc5NjFIMzYxLjEyNjc4MkwxNzYuNDY1NzcwLDMwNy4wODIwMzl6Ii8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB5PSI0MjAiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHk9Ijg0MCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTI2MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTY4MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjczQiIHg9IjI0NyIgeT0iMjEwIi8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzOSIgeD0iNDk0Ii8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzMTgiIHg9Ijk4OCIvPg0KPHVzZSB4bGluazpocmVmPSIjczkiIHg9IjE5NzYiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3M1IiB4PSIyNDcwIi8%2BDQo8L2c%2BDQo8L3N2Zz4%3D
[gitter-english-image]: https://img.shields.io/gitter/room/ant-design/ant-design-english.svg?style=flat-square&logoWidth=18&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjEyMzUiIGhlaWdodD0iNjUwIiB2aWV3Qm94PSIwIDAgNzQxMCAzOTAwIj4NCjxyZWN0IHdpZHRoPSI3NDEwIiBoZWlnaHQ9IjM5MDAiIGZpbGw9IiNiMjIyMzQiLz4NCjxwYXRoIGQ9Ik0wLDQ1MEg3NDEwbTAsNjAwSDBtMCw2MDBINzQxMG0wLDYwMEgwbTAsNjAwSDc0MTBtMCw2MDBIMCIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjMwMCIvPg0KPHJlY3Qgd2lkdGg9IjI5NjQiIGhlaWdodD0iMjEwMCIgZmlsbD0iIzNjM2I2ZSIvPg0KPGcgZmlsbD0iI2ZmZiI%2BDQo8ZyBpZD0iczE4Ij4NCjxnIGlkPSJzOSI%2BDQo8ZyBpZD0iczUiPg0KPGcgaWQ9InM0Ij4NCjxwYXRoIGlkPSJzIiBkPSJNMjQ3LDkwIDMxNy41MzQyMzAsMzA3LjA4MjAzOSAxMzIuODczMjE4LDE3Mi45MTc5NjFIMzYxLjEyNjc4MkwxNzYuNDY1NzcwLDMwNy4wODIwMzl6Ii8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB5PSI0MjAiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHk9Ijg0MCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTI2MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTY4MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjczQiIHg9IjI0NyIgeT0iMjEwIi8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzOSIgeD0iNDk0Ii8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzMTgiIHg9Ijk4OCIvPg0KPHVzZSB4bGluazpocmVmPSIjczkiIHg9IjE5NzYiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3M1IiB4PSIyNDcwIi8%2BDQo8L2c%2BDQo8L3N2Zz4%3D
[gitter-english-url]: https://gitter.im/ant-design/ant-design-english?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge
[gitter-chinese-image]: https://img.shields.io/gitter/room/ant-design/ant-design.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjkwMCIgaGVpZ2h0PSI2MDAiIHZpZXdCb3g9IjAgMCAzMCAyMCI%2BDQo8ZGVmcz4NCjxwYXRoIGlkPSJzIiBkPSJNMCwtMSAwLjU4Nzc4NSwwLjgwOTAxNyAtMC45NTEwNTcsLTAuMzA5MDE3SDAuOTUxMDU3TC0wLjU4Nzc4NSwwLjgwOTAxN3oiIGZpbGw9IiNmZmRlMDAiLz4NCjwvZGVmcz4NCjxyZWN0IHdpZHRoPSIzMCIgaGVpZ2h0PSIyMCIgZmlsbD0iI2RlMjkxMCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNSw1KSBzY2FsZSgzKSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsMikgcm90YXRlKDIzLjAzNjI0MykiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEyLDQpIHJvdGF0ZSg0NS44Njk4OTgpIi8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMiw3KSByb3RhdGUoNjkuOTQ1Mzk2KSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsOSkgcm90YXRlKDIwLjY1OTgwOCkiLz4NCjwvc3ZnPg%3D%3D
[gitter-chinese-image]: https://img.shields.io/gitter/room/ant-design/ant-design.svg?style=flat-square&logoWidth=18&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjkwMCIgaGVpZ2h0PSI2MDAiIHZpZXdCb3g9IjAgMCAzMCAyMCI%2BDQo8ZGVmcz4NCjxwYXRoIGlkPSJzIiBkPSJNMCwtMSAwLjU4Nzc4NSwwLjgwOTAxNyAtMC45NTEwNTcsLTAuMzA5MDE3SDAuOTUxMDU3TC0wLjU4Nzc4NSwwLjgwOTAxN3oiIGZpbGw9IiNmZmRlMDAiLz4NCjwvZGVmcz4NCjxyZWN0IHdpZHRoPSIzMCIgaGVpZ2h0PSIyMCIgZmlsbD0iI2RlMjkxMCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNSw1KSBzY2FsZSgzKSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsMikgcm90YXRlKDIzLjAzNjI0MykiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEyLDQpIHJvdGF0ZSg0NS44Njk4OTgpIi8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMiw3KSByb3RhdGUoNjkuOTQ1Mzk2KSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsOSkgcm90YXRlKDIwLjY1OTgwOCkiLz4NCjwvc3ZnPg%3D%3D
[gitter-chinese-url]: https://gitter.im/ant-design/ant-design?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
[semver-stability-url]: https://dependabot.com/compatibility-score.html/?dependency-name=antd&package-manager=npm_and_yarn&new-version=latest
[semver-stability-image]: https://api.dependabot.com/badges/compatibility_score?dependency-name=antd&package-manager=npm_and_yarn&target-version=latest&version-scheme=semver
</div>
@ -60,7 +62,7 @@ English | [Português](./README-pt_BR.md) | [简体中文](./README-zh_CN.md)
## 🖥 Environment Support
- Modern browsers and Internet Explorer 11+ (with [polyfills](https://ant.design/docs/react/getting-started#Compatibility))
- Modern browsers and Internet Explorer 11 (with [polyfills](https://ant.design/docs/react/getting-started#Compatibility))
- Server-side Rendering
- [Electron](https://www.electronjs.org/)
@ -110,7 +112,7 @@ See [i18n](http://ant.design/docs/react/i18n).
## 🔗 Links
- [Home page](http://ant.design/)
- [Components](http://ant.design/docs/react/introduce)
- [Components](https://ant.design/components/button/)
- [Ant Design Pro](http://pro.ant.design/)
- [Change Log](CHANGELOG.en-US.md)
- [rc-components](http://react-component.github.io/)

View File

@ -1,71 +0,0 @@
const __NULL__ = { notExist: true };
type ElementType<P> = {
prototype: P;
};
export function spyElementPrototypes<P extends {}>(Element: ElementType<P>, properties: P) {
const propNames = Object.keys(properties);
const originDescriptors = {};
propNames.forEach(propName => {
const originDescriptor = Object.getOwnPropertyDescriptor(Element.prototype, propName);
originDescriptors[propName] = originDescriptor || __NULL__;
const spyProp = properties[propName];
if (typeof spyProp === 'function') {
// If is a function
Element.prototype[propName] = function spyFunc(...args) {
return spyProp.call(this, originDescriptor, ...args);
};
} else {
// Otherwise tread as a property
Object.defineProperty(Element.prototype, propName, {
...spyProp,
set(value) {
if (spyProp.set) {
return spyProp.set.call(this, originDescriptor, value);
}
return originDescriptor.set(value);
},
get() {
if (spyProp.get) {
return spyProp.get.call(this, originDescriptor);
}
return originDescriptor.get();
},
});
}
});
return {
mockRestore() {
propNames.forEach(propName => {
const originDescriptor = originDescriptors[propName];
if (originDescriptor === __NULL__) {
delete Element.prototype[propName];
} else if (typeof originDescriptor === 'function') {
Element.prototype[propName] = originDescriptor;
} else {
Object.defineProperty(Element.prototype, propName, originDescriptor);
}
});
},
};
}
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends (...args: any[]) => any ? K : never;
}[keyof T] &
string;
export function spyElementPrototype<P extends {}, K extends FunctionPropertyNames<P>>(
Element: ElementType<P>,
propName: K,
property: P[K],
) {
return spyElementPrototypes(Element, {
[propName]: property,
});
}

View File

@ -9,6 +9,7 @@ interface Motion {
motionEnter?: boolean;
motionLeave?: boolean;
motionLeaveImmediately?: boolean; // Trigger leave motion immediately
motionDeadline?: number;
removeOnLeave?: boolean;
leavedClassName?: string;
onAppearStart?: MotionFunc;
@ -35,6 +36,7 @@ const collapseMotion: Motion = {
onEnterActive: getRealHeight,
onLeaveStart: getCurrentHeight,
onLeaveActive: getCollapsedHeight,
motionDeadline: 500,
};
export default collapseMotion;

View File

@ -1,13 +1,13 @@
import React from 'react';
import { mount } from 'enzyme';
import Affix from '..';
import { mount, ReactWrapper, HTMLAttributes } from 'enzyme';
import ResizeObserverImpl from 'rc-resize-observer';
import Affix, { AffixProps, AffixState } from '..';
import { getObserverEntities } from '../utils';
import Button from '../../button';
import { spyElementPrototype } from '../../__tests__/util/domHook';
import rtlTest from '../../../tests/shared/rtlTest';
import { sleep } from '../../../tests/utils';
const events: any = {};
const events: Partial<Record<keyof HTMLElementEventMap, (ev: Partial<Event>) => void>> = {};
class AffixMounter extends React.Component<{
offsetBottom?: number;
@ -16,12 +16,14 @@ class AffixMounter extends React.Component<{
}> {
private container: HTMLDivElement;
private affix: Affix;
public affix: Affix;
componentDidMount() {
this.container.addEventListener = jest.fn().mockImplementation((event, cb) => {
events[event] = cb;
});
this.container.addEventListener = jest
.fn()
.mockImplementation((event: keyof HTMLElementEventMap, cb: (ev: Partial<Event>) => void) => {
events[event] = cb;
});
}
getTarget = () => this.container;
@ -30,7 +32,7 @@ class AffixMounter extends React.Component<{
return (
<div
ref={node => {
this.container = node;
this.container = node!;
}}
className="container"
>
@ -38,7 +40,7 @@ class AffixMounter extends React.Component<{
className="fixed"
target={this.getTarget}
ref={ele => {
this.affix = ele;
this.affix = ele!;
}}
{...this.props}
>
@ -52,18 +54,19 @@ class AffixMounter extends React.Component<{
describe('Affix Render', () => {
rtlTest(Affix);
let wrapper;
let domMock;
const domMock = jest.spyOn(HTMLElement.prototype, 'getBoundingClientRect');
let affixMounterWrapper: ReactWrapper<unknown, unknown, AffixMounter>;
let affixWrapper: ReactWrapper<AffixProps, AffixState, Affix>;
const classRect: any = {
const classRect: Record<string, DOMRect> = {
container: {
top: 0,
bottom: 100,
},
} as DOMRect,
};
beforeAll(() => {
domMock = spyElementPrototype(HTMLElement, 'getBoundingClientRect', function mockBounding() {
domMock.mockImplementation(function fn(this: HTMLElement) {
return (
classRect[this.className] || {
top: 0,
@ -77,11 +80,14 @@ describe('Affix Render', () => {
domMock.mockRestore();
});
const movePlaceholder = async top => {
const movePlaceholder = async (top: number) => {
classRect.fixed = {
top,
bottom: top,
};
} as DOMRect;
if (events.scroll == null) {
throw new Error('scroll should be set');
}
events.scroll({
type: 'scroll',
});
@ -91,53 +97,53 @@ describe('Affix Render', () => {
it('Anchor render perfectly', async () => {
document.body.innerHTML = '<div id="mounter" />';
wrapper = mount(<AffixMounter />, { attachTo: document.getElementById('mounter') });
affixMounterWrapper = mount(<AffixMounter />, { attachTo: document.getElementById('mounter') });
await sleep(20);
await movePlaceholder(0);
expect(wrapper.instance().affix.state.affixStyle).toBeFalsy();
expect(affixMounterWrapper.instance().affix.state.affixStyle).toBeFalsy();
await movePlaceholder(-100);
expect(wrapper.instance().affix.state.affixStyle).toBeTruthy();
expect(affixMounterWrapper.instance().affix.state.affixStyle).toBeTruthy();
await movePlaceholder(0);
expect(wrapper.instance().affix.state.affixStyle).toBeFalsy();
expect(affixMounterWrapper.instance().affix.state.affixStyle).toBeFalsy();
});
it('support offsetBottom', async () => {
document.body.innerHTML = '<div id="mounter" />';
wrapper = mount(<AffixMounter offsetBottom={0} />, {
affixMounterWrapper = mount(<AffixMounter offsetBottom={0} />, {
attachTo: document.getElementById('mounter'),
});
await sleep(20);
await movePlaceholder(300);
expect(wrapper.instance().affix.state.affixStyle).toBeTruthy();
expect(affixMounterWrapper.instance().affix.state.affixStyle).toBeTruthy();
await movePlaceholder(0);
expect(wrapper.instance().affix.state.affixStyle).toBeFalsy();
expect(affixMounterWrapper.instance().affix.state.affixStyle).toBeFalsy();
await movePlaceholder(300);
expect(wrapper.instance().affix.state.affixStyle).toBeTruthy();
expect(affixMounterWrapper.instance().affix.state.affixStyle).toBeTruthy();
});
it('updatePosition when offsetTop changed', async () => {
document.body.innerHTML = '<div id="mounter" />';
wrapper = mount(<AffixMounter offsetTop={0} />, {
affixMounterWrapper = mount(<AffixMounter offsetTop={0} />, {
attachTo: document.getElementById('mounter'),
});
await sleep(20);
await movePlaceholder(-100);
expect(wrapper.instance().affix.state.affixStyle.top).toBe(0);
wrapper.setProps({
expect(affixMounterWrapper.instance().affix.state.affixStyle?.top).toBe(0);
affixMounterWrapper.setProps({
offsetTop: 10,
});
await sleep(20);
expect(wrapper.instance().affix.state.affixStyle.top).toBe(10);
expect(affixMounterWrapper.instance().affix.state.affixStyle?.top).toBe(10);
});
describe('updatePosition when target changed', () => {
@ -145,11 +151,11 @@ describe('Affix Render', () => {
document.body.innerHTML = '<div id="mounter" />';
const container = document.querySelector('#id') as HTMLDivElement;
const getTarget = () => container;
wrapper = mount(<Affix target={getTarget}>{null}</Affix>);
wrapper.setProps({ target: null });
expect(wrapper.instance().state.status).toBe(0);
expect(wrapper.instance().state.affixStyle).toBe(undefined);
expect(wrapper.instance().state.placeholderStyle).toBe(undefined);
affixWrapper = mount(<Affix target={getTarget}>{null}</Affix>);
affixWrapper.setProps({ target: () => null });
expect(affixWrapper.instance().state.status).toBe(0);
expect(affixWrapper.instance().state.affixStyle).toBe(undefined);
expect(affixWrapper.instance().state.placeholderStyle).toBe(undefined);
});
it('instance change', async () => {
@ -157,53 +163,68 @@ describe('Affix Render', () => {
const container = document.createElement('div');
document.body.appendChild(container);
let target = container;
let target: HTMLDivElement | null = container;
const originLength = getObserverLength();
const getTarget = () => target;
wrapper = mount(<Affix target={getTarget}>{null}</Affix>);
affixWrapper = mount(<Affix target={getTarget}>{null}</Affix>);
await sleep(50);
expect(getObserverLength()).toBe(originLength + 1);
target = null;
wrapper.setProps({});
wrapper.update();
affixWrapper.setProps({});
affixWrapper.update();
await sleep(50);
expect(getObserverLength()).toBe(originLength);
});
});
describe('updatePosition when size changed', () => {
function test(name, index) {
it(name, async () => {
document.body.innerHTML = '<div id="mounter" />';
it.each([
{ name: 'inner', index: 0 },
{ name: 'outer', index: 1 },
])(name, async ({ index }) => {
document.body.innerHTML = '<div id="mounter" />';
const updateCalled = jest.fn();
wrapper = mount(<AffixMounter offsetBottom={0} onTestUpdatePosition={updateCalled} />, {
const updateCalled = jest.fn();
affixMounterWrapper = mount(
<AffixMounter offsetBottom={0} onTestUpdatePosition={updateCalled} />,
{
attachTo: document.getElementById('mounter'),
});
},
);
await sleep(20);
await sleep(20);
await movePlaceholder(300);
expect(wrapper.instance().affix.state.affixStyle).toBeTruthy();
await sleep(20);
wrapper.update();
await movePlaceholder(300);
expect(affixMounterWrapper.instance().affix.state.affixStyle).toBeTruthy();
await sleep(20);
affixMounterWrapper.update();
// Mock trigger resize
updateCalled.mockReset();
wrapper
.find('ResizeObserver')
.at(index)
.instance()
.onResize([{ target: { getBoundingClientRect: () => ({ width: 99, height: 99 }) } }]);
await sleep(20);
// Mock trigger resize
updateCalled.mockReset();
const resizeObserverInstance: ReactWrapper<
HTMLAttributes,
unknown,
ResizeObserverImpl
> = affixMounterWrapper.find('ResizeObserver') as any;
resizeObserverInstance
.at(index)
.instance()
.onResize(
[
{
target: {
getBoundingClientRect: () => ({ width: 99, height: 99 }),
} as Element,
contentRect: {} as DOMRect,
},
],
({} as unknown) as ResizeObserver,
);
await sleep(20);
expect(updateCalled).toHaveBeenCalled();
});
}
test('inner', 0);
test('outer', 1);
expect(updateCalled).toHaveBeenCalled();
});
});
});

View File

@ -709,7 +709,7 @@ exports[`renders ./components/alert/demo/icon.md correctly 1`] = `
/>
</div>
<div
class="ant-alert ant-alert-warning"
class="ant-alert ant-alert-warning ant-alert-closable"
data-show="true"
>
<span
@ -740,6 +740,32 @@ exports[`renders ./components/alert/demo/icon.md correctly 1`] = `
<span
class="ant-alert-description"
/>
<button
class="ant-alert-close-icon"
tabindex="0"
type="button"
>
<span
aria-label="close"
class="anticon anticon-close"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="close"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"
/>
</svg>
</span>
</button>
</div>
<div
class="ant-alert ant-alert-error"
@ -851,7 +877,7 @@ exports[`renders ./components/alert/demo/icon.md correctly 1`] = `
</span>
</div>
<div
class="ant-alert ant-alert-warning ant-alert-with-description"
class="ant-alert ant-alert-warning ant-alert-with-description ant-alert-closable"
data-show="true"
>
<span
@ -887,6 +913,32 @@ exports[`renders ./components/alert/demo/icon.md correctly 1`] = `
>
This is a warning notice about copywriting.
</span>
<button
class="ant-alert-close-icon"
tabindex="0"
type="button"
>
<span
aria-label="close"
class="anticon anticon-close"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="close"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"
/>
</svg>
</span>
</button>
</div>
<div
class="ant-alert ant-alert-error ant-alert-with-description"

View File

@ -39,20 +39,20 @@ describe('Alert', () => {
describe('data and aria props', () => {
it('sets data attributes on input', () => {
const wrapper = mount(<Alert data-test="test-id" data-id="12345" />);
const wrapper = mount(<Alert data-test="test-id" data-id="12345" message={null} />);
const input = wrapper.find('.ant-alert').getDOMNode();
expect(input.getAttribute('data-test')).toBe('test-id');
expect(input.getAttribute('data-id')).toBe('12345');
});
it('sets aria attributes on input', () => {
const wrapper = mount(<Alert aria-describedby="some-label" />);
const wrapper = mount(<Alert aria-describedby="some-label" message={null} />);
const input = wrapper.find('.ant-alert').getDOMNode();
expect(input.getAttribute('aria-describedby')).toBe('some-label');
});
it('sets role attribute on input', () => {
const wrapper = mount(<Alert role="status" />);
const wrapper = mount(<Alert role="status" message={null} />);
const input = wrapper.find('.ant-alert').getDOMNode();
expect(input.getAttribute('role')).toBe('status');
});
@ -60,6 +60,8 @@ describe('Alert', () => {
const testIt = process.env.REACT === '15' ? it.skip : it;
testIt('ErrorBoundary', () => {
// TODO: Change to @ts-expect-error once typescript is at 3.9
// @ts-ignore
// eslint-disable-next-line react/jsx-no-undef
const ThrowError = () => <NotExisted />;
const wrapper = mount(
@ -83,12 +85,7 @@ describe('Alert', () => {
);
wrapper.find('.ant-alert').simulate('mouseenter');
await sleep(0);
expect(
wrapper
.find(Tooltip)
.instance()
.getPopupDomNode(),
).toBeTruthy();
expect(wrapper.find<Tooltip>(Tooltip).instance().getPopupDomNode()).toBeTruthy();
jest.useFakeTimers();
});
@ -104,12 +101,7 @@ describe('Alert', () => {
);
wrapper.find('.ant-alert').simulate('click');
await sleep(0);
expect(
wrapper
.find(Popconfirm)
.instance()
.getPopupDomNode(),
).toBeTruthy();
expect(wrapper.find<Popconfirm>(Popconfirm).instance().getPopupDomNode()).toBeTruthy();
jest.useFakeTimers();
});
});

View File

@ -20,7 +20,7 @@ ReactDOM.render(
<div>
<Alert message="Success Tips" type="success" showIcon />
<Alert message="Informational Notes" type="info" showIcon />
<Alert message="Warning" type="warning" showIcon />
<Alert message="Warning" type="warning" showIcon closable />
<Alert message="Error" type="error" showIcon />
<Alert
message="Success Tips"
@ -39,6 +39,7 @@ ReactDOM.render(
description="This is a warning notice about copywriting."
type="warning"
showIcon
closable
/>
<Alert
message="Error"

View File

@ -37,6 +37,8 @@ export interface AlertProps {
afterClose?: () => void;
/** Whether to show icon */
showIcon?: boolean;
/** https://www.w3.org/TR/2014/REC-html5-20141028/dom.html#aria-role-attribute */
role?: string;
style?: React.CSSProperties;
prefixCls?: string;
className?: string;

View File

@ -188,4 +188,4 @@
}
}
@import './rtl.less';
@import './rtl';

View File

@ -4,13 +4,20 @@
@alert-prefix-cls: ~'@{ant-prefix}-alert';
.@{alert-prefix-cls} {
&-rtl {
&&-rtl {
padding: 8px 37px 8px 15px;
direction: rtl;
}
&&-closable {
.@{alert-prefix-cls}-rtl& {
.@{alert-prefix-cls}.@{alert-prefix-cls}-rtl& {
padding-right: 37px;
padding-left: 30px;
}
}
&&-no-icon&-closable {
.@{alert-prefix-cls}.@{alert-prefix-cls}-rtl& {
padding-right: 15px;
padding-left: 30px;
}
@ -30,14 +37,15 @@
}
}
&-with-description {
.@{alert-prefix-cls}-rtl& {
&-with-description,
&-with-description&-closable {
.@{alert-prefix-cls}.@{alert-prefix-cls}-rtl& {
padding: 15px 64px 15px 15px;
}
}
&-with-description&-no-icon {
.@{alert-prefix-cls}-rtl& {
.@{alert-prefix-cls}.@{alert-prefix-cls}-rtl& {
padding: 15px;
}
}

View File

@ -1,27 +1,29 @@
import React from 'react';
import { mount } from 'enzyme';
import Anchor from '..';
import { spyElementPrototypes } from '../../__tests__/util/domHook';
import { sleep } from '../../../tests/utils';
const { Link } = Anchor;
describe('Anchor Render', () => {
const getBoundingClientRectMock = jest.fn(() => ({
width: 100,
height: 100,
top: 1000,
}));
const getClientRectsMock = jest.fn(() => ({
length: 1,
}));
const headingSpy = spyElementPrototypes(HTMLHeadingElement, {
getBoundingClientRect: getBoundingClientRectMock,
getClientRects: getClientRectsMock,
const getBoundingClientRectMock = jest.spyOn(
HTMLHeadingElement.prototype,
'getBoundingClientRect',
);
const getClientRectsMock = jest.spyOn(HTMLHeadingElement.prototype, 'getClientRects');
beforeAll(() => {
getBoundingClientRectMock.mockReturnValue({
width: 100,
height: 100,
top: 1000,
});
getClientRectsMock.mockReturnValue({ length: 1 });
});
afterAll(() => {
headingSpy.mockRestore();
getBoundingClientRectMock.mockRestore();
getClientRectsMock.mockRestore();
});
it('Anchor render perfectly', () => {

View File

@ -50,73 +50,50 @@ export interface ScrollNumberState {
count?: string | number | null;
}
class ScrollNumber extends React.Component<ScrollNumberProps, ScrollNumberState> {
static defaultProps = {
count: null,
onAnimated() {},
};
const ScrollNumber: React.FC<ScrollNumberProps> = props => {
const [animateStarted, setAnimateStarted] = React.useState(true);
const [count, setCount] = React.useState(props.count);
const [prevCount, setPrevCount] = React.useState(props.count);
const [lastCount, setLastCount] = React.useState(props.count);
static getDerivedStateFromProps(nextProps: ScrollNumberProps, nextState: ScrollNumberState) {
if ('count' in nextProps) {
if (nextState.count === nextProps.count) {
return null;
}
return {
animateStarted: true,
};
}
return null;
if (prevCount !== props.count) {
setAnimateStarted(true);
setPrevCount(props.count);
}
lastCount?: string | number | null;
private timeout?: number;
constructor(props: ScrollNumberProps) {
super(props);
this.state = {
animateStarted: true,
count: props.count,
};
}
componentDidUpdate(_: any, prevState: ScrollNumberState) {
this.lastCount = prevState.count;
const { animateStarted } = this.state;
React.useEffect(() => {
setLastCount(count);
let timeout: number;
if (animateStarted) {
this.clearTimeout();
// Let browser has time to reset the scroller before actually
// performing the transition.
this.timeout = setTimeout(() => {
// eslint-disable-next-line react/no-did-update-set-state
this.setState(
(__, props) => ({
animateStarted: false,
count: props.count,
}),
this.onAnimated,
);
timeout = setTimeout(() => {
setAnimateStarted(false);
setCount(props.count);
if (props.onAnimated) {
props.onAnimated();
}
});
}
}
return () => {
if (timeout) {
clearTimeout(timeout);
}
};
}, [animateStarted, count, props.count, props.onAnimated]);
componentWillUnmount() {
this.clearTimeout();
}
getPositionByNum(num: number, i: number) {
const { count } = this.state;
const getPositionByNum = (num: number, i: number) => {
const currentCount = Math.abs(Number(count));
const lastCount = Math.abs(Number(this.lastCount));
const currentDigit = Math.abs(getNumberArray(this.state.count)[i] as number);
const lastDigit = Math.abs(getNumberArray(this.lastCount)[i] as number);
const lstCount = Math.abs(Number(lastCount));
const currentDigit = Math.abs(getNumberArray(count)[i] as number);
const lastDigit = Math.abs(getNumberArray(lstCount)[i] as number);
if (this.state.animateStarted) {
if (animateStarted) {
return 10 + num;
}
// 同方向则在同一侧切换数字
if (currentCount > lastCount) {
if (currentCount > lstCount) {
if (currentDigit >= lastDigit) {
return 10 + num;
}
@ -126,20 +103,12 @@ class ScrollNumber extends React.Component<ScrollNumberProps, ScrollNumberState>
return 10 + num;
}
return num;
}
onAnimated = () => {
const { onAnimated } = this.props;
if (onAnimated) {
onAnimated();
}
};
renderCurrentNumber(prefixCls: string, num: number | string, i: number) {
const renderCurrentNumber = (prefixCls: string, num: number | string, i: number) => {
if (typeof num === 'number') {
const position = this.getPositionByNum(num, i);
const removeTransition =
this.state.animateStarted || getNumberArray(this.lastCount)[i] === undefined;
const position = getPositionByNum(num, i);
const removeTransition = animateStarted || getNumberArray(lastCount)[i] === undefined;
return React.createElement(
'span',
{
@ -161,19 +130,18 @@ class ScrollNumber extends React.Component<ScrollNumberProps, ScrollNumberState>
{num}
</span>
);
}
};
renderNumberElement(prefixCls: string) {
const { count } = this.state;
const renderNumberElement = (prefixCls: string) => {
if (count && Number(count) % 1 === 0) {
return getNumberArray(count)
.map((num, i) => this.renderCurrentNumber(prefixCls, num, i))
.map((num, i) => renderCurrentNumber(prefixCls, num, i))
.reverse();
}
return count;
}
};
renderScrollNumber = ({ getPrefixCls }: ConfigConsumerProps) => {
const renderScrollNumber = ({ getPrefixCls }: ConfigConsumerProps) => {
const {
prefixCls: customizePrefixCls,
className,
@ -181,9 +149,9 @@ class ScrollNumber extends React.Component<ScrollNumberProps, ScrollNumberState>
title,
component = 'sup',
displayComponent,
} = this.props;
} = props;
// fix https://fb.me/react-unknown-prop
const restProps = omit(this.props, [
const restProps = omit(props, [
'count',
'onAnimated',
'component',
@ -214,19 +182,15 @@ class ScrollNumber extends React.Component<ScrollNumberProps, ScrollNumberState>
),
});
}
return React.createElement(component as any, newProps, this.renderNumberElement(prefixCls));
return React.createElement(component as any, newProps, renderNumberElement(prefixCls));
};
render() {
return <ConfigConsumer>{this.renderScrollNumber}</ConfigConsumer>;
}
return <ConfigConsumer>{renderScrollNumber}</ConfigConsumer>;
};
private clearTimeout(): void {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = undefined;
}
}
}
ScrollNumber.defaultProps = {
count: null,
onAnimated() {},
};
export default ScrollNumber;

View File

@ -32,40 +32,47 @@ function isPresetColor(color?: string): boolean {
return (PresetColorTypes as any[]).indexOf(color) !== -1;
}
export default class Badge extends React.Component<BadgeProps, any> {
static defaultProps = {
count: null,
showZero: false,
dot: false,
overflowCount: 99,
};
getNumberedDisplayCount() {
const { count, overflowCount } = this.props;
const Badge: React.FC<BadgeProps> = props => {
const getNumberedDisplayCount = () => {
const { count, overflowCount } = props;
const displayCount =
(count as number) > (overflowCount as number) ? `${overflowCount}+` : count;
return displayCount as string | number | null;
}
};
getDisplayCount() {
const isDot = this.isDot();
const hasStatus = (): boolean => {
const { status, color } = props;
return !!status || !!color;
};
const isZero = () => {
const numberedDisplayCount = getNumberedDisplayCount();
return numberedDisplayCount === '0' || numberedDisplayCount === 0;
};
const isDot = () => {
const { dot } = props;
return (dot && !isZero()) || hasStatus();
};
const getDisplayCount = () => {
// dot mode don't need count
if (isDot) {
if (isDot()) {
return '';
}
return this.getNumberedDisplayCount();
}
return getNumberedDisplayCount();
};
getScrollNumberTitle() {
const { title, count } = this.props;
const getScrollNumberTitle = () => {
const { title, count } = props;
if (title) {
return title;
}
return typeof count === 'string' || typeof count === 'number' ? count : undefined;
}
};
getStyleWithOffset() {
const { offset, style } = this.props;
const getStyleWithOffset = () => {
const { offset, style } = props;
return offset
? {
right: -parseInt(offset[0] as string, 10),
@ -73,79 +80,61 @@ export default class Badge extends React.Component<BadgeProps, any> {
...style,
}
: style;
}
};
getBadgeClassName(prefixCls: string, direction: string = 'ltr') {
const { className, children } = this.props;
const getBadgeClassName = (prefixCls: string, direction: string = 'ltr') => {
const { className, children } = props;
return classNames(className, prefixCls, {
[`${prefixCls}-status`]: this.hasStatus(),
[`${prefixCls}-status`]: hasStatus(),
[`${prefixCls}-not-a-wrapper`]: !children,
[`${prefixCls}-rtl`]: direction === 'rtl',
}) as string;
}
};
hasStatus(): boolean {
const { status, color } = this.props;
return !!status || !!color;
}
isZero() {
const numberedDisplayCount = this.getNumberedDisplayCount();
return numberedDisplayCount === '0' || numberedDisplayCount === 0;
}
isDot() {
const { dot } = this.props;
const isZero = this.isZero();
return (dot && !isZero) || this.hasStatus();
}
isHidden() {
const { showZero } = this.props;
const displayCount = this.getDisplayCount();
const isZero = this.isZero();
const isDot = this.isDot();
const isHidden = () => {
const { showZero } = props;
const displayCount = getDisplayCount();
const isEmpty = displayCount === null || displayCount === undefined || displayCount === '';
return (isEmpty || (isZero && !showZero)) && !isDot;
}
return (isEmpty || (isZero() && !showZero)) && !isDot();
};
renderStatusText(prefixCls: string) {
const { text } = this.props;
const hidden = this.isHidden();
const renderStatusText = (prefixCls: string) => {
const { text } = props;
const hidden = isHidden();
return hidden || !text ? null : <span className={`${prefixCls}-status-text`}>{text}</span>;
}
};
renderDisplayComponent() {
const { count } = this.props;
const renderDisplayComponent = () => {
const { count } = props;
const customNode = count as React.ReactElement<any>;
if (!customNode || typeof customNode !== 'object') {
return undefined;
}
return React.cloneElement(customNode, {
style: {
...this.getStyleWithOffset(),
...getStyleWithOffset(),
...(customNode.props && customNode.props.style),
},
});
}
};
renderBadgeNumber(prefixCls: string, scrollNumberPrefixCls: string) {
const { status, count, color } = this.props;
const renderBadgeNumber = (prefixCls: string, scrollNumberPrefixCls: string) => {
const { status, count, color } = props;
const displayCount = this.getDisplayCount();
const isDot = this.isDot();
const hidden = this.isHidden();
const displayCount = getDisplayCount();
const dot = isDot();
const hidden = isHidden();
const scrollNumberCls = classNames({
[`${prefixCls}-dot`]: isDot,
[`${prefixCls}-count`]: !isDot,
[`${prefixCls}-dot`]: dot,
[`${prefixCls}-count`]: !dot,
[`${prefixCls}-multiple-words`]:
!isDot && count && count.toString && count.toString().length > 1,
!dot && count && count.toString && count.toString().length > 1,
[`${prefixCls}-status-${status}`]: !!status,
[`${prefixCls}-status-${color}`]: isPresetColor(color),
});
let statusStyle: React.CSSProperties | undefined = this.getStyleWithOffset();
let statusStyle: React.CSSProperties | undefined = getStyleWithOffset();
if (color && !isPresetColor(color)) {
statusStyle = statusStyle || {};
statusStyle.background = color;
@ -157,15 +146,15 @@ export default class Badge extends React.Component<BadgeProps, any> {
data-show={!hidden}
className={scrollNumberCls}
count={displayCount}
displayComponent={this.renderDisplayComponent()} // <Badge status="success" count={<Icon type="xxx" />}></Badge>
title={this.getScrollNumberTitle()}
displayComponent={renderDisplayComponent()} // <Badge status="success" count={<Icon type="xxx" />}></Badge>
title={getScrollNumberTitle()}
style={statusStyle}
key="scrollNumber"
/>
);
}
};
renderBadge = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
const renderBadge = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
const {
prefixCls: customizePrefixCls,
scrollNumberPrefixCls: customizeScrollNumberPrefixCls,
@ -174,7 +163,7 @@ export default class Badge extends React.Component<BadgeProps, any> {
text,
color,
...restProps
} = this.props;
} = props;
const omitArr = [
'count',
'showZero',
@ -189,11 +178,11 @@ export default class Badge extends React.Component<BadgeProps, any> {
const prefixCls = getPrefixCls('badge', customizePrefixCls);
const scrollNumberPrefixCls = getPrefixCls('scroll-number', customizeScrollNumberPrefixCls);
const scrollNumber = this.renderBadgeNumber(prefixCls, scrollNumberPrefixCls);
const statusText = this.renderStatusText(prefixCls);
const scrollNumber = renderBadgeNumber(prefixCls, scrollNumberPrefixCls);
const statusText = renderStatusText(prefixCls);
const statusCls = classNames({
[`${prefixCls}-status-dot`]: this.hasStatus(),
[`${prefixCls}-status-dot`]: hasStatus(),
[`${prefixCls}-status-${status}`]: !!status,
[`${prefixCls}-status-${color}`]: isPresetColor(color),
});
@ -203,13 +192,13 @@ export default class Badge extends React.Component<BadgeProps, any> {
}
// <Badge status="success" />
if (!children && this.hasStatus()) {
const styleWithOffset = this.getStyleWithOffset();
if (!children && hasStatus()) {
const styleWithOffset = getStyleWithOffset();
const statusTextColor = styleWithOffset && styleWithOffset.color;
return (
<span
{...omit(restProps, omitArr)}
className={this.getBadgeClassName(prefixCls, direction)}
className={getBadgeClassName(prefixCls, direction)}
style={styleWithOffset}
>
<span className={statusCls} style={statusStyle} />
@ -221,7 +210,7 @@ export default class Badge extends React.Component<BadgeProps, any> {
}
return (
<span {...omit(restProps, omitArr)} className={this.getBadgeClassName(prefixCls, direction)}>
<span {...omit(restProps, omitArr)} className={getBadgeClassName(prefixCls, direction)}>
{children}
<Animate
component=""
@ -236,7 +225,14 @@ export default class Badge extends React.Component<BadgeProps, any> {
);
};
render() {
return <ConfigConsumer>{this.renderBadge}</ConfigConsumer>;
}
}
return <ConfigConsumer>{renderBadge}</ConfigConsumer>;
};
Badge.defaultProps = {
count: null,
showZero: false,
dot: false,
overflowCount: 99,
};
export default Badge;

View File

@ -531,8 +531,66 @@ exports[`renders ./components/button/demo/legacy-group.md correctly 1`] = `
Button 2
</span>
</button>
<span
class="ant-tooltip-disabled-compatible-wrapper"
style="display:inline-block;cursor:not-allowed"
>
<button
class="ant-btn ant-btn-primary ant-btn-icon-only"
disabled=""
style="pointer-events:none"
type="button"
>
<span
aria-label="download"
class="anticon anticon-download"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="download"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M505.7 661a8 8 0 0012.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V168c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"
/>
</svg>
</span>
</button>
</span>
<button
class="ant-btn ant-btn-primary ant-btn-icon-only"
type="button"
>
<span
aria-label="download"
class="anticon anticon-download"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="download"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M505.7 661a8 8 0 0012.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V168c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"
/>
</svg>
</span>
</button>
</div>
</div>
<br />
<div>
<div
class="ant-btn-group"
@ -553,8 +611,66 @@ exports[`renders ./components/button/demo/legacy-group.md correctly 1`] = `
Button 2
</span>
</button>
<span
class="ant-tooltip-disabled-compatible-wrapper"
style="display:inline-block;cursor:not-allowed"
>
<button
class="ant-btn ant-btn-primary ant-btn-icon-only"
disabled=""
style="pointer-events:none"
type="button"
>
<span
aria-label="download"
class="anticon anticon-download"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="download"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M505.7 661a8 8 0 0012.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V168c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"
/>
</svg>
</span>
</button>
</span>
<button
class="ant-btn ant-btn-primary ant-btn-icon-only"
type="button"
>
<span
aria-label="download"
class="anticon anticon-download"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="download"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M505.7 661a8 8 0 0012.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V168c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"
/>
</svg>
</span>
</button>
</div>
</div>
<br />
<div>
<div
class="ant-btn-group ant-btn-group-lg"
@ -575,6 +691,63 @@ exports[`renders ./components/button/demo/legacy-group.md correctly 1`] = `
Button 2
</span>
</button>
<span
class="ant-tooltip-disabled-compatible-wrapper"
style="display:inline-block;cursor:not-allowed"
>
<button
class="ant-btn ant-btn-primary ant-btn-icon-only"
disabled=""
style="pointer-events:none"
type="button"
>
<span
aria-label="download"
class="anticon anticon-download"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="download"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M505.7 661a8 8 0 0012.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V168c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"
/>
</svg>
</span>
</button>
</span>
<button
class="ant-btn ant-btn-primary ant-btn-icon-only"
type="button"
>
<span
aria-label="download"
class="anticon anticon-download"
role="img"
>
<svg
aria-hidden="true"
class=""
data-icon="download"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M505.7 661a8 8 0 0012.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V168c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"
/>
</svg>
</span>
</button>
</div>
</div>
</div>

View File

@ -15,7 +15,8 @@ Debug usage
Debug usage
```jsx
import { Button } from 'antd';
import { Button, Tooltip } from 'antd';
import { DownloadOutlined } from '@ant-design/icons';
function getGroup(props) {
return (
@ -23,6 +24,12 @@ function getGroup(props) {
<Button.Group {...props}>
<Button type="primary">Button 1</Button>
<Button type="primary">Button 2</Button>
<Tooltip title="Tooltip">
<Button type="primary" icon={<DownloadOutlined />} disabled />
</Tooltip>
<Tooltip title="Tooltip">
<Button type="primary" icon={<DownloadOutlined />} />
</Tooltip>
</Button.Group>
</div>
);
@ -31,9 +38,17 @@ function getGroup(props) {
ReactDOM.render(
<div>
{getGroup({ size: 'small' })}
<br />
{getGroup()}
<br />
{getGroup({ size: 'large' })}
</div>,
mountNode,
);
```
```css
#components-button-demo-legacy-group .ant-btn {
margin: 0;
}
```

View File

@ -165,7 +165,7 @@
}
.button-group-base(@btnClassName) {
position: relative;
display: inline-block;
display: inline-flex;
> .@{btnClassName},
> span > .@{btnClassName} {
position: relative;
@ -179,7 +179,7 @@
z-index: 0;
}
}
> .@{btnClassName}-icon-only {
.@{btnClassName}-icon-only {
font-size: @font-size-base;
}
// size
@ -187,7 +187,7 @@
&-lg > span > .@{btnClassName} {
.button-size(@btn-height-lg; @btn-padding-horizontal-lg; @btn-font-size-lg; 0);
}
&-lg > .@{btnClassName}.@{btnClassName}-icon-only {
&-lg .@{btnClassName}.@{btnClassName}-icon-only {
.square(@btn-height-lg);
padding-right: 0;
padding-left: 0;
@ -199,7 +199,7 @@
font-size: @font-size-base;
}
}
&-sm > .@{btnClassName}.@{btnClassName}-icon-only {
&-sm .@{btnClassName}.@{btnClassName}-icon-only {
.square(@btn-height-sm);
padding-right: 0;
padding-left: 0;

View File

@ -1,6 +1,6 @@
import * as React from 'react';
import classNames from 'classnames';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import { ConfigContext } from '../config-provider';
function getAction(actions: React.ReactNode[]) {
if (!actions || !actions.length) {
@ -32,72 +32,68 @@ export interface CommentProps {
datetime?: React.ReactNode;
}
export default class Comment extends React.Component<CommentProps, {}> {
renderNested = (prefixCls: string, children: any) => {
return <div className={classNames(`${prefixCls}-nested`)}>{children}</div>;
const Comment: React.FC<CommentProps> = ({
actions,
author,
avatar,
children,
className,
content,
prefixCls: customizePrefixCls,
style,
datetime,
...otherProps
}) => {
const { getPrefixCls, direction } = React.useContext(ConfigContext);
const renderNested = (prefixCls: string, nestedChildren: any) => {
return <div className={classNames(`${prefixCls}-nested`)}>{nestedChildren}</div>;
};
renderComment = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
const {
actions,
author,
avatar,
children,
className,
content,
prefixCls: customizePrefixCls,
style,
datetime,
...otherProps
} = this.props;
const prefixCls = getPrefixCls('comment', customizePrefixCls);
const prefixCls = getPrefixCls('comment', customizePrefixCls);
const avatarDom = (
<div className={`${prefixCls}-avatar`}>
{typeof avatar === 'string' ? <img src={avatar} alt="comment-avatar" /> : avatar}
</div>
);
const avatarDom = (
<div className={`${prefixCls}-avatar`}>
{typeof avatar === 'string' ? <img src={avatar} alt="comment-avatar" /> : avatar}
</div>
);
const actionDom =
actions && actions.length ? (
<ul className={`${prefixCls}-actions`}>{getAction(actions)}</ul>
) : null;
const actionDom =
actions && actions.length ? (
<ul className={`${prefixCls}-actions`}>{getAction(actions)}</ul>
) : null;
const authorContent = (
<div className={`${prefixCls}-content-author`}>
{author && <span className={`${prefixCls}-content-author-name`}>{author}</span>}
{datetime && <span className={`${prefixCls}-content-author-time`}>{datetime}</span>}
</div>
);
const authorContent = (
<div className={`${prefixCls}-content-author`}>
{author && <span className={`${prefixCls}-content-author-name`}>{author}</span>}
{datetime && <span className={`${prefixCls}-content-author-time`}>{datetime}</span>}
</div>
);
const contentDom = (
<div className={`${prefixCls}-content`}>
{authorContent}
<div className={`${prefixCls}-content-detail`}>{content}</div>
{actionDom}
</div>
);
const contentDom = (
<div className={`${prefixCls}-content`}>
{authorContent}
<div className={`${prefixCls}-content-detail`}>{content}</div>
{actionDom}
</div>
);
const comment = (
<div className={`${prefixCls}-inner`}>
{avatarDom}
{contentDom}
</div>
);
const comment = (
<div className={`${prefixCls}-inner`}>
{avatarDom}
{contentDom}
</div>
);
const cls = classNames(prefixCls, className, {
[`${prefixCls}-rtl`]: direction === 'rtl',
});
return (
<div {...otherProps} className={cls} style={style}>
{comment}
{children ? renderNested(prefixCls, children) : null}
</div>
);
};
const cls = classNames(prefixCls, className, {
[`${prefixCls}-rtl`]: direction === 'rtl',
});
return (
<div {...otherProps} className={cls} style={style}>
{comment}
{children ? this.renderNested(prefixCls, children) : null}
</div>
);
};
render() {
return <ConfigConsumer>{this.renderComment}</ConfigConsumer>;
}
}
export default Comment;

View File

@ -0,0 +1,28 @@
import React from 'react';
import { mount } from 'enzyme';
import ConfigProvider from '..';
import DatePicker from '../../date-picker';
import Slider from '../../slider';
describe('ConfigProvider.GetPopupContainer', () => {
it('Datepicker', () => {
const getPopupContainer = jest.fn(node => node.parentNode);
mount(
<ConfigProvider getPopupContainer={getPopupContainer}>
<DatePicker open />
</ConfigProvider>,
);
expect(getPopupContainer).toHaveBeenCalled();
});
it('Slider', () => {
const getPopupContainer = jest.fn(node => node.parentNode);
const wrapper = mount(
<ConfigProvider getPopupContainer={getPopupContainer}>
<Slider />
</ConfigProvider>,
);
wrapper.find('.ant-slider-handle').first().simulate('mouseenter');
expect(getPopupContainer).toHaveBeenCalled();
});
});

View File

@ -8,7 +8,7 @@ import focusTest from '../../../tests/shared/focusTest';
describe('DatePicker', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
focusTest(DatePicker, true);
focusTest(DatePicker, { refFocus: true });
beforeEach(() => {
MockDate.set(moment('2016-11-22'));

View File

@ -9,7 +9,7 @@ import focusTest from '../../../tests/shared/focusTest';
const { RangePicker } = DatePicker;
describe('RangePicker', () => {
focusTest(RangePicker, true);
focusTest(RangePicker, { refFocus: true });
beforeEach(() => {
setMockDate();

View File

@ -15,7 +15,7 @@ describe('WeekPicker', () => {
resetMockDate();
});
focusTest(WeekPicker, true);
focusTest(WeekPicker, { refFocus: true });
it('should support style prop', () => {
const wrapper = mount(<WeekPicker style={{ width: 400 }} />);

View File

@ -49,9 +49,10 @@ export default function generateRangePicker<DateType>(
};
renderPicker = (locale: PickerLocale) => {
const { getPrefixCls, direction } = this.context;
const { getPrefixCls, direction, getPopupContainer } = this.context;
const {
prefixCls: customizePrefixCls,
getPopupContainer: customGetPopupContainer,
className,
size: customizeSize,
bordered = true,
@ -94,6 +95,7 @@ export default function generateRangePicker<DateType>(
{...additionalOverrideProps}
locale={locale!.lang}
prefixCls={prefixCls}
getPopupContainer={customGetPopupContainer || getPopupContainer}
generateConfig={generateConfig}
prevIcon={<span className={`${prefixCls}-prev-icon`} />}
nextIcon={<span className={`${prefixCls}-next-icon`} />}

View File

@ -62,9 +62,10 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
};
renderPicker = (locale: PickerLocale) => {
const { getPrefixCls, direction } = this.context;
const { getPrefixCls, direction, getPopupContainer } = this.context;
const {
prefixCls: customizePrefixCls,
getPopupContainer: customizeGetPopupContainer,
className,
size: customizeSize,
bordered = true,
@ -115,6 +116,7 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
[`${prefixCls}-borderless`]: !bordered,
})}
prefixCls={prefixCls}
getPopupContainer={customizeGetPopupContainer || getPopupContainer}
generateConfig={generateConfig}
prevIcon={<span className={`${prefixCls}-prev-icon`} />}
nextIcon={<span className={`${prefixCls}-next-icon`} />}

View File

@ -48,6 +48,10 @@
cursor: not-allowed;
}
&&-disabled &-suffix {
color: @disabled-color;
}
&&-borderless {
background-color: transparent !important;
border-color: transparent !important;
@ -105,7 +109,7 @@
&-suffix {
align-self: center;
margin-left: @padding-xs / 2;
color: @text-color-secondary;
color: @disabled-color;
pointer-events: none;
}

View File

@ -6,7 +6,7 @@ import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
describe('InputNumber', () => {
focusTest(InputNumber, true);
focusTest(InputNumber, { refFocus: true });
mountTest(InputNumber);
rtlTest(InputNumber);

View File

@ -197,17 +197,13 @@ class ClearableLabeledInput extends React.Component<ClearableInputProps> {
);
}
renderClearableLabeledInput() {
render() {
const { prefixCls, inputType, element } = this.props;
if (inputType === ClearableInputType[0]) {
return this.renderTextAreaWithClearIcon(prefixCls, element);
}
return this.renderInputWithLabel(prefixCls, this.renderLabeledIcon(prefixCls, element));
}
render() {
return this.renderClearableLabeledInput();
}
}
export default ClearableLabeledInput;

View File

@ -70,14 +70,9 @@ export default class Search extends React.Component<SearchProps, any> {
if (enterButton) {
return (
<SizeContext.Consumer>
<SizeContext.Consumer key="enterButton">
{size => (
<Button
className={`${prefixCls}-button`}
type="primary"
size={customizeSize || size}
key="enterButton"
>
<Button className={`${prefixCls}-button`} type="primary" size={customizeSize || size}>
<LoadingOutlined />
</Button>
)}

View File

@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders ./components/input/demo/addon.md correctly 1`] = `
<div>
Array [
<div
style="margin-bottom:16px"
>
@ -28,7 +28,7 @@ exports[`renders ./components/input/demo/addon.md correctly 1`] = `
</span>
</span>
</span>
</div>
</div>,
<div
style="margin-bottom:16px"
>
@ -167,7 +167,7 @@ exports[`renders ./components/input/demo/addon.md correctly 1`] = `
</span>
</span>
</span>
</div>
</div>,
<div
style="margin-bottom:16px"
>
@ -208,7 +208,7 @@ exports[`renders ./components/input/demo/addon.md correctly 1`] = `
</span>
</span>
</span>
</div>
</div>,
<div
style="margin-bottom:16px"
>
@ -239,12 +239,12 @@ exports[`renders ./components/input/demo/addon.md correctly 1`] = `
</span>
</span>
</span>
</div>
</div>
</div>,
]
`;
exports[`renders ./components/input/demo/align.md correctly 1`] = `
<div>
Array [
<div
class="ant-mentions"
style="width:100px"
@ -252,12 +252,12 @@ exports[`renders ./components/input/demo/align.md correctly 1`] = `
<textarea
rows="1"
/>
</div>
</div>,
<textarea
class="ant-input"
rows="1"
style="width:100px"
/>
/>,
<button
class="ant-btn ant-btn-primary"
type="button"
@ -265,13 +265,13 @@ exports[`renders ./components/input/demo/align.md correctly 1`] = `
<span>
Button
</span>
</button>
</button>,
<input
class="ant-input"
style="width:100px"
type="text"
value=""
/>
/>,
<span
class="ant-typography"
>
@ -304,7 +304,7 @@ exports[`renders ./components/input/demo/align.md correctly 1`] = `
</svg>
</span>
</div>
</span>
</span>,
<span
class="ant-input-affix-wrapper"
style="width:100px"
@ -324,7 +324,7 @@ exports[`renders ./components/input/demo/align.md correctly 1`] = `
>
2
</span>
</span>
</span>,
<span
class="ant-input-group-wrapper"
style="width:100px"
@ -348,7 +348,7 @@ exports[`renders ./components/input/demo/align.md correctly 1`] = `
2
</span>
</span>
</span>
</span>,
<div
class="ant-input-number"
style="width:100px"
@ -426,7 +426,7 @@ exports[`renders ./components/input/demo/align.md correctly 1`] = `
value=""
/>
</div>
</div>
</div>,
<div
class="ant-picker"
style="width:100px"
@ -466,7 +466,7 @@ exports[`renders ./components/input/demo/align.md correctly 1`] = `
</span>
</span>
</div>
</div>
</div>,
<div
class="ant-picker"
style="width:100px"
@ -509,7 +509,7 @@ exports[`renders ./components/input/demo/align.md correctly 1`] = `
</span>
</span>
</div>
</div>
</div>,
<div
class="ant-select ant-select-single ant-select-show-arrow"
style="width:100px"
@ -567,7 +567,7 @@ exports[`renders ./components/input/demo/align.md correctly 1`] = `
</svg>
</span>
</span>
</div>
</div>,
<div
class="ant-select ant-tree-select ant-select-single ant-select-show-arrow"
style="width:100px"
@ -623,7 +623,7 @@ exports[`renders ./components/input/demo/align.md correctly 1`] = `
</svg>
</span>
</span>
</div>
</div>,
<span
class="ant-cascader-picker"
tabindex="0"
@ -682,7 +682,7 @@ exports[`renders ./components/input/demo/align.md correctly 1`] = `
/>
</svg>
</span>
</span>
</span>,
<div
class="ant-picker ant-picker-range"
>
@ -763,7 +763,7 @@ exports[`renders ./components/input/demo/align.md correctly 1`] = `
</svg>
</span>
</span>
</div>
</div>,
<div
class="ant-picker"
>
@ -802,7 +802,7 @@ exports[`renders ./components/input/demo/align.md correctly 1`] = `
</span>
</span>
</div>
</div>
</div>,
<div
class="ant-radio-group ant-radio-group-outline"
>
@ -845,7 +845,7 @@ exports[`renders ./components/input/demo/align.md correctly 1`] = `
Shanghai
</span>
</label>
</div>
</div>,
<div
class="ant-select ant-select-auto-complete ant-select-single ant-select-show-search"
style="width:100px"
@ -874,8 +874,8 @@ exports[`renders ./components/input/demo/align.md correctly 1`] = `
input here
</span>
</div>
</div>
<br />
</div>,
<br />,
<span
class="ant-input-group-wrapper"
>
@ -907,7 +907,7 @@ exports[`renders ./components/input/demo/align.md correctly 1`] = `
.com
</span>
</span>
</span>
</span>,
<span
class="ant-input-affix-wrapper"
style="width:50px"
@ -922,13 +922,13 @@ exports[`renders ./components/input/demo/align.md correctly 1`] = `
>
Y
</span>
</span>
</span>,
<input
class="ant-input"
style="width:50px"
type="text"
value=""
/>
/>,
<span
class="ant-input-affix-wrapper"
style="width:50px"
@ -943,12 +943,12 @@ exports[`renders ./components/input/demo/align.md correctly 1`] = `
>
Y
</span>
</span>
</div>
</span>,
]
`;
exports[`renders ./components/input/demo/allowClear.md correctly 1`] = `
<div>
Array [
<span
class="ant-input-affix-wrapper"
>
@ -983,9 +983,9 @@ exports[`renders ./components/input/demo/allowClear.md correctly 1`] = `
</svg>
</span>
</span>
</span>
<br />
<br />
</span>,
<br />,
<br />,
<span
class="ant-input-affix-wrapper ant-input-affix-wrapper-textarea-with-clear-btn"
>
@ -1014,31 +1014,31 @@ exports[`renders ./components/input/demo/allowClear.md correctly 1`] = `
/>
</svg>
</span>
</span>
</div>
</span>,
]
`;
exports[`renders ./components/input/demo/autosize-textarea.md correctly 1`] = `
<div>
Array [
<textarea
class="ant-input"
placeholder="Autosize height based on content lines"
/>
/>,
<div
style="margin:24px 0"
/>
/>,
<textarea
class="ant-input"
placeholder="Autosize height with minimum and maximum number of lines"
/>
/>,
<div
style="margin:24px 0"
/>
/>,
<textarea
class="ant-input"
placeholder="Controlled autosize"
/>
</div>
/>,
]
`;
exports[`renders ./components/input/demo/basic.md correctly 1`] = `
@ -1945,7 +1945,7 @@ exports[`renders ./components/input/demo/password-input.md correctly 1`] = `
`;
exports[`renders ./components/input/demo/presuffix.md correctly 1`] = `
<div>
Array [
<span
class="ant-input-affix-wrapper"
>
@ -2007,9 +2007,9 @@ exports[`renders ./components/input/demo/presuffix.md correctly 1`] = `
</svg>
</span>
</span>
</span>
<br />
<br />
</span>,
<br />,
<br />,
<span
class="ant-input-affix-wrapper"
>
@ -2028,9 +2028,9 @@ exports[`renders ./components/input/demo/presuffix.md correctly 1`] = `
>
RMB
</span>
</span>
<br />
<br />
</span>,
<br />,
<br />,
<span
class="ant-input-affix-wrapper ant-input-affix-wrapper-disabled"
>
@ -2050,12 +2050,12 @@ exports[`renders ./components/input/demo/presuffix.md correctly 1`] = `
>
RMB
</span>
</span>
</div>
</span>,
]
`;
exports[`renders ./components/input/demo/search-input.md correctly 1`] = `
<div>
Array [
<span
class="ant-input-search ant-input-affix-wrapper"
style="width:200px"
@ -2091,9 +2091,9 @@ exports[`renders ./components/input/demo/search-input.md correctly 1`] = `
</svg>
</span>
</span>
</span>
<br />
<br />
</span>,
<br />,
<br />,
<span
class="ant-input-search ant-input-search-enter-button ant-input-group-wrapper"
>
@ -2136,9 +2136,9 @@ exports[`renders ./components/input/demo/search-input.md correctly 1`] = `
</button>
</span>
</span>
</span>
<br />
<br />
</span>,
<br />,
<br />,
<span
class="ant-input-search ant-input-search-enter-button ant-input-search-large ant-input-group-wrapper ant-input-group-wrapper-lg"
>
@ -2164,12 +2164,69 @@ exports[`renders ./components/input/demo/search-input.md correctly 1`] = `
</button>
</span>
</span>
</span>
</div>
</span>,
<br />,
<br />,
<span
class="ant-input-search ant-input-search-enter-button ant-input-search-large ant-input-group-wrapper ant-input-group-wrapper-lg"
>
<span
class="ant-input-wrapper ant-input-group"
>
<span
class="ant-input-search ant-input-search-enter-button ant-input-search-large ant-input-affix-wrapper ant-input-affix-wrapper-lg"
>
<input
class="ant-input ant-input-lg"
placeholder="input search text"
type="text"
value=""
/>
<span
class="ant-input-suffix"
>
<span
aria-label="audio"
class="anticon anticon-audio"
role="img"
style="font-size:16px;color:#1890ff;padding-right:4px"
>
<svg
aria-hidden="true"
class=""
data-icon="audio"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M842 454c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8 0 140.3-113.7 254-254 254S258 594.3 258 454c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8 0 168.7 126.6 307.9 290 327.6V884H326.7c-13.7 0-24.7 14.3-24.7 32v36c0 4.4 2.8 8 6.2 8h407.6c3.4 0 6.2-3.6 6.2-8v-36c0-17.7-11-32-24.7-32H548V782.1c165.3-18 294-158 294-328.1zM512 624c93.9 0 170-75.2 170-168V232c0-92.8-76.1-168-170-168s-170 75.2-170 168v224c0 92.8 76.1 168 170 168zm-94-392c0-50.6 41.9-92 94-92s94 41.4 94 92v224c0 50.6-41.9 92-94 92s-94-41.4-94-92V232z"
/>
</svg>
</span>
</span>
</span>
<span
class="ant-input-group-addon"
>
<button
class="ant-btn ant-input-search-button ant-btn-primary ant-btn-lg"
type="button"
>
<span>
Search
</span>
</button>
</span>
</span>
</span>,
]
`;
exports[`renders ./components/input/demo/search-input-loading.md correctly 1`] = `
<div>
Array [
<span
class="ant-input-search ant-input-affix-wrapper"
>
@ -2203,9 +2260,9 @@ exports[`renders ./components/input/demo/search-input-loading.md correctly 1`] =
</svg>
</span>
</span>
</span>
<br />
<br />
</span>,
<br />,
<br />,
<span
class="ant-input-search ant-input-search-enter-button ant-input-group-wrapper"
>
@ -2248,14 +2305,12 @@ exports[`renders ./components/input/demo/search-input-loading.md correctly 1`] =
</button>
</span>
</span>
</span>
</div>
</span>,
]
`;
exports[`renders ./components/input/demo/size.md correctly 1`] = `
<div
class="example-input"
>
Array [
<span
class="ant-input-affix-wrapper ant-input-affix-wrapper-lg"
>
@ -2289,7 +2344,9 @@ exports[`renders ./components/input/demo/size.md correctly 1`] = `
type="text"
value=""
/>
</span>
</span>,
<br />,
<br />,
<span
class="ant-input-affix-wrapper"
>
@ -2323,7 +2380,9 @@ exports[`renders ./components/input/demo/size.md correctly 1`] = `
type="text"
value=""
/>
</span>
</span>,
<br />,
<br />,
<span
class="ant-input-affix-wrapper ant-input-affix-wrapper-sm"
>
@ -2357,47 +2416,8 @@ exports[`renders ./components/input/demo/size.md correctly 1`] = `
type="text"
value=""
/>
</span>
<span
class="ant-input-password ant-input-password-large ant-input-affix-wrapper ant-input-affix-wrapper-lg"
>
<input
action="click"
class="ant-input ant-input-lg"
placeholder="large Password"
type="password"
value=""
/>
<span
class="ant-input-suffix"
>
<span
aria-label="eye-invisible"
class="anticon anticon-eye-invisible ant-input-password-icon"
role="img"
tabindex="-1"
>
<svg
aria-hidden="true"
class=""
data-icon="eye-invisible"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M942.2 486.2Q889.47 375.11 816.7 305l-50.88 50.88C807.31 395.53 843.45 447.4 874.7 512 791.5 684.2 673.4 766 512 766q-72.67 0-133.87-22.38L323 798.75Q408 838 512 838q288.3 0 430.2-300.3a60.29 60.29 0 000-51.5zm-63.57-320.64L836 122.88a8 8 0 00-11.32 0L715.31 232.2Q624.86 186 512 186q-288.3 0-430.2 300.3a60.3 60.3 0 000 51.5q56.69 119.4 136.5 191.41L112.48 835a8 8 0 000 11.31L155.17 889a8 8 0 0011.31 0l712.15-712.12a8 8 0 000-11.32zM149.3 512C232.6 339.8 350.7 258 512 258c54.54 0 104.13 9.36 149.12 28.39l-70.3 70.3a176 176 0 00-238.13 238.13l-83.42 83.42C223.1 637.49 183.3 582.28 149.3 512zm246.7 0a112.11 112.11 0 01146.2-106.69L401.31 546.2A112 112 0 01396 512z"
/>
<path
d="M508 624c-3.46 0-6.87-.16-10.25-.47l-52.82 52.82a176.09 176.09 0 00227.42-227.42l-52.82 52.82c.31 3.38.47 6.79.47 10.25a111.94 111.94 0 01-112 112z"
/>
</svg>
</span>
</span>
</span>
</div>
</span>,
]
`;
exports[`renders ./components/input/demo/textarea.md correctly 1`] = `
@ -2408,7 +2428,7 @@ exports[`renders ./components/input/demo/textarea.md correctly 1`] = `
`;
exports[`renders ./components/input/demo/textarea-resize.md correctly 1`] = `
<div>
Array [
<button
class="ant-btn"
style="margin-bottom:16px"
@ -2417,14 +2437,14 @@ exports[`renders ./components/input/demo/textarea-resize.md correctly 1`] = `
<span>
Auto Resize: false
</span>
</button>
</button>,
<textarea
class="ant-input"
rows="4"
>
The autoSize property applies to textarea nodes, and only the height changes automatically. In addition, autoSize can be set to an object, specifying the minimum number of rows and the maximum number of rows. The autoSize property applies to textarea nodes, and only the height changes automatically. In addition, autoSize can be set to an object, specifying the minimum number of rows and the maximum number of rows.
</textarea>
</div>
</textarea>,
]
`;
exports[`renders ./components/input/demo/tooltip.md correctly 1`] = `

View File

@ -35,7 +35,7 @@ const selectAfter = (
);
ReactDOM.render(
<div>
<>
<div style={{ marginBottom: 16 }}>
<Input addonBefore="http://" addonAfter=".com" defaultValue="mysite" />
</div>
@ -48,7 +48,7 @@ ReactDOM.render(
<div style={{ marginBottom: 16 }}>
<Input addonBefore="http://" suffix=".com" defaultValue="mysite" />
</div>
</div>,
</>,
mountNode,
);
```

View File

@ -68,7 +68,7 @@ const options = [
];
ReactDOM.render(
<div>
<>
<Mentions style={{ width: 100 }} rows={1} />
<Input.TextArea rows={1} style={{ width: 100 }} />
<Button type="primary">Button</Button>
@ -101,7 +101,7 @@ ReactDOM.render(
<Input style={narrowStyle} suffix="Y" />
<Input style={narrowStyle} />
<Input style={narrowStyle} defaultValue="1" suffix="Y" />
</div>,
</>,
mountNode,
);
```

View File

@ -23,12 +23,12 @@ const onChange = e => {
};
ReactDOM.render(
<div>
<>
<Input placeholder="input with clear icon" allowClear onChange={onChange} />
<br />
<br />
<TextArea placeholder="textarea with clear icon" allowClear onChange={onChange} />
</div>,
</>,
mountNode,
);
```

View File

@ -31,7 +31,7 @@ class Demo extends React.Component {
const { value } = this.state;
return (
<div>
<>
<TextArea placeholder="Autosize height based on content lines" autoSize />
<div style={{ margin: '24px 0' }} />
<TextArea
@ -45,7 +45,7 @@ class Demo extends React.Component {
placeholder="Controlled autosize"
autoSize={{ minRows: 3, maxRows: 5 }}
/>
</div>
</>
);
}
}

View File

@ -18,7 +18,7 @@ import { Input, Tooltip } from 'antd';
import { InfoCircleOutlined, UserOutlined } from '@ant-design/icons';
ReactDOM.render(
<div>
<>
<Input
placeholder="Enter your username"
prefix={<UserOutlined className="site-form-item-icon" />}
@ -31,12 +31,10 @@ ReactDOM.render(
<br />
<br />
<Input prefix="¥" suffix="RMB" />
<br />
<br />
<Input prefix="¥" suffix="RMB" disabled />
</div>,
</>,
mountNode,
);
```

View File

@ -19,12 +19,12 @@ import { Input } from 'antd';
const { Search } = Input;
ReactDOM.render(
<div>
<>
<Search placeholder="input search loading deault" loading />
<br />
<br />
<Search placeholder="input search loading with enterButton" loading enterButton />
</div>,
</>,
mountNode,
);
```

View File

@ -15,11 +15,22 @@ Example of creating a search box by grouping a standard input with a search butt
```jsx
import { Input } from 'antd';
import { AudioOutlined } from '@ant-design/icons';
const { Search } = Input;
const suffix = (
<AudioOutlined
style={{
fontSize: 16,
color: '#1890ff',
paddingRight: 4,
}}
/>
);
ReactDOM.render(
<div>
<>
<Search
placeholder="input search text"
onSearch={value => console.log(value)}
@ -36,7 +47,16 @@ ReactDOM.render(
size="large"
onSearch={value => console.log(value)}
/>
</div>,
<br />
<br />
<Search
placeholder="input search text"
enterButton="Search"
size="large"
suffix={suffix}
onSearch={value => console.log(value)}
/>
</>,
mountNode,
);
```

View File

@ -18,19 +18,15 @@ import { Input } from 'antd';
import { UserOutlined } from '@ant-design/icons';
ReactDOM.render(
<div className="example-input">
<>
<Input size="large" placeholder="large size" prefix={<UserOutlined />} />
<br />
<br />
<Input placeholder="default size" prefix={<UserOutlined />} />
<br />
<br />
<Input size="small" placeholder="small size" prefix={<UserOutlined />} />
<Input.Password size="large" placeholder="large Password" />
</div>,
</>,
mountNode,
);
```
```css
.example-input > span {
width: 200px;
margin: 0 8px 8px 0;
}
```

View File

@ -31,7 +31,7 @@ class Demo extends React.Component {
const { autoResize } = this.state;
return (
<div>
<>
<Button
onClick={() => this.setState({ autoResize: !autoResize })}
style={{ marginBottom: 16 }}
@ -39,7 +39,7 @@ class Demo extends React.Component {
Auto Resize: {String(autoResize)}
</Button>
<TextArea rows={4} autoSize={autoResize} defaultValue={defaultValue} />
</div>
</>
);
}
}

View File

@ -7,6 +7,15 @@
&-affix-wrapper {
.input();
display: inline-flex;
max-height: @input-height-base;
&-lg {
max-height: @input-height-lg;
}
&-sm {
max-height: @input-height-sm;
}
&-disabled {
.@{ant-prefix}-input[disabled] {
@ -15,7 +24,8 @@
}
> input.@{ant-prefix}-input {
padding: 0;
padding-right: 0;
padding-left: 0;
border: none;
outline: none;
@ -33,7 +43,9 @@
&-prefix,
&-suffix {
display: flex;
flex: none;
align-items: center;
}
&-prefix {

View File

@ -210,17 +210,26 @@
z-index: @zindex-dropdown;
border-radius: @border-radius-base;
// https://github.com/ant-design/ant-design/issues/13955
&::before {
position: absolute;
top: -7px;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0.0001;
content: ' ';
}
}
// https://github.com/ant-design/ant-design/issues/13955
&-placement-rightTop::before {
top: 0;
left: -7px;
}
> .@{menu-prefix-cls} {
background-color: @menu-bg;
border-radius: @border-radius-base;

View File

@ -4,24 +4,19 @@ import PageHeader from '..';
import ConfigProvider from '../../config-provider';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { spyElementPrototypes } from '../../__tests__/util/domHook';
describe('PageHeader', () => {
mountTest(PageHeader);
rtlTest(PageHeader);
let spy;
const mockGetBoundingClientRect = jest.spyOn(HTMLElement.prototype, 'getBoundingClientRect');
beforeAll(() => {
spy = spyElementPrototypes(HTMLElement, {
getBoundingClientRect: () => ({
width: 100,
}),
});
mockGetBoundingClientRect.mockReturnValue({ width: 100 });
});
afterAll(() => {
spy.mockRestore();
mockGetBoundingClientRect.mockRestore();
});
it('pageHeader should not contain back it back', () => {

View File

@ -17,7 +17,7 @@ export interface PageHeaderProps {
subTitle?: React.ReactNode;
style?: React.CSSProperties;
breadcrumb?: BreadcrumbProps;
tags?: React.ReactElement<Tag> | React.ReactElement<Tag>[];
tags?: typeof Tag | typeof Tag[];
footer?: React.ReactNode;
extra?: React.ReactNode;
avatar?: AvatarProps;

View File

@ -1005,7 +1005,7 @@ exports[`renders ./components/result/demo/500.md correctly 1`] = `
<div
class="ant-result-subtitle"
>
Sorry, the server is wrong.
Sorry, something went wrong.
</div>
<div
class="ant-result-extra"

View File

@ -11,7 +11,7 @@ title:
## en-US
The server is wrong.
Something went wrong on server.
```jsx
import { Result, Button } from 'antd';
@ -20,7 +20,7 @@ ReactDOM.render(
<Result
status="500"
title="500"
subTitle="Sorry, the server is wrong."
subTitle="Sorry, something went wrong."
extra={<Button type="primary">Back Home</Button>}
/>,
mountNode,

View File

@ -10,64 +10,23 @@ import { sleep } from '../../../tests/utils';
describe('Slider', () => {
mountTest(Slider);
rtlTest(Slider);
focusTest(Slider);
focusTest(Slider, { refFocus: true });
it('should show tooltip when hovering slider handler', () => {
const wrapper = mount(<Slider defaultValue={30} />);
wrapper
.find('.ant-slider-handle')
.at(0)
.simulate('mouseEnter');
expect(
render(
wrapper
.find('Trigger')
.instance()
.getComponent(),
),
).toMatchSnapshot();
wrapper
.find('.ant-slider-handle')
.at(0)
.simulate('mouseLeave');
expect(
render(
wrapper
.find('Trigger')
.instance()
.getComponent(),
),
).toMatchSnapshot();
wrapper.find('.ant-slider-handle').at(0).simulate('mouseEnter');
expect(render(wrapper.find('Trigger').instance().getComponent())).toMatchSnapshot();
wrapper.find('.ant-slider-handle').at(0).simulate('mouseLeave');
expect(render(wrapper.find('Trigger').instance().getComponent())).toMatchSnapshot();
});
it('when tooltipVisible is true, tooltip should show always, or should never show', () => {
let wrapper = mount(<Slider defaultValue={30} tooltipVisible />);
expect(
wrapper
.find('.ant-tooltip-content')
.at(0)
.hasClass('ant-tooltip-hidden'),
).toBe(false);
wrapper
.find('.ant-slider-handle')
.at(0)
.simulate('mouseEnter');
expect(
wrapper
.find('.ant-tooltip-content')
.at(0)
.hasClass('ant-tooltip-hidden'),
).toBe(false);
wrapper
.find('.ant-slider-handle')
.at(0)
.simulate('click');
expect(
wrapper
.find('.ant-tooltip-content')
.at(0)
.hasClass('ant-tooltip-hidden'),
).toBe(false);
expect(wrapper.find('.ant-tooltip-content').at(0).hasClass('ant-tooltip-hidden')).toBe(false);
wrapper.find('.ant-slider-handle').at(0).simulate('mouseEnter');
expect(wrapper.find('.ant-tooltip-content').at(0).hasClass('ant-tooltip-hidden')).toBe(false);
wrapper.find('.ant-slider-handle').at(0).simulate('click');
expect(wrapper.find('.ant-tooltip-content').at(0).hasClass('ant-tooltip-hidden')).toBe(false);
wrapper = mount(<Slider defaultValue={30} tooltipVisible={false} />);
expect(wrapper.find('.ant-tooltip-content').length).toBe(0);
});

View File

@ -5,7 +5,7 @@ import RcHandle from 'rc-slider/lib/Handle';
import classNames from 'classnames';
import { TooltipPlacement } from '../tooltip';
import SliderTooltip from './SliderTooltip';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import { ConfigContext } from '../config-provider';
export interface SliderMarks {
[key: number]:
@ -57,36 +57,19 @@ export interface SliderProps {
getTooltipPopupContainer?: (triggerNode: HTMLElement) => HTMLElement;
}
export interface SliderState {
visibles: { [index: number]: boolean };
}
export type Visibles = { [index: number]: boolean };
export default class Slider extends React.Component<SliderProps, SliderState> {
static defaultProps = {
tipFormatter(value: number) {
return typeof value === 'number' ? value.toString() : '';
},
const Slider = React.forwardRef<unknown, SliderProps>((props, ref) => {
const { getPrefixCls, direction, getPopupContainer } = React.useContext(ConfigContext);
const [visibles, setVisibles] = React.useState<Visibles>({});
const toggleTooltipVisible = (index: number, visible: boolean) => {
const temp = { ...visibles };
temp[index] = visible;
setVisibles(temp);
};
rcSlider: any;
constructor(props: SliderProps) {
super(props);
this.state = {
visibles: {},
};
}
toggleTooltipVisible = (index: number, visible: boolean) => {
this.setState(({ visibles }) => ({
visibles: {
...visibles,
[index]: visible,
},
}));
};
handleWithTooltip: HandleGeneratorFn = ({
const handleWithTooltip: HandleGeneratorFn = ({
tooltipPrefixCls,
prefixCls,
info: { value, dragging, index, ...restProps },
@ -97,8 +80,7 @@ export default class Slider extends React.Component<SliderProps, SliderState> {
tooltipPlacement,
getTooltipPopupContainer,
vertical,
} = this.props;
const { visibles } = this.state;
} = props;
const isTipFormatter = tipFormatter ? visibles[index] || dragging : false;
const visible = tooltipVisible || (tooltipVisible === undefined && isTipFormatter);
return (
@ -110,72 +92,41 @@ export default class Slider extends React.Component<SliderProps, SliderState> {
transitionName="zoom-down"
key={index}
overlayClassName={`${prefixCls}-tooltip`}
getPopupContainer={getTooltipPopupContainer || (() => document.body)}
getPopupContainer={getTooltipPopupContainer || getPopupContainer || (() => document.body)}
>
<RcHandle
{...restProps}
value={value}
onMouseEnter={() => this.toggleTooltipVisible(index, true)}
onMouseLeave={() => this.toggleTooltipVisible(index, false)}
onMouseEnter={() => toggleTooltipVisible(index, true)}
onMouseLeave={() => toggleTooltipVisible(index, false)}
/>
</SliderTooltip>
);
};
saveSlider = (node: any) => {
this.rcSlider = node;
};
focus() {
this.rcSlider.focus();
const {
prefixCls: customizePrefixCls,
tooltipPrefixCls: customizeTooltipPrefixCls,
range,
className,
...restProps
} = props;
const prefixCls = getPrefixCls('slider', customizePrefixCls);
const tooltipPrefixCls = getPrefixCls('tooltip', customizeTooltipPrefixCls);
const cls = classNames(className, {
[`${prefixCls}-rtl`]: direction === 'rtl',
});
// make reverse default on rtl direction
if (direction === 'rtl' && !restProps.vertical) {
restProps.reverse = !restProps.reverse;
}
blur() {
this.rcSlider.blur();
}
renderSlider = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
const {
prefixCls: customizePrefixCls,
tooltipPrefixCls: customizeTooltipPrefixCls,
range,
className,
...restProps
} = this.props;
const prefixCls = getPrefixCls('slider', customizePrefixCls);
const tooltipPrefixCls = getPrefixCls('tooltip', customizeTooltipPrefixCls);
const cls = classNames(className, {
[`${prefixCls}-rtl`]: direction === 'rtl',
});
// make reverse default on rtl direction
if (direction === 'rtl' && !restProps.vertical) {
restProps.reverse = !restProps.reverse;
}
if (range) {
return (
<RcRange
{...restProps}
className={cls}
ref={this.saveSlider}
handle={(info: HandleGeneratorInfo) =>
this.handleWithTooltip({
tooltipPrefixCls,
prefixCls,
info,
})
}
prefixCls={prefixCls}
tooltipPrefixCls={tooltipPrefixCls}
/>
);
}
if (range) {
return (
<RcSlider
<RcRange
{...restProps}
className={cls}
ref={this.saveSlider}
ref={ref}
handle={(info: HandleGeneratorInfo) =>
this.handleWithTooltip({
handleWithTooltip({
tooltipPrefixCls,
prefixCls,
info,
@ -185,9 +136,31 @@ export default class Slider extends React.Component<SliderProps, SliderState> {
tooltipPrefixCls={tooltipPrefixCls}
/>
);
};
render() {
return <ConfigConsumer>{this.renderSlider}</ConfigConsumer>;
}
}
return (
<RcSlider
{...restProps}
className={cls}
ref={ref}
handle={(info: HandleGeneratorInfo) =>
handleWithTooltip({
tooltipPrefixCls,
prefixCls,
info,
})
}
prefixCls={prefixCls}
tooltipPrefixCls={tooltipPrefixCls}
/>
);
});
Slider.displayName = 'Slider';
Slider.defaultProps = {
tipFormatter(value: number) {
return typeof value === 'number' ? value.toString() : '';
},
};
export default Slider;

View File

@ -11,7 +11,7 @@
.@{steps-prefix-cls}-item {
&-icon {
.@{steps-prefix-cls}-rtl & {
.@{steps-prefix-cls}.@{steps-prefix-cls}-rtl & {
margin-right: 0;
margin-left: 8px;
}
@ -129,6 +129,7 @@
.@{steps-prefix-cls}-item-title {
.@{steps-prefix-cls}-rtl& {
padding-right: 0;
padding-left: 12px;
}
}
}

View File

@ -3,6 +3,7 @@
flex-direction: column;
.@{steps-prefix-cls}-item {
display: block;
flex: 1 0 auto;
overflow: visible;
&-icon {
float: left;

View File

@ -11261,14 +11261,16 @@ exports[`renders ./components/table/demo/resizable-column.md correctly 1`] = `
<tr>
<th
class="ant-table-cell react-resizable"
handle="[object Object]"
>
Date
<span
class="react-resizable-handle react-resizable-handle-se"
class="react-resizable-handle"
/>
</th>
<th
class="ant-table-cell ant-table-column-has-sorters react-resizable"
handle="[object Object]"
>
<div
class="ant-table-column-sorters-with-tooltip"
@ -11330,23 +11332,25 @@ exports[`renders ./components/table/demo/resizable-column.md correctly 1`] = `
</div>
</div>
<span
class="react-resizable-handle react-resizable-handle-se"
class="react-resizable-handle"
/>
</th>
<th
class="ant-table-cell react-resizable"
handle="[object Object]"
>
Type
<span
class="react-resizable-handle react-resizable-handle-se"
class="react-resizable-handle"
/>
</th>
<th
class="ant-table-cell react-resizable"
handle="[object Object]"
>
Note
<span
class="react-resizable-handle react-resizable-handle-se"
class="react-resizable-handle"
/>
</th>
<th

View File

@ -24,6 +24,10 @@ describe('Table.typescript', () => {
);
expect(table).toBeTruthy();
});
it('selections', () => {
const table = <Table rowSelection={{ selections: [Table.SELECTION_ALL] }} />;
expect(table).toBeTruthy();
});
});
describe('Table.typescript types', () => {

View File

@ -46,64 +46,69 @@ const columns = [
},
];
const getRandomuserParams = params => {
return {
results: params.pagination.pageSize,
page: params.pagination.current,
...params,
};
};
class App extends React.Component {
state = {
data: [],
pagination: {},
pagination: {
current: 1,
pageSize: 10,
},
loading: false,
};
componentDidMount() {
this.fetch();
const { pagination } = this.state;
this.fetch({ pagination });
}
handleTableChange = (pagination, filters, sorter) => {
const pager = { ...this.state.pagination };
pager.current = pagination.current;
this.setState({
pagination: pager,
});
this.fetch({
results: pagination.pageSize,
page: pagination.current,
sortField: sorter.field,
sortOrder: sorter.order,
pagination,
...filters,
});
};
fetch = (params = {}) => {
console.log('params:', params);
this.setState({ loading: true });
reqwest({
url: 'https://randomuser.me/api',
method: 'get',
data: {
results: 10,
...params,
},
type: 'json',
data: getRandomuserParams(params),
}).then(data => {
const pagination = { ...this.state.pagination };
// Read total count from server
// pagination.total = data.totalCount;
pagination.total = 200;
console.log(data);
this.setState({
loading: false,
data: data.results,
pagination,
pagination: {
...params.pagination,
total: 200,
// 200 is mock data, you should read it from server
// total: data.totalCount,
},
});
});
};
render() {
const { data, pagination, loading } = this.state;
return (
<Table
columns={columns}
rowKey={record => record.login.uuid}
dataSource={this.state.data}
pagination={this.state.pagination}
loading={this.state.loading}
dataSource={data}
pagination={pagination}
loading={loading}
onChange={this.handleTableChange}
/>
);

View File

@ -28,14 +28,14 @@ const ResizeableTitle = props => {
<Resizable
width={width}
height={0}
handle={resizeHandle => (
handle={
<span
className={`react-resizable-handle react-resizable-handle-${resizeHandle}`}
className="react-resizable-handle"
onClick={e => {
e.stopPropagation();
}}
/>
)}
}
onResize={onResize}
draggableOpts={{ enableUserSelectHack: false }}
>

View File

@ -21,8 +21,8 @@ import {
const EMPTY_LIST: any[] = [];
// TODO: warning if use ajax!!!
export const SELECTION_ALL = 'SELECT_ALL';
export const SELECTION_INVERT = 'SELECT_INVERT';
export const SELECTION_ALL = 'SELECT_ALL' as const;
export const SELECTION_INVERT = 'SELECT_INVERT' as const;
function getFixedType<RecordType>(column: ColumnsType<RecordType>[number]): FixedType | undefined {
return column && column.fixed;
@ -41,7 +41,10 @@ interface UseSelectionConfig<RecordType> {
getPopupContainer?: GetPopupContainer;
}
type INTERNAL_SELECTION_ITEM = SelectionItem | typeof SELECTION_ALL | typeof SELECTION_INVERT;
export type INTERNAL_SELECTION_ITEM =
| SelectionItem
| typeof SELECTION_ALL
| typeof SELECTION_INVERT;
function flattenData<RecordType>(
data: RecordType[] | undefined,

View File

@ -7,7 +7,7 @@ import {
import { CheckboxProps } from '../checkbox';
import { PaginationConfig } from '../pagination';
import { Breakpoint } from '../_util/responsiveObserve';
import { INTERNAL_SELECTION_ITEM } from './hooks/useSelection';
export { GetRowKey, ExpandableConfig };
export type Key = React.Key;
@ -134,7 +134,7 @@ export interface TableRowSelection<T> {
onSelectAll?: (selected: boolean, selectedRows: T[], changeRows: T[]) => void;
/** @deprecated This function is meaningless and should use `onChange` instead */
onSelectInvert?: (selectedRowKeys: Key[]) => void;
selections?: SelectionItem[] | boolean;
selections?: INTERNAL_SELECTION_ITEM[] | boolean;
hideDefaultSelections?: boolean;
fixed?: boolean;
columnWidth?: string | number;

View File

@ -32,6 +32,16 @@
.@{table-prefix-cls}-expanded-row-fixed {
margin: -@padding-vertical -@padding-horizontal;
}
.@{table-prefix-cls}-tbody {
// ========================= Nest Table ===========================
.@{table-prefix-cls}-wrapper:only-child {
.@{table-prefix-cls} {
margin: -@padding-vertical -@padding-horizontal -@padding-vertical (@padding-horizontal +
ceil(@font-size-sm * 1.4));
}
}
}
}
}

View File

@ -179,5 +179,3 @@
border-top-color: @component-background;
}
}
@import './card-style.rtl.less';

View File

@ -1,20 +0,0 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@tab-prefix-cls: ~'@{ant-prefix}-tabs';
// card style
.@{tab-prefix-cls} {
&&-card &-card-bar &-tab &-close-x {
.@{tab-prefix-cls}-rtl& {
margin-right: 3px;
margin-left: -5px;
}
}
&-extra-content {
.@{tab-prefix-cls}-rtl & {
float: left !important;
}
}
}

View File

@ -297,6 +297,7 @@
float: none;
margin: @tabs-vertical-margin;
padding: @tabs-vertical-padding;
text-align: center;
&:last-child {
margin-bottom: 0;
@ -368,9 +369,6 @@
margin-right: -1px;
margin-bottom: 0;
border-right: @border-width-base @border-style-base @border-color-split;
.@{tab-prefix-cls}-tab {
text-align: right;
}
.@{tab-prefix-cls}-nav-container {
margin-right: -1px;
}

View File

@ -44,4 +44,40 @@
}
}
}
.@{tab-prefix-cls}-left-bar,
.@{tab-prefix-cls}-right-bar {
.@{tab-prefix-cls}-tab {
.@{tab-prefix-cls}-rtl& {
margin: @tabs-vertical-margin;
}
&:last-child {
margin-bottom: 0;
}
}
}
.@{tab-prefix-cls}-right-bar {
.@{tab-prefix-cls}-ink-bar {
.@{tab-prefix-cls}-rtl& {
right: auto;
left: 1px;
}
}
}
// card
&&-card &-card-bar &-tab &-close-x {
.@{tab-prefix-cls}-rtl& {
margin-right: 3px;
margin-left: -5px;
}
}
&-extra-content {
.@{tab-prefix-cls}-rtl & {
float: left !important;
}
}
}

View File

@ -10,16 +10,16 @@ export interface CheckableTagProps {
onChange?: (checked: boolean) => void;
}
export default class CheckableTag extends React.Component<CheckableTagProps> {
handleClick = () => {
const { checked, onChange } = this.props;
const CheckableTag: React.FC<CheckableTagProps> = props => {
const handleClick = () => {
const { checked, onChange } = props;
if (onChange) {
onChange(!checked);
}
};
renderCheckableTag = ({ getPrefixCls }: ConfigConsumerProps) => {
const { prefixCls: customizePrefixCls, className, checked, ...restProps } = this.props;
const renderCheckableTag = ({ getPrefixCls }: ConfigConsumerProps) => {
const { prefixCls: customizePrefixCls, className, checked, ...restProps } = props;
const prefixCls = getPrefixCls('tag', customizePrefixCls);
const cls = classNames(
prefixCls,
@ -31,10 +31,10 @@ export default class CheckableTag extends React.Component<CheckableTagProps> {
);
delete (restProps as any).onChange; // TypeScript cannot check delete now.
return <span {...(restProps as any)} className={cls} onClick={this.handleClick} />;
return <span {...(restProps as any)} className={cls} onClick={handleClick} />;
};
render() {
return <ConfigConsumer>{this.renderCheckableTag}</ConfigConsumer>;
}
}
return <ConfigConsumer>{renderCheckableTag}</ConfigConsumer>;
};
export default CheckableTag;

View File

@ -27,61 +27,57 @@ export interface TagProps extends React.HTMLAttributes<HTMLSpanElement> {
icon?: React.ReactNode;
}
interface TagState {
visible: boolean;
}
const PresetColorRegex = new RegExp(`^(${PresetColorTypes.join('|')})(-inverse)?$`);
const PresetStatusColorRegex = new RegExp(`^(${PresetStatusColorTypes.join('|')})$`);
class Tag extends React.Component<TagProps, TagState> {
static CheckableTag = CheckableTag;
interface TagType extends React.FC<TagProps> {
CheckableTag: typeof CheckableTag;
}
static defaultProps = {
closable: false,
};
const Tag: TagType = props => {
const [visible, setVisible] = React.useState(true);
static getDerivedStateFromProps(nextProps: TagProps) {
if ('visible' in nextProps) {
return {
visible: nextProps.visible,
};
React.useEffect(() => {
if ('visible' in props) {
setVisible(props.visible!);
}
return null;
}
}, [props.visible]);
state = {
visible: true,
const isPresetColor = (): boolean => {
const { color } = props;
if (!color) {
return false;
}
return PresetColorRegex.test(color) || PresetStatusColorRegex.test(color);
};
getTagStyle() {
const { color, style } = this.props;
const isPresetColor = this.isPresetColor();
const getTagStyle = () => {
const { color, style } = props;
return {
backgroundColor: color && !isPresetColor ? color : undefined,
backgroundColor: color && !isPresetColor() ? color : undefined,
...style,
};
}
};
getTagClassName({ getPrefixCls, direction }: ConfigConsumerProps) {
const { prefixCls: customizePrefixCls, className, color } = this.props;
const { visible } = this.state;
const isPresetColor = this.isPresetColor();
const getTagClassName = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
const { prefixCls: customizePrefixCls, className, color } = props;
const presetColor = isPresetColor();
const prefixCls = getPrefixCls('tag', customizePrefixCls);
return classNames(
prefixCls,
{
[`${prefixCls}-${color}`]: isPresetColor,
[`${prefixCls}-has-color`]: color && !isPresetColor,
[`${prefixCls}-${color}`]: presetColor,
[`${prefixCls}-has-color`]: color && !presetColor,
[`${prefixCls}-hidden`]: !visible,
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
);
}
};
setVisible(visible: boolean, e: React.MouseEvent<HTMLElement>) {
const { onClose } = this.props;
const handleIconClick = (e: React.MouseEvent<HTMLElement>) => {
e.stopPropagation();
const { onClose } = props;
if (onClose) {
onClose(e);
}
@ -89,31 +85,18 @@ class Tag extends React.Component<TagProps, TagState> {
if (e.defaultPrevented) {
return;
}
if (!('visible' in this.props)) {
this.setState({ visible });
if (!('visible' in props)) {
setVisible(false);
}
}
handleIconClick = (e: React.MouseEvent<HTMLElement>) => {
e.stopPropagation();
this.setVisible(false, e);
};
isPresetColor(): boolean {
const { color } = this.props;
if (!color) {
return false;
}
return PresetColorRegex.test(color) || PresetStatusColorRegex.test(color);
}
const renderCloseIcon = () => {
const { closable } = props;
return closable ? <CloseOutlined onClick={handleIconClick} /> : null;
};
renderCloseIcon() {
const { closable } = this.props;
return closable ? <CloseOutlined onClick={this.handleIconClick} /> : null;
}
renderTag = (configProps: ConfigConsumerProps) => {
const { children, icon, ...otherProps } = this.props;
const renderTag = (configProps: ConfigConsumerProps) => {
const { children, icon, ...otherProps } = props;
const isNeedWave =
'onClick' in otherProps || (children && (children as React.ReactElement<any>).type === 'a');
const tagProps = omit(otherProps, ['onClose', 'color', 'visible', 'closable', 'prefixCls']);
@ -129,26 +112,26 @@ class Tag extends React.Component<TagProps, TagState> {
return isNeedWave ? (
<Wave>
<span
{...tagProps}
className={this.getTagClassName(configProps)}
style={this.getTagStyle()}
>
<span {...tagProps} className={getTagClassName(configProps)} style={getTagStyle()}>
{kids}
{this.renderCloseIcon()}
{renderCloseIcon()}
</span>
</Wave>
) : (
<span {...tagProps} className={this.getTagClassName(configProps)} style={this.getTagStyle()}>
<span {...tagProps} className={getTagClassName(configProps)} style={getTagStyle()}>
{kids}
{this.renderCloseIcon()}
{renderCloseIcon()}
</span>
);
};
render() {
return <ConfigConsumer>{this.renderTag}</ConfigConsumer>;
}
}
return <ConfigConsumer>{renderTag}</ConfigConsumer>;
};
Tag.defaultProps = {
closable: false,
};
Tag.CheckableTag = CheckableTag;
export default Tag;

View File

@ -18,7 +18,7 @@ describe('TimePicker', () => {
errorSpy.mockRestore();
});
focusTest(TimePicker, true);
focusTest(TimePicker, { refFocus: true });
mountTest(TimePicker);
rtlTest(TimePicker);

View File

@ -1,6 +1,5 @@
import * as React from 'react';
import classNames from 'classnames';
import omit from 'omit.js';
import debounce from 'lodash/debounce';
import { conductExpandParent } from 'rc-tree/lib/util';
import { EventDataNode, DataNode, Key } from 'rc-tree/lib/interface';
@ -8,7 +7,7 @@ import { convertDataToEntities, convertTreeToData } from 'rc-tree/lib/utils/tree
import FileOutlined from '@ant-design/icons/FileOutlined';
import FolderOpenOutlined from '@ant-design/icons/FolderOpenOutlined';
import FolderOutlined from '@ant-design/icons/FolderOutlined';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import { ConfigContext } from '../config-provider';
import Tree, { TreeProps, AntdTreeNodeAttribute } from './Tree';
import { calcRangeKeys, convertDirectoryKeysToNodes } from './utils/dictUtil';
@ -36,109 +35,116 @@ function getTreeData({ treeData, children }: DirectoryTreeProps) {
return treeData || convertTreeToData(children);
}
class DirectoryTree extends React.Component<DirectoryTreeProps, DirectoryTreeState> {
static defaultProps = {
showIcon: true,
expandAction: 'click' as DirectoryTreeProps['expandAction'],
};
static getDerivedStateFromProps(nextProps: DirectoryTreeProps) {
const newState: DirectoryTreeState = {};
if ('expandedKeys' in nextProps) {
newState.expandedKeys = nextProps.expandedKeys;
}
if ('selectedKeys' in nextProps) {
newState.selectedKeys = nextProps.selectedKeys;
}
return newState;
}
state: DirectoryTreeState;
tree: Tree;
onDebounceExpand: (event: React.MouseEvent<HTMLElement>, node: EventDataNode) => void;
const DirectoryTree: React.FC<DirectoryTreeProps> = ({
defaultExpandAll,
defaultExpandParent,
defaultExpandedKeys,
...props
}) => {
// Shift click usage
lastSelectedKey?: Key;
const lastSelectedKey = React.useRef<Key>();
cachedSelectedKeys?: Key[];
const cachedSelectedKeys = React.useRef<Key[]>();
constructor(props: DirectoryTreeProps) {
super(props);
const ref = React.createRef<any>();
const { defaultExpandAll, defaultExpandParent, expandedKeys, defaultExpandedKeys } = props;
const getInitExpandedKeys = () => {
const { keyEntities } = convertDataToEntities(getTreeData(props));
// Selected keys
this.state = {
selectedKeys: props.selectedKeys || props.defaultSelectedKeys || [],
};
let initExpandedKeys: any;
// Expanded keys
if (defaultExpandAll) {
this.state.expandedKeys = Object.keys(keyEntities);
initExpandedKeys = Object.keys(keyEntities);
} else if (defaultExpandParent) {
this.state.expandedKeys = conductExpandParent(
expandedKeys || defaultExpandedKeys,
initExpandedKeys = conductExpandParent(
props.expandedKeys || defaultExpandedKeys,
keyEntities,
);
} else {
this.state.expandedKeys = expandedKeys || defaultExpandedKeys;
initExpandedKeys = props.expandedKeys || defaultExpandedKeys;
}
return initExpandedKeys;
};
const [selectedKeys, setSelectedKeys] = React.useState(
props.selectedKeys || props.defaultSelectedKeys || [],
);
const [expandedKeys, setExpandedKeys] = React.useState(getInitExpandedKeys());
React.useEffect(() => {
if ('selectedKeys' in props) {
setSelectedKeys(props.selectedKeys!);
}
}, [props.selectedKeys]);
React.useEffect(() => {
if ('expandedKeys' in props) {
setExpandedKeys(props.expandedKeys);
}
}, [props.expandedKeys]);
const expandFolderNode = (event: React.MouseEvent<HTMLElement>, node: any) => {
const { isLeaf } = node;
if (isLeaf || event.shiftKey || event.metaKey || event.ctrlKey) {
return;
}
this.onDebounceExpand = debounce(this.expandFolderNode, 200, {
leading: true,
});
}
// Call internal rc-tree expand function
// https://github.com/ant-design/ant-design/issues/12567
ref.current.onNodeExpand(event, node);
};
onExpand = (
expandedKeys: Key[],
const onDebounceExpand = debounce(expandFolderNode, 200, {
leading: true,
});
const onExpand = (
keys: Key[],
info: {
node: EventDataNode;
expanded: boolean;
nativeEvent: MouseEvent;
},
) => {
const { onExpand } = this.props;
this.setUncontrolledState({ expandedKeys });
if (!('expandedKeys' in props)) {
setExpandedKeys(keys);
}
// Call origin function
if (onExpand) {
return onExpand(expandedKeys, info);
if (props.onExpand) {
return props.onExpand(keys, info);
}
return undefined;
};
onClick = (event: React.MouseEvent<HTMLElement>, node: EventDataNode) => {
const { onClick, expandAction } = this.props;
const onClick = (event: React.MouseEvent<HTMLElement>, node: EventDataNode) => {
const { expandAction } = props;
// Expand the tree
if (expandAction === 'click') {
this.onDebounceExpand(event, node);
onDebounceExpand(event, node);
}
if (onClick) {
onClick(event, node);
if (props.onClick) {
props.onClick(event, node);
}
};
onDoubleClick = (event: React.MouseEvent<HTMLElement>, node: EventDataNode) => {
const { onDoubleClick, expandAction } = this.props;
const onDoubleClick = (event: React.MouseEvent<HTMLElement>, node: EventDataNode) => {
const { expandAction } = props;
// Expand the tree
if (expandAction === 'doubleClick') {
this.onDebounceExpand(event, node);
onDebounceExpand(event, node);
}
if (onDoubleClick) {
onDoubleClick(event, node);
if (props.onDoubleClick) {
props.onDoubleClick(event, node);
}
};
onSelect = (
const onSelect = (
keys: Key[],
event: {
event: 'select';
@ -148,13 +154,12 @@ class DirectoryTree extends React.Component<DirectoryTreeProps, DirectoryTreeSta
nativeEvent: MouseEvent;
},
) => {
const { onSelect, multiple } = this.props;
const { expandedKeys = [] } = this.state;
const { multiple } = props;
const { node, nativeEvent } = event;
const { key = '' } = node;
const treeData = getTreeData(this.props);
const newState: DirectoryTreeState = {};
const treeData = getTreeData(props);
// const newState: DirectoryTreeState = {};
// We need wrap this event since some value is not same
const newEvent: any = {
@ -171,90 +176,63 @@ class DirectoryTree extends React.Component<DirectoryTreeProps, DirectoryTreeSta
if (multiple && ctrlPick) {
// Control click
newSelectedKeys = keys;
this.lastSelectedKey = key;
this.cachedSelectedKeys = newSelectedKeys;
lastSelectedKey.current = key;
cachedSelectedKeys.current = newSelectedKeys;
newEvent.selectedNodes = convertDirectoryKeysToNodes(treeData, newSelectedKeys);
} else if (multiple && shiftPick) {
// Shift click
newSelectedKeys = Array.from(
new Set([
...(this.cachedSelectedKeys || []),
...calcRangeKeys(treeData, expandedKeys, key, this.lastSelectedKey),
...(cachedSelectedKeys.current || []),
...calcRangeKeys(treeData, expandedKeys, key, lastSelectedKey.current),
]),
);
newEvent.selectedNodes = convertDirectoryKeysToNodes(treeData, newSelectedKeys);
} else {
// Single click
newSelectedKeys = [key];
this.lastSelectedKey = key;
this.cachedSelectedKeys = newSelectedKeys;
lastSelectedKey.current = key;
cachedSelectedKeys.current = newSelectedKeys;
newEvent.selectedNodes = convertDirectoryKeysToNodes(treeData, newSelectedKeys);
}
newState.selectedKeys = newSelectedKeys;
if (onSelect) {
onSelect(newSelectedKeys, newEvent);
if (props.onSelect) {
props.onSelect(newSelectedKeys, newEvent);
}
this.setUncontrolledState(newState);
};
setTreeRef = (node: Tree) => {
this.tree = node;
};
expandFolderNode = (event: React.MouseEvent<HTMLElement>, node: any) => {
const { isLeaf } = node;
if (isLeaf || event.shiftKey || event.metaKey || event.ctrlKey) {
return;
}
// Get internal rc-tree
const internalTree = this.tree.tree;
// Call internal rc-tree expand function
// https://github.com/ant-design/ant-design/issues/12567
internalTree.onNodeExpand(event, node);
};
setUncontrolledState = (state: DirectoryTreeState) => {
const newState = omit(state, Object.keys(this.props));
if (Object.keys(newState).length) {
this.setState(newState);
if (!('selectedKeys' in props)) {
setSelectedKeys(newSelectedKeys);
}
};
const { getPrefixCls, direction } = React.useContext(ConfigContext);
renderDirectoryTree = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
const { prefixCls: customizePrefixCls, className, ...props } = this.props;
const { expandedKeys, selectedKeys } = this.state;
const { prefixCls: customizePrefixCls, className, ...otherProps } = props;
const prefixCls = getPrefixCls('tree', customizePrefixCls);
const connectClassName = classNames(`${prefixCls}-directory`, className, {
[`${prefixCls}-directory-rtl`]: direction === 'rtl',
});
const prefixCls = getPrefixCls('tree', customizePrefixCls);
const connectClassName = classNames(`${prefixCls}-directory`, className, {
[`${prefixCls}-directory-rtl`]: direction === 'rtl',
});
return (
<Tree
icon={getIcon}
ref={this.setTreeRef}
blockNode
{...props}
prefixCls={prefixCls}
className={connectClassName}
expandedKeys={expandedKeys}
selectedKeys={selectedKeys}
onSelect={this.onSelect}
onClick={this.onClick}
onDoubleClick={this.onDoubleClick}
onExpand={this.onExpand}
/>
);
};
return (
<Tree
icon={getIcon}
ref={ref}
blockNode
{...otherProps}
prefixCls={prefixCls}
className={connectClassName}
expandedKeys={expandedKeys}
selectedKeys={selectedKeys}
onSelect={onSelect}
onClick={onClick}
onDoubleClick={onDoubleClick}
onExpand={onExpand}
/>
);
};
render() {
return <ConfigConsumer>{this.renderDirectoryTree}</ConfigConsumer>;
}
}
DirectoryTree.defaultProps = {
showIcon: true,
expandAction: 'click' as DirectoryTreeProps['expandAction'],
};
export default DirectoryTree;

View File

@ -4,7 +4,7 @@ import classNames from 'classnames';
import { DataNode, Key } from 'rc-tree/lib/interface';
import DirectoryTree from './DirectoryTree';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import { ConfigContext } from '../config-provider';
import collapseMotion from '../_util/motion';
import renderSwitcherIcon from './utils/iconUtil';
@ -135,62 +135,58 @@ export interface TreeProps extends Omit<RcTreeProps, 'prefixCls'> {
blockNode?: boolean;
}
export default class Tree extends React.Component<TreeProps, any> {
static TreeNode = TreeNode;
static DirectoryTree = DirectoryTree;
static defaultProps = {
checkable: false,
showIcon: false,
motion: {
...collapseMotion,
motionAppear: false,
},
blockNode: false,
};
tree: any;
setTreeRef = (node: any) => {
this.tree = node;
};
renderTree = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
const { props } = this;
const {
prefixCls: customizePrefixCls,
className,
showIcon,
showLine,
switcherIcon,
blockNode,
children,
} = props;
const { checkable } = props;
const prefixCls = getPrefixCls('tree', customizePrefixCls);
return (
<RcTree
itemHeight={20}
ref={this.setTreeRef}
{...props}
prefixCls={prefixCls}
className={classNames(className, {
[`${prefixCls}-icon-hide`]: !showIcon,
[`${prefixCls}-block-node`]: blockNode,
[`${prefixCls}-rtl`]: direction === 'rtl',
})}
checkable={checkable ? <span className={`${prefixCls}-checkbox-inner`} /> : checkable}
switcherIcon={(nodeProps: AntTreeNodeProps) =>
renderSwitcherIcon(prefixCls, switcherIcon, showLine, nodeProps)
}
>
{children}
</RcTree>
);
};
render() {
return <ConfigConsumer>{this.renderTree}</ConfigConsumer>;
}
interface CompoundedComponent
extends React.ForwardRefExoticComponent<TreeProps & React.RefAttributes<RcTree>> {
TreeNode: typeof TreeNode;
DirectoryTree: typeof DirectoryTree;
}
const Tree = React.forwardRef<RcTree, TreeProps>((props, ref) => {
const { getPrefixCls, direction } = React.useContext(ConfigContext);
const {
prefixCls: customizePrefixCls,
className,
showIcon,
showLine,
switcherIcon,
blockNode,
children,
checkable,
} = props;
const prefixCls = getPrefixCls('tree', customizePrefixCls);
return (
<RcTree
itemHeight={20}
ref={ref}
{...props}
prefixCls={prefixCls}
className={classNames(className, {
[`${prefixCls}-icon-hide`]: !showIcon,
[`${prefixCls}-block-node`]: blockNode,
[`${prefixCls}-rtl`]: direction === 'rtl',
})}
checkable={checkable ? <span className={`${prefixCls}-checkbox-inner`} /> : checkable}
switcherIcon={(nodeProps: AntTreeNodeProps) =>
renderSwitcherIcon(prefixCls, switcherIcon, showLine, nodeProps)
}
>
{children}
</RcTree>
);
}) as CompoundedComponent;
Tree.TreeNode = TreeNode;
Tree.DirectoryTree = DirectoryTree;
Tree.defaultProps = {
checkable: false,
showIcon: false,
motion: {
...collapseMotion,
motionAppear: false,
},
blockNode: false,
};
export default Tree;

View File

@ -46,36 +46,20 @@ describe('Directory Tree', () => {
it('click', () => {
const wrapper = mount(createTree());
wrapper
.find(TreeNode)
.find('.ant-tree-node-content-wrapper')
.at(0)
.simulate('click');
wrapper.find(TreeNode).find('.ant-tree-node-content-wrapper').at(0).simulate('click');
expect(wrapper.render()).toMatchSnapshot();
jest.runAllTimers();
wrapper
.find(TreeNode)
.find('.ant-tree-node-content-wrapper')
.at(0)
.simulate('click');
wrapper.find(TreeNode).find('.ant-tree-node-content-wrapper').at(0).simulate('click');
expect(wrapper.render()).toMatchSnapshot();
});
it('double click', () => {
const wrapper = mount(createTree({ expandAction: 'doubleClick' }));
wrapper
.find(TreeNode)
.find('.ant-tree-node-content-wrapper')
.at(0)
.simulate('doubleClick');
wrapper.find(TreeNode).find('.ant-tree-node-content-wrapper').at(0).simulate('doubleClick');
expect(wrapper.render()).toMatchSnapshot();
jest.runAllTimers();
wrapper
.find(TreeNode)
.find('.ant-tree-node-content-wrapper')
.at(0)
.simulate('doubleClick');
wrapper.find(TreeNode).find('.ant-tree-node-content-wrapper').at(0).simulate('doubleClick');
expect(wrapper.render()).toMatchSnapshot();
});
@ -106,11 +90,7 @@ describe('Directory Tree', () => {
it(action, () => {
const wrapper = mount(<StateDirTree expandAction={action} />);
wrapper
.find(TreeNode)
.find('.ant-tree-node-content-wrapper')
.at(0)
.simulate(action);
wrapper.find(TreeNode).find('.ant-tree-node-content-wrapper').at(0).simulate(action);
jest.runAllTimers();
expect(wrapper.render()).toMatchSnapshot();
});
@ -179,20 +159,12 @@ describe('Directory Tree', () => {
}),
);
wrapper
.find(TreeNode)
.find('.ant-tree-node-content-wrapper')
.at(0)
.simulate('click');
wrapper.find(TreeNode).find('.ant-tree-node-content-wrapper').at(0).simulate('click');
expect(onSelect.mock.calls[0][1].selected).toBeTruthy();
expect(onSelect.mock.calls[0][1].selectedNodes.length).toBe(1);
// Click twice should keep selected
wrapper
.find(TreeNode)
.find('.ant-tree-node-content-wrapper')
.at(0)
.simulate('click');
wrapper.find(TreeNode).find('.ant-tree-node-content-wrapper').at(0).simulate('click');
expect(onSelect.mock.calls[1][1].selected).toBeTruthy();
expect(onSelect.mock.calls[0][0]).toEqual(onSelect.mock.calls[1][0]);
expect(onSelect.mock.calls[1][1].selectedNodes.length).toBe(1);
@ -201,11 +173,7 @@ describe('Directory Tree', () => {
// Ref: https://github.com/facebook/react/blob/master/packages/react-dom/src/test-utils/ReactTestUtils.js#L360
nativeEventProto.ctrlKey = true;
wrapper
.find(TreeNode)
.find('.ant-tree-node-content-wrapper')
.at(1)
.simulate('click');
wrapper.find(TreeNode).find('.ant-tree-node-content-wrapper').at(1).simulate('click');
expect(wrapper.render()).toMatchSnapshot();
expect(onSelect.mock.calls[2][0].length).toBe(2);
expect(onSelect.mock.calls[2][1].selected).toBeTruthy();
@ -214,11 +182,7 @@ describe('Directory Tree', () => {
delete nativeEventProto.ctrlKey;
nativeEventProto.shiftKey = true;
wrapper
.find(TreeNode)
.find('.ant-tree-node-content-wrapper')
.at(4)
.simulate('click');
wrapper.find(TreeNode).find('.ant-tree-node-content-wrapper').at(4).simulate('click');
expect(wrapper.render()).toMatchSnapshot();
expect(onSelect.mock.calls[3][0].length).toBe(5);
expect(onSelect.mock.calls[3][1].selected).toBeTruthy();
@ -226,4 +190,11 @@ describe('Directory Tree', () => {
delete nativeEventProto.shiftKey;
});
it('onDoubleClick', () => {
const onDoubleClick = jest.fn();
const wrapper = mount(createTree({ onDoubleClick }));
wrapper.find(TreeNode).find('.ant-tree-node-content-wrapper').at(0).simulate('doubleclick');
expect(onDoubleClick).toBeCalled();
});
});

View File

@ -96,6 +96,10 @@
}
}
}
&-loading-icon {
color: @primary-color;
}
}
// >>> Checkbox
@ -137,14 +141,6 @@
}
}
// ===================== Loading ======================
.@{tree-node-prefix-cls}-loading {
// Icon
.@{tree-prefix-cls}-iconEle {
display: none;
}
}
// ==================== Draggable =====================
&-node-content-wrapper[draggable='true'] {
line-height: @tree-title-height - 4px;

View File

@ -313,6 +313,7 @@ class Upload extends React.Component<UploadProps, UploadState> {
[`${prefixCls}-drag-uploading`]: fileList.some(file => file.status === 'uploading'),
[`${prefixCls}-drag-hover`]: dragState === 'dragover',
[`${prefixCls}-disabled`]: disabled,
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
);

View File

@ -3,7 +3,6 @@ import { mount } from 'enzyme';
import Upload from '..';
import UploadList from '../UploadList';
import Form from '../../form';
import { spyElementPrototypes } from '../../__tests__/util/domHook';
import { errorRequest, successRequest } from './requests';
import { setup, teardown } from './mock';
import { sleep } from '../../../tests/utils';
@ -35,35 +34,16 @@ describe('Upload List', () => {
function setSize(width, height) {
size = { width, height };
}
const imageSpy = spyElementPrototypes(Image, {
src: {
set() {
if (this.onload) {
this.onload();
}
},
},
width: {
get: () => size.width,
},
height: {
get: () => size.height,
},
});
const mockWidthGet = jest.spyOn(Image.prototype, 'width', 'get');
const mockHeightGet = jest.spyOn(Image.prototype, 'height', 'get');
const mockSrcSet = jest.spyOn(Image.prototype, 'src', 'set');
let drawImageCallback = null;
function hookDrawImageCall(callback) {
drawImageCallback = callback;
}
const canvasSpy = spyElementPrototypes(HTMLCanvasElement, {
getContext: () => ({
drawImage: (...args) => {
if (drawImageCallback) drawImageCallback(...args);
},
}),
toDataURL: () => 'data:image/png;base64,',
});
const mockGetCanvasContext = jest.spyOn(HTMLCanvasElement.prototype, 'getContext');
const mockToDataURL = jest.spyOn(HTMLCanvasElement.prototype, 'toDataURL');
// HTMLCanvasElement.prototype
@ -76,12 +56,29 @@ describe('Upload List', () => {
let open;
beforeAll(() => {
open = jest.spyOn(window, 'open').mockImplementation(() => {});
mockWidthGet.mockImplementation(() => size.width);
mockHeightGet.mockImplementation(() => size.height);
mockSrcSet.mockImplementation(function fn() {
if (this.onload) {
this.onload();
}
});
mockGetCanvasContext.mockReturnValue({
drawImage: (...args) => {
if (drawImageCallback) drawImageCallback(...args);
},
});
mockToDataURL.mockReturnValue('data:image/png;base64,');
});
afterAll(() => {
window.URL.createObjectURL = originCreateObjectURL;
imageSpy.mockRestore();
canvasSpy.mockRestore();
mockWidthGet.mockRestore();
mockHeightGet.mockRestore();
mockSrcSet.mockRestore();
mockGetCanvasContext.mockRestore();
mockToDataURL.mockRestore();
open.mockRestore();
});

View File

@ -59,6 +59,9 @@
&:hover {
border-color: @primary-color;
.@{upload-prefix-cls}-disabled& {
border-color: @border-color-base;
}
}
}

View File

@ -22,7 +22,7 @@ According to our [release schedule](changelog#Release-Schedule), we maintain two
We are using [GitHub Issues](https://github.com/ant-design/ant-design/issues) for bug tracking. The best way to get your bug fixed is using our [issue helper](http://new-issue.ant.design) and provide reproduction steps with this [template](https://u.ant.design/codesandbox-repro).
Before you report a bug, please make sure you've searched exists issues, and read our [FAQ](/docs/react/faq).
Before you report a bug, please make sure you've searched existing issues, and read our [FAQ](/docs/react/faq).
## Proposing a Change

View File

@ -51,14 +51,14 @@ module.exports = {
}, {
loader: 'less-loader', // compiles Less to CSS
+ options: {
+ modifyVars: {
+ 'primary-color': '#1DA57A',
+ 'link-color': '#1DA57A',
+ 'border-radius-base': '2px',
+ // or
+ 'hack': `true; @import "your-less-file-path.less";`, // Override with less file
+ lessOptions: { // If you are using less-loader@5 please spread the lessOptions to options directly
+ modifyVars: {
+ 'primary-color': '#1DA57A',
+ 'link-color': '#1DA57A',
+ 'border-radius-base': '2px',
+ },
+ javascriptEnabled: true,
+ },
+ javascriptEnabled: true,
+ },
}],
// ...other rules
@ -67,7 +67,10 @@ module.exports = {
}
```
Note that do not exclude antd package in node_modules when using less-loader.
Note:
1. Don't exclude `node_modules/antd` when using less-loader.
2. `lessOptions` usage is supported at [less-loader@6.0.0](https://github.com/webpack-contrib/less-loader/releases/tag/v6.0.0).
### Customize in Umi
@ -195,16 +198,17 @@ module.exports = {
}, {
loader: 'less-loader', // compiles Less to CSS
+ options: {
+ modifyVars: getThemeVariables({
+ dark: true, // enable dark mode
+ compact: true, // enable compact mode
+ }),
+ javascriptEnabled: true,
+ lessOptions: { // If you are using less-loader@5 please spread the lessOptions to options directly
+ modifyVars: getThemeVariables({
+ dark: true, // 开启暗黑模式
+ compact: true, // 开启紧凑模式
+ }),
+ javascriptEnabled: true,
+ },
+ },
}],
}],
};
```
## Related Articles
@ -213,3 +217,4 @@ module.exports = {
- [Theming Ant Design with Sass and Webpack](https://gist.github.com/Kruemelkatze/057f01b8e15216ae707dc7e6c9061ef7)
- [Using Sass/Scss with React App (create-react-app)](https://medium.com/@mzohaib.qc/using-sass-scss-with-react-app-create-react-app-d03072083ef8)
- [Dynamic Theming in Browser using Ant Design](https://medium.com/@mzohaib.qc/ant-design-dynamic-runtime-theme-1f9a1a030ba0)
```

View File

@ -51,14 +51,14 @@ module.exports = {
}, {
loader: 'less-loader', // compiles Less to CSS
+ options: {
+ modifyVars: {
+ 'primary-color': '#1DA57A',
+ 'link-color': '#1DA57A',
+ 'border-radius-base': '2px',
+ // or
+ 'hack': `true; @import "your-less-file-path.less";`, // Override with less file
+ lessOptions: { // 如果使用less-loader@5请移除 lessOptions 这一级直接配置选项。
+ modifyVars: {
+ 'primary-color': '#1DA57A',
+ 'link-color': '#1DA57A',
+ 'border-radius-base': '2px',
+ },
+ javascriptEnabled: true,
+ },
+ javascriptEnabled: true,
+ },
}],
// ...other rules
@ -67,7 +67,10 @@ module.exports = {
}
```
注意 less-loader 的处理范围不要过滤掉 `node_modules` 下的 antd 包。
注意:
1. less-loader 的处理范围不要过滤掉 `node_modules` 下的 antd 包。
2. `lessOptions` 的配置写法在 [less-loader@6.0.0](https://github.com/webpack-contrib/less-loader/releases/tag/v6.0.0) 里支持。
### 在 Umi 里配置主题
@ -173,11 +176,13 @@ module.exports = {
}, {
loader: 'less-loader', // compiles Less to CSS
+ options: {
+ modifyVars: getThemeVariables({
+ dark: true, // 开启暗黑模式
+ compact: true, // 开启紧凑模式
+ }),
+ javascriptEnabled: true,
+ lessOptions: { // 如果使用less-loader@5请移除 lessOptions 这一级直接配置选项。
+ modifyVars: getThemeVariables({
+ dark: true, // 开启暗黑模式
+ compact: true, // 开启紧凑模式
+ }),
+ javascriptEnabled: true,
+ },
+ },
}],
}],

View File

@ -36,7 +36,7 @@ Following the Ant Design specification, we developed a React UI library `antd` t
## Environment Support
- Modern browsers and Internet Explorer 11+ (with [polyfills](https://ant.design/docs/react/getting-started#Compatibility))
- Modern browsers and Internet Explorer 11 (with [polyfills](https://ant.design/docs/react/getting-started#Compatibility))
- Server-side Rendering
- [Electron](https://www.electronjs.org/)
@ -76,6 +76,7 @@ We provide `antd.js` `antd.css` and `antd.min.js` `antd.min.css` under `antd/dis
```jsx
import { DatePicker } from 'antd';
ReactDOM.render(<DatePicker />, mountNode);
```
@ -122,7 +123,7 @@ import 'antd/dist/antd.css'; // or 'antd/dist/antd.less'
## Links
- [Home page](https://ant.design/)
- [Components](/docs/react/introduce)
- [Components](https://ant.design/components/button/)
- [Ant Design Pro](https://pro.ant.design/)
- [Change Log](/changelog)
- [rc-components](http://react-component.github.io/)

View File

@ -36,7 +36,7 @@ title: Ant Design of React
## 支持环境
- 现代浏览器和 IE11 及以上(需要 [polyfills](https://ant.design/docs/react/getting-started-cn#兼容性))。
- 现代浏览器和 IE11需要 [polyfills](https://ant.design/docs/react/getting-started-cn#兼容性))。
- 支持服务端渲染。
- [Electron](https://electronjs.org/)
@ -76,6 +76,7 @@ $ yarn add antd
```jsx
import { DatePicker } from 'antd';
ReactDOM.render(<DatePicker />, mountNode);
```
@ -124,7 +125,7 @@ import 'antd/dist/antd.css'; // or 'antd/dist/antd.less'
## 链接
- [首页](https://ant.design/)
- [组件库](/docs/react/introduce)
- [组件库](https://ant.design/components/button-cn/)
- [Ant Design Pro](https://pro.ant.design/)
- [更新日志](/changelog)
- [React 底层基础组件](http://react-component.github.io/)

View File

@ -123,6 +123,7 @@ const Demo = () => (
- AutoComplete no longer support `optionLabelProp`. Please set Option `value` directly.
- Select remove `dropdownMenuStyle` prop.
- Use `listHeight` to config popup height instead of `dropdownStyle`.
- `filterOption` return origin data with second params instead. No need to use `option.props.children` for matching.
- The Grid component uses flex layout.
- Button's `danger` is now treated as a property instead of a button type.
- Input, Select set `value` to `undefined` is uncontrolled mode now.

View File

@ -123,6 +123,7 @@ const Demo = () => (
- AutoComplete 不再支持 `optionLabelProp`,请直接设置 Option `value` 属性。
- Select 移除 `dropdownMenuStyle` 属性。
- 如果你需要设置弹窗高度请使用 `listHeight` 来代替 `dropdownStyle` 的高度样式。
- `filterOption` 第二个参数直接返回原数据,不在需要通过 `option.props.children` 来进行匹配。
- Grid 组件使用 flex 布局。
- Button 的 `danger` 现在作为一个属性而不是按钮类型。
- Input、Select 的 `value``undefined` 时改为非受控状态。

View File

@ -191,8 +191,10 @@ module.exports = override(
+ style: true,
}),
+ addLessLoader({
+ javascriptEnabled: true,
+ modifyVars: { '@primary-color': '#1DA57A' },
+ lessOptions: { // If you are using less-loader@5 please spread the lessOptions to options directly
+ javascriptEnabled: true,
+ modifyVars: { '@primary-color': '#1DA57A' },
+ },
+ }),
);
```

View File

@ -191,8 +191,10 @@ module.exports = override(
+ style: true,
}),
+ addLessLoader({
+ javascriptEnabled: true,
+ modifyVars: { '@primary-color': '#1DA57A' },
+ lessOptions: { // 如果使用less-loader@5请移除 lessOptions 这一级直接配置选项。
+ javascriptEnabled: true,
+ modifyVars: { '@primary-color': '#1DA57A' },
+ },
+ }),
);
```

View File

@ -21,7 +21,7 @@ The table is recognized as one of the clearest and most efficient forms of prese
> Note:
>
> 1. The time, status, and action bar in the table need to keep the words intact without occupying multiple lines.
> 2. When the data is empty, use "- -" to indicate that there is no data.
> 2. When table cell is empty, use `-` to indicate that there is no data.
## Collapse

View File

@ -21,7 +21,7 @@ title: 数据展示
> 注:
>
> 1. 表格中的时间、状态、操作栏需保持词语完整不过行。
> 2. 当数据为空时,可使用『- -』来表示暂无数据。
> 2. 当单元格数据为空时,可使用 `-` 来表示暂无数据。
## 折叠面板Collapse

View File

@ -83,6 +83,11 @@ const LinksList = () => (
antue (vue)<LinkIcon />
</a>
</li>
<li>
<a href="https://append-it.github.io/ant-design-blazor/" target="_blank">
Ant Design of Blazor<LinkIcon />
</a>
</li>
</ul>
);

View File

@ -83,6 +83,9 @@ const LinksList = () => (
antue (vue)<LinkIcon />
</a>
</li>
<li>
<a href="https://append-it.github.io/ant-design-blazor/" target="_blank">Ant Design of Blazor<LinkIcon /></a>
</li>
</ul>
);

View File

@ -100,7 +100,7 @@ title: 结果页
#### 补充信息类型
<img class="preview-img no-padding" align="right" src="https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*QjpBRpyx5ecAAAAAAAAAAABkARQnAQ">
<img class="preview-img no-padding" align="right" src="https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*avS5TZcMawwAAAAAAAAAAABkARQnAQ">
## 延伸阅读

View File

@ -1,6 +1,6 @@
{
"name": "antd",
"version": "4.1.4",
"version": "4.1.5",
"description": "An enterprise-class UI design language and React components implementation",
"keywords": [
"ant",
@ -107,7 +107,7 @@
"omit.js": "^1.0.2",
"prop-types": "^15.7.2",
"raf": "^3.4.1",
"rc-animate": "~2.10.2",
"rc-animate": "~2.11.0",
"rc-cascader": "~1.0.0",
"rc-checkbox": "~2.2.0",
"rc-collapse": "~1.11.3",
@ -191,7 +191,7 @@
"eslint-plugin-markdown": "^1.0.0",
"eslint-plugin-react": "^7.14.2",
"eslint-plugin-react-hooks": "^3.0.0",
"eslint-plugin-unicorn": "^18.0.1",
"eslint-plugin-unicorn": "^19.0.0",
"eslint-tinker": "^0.5.0",
"fetch-jsonp": "^1.1.3",
"fs-extra": "^9.0.0",
@ -202,7 +202,7 @@
"ignore-emit-webpack-plugin": "^2.0.2",
"immutability-helper": "^3.0.0",
"inquirer": "^7.1.0",
"intersection-observer": "^0.9.0",
"intersection-observer": "^0.10.0",
"jest": "^25.1.0",
"jquery": "^3.4.1",
"jsdom": "^16.0.0",
@ -287,5 +287,8 @@
"maxSize": "65 kB"
}
],
"tnpm": {
"mode": "npm"
},
"title": "Ant Design"
}

View File

@ -6,8 +6,6 @@ const cheerio = require('cheerio');
const glob = require('glob');
const uniq = require('lodash/uniq');
const { createServer } = require('http-server');
const zhCN = require('../site/theme/zh-CN');
const enUS = require('../site/theme/en-US');
const components = uniq(
glob
@ -43,11 +41,7 @@ describe('site test', () => {
const expectComponent = async component => {
const { status, $ } = await render(`/${component}/`);
expect(status).toBe(200);
expect(
$('.markdown > h1')
.text()
.toLowerCase(),
).toMatch(handleComponentName(component));
expect($('.markdown > h1').text().toLowerCase()).toMatch(handleComponentName(component));
};
beforeAll(() => {
@ -67,13 +61,15 @@ describe('site test', () => {
it('Basic Pages en', async () => {
const { status, $ } = await render('/');
expect($('title').text()).toEqual(`Ant Design - ${enUS.messages['app.home.slogan']}`);
expect($('title').text()).toEqual(
`Ant Design - The world's second most popular React UI framework`,
);
expect(status).toBe(200);
});
it('Basic Pages zh', async () => {
const { status, $ } = await render('/index-cn');
expect($('title').text()).toEqual(`Ant Design - ${zhCN.messages['app.home.slogan']}`);
expect($('title').text()).toEqual(`Ant Design - 一套企业级 UI 设计语言和 React 组件库`);
expect(status).toBe(200);
});

View File

@ -31,7 +31,6 @@ module.exports = {
'app.demo.codesandbox': 'Open in CodeSandbox',
'app.demo.stackblitz': 'Open in Stackblitz',
'app.demo.riddle': 'Open in Riddle',
'app.home.slogan': 'A UI Design Language and React UI library',
'app.home.introduce':
'A design system for enterprise-level products. Create an efficient and enjoyable work experience.',
'app.home.recommend': 'Recommend',

View File

@ -1,3 +1,7 @@
html.rtl {
direction: rtl;
}
body {
color: @site-text-color;
font-size: 14px;
@ -171,15 +175,6 @@ a {
height: 100%;
transition: transform 0.3s @ease-in-out-circ;
}
.page-wrapper {
width: 100%;
padding: 0;
overflow: hidden;
&-rtl {
direction: rtl;
}
}
.drawer-content {
padding: 40px 0;
@ -224,7 +219,7 @@ a {
color: #000;
background-color: #fff;
box-shadow: @shadow-2;
transition: color .3s;
transition: color 0.3s;
&:hover {
color: @primary-color;
}

View File

@ -17,14 +17,14 @@
justify-content: space-between;
}
&-item {
.page-wrapper-rtl & {
html.rtl & {
display: flex;
justify-content: flex-start;
text-align: right;
}
&-icon {
.page-wrapper-rtl & {
html.rtl & {
margin-right: 0;
margin-left: 0.4em;
}

View File

@ -17,7 +17,7 @@
padding-right: 16px;
padding-left: 40px;
.page-wrapper-rtl & {
html.rtl & {
padding-right: 40px;
padding-left: 16px;
}

View File

@ -251,6 +251,16 @@ export default function DesignPage() {
<RightOutlined className="home-link-arrow" />
</a>
</li>
<li>
<a
href="https://append-it.github.io/ant-design-blazor/"
target="_blank"
rel="noopener noreferrer"
>
Ant Design of Blazor
<RightOutlined className="home-link-arrow" />
</a>
</li>
</ul>
</Col>
<Col xs={24} sm={15} style={{ alignSelf: 'flex-end', textAlign: 'right' }}>

View File

@ -27,7 +27,7 @@
font-weight: 200;
font-size: 16px;
.page-wrapper-rtl & {
html.rtl & {
float: left;
}
}

View File

@ -79,6 +79,11 @@ class Footer extends React.Component<WrappedComponentProps> {
url: 'https://vue.ant.design',
openExternal: true,
},
{
title: 'Ant Design Blazor',
url: 'https://append-it.github.io/ant-design-blazor/',
openExternal: true,
},
{
title: 'Ant Design Landing',
description: <FormattedMessage id="app.footer.landing" />,

View File

@ -37,6 +37,16 @@ export function getEcosystemGroup({ isZhCN }: SharedProps): React.ReactElement {
Ant Design of Vue
</a>
</Menu.Item>
<Menu.Item key="blazor">
<a
href="https://append-it.github.io/ant-design-blazor/"
className="header-link"
target="_blank"
rel="noopener noreferrer"
>
Ant Design of Blazor
</a>
</Menu.Item>
{isZhCN ? (
<Menu.Item key="course" className="hide-in-home-page">
<a

View File

@ -95,6 +95,11 @@ export default ({
<FormattedMessage id="app.header.menu.resource" />
</Link>
</Menu.Item>
{isZhCN && (
<Menu.Item key="mirror">
<a href="https://ant-design.gitee.io"></a>
</Menu.Item>
)}
{additional}
</Menu>
);

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