Merge pull request #23796 from ant-design/master

chore: Merge master into feature
This commit is contained in:
偏右 2020-05-01 16:35:53 +08:00 committed by GitHub
commit 35e390018c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 1550 additions and 1255 deletions

16
.snyk Normal file
View File

@ -0,0 +1,16 @@
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
version: v1.14.1
ignore: {}
# patches apply the minimum changes required to fix a vulnerability
patch:
SNYK-JS-LODASH-567746:
- lodash:
patched: '2020-05-01T06:50:59.565Z'
- '@ant-design/react-slick > lodash':
patched: '2020-05-01T06:50:59.565Z'
- rc-steps > lodash:
patched: '2020-05-01T06:50:59.565Z'
- rc-table > lodash:
patched: '2020-05-01T06:50:59.565Z'
- rc-tabs > lodash:
patched: '2020-05-01T06:50:59.565Z'

View File

@ -15,6 +15,61 @@ timeline: true
---
## 4.2.0
`2020-04-29`
- 🆕 List `grid` support all column count like 5. [#23630](https://github.com/ant-design/ant-design/pull/23630)
- 🆕 Divider add `plain` prop which allows a non-heading style divider text. [#23405](https://github.com/ant-design/ant-design/pull/23405)
- 🆕 Typography `ellipsis` support `onEllipsis` event handler. [#23414](https://github.com/ant-design/ant-design/pull/23414)
- 🆕 Space support `align` prop. [#23306](https://github.com/ant-design/ant-design/pull/23306)
- 🆕 Upload support `isImageUrl` to force trade file as image. [#23248](https://github.com/ant-design/ant-design/pull/23248) [@onjuju](https://github.com/onjuju)
- 🆕 Form.Item support `initialValue` and `getValueProps` props. [#22993](https://github.com/ant-design/ant-design/pull/22993)
- ConfigProvider
- 🆕 ConfigProvider support `getTargetContainer` to config Affix `target` props. [#23751](https://github.com/ant-design/ant-design/pull/23751)
- 🆕 ConfigProvider support `input` prop to config Input `autoComplete`. [#23455](https://github.com/ant-design/ant-design/pull/23455)
- 🐞 Fix ConfigProvider `getPopupContainer` not working on DatePicker and Slider. [#23594](https://github.com/ant-design/ant-design/pull/23594) [@hengkx](https://github.com/hengkx)
- Table
- 🆕 Table `summary` support fixed columns. [#23647](https://github.com/ant-design/ant-design/pull/23647)
- 🆕 Table support responsive columns. [#23298](https://github.com/ant-design/ant-design/pull/23298) [@vbudovski](https://github.com/vbudovski)
- 🐞 Fix Table pagination default position in RTL. [#23747](https://github.com/ant-design/ant-design/pull/23747)
- 🐞 Fix Table crash when `pageSize` is `undefined`. [#23724](https://github.com/ant-design/ant-design/pull/23724)
- 🐞 fix Table nested margin when size is `small` or `middle`. [#23602](https://github.com/ant-design/ant-design/pull/23602) [@hengkx](https://github.com/hengkx)
- 🐞 Fix RangePicker `ranges` tag color to primary color. [#23705](https://github.com/ant-design/ant-design/pull/23705)
- 🐞 Fix Transfer with custom empty style issue. [#23694](https://github.com/ant-design/ant-design/pull/23694) [@hengkx](https://github.com/hengkx)
- Input
- 🐞 Fix Password caret position. [#23633](https://github.com/ant-design/ant-design/pull/23633) [@huntdream](https://github.com/huntdream)
- 💄 Adjust Input.Search icon style. [#23406](https://github.com/ant-design/ant-design/pull/23406)
- Button
- 🐞 Fix Button align problem of icon only. [#23671](https://github.com/ant-design/ant-design/pull/23671)
- 🐞 Fix Button of icon only wrong `loading` style. [#23614](https://github.com/ant-design/ant-design/pull/23614)
- 🐞 fix Button cannot be directly called by `react-dnd`. [#23571](https://github.com/ant-design/ant-design/pull/23571) [@hengkx](https://github.com/hengkx)
- Menu
- 🆕 Menu Item and SubMenu support `icon` prop. [#23629](https://github.com/ant-design/ant-design/pull/23629)
- 🐞 Fix Menu duplicated shadow style. [#23664](https://github.com/ant-design/ant-design/pull/23664)
- 🐞 Fix Tag cannot be directly called by `react-dnd`. [#23632](https://github.com/ant-design/ant-design/pull/23632) [@hengkx](https://github.com/hengkx)
- Anchor
- 🐞 Fix Anchor Link with multiple `#` can not jump correctly. [#23595](https://github.com/ant-design/ant-design/pull/23595) [@wuzekang](https://github.com/wuzekang)
- 🐞 Fix Input with `suffix` align problem. [#23606](https://github.com/ant-design/ant-design/pull/23606)
- 💄 Select arrow won't rotate when open. [#23468](https://github.com/ant-design/ant-design/pull/23468)
- 💄 Rate support `direction`. [#23321](https://github.com/ant-design/ant-design/pull/23321)
- 💄 Adjust font-size in compact mode. [#23135](https://github.com/ant-design/ant-design/pull/23135)
- RTL
- 💄 Optimize Result button style in RTL. [#23733](https://github.com/ant-design/ant-design/pull/23733)
- 💄 Add Divider RTL support. [#23734](https://github.com/ant-design/ant-design/pull/23734)
- 💄 Fix Alert style in RTL when no-icon. [#23714](https://github.com/ant-design/ant-design/pull/23714)
- 💄 Optimize Table expand animation and pagination style in RTL. [#23706](https://github.com/ant-design/ant-design/pull/23706)
- 💄 Fix Table filter dropdown position in RTL. [#23695](https://github.com/ant-design/ant-design/pull/23695)
- 💄 Fix Table rowSelect icon style in RTL. [#23690](https://github.com/ant-design/ant-design/pull/23690)
- 💄 Optimize List style in RTL. [#23676](https://github.com/ant-design/ant-design/pull/23676)
- 💄 Add Calendar RTL. [#23394](https://github.com/ant-design/ant-design/pull/23394)
- 💄 Optimize Input.Search style in RTL. [#23424](https://github.com/ant-design/ant-design/pull/23424)
- 💄 Add Notification RTL config. [#23185](https://github.com/ant-design/ant-design/pull/23185)
- TypeScript
- 🐞 Fix PageHeader `tag` definition. [#23712](https://github.com/ant-design/ant-design/pull/23712) [@hengkx](https://github.com/hengkx)
- 🗑 Remove Button deprecated `type="danger"` TypeScript definition and warn it. [#23709](https://github.com/ant-design/ant-design/pull/23709)
- 🐞 Fix Table pagination `position` definition. [#23681](https://github.com/ant-design/ant-design/pull/23681) [@hengkx](https://github.com/hengkx)
## 4.1.5
`2020-04-25`

View File

@ -15,6 +15,61 @@ timeline: true
---
## 4.2.0
`2020-04-29`
- 🆕 List `grid` 支持所有分栏数字,比如分为 5 栏。[#23630](https://github.com/ant-design/ant-design/pull/23630)
- 🆕 Divider 新增 `plain` 属性,可用于设置一个非标题样式的分割文字。[#23405](https://github.com/ant-design/ant-design/pull/23405)
- 🆕 Typography `ellipsis` 支持 `onEllipsis` 事件。[#23414](https://github.com/ant-design/ant-design/pull/23414)
- 🆕 Space 支持 `align` 属性。[#23306](https://github.com/ant-design/ant-design/pull/23306)
- 🆕 Upload 添加 `isImageUrl` 属性以强制将文件作为图标文件。[#23248](https://github.com/ant-design/ant-design/pull/23248) [@onjuju](https://github.com/onjuju)
- 🆕 Form.Item 支持 `initialValue``getValueProps` 属性。[#22993](https://github.com/ant-design/ant-design/pull/22993)
- ConfigProvider
- 🆕 ConfigProvider 支持 `getTargetContainer` 以配置 Affix `target` 属性。[#23751](https://github.com/ant-design/ant-design/pull/23751)
- 🆕 ConfigProvider 添加 `input` 属性以支持全局化配置 Input `autoComplete` 的默认值。[#23455](https://github.com/ant-design/ant-design/pull/23455)
- 🐞 修复 ConfigProvider `getPopupContainer` 对 DatePicker 和 Slider 不生效的问题。[#23594](https://github.com/ant-design/ant-design/pull/23594) [@hengkx](https://github.com/hengkx)
- Table
- 🆕 Table `summary` 支持固定列。[#23647](https://github.com/ant-design/ant-design/pull/23647)
- 🆕 Table 支持响应式展现列。[#23298](https://github.com/ant-design/ant-design/pull/23298) [@vbudovski](https://github.com/vbudovski)
- 🐞 修复 Table pagination 在 RTL 下默认位置。[#23747](https://github.com/ant-design/ant-design/pull/23747)
- 🐞 修复 Table 在 `pageSize``undefined` 时崩溃的问题。[#23724](https://github.com/ant-design/ant-design/pull/23724)
- 🐞 修复 Table 大小为 `small``middle` 时嵌套表格错位的问题。[#23602](https://github.com/ant-design/ant-design/pull/23602) [@hengkx](https://github.com/hengkx)
- 🐞 修正 RangePicker 范围标签的颜色为主色。[#23705](https://github.com/ant-design/ant-design/pull/23705)
- 🐞 修复 Transfer 为空自定义图片样式问题。[#23694](https://github.com/ant-design/ant-design/pull/23694) [@hengkx](https://github.com/hengkx)
- Input
- 🐞 修复 Password 组件输入光标位置。[#23633](https://github.com/ant-design/ant-design/pull/23633) [@huntdream](https://github.com/huntdream)
- 💄 调整 Input.Search 的搜索图标样式。[#23406](https://github.com/ant-design/ant-design/pull/23406)
- Button
- 🐞 修复 Button 图标类型按钮的对齐问题。[#23671](https://github.com/ant-design/ant-design/pull/23671)
- 🐞 修复 Button 图标按钮 `loading` 样式错误的问题。[#23614](https://github.com/ant-design/ant-design/pull/23614)
- 🐞 解决 Button 无法直接被 `react-dnd` 调用的问题。[#23571](https://github.com/ant-design/ant-design/pull/23571) [@hengkx](https://github.com/hengkx)
- Menu
- 🆕 Menu Item 和 SubMenu 新增 `icon` 属性。[#23629](https://github.com/ant-design/ant-design/pull/23629)
- 🐞 修复 Menu 菜单重复阴影的问题。[#23664](https://github.com/ant-design/ant-design/pull/23664)
- 🐞 解决 Tag 无法直接被 `react-dnd` 调用的问题。[#23632](https://github.com/ant-design/ant-design/pull/23632) [@hengkx](https://github.com/hengkx)
- Anchor
- 🐞 修复 Anchor Link 包含多个 `#` 时无法跳转的问题。[#23595](https://github.com/ant-design/ant-design/pull/23595) [@wuzekang](https://github.com/wuzekang)
- 🐞 修复 Input 带 `suffix` 时的元素对齐问题。[#23606](https://github.com/ant-design/ant-design/pull/23606)
- 💄 Select 箭头打开时不再翻转。[#23468](https://github.com/ant-design/ant-design/pull/23468)
- 💄 新增 Rate 的 `direction` 支持优化。[#23321](https://github.com/ant-design/ant-design/pull/23321)
- 💄 调整紧凑模式下默认的字体大小。[#23135](https://github.com/ant-design/ant-design/pull/23135)
- RTL
- 💄 优化 Result RTL 下按钮样式。[#23733](https://github.com/ant-design/ant-design/pull/23733)
- 💄 新增 Divider RTL 支持。[#23734](https://github.com/ant-design/ant-design/pull/23734)
- 💄 修复 Alert 在 RTL 下无 icon 的间隔问题。[#23714](https://github.com/ant-design/ant-design/pull/23714)
- 💄 优化 Table RTL 模式下扩展按钮动画与分页样式问题。[#23706](https://github.com/ant-design/ant-design/pull/23706)
- 💄 修复 Table 筛选下拉框在 RTL 下的位置。[#23695](https://github.com/ant-design/ant-design/pull/23695)
- 💄 修复 Table 勾选框图标 RTL 样式。[#23690](https://github.com/ant-design/ant-design/pull/23690)
- 💄 优化 List RTL 样式。[#23676](https://github.com/ant-design/ant-design/pull/23676)
- 💄 新增 Calendar RTL 支持。[#23394](https://github.com/ant-design/ant-design/pull/23394)
- 💄 优化 Input.Search RTL 样式。[#23424](https://github.com/ant-design/ant-design/pull/23424)
- 💄 增加 Notification RTL 设置。[#23185](https://github.com/ant-design/ant-design/pull/23185)
- TypeScript
- 🐞 修复 PageHeader `tag` 属性定义错误。[#23712](https://github.com/ant-design/ant-design/pull/23712) [@hengkx](https://github.com/hengkx)
- 🗑 移除已废弃的 Button `type="danger"` TypeScript 定义并增加警告信息。[#23709](https://github.com/ant-design/ant-design/pull/23709)
- 🐞 修复 Table pagination `position` Typescript 定义错误。[#23681](https://github.com/ant-design/ant-design/pull/23681) [@hengkx](https://github.com/hengkx)
## 4.1.5
`2020-04-25`

View File

@ -1,4 +1,4 @@
import React from 'react';
import * as React from 'react';
export function fillRef<T>(ref: React.Ref<T>, node: T) {
if (typeof ref === 'function') {

View File

@ -1,360 +0,0 @@
import React from 'react';
import { mount } from 'enzyme';
import Anchor from '..';
import { sleep } from '../../../tests/utils';
const { Link } = Anchor;
describe('Anchor Render', () => {
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(() => {
getBoundingClientRectMock.mockRestore();
getClientRectsMock.mockRestore();
});
it('Anchor render perfectly', () => {
const wrapper = mount(
<Anchor>
<Link href="#API" title="API" />
</Anchor>,
);
wrapper.find('a[href="#API"]').simulate('click');
wrapper.instance().handleScroll();
expect(wrapper.instance().state).not.toBe(null);
});
it('Anchor render perfectly for complete href - click', () => {
const wrapper = mount(
<Anchor>
<Link href="http://www.example.com/#API" title="API" />
</Anchor>,
);
wrapper.find('a[href="http://www.example.com/#API"]').simulate('click');
expect(wrapper.instance().state.activeLink).toBe('http://www.example.com/#API');
});
it('Anchor render perfectly for complete href - hash router', async () => {
const scrollToSpy = jest.spyOn(window, 'scrollTo');
let root = document.getElementById('root');
if (!root) {
root = document.createElement('div', { id: 'root' });
root.id = 'root';
document.body.appendChild(root);
}
mount(<div id="/faq?locale=en#Q1">Q1</div>, { attachTo: root });
const wrapper = mount(
<Anchor>
<Link href="/#/faq?locale=en#Q1" title="Q1" />
</Anchor>,
);
wrapper.instance().handleScrollTo('/#/faq?locale=en#Q1');
expect(wrapper.instance().state.activeLink).toBe('/#/faq?locale=en#Q1');
expect(scrollToSpy).not.toHaveBeenCalled();
await sleep(1000);
expect(scrollToSpy).toHaveBeenCalled();
});
it('Anchor render perfectly for complete href - scroll', () => {
let root = document.getElementById('root');
if (!root) {
root = document.createElement('div', { id: 'root' });
root.id = 'root';
document.body.appendChild(root);
}
mount(<div id="API">Hello</div>, { attachTo: root });
const wrapper = mount(
<Anchor>
<Link href="http://www.example.com/#API" title="API" />
</Anchor>,
);
wrapper.instance().handleScroll();
expect(wrapper.instance().state.activeLink).toBe('http://www.example.com/#API');
});
it('Anchor render perfectly for complete href - scrollTo', async () => {
const scrollToSpy = jest.spyOn(window, 'scrollTo');
let root = document.getElementById('root');
if (!root) {
root = document.createElement('div', { id: 'root' });
root.id = 'root';
document.body.appendChild(root);
}
mount(<div id="#API">Hello</div>, { attachTo: root });
const wrapper = mount(
<Anchor>
<Link href="##API" title="API" />
</Anchor>,
);
wrapper.instance().handleScrollTo('##API');
expect(wrapper.instance().state.activeLink).toBe('##API');
const calls = scrollToSpy.mock.calls.length;
await sleep(1000);
expect(scrollToSpy.mock.calls.length).toBeGreaterThan(calls);
});
it('should remove listener when unmount', async () => {
const wrapper = mount(
<Anchor>
<Link href="#API" title="API" />
</Anchor>,
);
const removeListenerSpy = jest.spyOn(wrapper.instance().scrollEvent, 'remove');
wrapper.unmount();
expect(removeListenerSpy).toHaveBeenCalled();
});
it('should unregister link when unmount children', async () => {
const wrapper = mount(
<Anchor>
<Link href="#API" title="API" />
</Anchor>,
);
expect(wrapper.instance().links).toEqual(['#API']);
wrapper.setProps({ children: null });
expect(wrapper.instance().links).toEqual([]);
});
it('should update links when link href update', async () => {
let anchorInstance = null;
function AnchorUpdate({ href }) {
return (
<Anchor
ref={c => {
anchorInstance = c;
}}
>
<Link href={href} title="API" />
</Anchor>
);
}
const wrapper = mount(<AnchorUpdate href="#API" />);
expect(anchorInstance.links).toEqual(['#API']);
wrapper.setProps({ href: '#API_1' });
expect(anchorInstance.links).toEqual(['#API_1']);
});
it('Anchor onClick event', () => {
let event;
let link;
const handleClick = (...arg) => {
[event, link] = arg;
};
const href = '#API';
const title = 'API';
const wrapper = mount(
<Anchor onClick={handleClick}>
<Link href={href} title={title} />
</Anchor>,
);
wrapper.find(`a[href="${href}"]`).simulate('click');
wrapper.instance().handleScroll();
expect(event).not.toBe(undefined);
expect(link).toEqual({ href, title });
});
it('Different function returns the same DOM', async () => {
let root = document.getElementById('root');
if (!root) {
root = document.createElement('div', { id: 'root' });
root.id = 'root';
document.body.appendChild(root);
}
mount(<div id="API">Hello</div>, { attachTo: root });
const getContainerA = () => {
return document.getElementById('API');
};
const getContainerB = () => {
return document.getElementById('API');
};
const wrapper = mount(
<Anchor getContainer={getContainerA}>
<Link href="#API" title="API" />
</Anchor>,
);
const removeListenerSpy = jest.spyOn(wrapper.instance().scrollEvent, 'remove');
await sleep(1000);
wrapper.setProps({ getContainer: getContainerB });
expect(removeListenerSpy).not.toHaveBeenCalled();
});
it('Different function returns different DOM', async () => {
let root = document.getElementById('root');
if (!root) {
root = document.createElement('div', { id: 'root' });
root.id = 'root';
document.body.appendChild(root);
}
mount(
<div>
<div id="API1">Hello</div>
<div id="API2">World</div>
</div>,
{ attachTo: root },
);
const getContainerA = () => {
return document.getElementById('API1');
};
const getContainerB = () => {
return document.getElementById('API2');
};
const wrapper = mount(
<Anchor getContainer={getContainerA}>
<Link href="#API1" title="API1" />
<Link href="#API2" title="API2" />
</Anchor>,
);
const removeListenerSpy = jest.spyOn(wrapper.instance().scrollEvent, 'remove');
expect(removeListenerSpy).not.toHaveBeenCalled();
await sleep(1000);
wrapper.setProps({ getContainer: getContainerB });
expect(removeListenerSpy).toHaveBeenCalled();
});
it('Same function returns the same DOM', () => {
let root = document.getElementById('root');
if (!root) {
root = document.createElement('div', { id: 'root' });
root.id = 'root';
document.body.appendChild(root);
}
mount(<div id="API">Hello</div>, { attachTo: root });
const getContainer = () => document.getElementById('API');
const wrapper = mount(
<Anchor getContainer={getContainer}>
<Link href="#API" title="API" />
</Anchor>,
);
wrapper.find('a[href="#API"]').simulate('click');
wrapper.instance().handleScroll();
expect(wrapper.instance().state).not.toBe(null);
});
it('Same function returns different DOM', async () => {
let root = document.getElementById('root');
if (!root) {
root = document.createElement('div', { id: 'root' });
root.id = 'root';
document.body.appendChild(root);
}
mount(
<div>
<div id="API1">Hello</div>
<div id="API2">World</div>
</div>,
{ attachTo: root },
);
const holdContainer = {
container: document.getElementById('API1'),
};
const getContainer = () => {
return holdContainer.container;
};
const wrapper = mount(
<Anchor getContainer={getContainer}>
<Link href="#API1" title="API1" />
<Link href="#API2" title="API2" />
</Anchor>,
);
const removeListenerSpy = jest.spyOn(wrapper.instance().scrollEvent, 'remove');
expect(removeListenerSpy).not.toHaveBeenCalled();
await sleep(1000);
holdContainer.container = document.getElementById('API2');
wrapper.setProps({ 'data-only-trigger-re-render': true });
expect(removeListenerSpy).toHaveBeenCalled();
});
it('Anchor getCurrentAnchor prop', () => {
const getCurrentAnchor = () => '#API2';
const wrapper = mount(
<Anchor getCurrentAnchor={getCurrentAnchor}>
<Link href="#API1" title="API1" />
<Link href="#API2" title="API2" />
</Anchor>,
);
expect(wrapper.instance().state.activeLink).toBe('#API2');
});
it('Anchor targetOffset prop', async () => {
let dateNowMock;
function dataNowMockFn() {
let start = 0;
const handler = () => {
return (start += 1000);
};
return jest.spyOn(Date, 'now').mockImplementation(handler);
}
dateNowMock = dataNowMockFn();
const scrollToSpy = jest.spyOn(window, 'scrollTo');
let root = document.getElementById('root');
if (!root) {
root = document.createElement('div', { id: 'root' });
root.id = 'root';
document.body.appendChild(root);
}
mount(<h1 id="API">Hello</h1>, { attachTo: root });
const wrapper = mount(
<Anchor>
<Link href="#API" title="API" />
</Anchor>,
);
wrapper.instance().handleScrollTo('#API');
await sleep(30);
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 1000);
dateNowMock = dataNowMockFn();
wrapper.setProps({ offsetTop: 100 });
wrapper.instance().handleScrollTo('#API');
await sleep(30);
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 900);
dateNowMock = dataNowMockFn();
wrapper.setProps({ targetOffset: 200 });
wrapper.instance().handleScrollTo('#API');
await sleep(30);
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800);
dateNowMock.mockRestore();
});
it('Anchor onChange prop', async () => {
const onChange = jest.fn();
const wrapper = mount(
<Anchor onChange={onChange}>
<Link href="#API1" title="API1" />
<Link href="#API2" title="API2" />
</Anchor>,
);
expect(onChange).toHaveBeenCalledTimes(1);
wrapper.instance().handleScrollTo('#API2');
expect(onChange).toHaveBeenCalledTimes(2);
expect(onChange).toHaveBeenCalledWith('#API2');
});
});

View File

@ -0,0 +1,360 @@
import React from 'react';
import { mount } from 'enzyme';
import Anchor from '..';
import { sleep } from '../../../tests/utils';
const { Link } = Anchor;
function createGetContainer(id: string) {
return () => {
const container = document.getElementById(id);
if (container == null) {
throw new Error();
}
return container;
};
}
function createDiv() {
const root = document.createElement('div');
document.body.appendChild(root);
return root;
}
let idCounter = 0;
const getHashUrl = () => `Anchor-API-${idCounter++}`;
describe('Anchor Render', () => {
const getBoundingClientRectMock = jest.spyOn(
HTMLHeadingElement.prototype,
'getBoundingClientRect',
);
const getClientRectsMock = jest.spyOn(HTMLHeadingElement.prototype, 'getClientRects');
beforeAll(() => {
getBoundingClientRectMock.mockReturnValue({
width: 100,
height: 100,
top: 1000,
} as DOMRect);
getClientRectsMock.mockReturnValue({ length: 1 } as DOMRectList);
});
afterAll(() => {
getBoundingClientRectMock.mockRestore();
getClientRectsMock.mockRestore();
});
it('Anchor render perfectly', () => {
const hash = getHashUrl();
const wrapper = mount<Anchor>(
<Anchor>
<Link href={`#${hash}`} title={hash} />
</Anchor>,
);
wrapper.find(`a[href="#${hash}"]`).simulate('click');
wrapper.instance().handleScroll();
expect(wrapper.instance().state).not.toBe(null);
});
it('Anchor render perfectly for complete href - click', () => {
const hash = getHashUrl();
const wrapper = mount<Anchor>(
<Anchor>
<Link href={`http://www.example.com/#${hash}`} title={hash} />
</Anchor>,
);
wrapper.find(`a[href="http://www.example.com/#${hash}"]`).simulate('click');
expect(wrapper.instance().state.activeLink).toBe(`http://www.example.com/#${hash}`);
});
it('Anchor render perfectly for complete href - hash router', async () => {
const root = createDiv();
const scrollToSpy = jest.spyOn(window, 'scrollTo');
mount(<div id="/faq?locale=en#Q1">Q1</div>, { attachTo: root });
const wrapper = mount<Anchor>(
<Anchor>
<Link href="/#/faq?locale=en#Q1" title="Q1" />
</Anchor>,
);
wrapper.instance().handleScrollTo('/#/faq?locale=en#Q1');
expect(wrapper.instance().state.activeLink).toBe('/#/faq?locale=en#Q1');
expect(scrollToSpy).not.toHaveBeenCalled();
await sleep(1000);
expect(scrollToSpy).toHaveBeenCalled();
});
it('Anchor render perfectly for complete href - scroll', () => {
const hash = getHashUrl();
const root = createDiv();
mount(<div id={hash}>Hello</div>, { attachTo: root });
const wrapper = mount<Anchor>(
<Anchor>
<Link href={`http://www.example.com/#${hash}`} title={hash} />
</Anchor>,
);
wrapper.instance().handleScroll();
expect(wrapper.instance().state.activeLink).toBe(`http://www.example.com/#${hash}`);
});
it('Anchor render perfectly for complete href - scrollTo', async () => {
const hash = getHashUrl();
const scrollToSpy = jest.spyOn(window, 'scrollTo');
const root = createDiv();
mount(<div id={`#${hash}`}>Hello</div>, { attachTo: root });
const wrapper = mount<Anchor>(
<Anchor>
<Link href={`##${hash}`} title={hash} />
</Anchor>,
);
wrapper.instance().handleScrollTo(`##${hash}`);
expect(wrapper.instance().state.activeLink).toBe(`##${hash}`);
const calls = scrollToSpy.mock.calls.length;
await sleep(1000);
expect(scrollToSpy.mock.calls.length).toBeGreaterThan(calls);
});
it('should remove listener when unmount', async () => {
const hash = getHashUrl();
const wrapper = mount<Anchor>(
<Anchor>
<Link href={`#${hash}`} title={hash} />
</Anchor>,
);
const removeListenerSpy = jest.spyOn((wrapper.instance() as any).scrollEvent, 'remove');
wrapper.unmount();
expect(removeListenerSpy).toHaveBeenCalled();
});
it('should unregister link when unmount children', async () => {
const hash = getHashUrl();
const wrapper = mount<Anchor>(
<Anchor>
<Link href={`#${hash}`} title={hash} />
</Anchor>,
);
expect((wrapper.instance() as any).links).toEqual([`#${hash}`]);
wrapper.setProps({ children: null });
expect((wrapper.instance() as any).links).toEqual([]);
});
it('should update links when link href update', async () => {
const hash = getHashUrl();
let anchorInstance: Anchor | null = null;
function AnchorUpdate({ href }: { href: string }) {
return (
<Anchor
ref={c => {
anchorInstance = c;
}}
>
<Link href={href} title={hash} />
</Anchor>
);
}
const wrapper = mount(<AnchorUpdate href={`#${hash}`} />);
if (anchorInstance == null) {
throw new Error('anchorInstance should not be null');
}
expect((anchorInstance as any).links).toEqual([`#${hash}`]);
wrapper.setProps({ href: `#${hash}_1` });
expect((anchorInstance as any).links).toEqual([`#${hash}_1`]);
});
it('Anchor onClick event', () => {
const hash = getHashUrl();
let event;
let link;
const handleClick = (
e: React.MouseEvent<HTMLElement>,
_link: { title: React.ReactNode; href: string },
) => {
event = e;
link = _link;
};
const href = `#${hash}`;
const title = hash;
const wrapper = mount<Anchor>(
<Anchor onClick={handleClick}>
<Link href={href} title={title} />
</Anchor>,
);
wrapper.find(`a[href="${href}"]`).simulate('click');
wrapper.instance().handleScroll();
expect(event).not.toBe(undefined);
expect(link).toEqual({ href, title });
});
it('Different function returns the same DOM', async () => {
const hash = getHashUrl();
const root = createDiv();
mount(<div id={hash}>Hello</div>, { attachTo: root });
const getContainerA = createGetContainer(hash);
const getContainerB = createGetContainer(hash);
const wrapper = mount<Anchor>(
<Anchor getContainer={getContainerA}>
<Link href={`#${hash}`} title={hash} />
</Anchor>,
);
const removeListenerSpy = jest.spyOn((wrapper.instance() as any).scrollEvent, 'remove');
await sleep(1000);
wrapper.setProps({ getContainer: getContainerB });
expect(removeListenerSpy).not.toHaveBeenCalled();
});
it('Different function returns different DOM', async () => {
const hash1 = getHashUrl();
const hash2 = getHashUrl();
const root = createDiv();
mount(
<div>
<div id={hash1}>Hello</div>
<div id={hash2}>World</div>
</div>,
{ attachTo: root },
);
const getContainerA = createGetContainer(hash1);
const getContainerB = createGetContainer(hash2);
const wrapper = mount<Anchor>(
<Anchor getContainer={getContainerA}>
<Link href={`#${hash1}`} title={hash1} />
<Link href={`#${hash2}`} title={hash2} />
</Anchor>,
);
const removeListenerSpy = jest.spyOn((wrapper.instance() as any).scrollEvent, 'remove');
expect(removeListenerSpy).not.toHaveBeenCalled();
await sleep(1000);
wrapper.setProps({ getContainer: getContainerB });
expect(removeListenerSpy).toHaveBeenCalled();
});
it('Same function returns the same DOM', () => {
const hash = getHashUrl();
const root = createDiv();
mount(<div id={hash}>Hello</div>, { attachTo: root });
const getContainer = createGetContainer(hash);
const wrapper = mount(
<Anchor getContainer={getContainer}>
<Link href={`#${hash}`} title={hash} />
</Anchor>,
);
wrapper.find(`a[href="#${hash}"]`).simulate('click');
(wrapper.instance() as any).handleScroll();
expect(wrapper.instance().state).not.toBe(null);
});
it('Same function returns different DOM', async () => {
const hash1 = getHashUrl();
const hash2 = getHashUrl();
const root = createDiv();
mount(
<div>
<div id={hash1}>Hello</div>
<div id={hash2}>World</div>
</div>,
{ attachTo: root },
);
const holdContainer = {
container: document.getElementById(hash1),
};
const getContainer = () => {
if (holdContainer.container == null) {
throw new Error('container should not be null');
}
return holdContainer.container;
};
const wrapper = mount(
<Anchor getContainer={getContainer}>
<Link href={`#${hash1}`} title={hash1} />
<Link href={`#${hash2}`} title={hash2} />
</Anchor>,
);
const removeListenerSpy = jest.spyOn((wrapper.instance() as any).scrollEvent, 'remove');
expect(removeListenerSpy).not.toHaveBeenCalled();
await sleep(1000);
holdContainer.container = document.getElementById(hash2);
wrapper.setProps({ 'data-only-trigger-re-render': true });
expect(removeListenerSpy).toHaveBeenCalled();
});
it('Anchor getCurrentAnchor prop', () => {
const hash1 = getHashUrl();
const hash2 = getHashUrl();
const getCurrentAnchor = () => `#${hash2}`;
const wrapper = mount<Anchor>(
<Anchor getCurrentAnchor={getCurrentAnchor}>
<Link href={`#${hash1}`} title={hash1} />
<Link href={`#${hash2}`} title={hash2} />
</Anchor>,
);
expect(wrapper.instance().state.activeLink).toBe(`#${hash2}`);
});
it('Anchor targetOffset prop', async () => {
const hash = getHashUrl();
let dateNowMock;
function dataNowMockFn() {
let start = 0;
const handler = () => {
return (start += 1000);
};
return jest.spyOn(Date, 'now').mockImplementation(handler);
}
dateNowMock = dataNowMockFn();
const scrollToSpy = jest.spyOn(window, 'scrollTo');
const root = createDiv();
mount(<h1 id={hash}>Hello</h1>, { attachTo: root });
const wrapper = mount<Anchor>(
<Anchor>
<Link href={`#${hash}`} title={hash} />
</Anchor>,
);
wrapper.instance().handleScrollTo(`#${hash}`);
await sleep(30);
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 1000);
dateNowMock = dataNowMockFn();
wrapper.setProps({ offsetTop: 100 });
wrapper.instance().handleScrollTo(`#${hash}`);
await sleep(30);
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 900);
dateNowMock = dataNowMockFn();
wrapper.setProps({ targetOffset: 200 });
wrapper.instance().handleScrollTo(`#${hash}`);
await sleep(30);
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800);
dateNowMock.mockRestore();
});
it('Anchor onChange prop', async () => {
const hash1 = getHashUrl();
const hash2 = getHashUrl();
const onChange = jest.fn();
const wrapper = mount<Anchor>(
<Anchor onChange={onChange}>
<Link href={`#${hash1}`} title={hash1} />
<Link href={`#${hash2}`} title={hash2} />
</Anchor>,
);
expect(onChange).toHaveBeenCalledTimes(1);
wrapper.instance().handleScrollTo(hash2);
expect(onChange).toHaveBeenCalledTimes(2);
expect(onChange).toHaveBeenCalledWith(hash2);
});
});

View File

@ -1,4 +1,4 @@
import React from 'react';
import * as React from 'react';
import { AntAnchor } from './Anchor';
const AnchorContext = React.createContext<AntAnchor>(null as any);

View File

@ -150,13 +150,18 @@
}
& > &-loading-icon {
padding-right: @margin-xs;
transition: all 0.3s @ease-in-out;
.@{iconfont-css-prefix} {
padding-right: @margin-xs;
}
&:only-child {
.@{iconfont-css-prefix} {
padding-right: 0;
}
}
}
&-group {
.btn-group(@btn-prefix-cls);

View File

@ -66,8 +66,14 @@ exports[`renders ./components/drawer/demo/multi-level-drawer.md correctly 1`] =
exports[`renders ./components/drawer/demo/placement.md correctly 1`] = `
<div>
<div
class="ant-radio-group ant-radio-group-outline"
class="ant-space ant-space-horizontal ant-space-align-center"
>
<div
class="ant-space-item"
style="margin-right:8px"
>
<div
class="ant-radio-group ant-radio-group-outline"
>
<label
class="ant-radio-wrapper"
@ -147,6 +153,10 @@ exports[`renders ./components/drawer/demo/placement.md correctly 1`] = `
</span>
</label>
</div>
</div>
<div
class="ant-space-item"
>
<button
class="ant-btn ant-btn-primary"
type="button"
@ -155,6 +165,8 @@ exports[`renders ./components/drawer/demo/placement.md correctly 1`] = `
Open
</span>
</button>
</div>
</div>
</div>
`;

View File

@ -14,7 +14,7 @@ title:
The Drawer can appear from any edge of the screen.
```jsx
import { Drawer, Button, Radio } from 'antd';
import { Drawer, Button, Radio, Space } from 'antd';
const RadioGroup = Radio.Group;
@ -42,11 +42,8 @@ class App extends React.Component {
render() {
return (
<div>
<RadioGroup
style={{ marginRight: 8 }}
defaultValue={this.state.placement}
onChange={this.onChange}
>
<Space>
<RadioGroup defaultValue={this.state.placement} onChange={this.onChange}>
<Radio value="top">top</Radio>
<Radio value="right">right</Radio>
<Radio value="bottom">bottom</Radio>
@ -55,6 +52,7 @@ class App extends React.Component {
<Button type="primary" onClick={this.showDrawer}>
Open
</Button>
</Space>
<Drawer
title="Basic Drawer"
placement={this.state.placement}

View File

@ -194,7 +194,7 @@ Provide linkage between forms. If a sub form with `name` prop update, it will au
| isFieldValidating | Check fields if is in validating | (name: [NamePath](#NamePath)) => boolean |
| resetFields | Reset fields to `initialValues` | (fields?: [NamePath](#NamePath)[]) => void |
| scrollToField | Scroll to field position | (name: [NamePath](#NamePath), options: [[ScrollOptions](https://github.com/stipsan/scroll-into-view-if-needed/blob/ece40bd9143f48caf4b99503425ecb16b0ad8249/src/types.ts#L10)]) => void |
| setFields | Set fields status | (fields: FieldData[]) => void |
| setFields | Set fields status | (fields: [FieldData](#FieldData)[]) => void |
| setFieldsValue | Set fields value | (values) => void |
| submit | Submit the form. It's same as click `submit` button | () => void |
| validateFields | Validate fields | (nameList?: [NamePath](#NamePath)[]) => Promise |
@ -325,6 +325,16 @@ In most case, we always recommend to use Form `initialValues`. Use Item `initial
1. Form `initialValues` is the first priority
2. Field `initialValue` is secondary \*. Not work when multiple Item with same `name` setting the `initialValue`
### Why `onFieldsChange` trigger three times on change when field set `rules`?
Validating is also part of the value updating. It pass follow steps:
1. Trigger value change
2. Rule validating
3. Rule validated
In each `onFieldsChange`, you will get `false` > `true` > `false` with `isFieldValidating`.
<style>
.site-form-item-icon {
color: rgba(0, 0, 0, 0.25);

View File

@ -195,7 +195,7 @@ Form 通过增量更新方式,只更新被修改的字段相关组件以达到
| isFieldValidating | 检查一组字段是否正在校验 | (name: [NamePath](#NamePath)) => boolean |
| resetFields | 重置一组字段到 `initialValues` | (fields?: [NamePath](#NamePath)[]) => void |
| scrollToField | 滚动到对应字段位置 | (name: [NamePath](#NamePath), options: [[ScrollOptions](https://github.com/stipsan/scroll-into-view-if-needed/blob/ece40bd9143f48caf4b99503425ecb16b0ad8249/src/types.ts#L10)]) => void |
| setFields | 设置一组字段状态 | (fields: FieldData[]) => void |
| setFields | 设置一组字段状态 | (fields: [FieldData](#FieldData)[]) => void |
| setFieldsValue | 设置表单的值 | (values) => void |
| submit | 提交表单,与点击 `submit` 按钮效果相同 | () => void |
| validateFields | 触发表单验证 | (nameList?: [NamePath](#NamePath)[]) => Promise |
@ -326,6 +326,16 @@ validator(rule, value, callback) => {
1. Form 的 `initialValues` 拥有最高优先级
2. Field 的 `initialValue` 次之 \*. 多个同 `name` Item 都设置 `initialValue` 时,则 Item 的 `initialValue` 不生效
### 为什么字段设置 `rules` 后更改值 `onFieldsChange` 会触发三次?
字段除了本身的值变化外,校验也是其状态之一。因而在触发字段变化会经历以下几个阶段:
1. Trigger value change
2. Rule validating
3. Rule validated
在触发过程中,调用 `isFieldValidating` 会经历 `false` > `true` > `false` 的变化过程。
<style>
.site-form-item-icon {
color: rgba(0, 0, 0, 0.25);

View File

@ -16,30 +16,26 @@ A simple playground for column count and gutter.
```jsx
import { Row, Col, Slider } from 'antd';
const gutters = {};
const vgutters = {};
const colCounts = {};
[8, 16, 24, 32, 40, 48].forEach((value, i) => {
gutters[i] = value;
});
[8, 16, 24, 32, 40, 48].forEach((value, i) => {
vgutters[i] = value;
});
[2, 3, 4, 6, 8, 12].forEach((value, i) => {
colCounts[i] = value;
});
class App extends React.Component {
gutters = {};
vgutters = {};
colCounts = {};
constructor() {
super();
this.state = {
state = {
gutterKey: 1,
vgutterKey: 1,
colCountKey: 2,
};
[8, 16, 24, 32, 40, 48].forEach((value, i) => {
this.gutters[i] = value;
});
[8, 16, 24, 32, 40, 48].forEach((value, i) => {
this.vgutters[i] = value;
});
[2, 3, 4, 6, 8, 12].forEach((value, i) => {
this.colCounts[i] = value;
});
}
onGutterChange = gutterKey => {
this.setState({ gutterKey });
@ -56,7 +52,7 @@ class App extends React.Component {
render() {
const { gutterKey, vgutterKey, colCountKey } = this.state;
const cols = [];
const colCount = this.colCounts[colCountKey];
const colCount = colCounts[colCountKey];
let colCode = '';
for (let i = 0; i < colCount; i++) {
cols.push(
@ -72,39 +68,42 @@ class App extends React.Component {
<div style={{ width: '50%' }}>
<Slider
min={0}
max={Object.keys(this.gutters).length - 1}
max={Object.keys(gutters).length - 1}
value={gutterKey}
onChange={this.onGutterChange}
marks={this.gutters}
marks={gutters}
step={null}
tipFormatter={value => gutters[value]}
/>
</div>
<span style={{ marginRight: 6 }}>Vertical Gutter (px): </span>
<div style={{ width: '50%' }}>
<Slider
min={0}
max={Object.keys(this.vgutters).length - 1}
max={Object.keys(vgutters).length - 1}
value={vgutterKey}
onChange={this.onVGutterChange}
marks={this.vgutters}
marks={vgutters}
step={null}
tipFormatter={value => vgutters[value]}
/>
</div>
<span style={{ marginRight: 6 }}>Column Count:</span>
<div style={{ width: '50%', marginBottom: 48 }}>
<Slider
min={0}
max={Object.keys(this.colCounts).length - 1}
max={Object.keys(colCounts).length - 1}
value={colCountKey}
onChange={this.onColCountChange}
marks={this.colCounts}
marks={colCounts}
step={null}
tipFormatter={value => colCounts[value]}
/>
</div>
<Row gutter={[this.gutters[gutterKey], this.vgutters[vgutterKey]]}>{cols}</Row>
<Row gutter={[this.gutters[gutterKey], this.vgutters[vgutterKey]]}>{cols}</Row>
<pre className="demo-code">{`<Row gutter={[${this.gutters[gutterKey]}, ${this.vgutters[vgutterKey]}]}>\n${colCode}</Row>`}</pre>
<pre className="demo-code">{`<Row gutter={[${this.gutters[gutterKey]}, ${this.vgutters[vgutterKey]}]}>\n${colCode}</Row>`}</pre>
<Row gutter={[gutters[gutterKey], vgutters[vgutterKey]]}>{cols}</Row>
<Row gutter={[gutters[gutterKey], vgutters[vgutterKey]]}>{cols}</Row>
<pre className="demo-code">{`<Row gutter={[${gutters[gutterKey]}, ${vgutters[vgutterKey]}]}>\n${colCode}</Row>`}</pre>
<pre className="demo-code">{`<Row gutter={[${gutters[gutterKey]}, ${vgutters[vgutterKey]}]}>\n${colCode}</Row>`}</pre>
</>
);
}

View File

@ -2237,7 +2237,7 @@ Array [
aria-label="audio"
class="anticon anticon-audio"
role="img"
style="font-size:16px;color:#1890ff;padding-right:4px"
style="font-size:16px;color:#1890ff"
>
<svg
aria-hidden="true"

View File

@ -182,6 +182,14 @@ ReactDOM.render(<App />, mountNode);
.site-input-group-wrapper .site-input-right:hover {
border-left-width: 1px;
}
.site-input-group-wrapper .ant-input-rtl.site-input-right {
border-right-width: 0;
}
.site-input-group-wrapper .ant-input-rtl.site-input-right:hover {
border-right-width: 1px;
}
```
<style>

View File

@ -24,7 +24,6 @@ const suffix = (
style={{
fontSize: 16,
color: '#1890ff',
paddingRight: 4,
}}
/>
);

View File

@ -17,6 +17,13 @@
@input-affix-margin: 4px;
.@{ant-prefix}-input {
&-affix-wrapper&-affix-wrapper-rtl {
> input.@{ant-prefix}-input {
border: none;
outline: none;
}
}
&-affix-wrapper-rtl {
.@{ant-prefix}-input-prefix {
margin: 0 0 0 @input-affix-margin;
@ -83,6 +90,26 @@
}
}
.@{inputClass}-affix-wrapper {
&:not(:first-child) {
.@{inputClass}-group-rtl& {
border-top-left-radius: @border-radius-base;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-bottom-left-radius: @border-radius-base;
}
}
&:not(:last-child) {
.@{inputClass}-group-rtl& {
border-top-left-radius: 0;
border-top-right-radius: @border-radius-base;
border-bottom-right-radius: @border-radius-base;
border-bottom-left-radius: 0;
}
}
}
&&-compact {
& > *:not(:last-child) {
.@{inputClass}-group-rtl& {
@ -127,6 +154,7 @@
}
// search-input
@search-prefix: ~'@{ant-prefix}-input-search';
@search-rtl-cls: ~'@{search-prefix}-rtl';
.@{search-prefix} {
@ -166,15 +194,31 @@
&-enter-button {
input {
.@{search-rtl-cls}& {
border: @border-width-base @border-style-base @input-border-color;
border-right: @border-width-base @border-style-base @input-border-color;
border-left: 0;
}
&:hover,
&:focus {
.@{search-rtl-cls}& {
border-color: @input-hover-border-color;
}
}
}
&.@{ant-prefix}-input-affix-wrapper {
.@{search-rtl-cls}& {
border-right: @border-width-base @border-style-base @input-border-color;
border-left: 0;
}
&:hover,
&:focus {
.@{search-rtl-cls}& {
border-color: @input-hover-border-color;
}
}
}
& + .@{ant-prefix}-input-group-addon,
input + .@{ant-prefix}-input-group-addon {

View File

@ -158,18 +158,19 @@ class InternalMenu extends React.Component<InternalMenuProps, MenuState> {
}
getRealMenuMode() {
const { mode } = this.props;
const { switchingModeFromInline } = this.state;
const inlineCollapsed = this.getInlineCollapsed();
if (this.state.switchingModeFromInline && inlineCollapsed) {
if (switchingModeFromInline && inlineCollapsed) {
return 'inline';
}
const { mode } = this.props;
return inlineCollapsed ? 'vertical' : mode;
}
getInlineCollapsed() {
const { inlineCollapsed } = this.props;
if (this.props.siderCollapsed !== undefined) {
return this.props.siderCollapsed;
const { inlineCollapsed, siderCollapsed } = this.props;
if (siderCollapsed !== undefined) {
return siderCollapsed;
}
return inlineCollapsed;
}
@ -178,6 +179,7 @@ class InternalMenu extends React.Component<InternalMenuProps, MenuState> {
menuMode: MenuMode,
): { openTransitionName?: any; openAnimation?: any; motion?: Object } {
const { openTransitionName, openAnimation, motion } = this.props;
const { switchingModeFromInline } = this.state;
// Provides by user
if (motion) {
@ -210,7 +212,7 @@ class InternalMenu extends React.Component<InternalMenuProps, MenuState> {
// submenu should hide without animation
return {
motion: {
motionName: this.state.switchingModeFromInline ? '' : 'zoom-big',
motionName: switchingModeFromInline ? '' : 'zoom-big',
},
};
}
@ -277,6 +279,7 @@ class InternalMenu extends React.Component<InternalMenuProps, MenuState> {
renderMenu = ({ getPopupContainer, getPrefixCls, direction }: ConfigConsumerProps) => {
const { prefixCls: customizePrefixCls, className, theme, collapsedWidth } = this.props;
const { openKeys } = this.state;
const passProps = omit(this.props, ['collapsedWidth', 'siderCollapsed']);
const menuMode = this.getRealMenuMode();
const menuOpenMotion = this.getOpenMotionProps(menuMode!);
@ -287,7 +290,7 @@ class InternalMenu extends React.Component<InternalMenuProps, MenuState> {
});
const menuProps: MenuProps = {
openKeys: this.state.openKeys,
openKeys,
onOpenChange: this.handleOpenChange,
className: menuClassName,
mode: menuMode,
@ -313,7 +316,7 @@ class InternalMenu extends React.Component<InternalMenuProps, MenuState> {
<MenuContext.Provider
value={{
inlineCollapsed: this.getInlineCollapsed() || false,
antdMenuTheme: this.props.theme,
antdMenuTheme: theme,
direction,
}}
>

View File

@ -34,7 +34,13 @@ exports[`renders ./components/message/demo/loading.md correctly 1`] = `
`;
exports[`renders ./components/message/demo/other.md correctly 1`] = `
<div>
<div
class="ant-space ant-space-horizontal ant-space-align-center"
>
<div
class="ant-space-item"
style="margin-right:8px"
>
<button
class="ant-btn"
type="button"
@ -43,6 +49,11 @@ exports[`renders ./components/message/demo/other.md correctly 1`] = `
Success
</span>
</button>
</div>
<div
class="ant-space-item"
style="margin-right:8px"
>
<button
class="ant-btn"
type="button"
@ -51,6 +62,10 @@ exports[`renders ./components/message/demo/other.md correctly 1`] = `
Error
</span>
</button>
</div>
<div
class="ant-space-item"
>
<button
class="ant-btn"
type="button"
@ -59,6 +74,7 @@ exports[`renders ./components/message/demo/other.md correctly 1`] = `
Warning
</span>
</button>
</div>
</div>
`;

View File

@ -14,7 +14,7 @@ title:
Messages of success, error and warning types.
```jsx
import { message, Button } from 'antd';
import { message, Button, Space } from 'antd';
const success = () => {
message.success('This is a success message');
@ -29,17 +29,11 @@ const warning = () => {
};
ReactDOM.render(
<div>
<Space>
<Button onClick={success}>Success</Button>
<Button onClick={error}>Error</Button>
<Button onClick={warning}>Warning</Button>
</div>,
</Space>,
mountNode,
);
```
<style>
#components-message-demo-other .ant-btn {
margin-right: 8px;
}
</style>

View File

@ -14,7 +14,7 @@ title:
Use `confirm()` to show a confirmation modal dialog.
```jsx
import { Modal, Button } from 'antd';
import { Modal, Button, Space } from 'antd';
import { ExclamationCircleOutlined } from '@ant-design/icons';
const { confirm } = Modal;
@ -71,7 +71,7 @@ function showPropsConfirm() {
}
ReactDOM.render(
<div>
<Space>
<Button onClick={showConfirm}>Confirm</Button>
<Button onClick={showDeleteConfirm} type="dashed">
Delete
@ -79,7 +79,7 @@ ReactDOM.render(
<Button onClick={showPropsConfirm} type="dashed">
With extra props
</Button>
</div>,
</Space>,
mountNode,
);
```

View File

@ -14,7 +14,7 @@ title:
Use `Modal.useModal` to get `contextHolder` with context accessible issue.
```jsx
import { Modal, Button } from 'antd';
import { Modal, Button, Space } from 'antd';
const ReachableContext = React.createContext();
const UnreachableContext = React.createContext();
@ -35,6 +35,7 @@ const App = () => {
return (
<ReachableContext.Provider value="Light">
<Space>
<Button
onClick={() => {
modal.confirm(config);
@ -63,7 +64,7 @@ const App = () => {
>
Error
</Button>
</Space>
{/* `contextHolder` should always under the context you want to access */}
{contextHolder}

View File

@ -14,7 +14,7 @@ title:
In the various types of information modal dialog, only one button to close dialog is provided.
```jsx
import { Modal, Button } from 'antd';
import { Modal, Button, Space } from 'antd';
function info() {
Modal.info({
@ -50,12 +50,12 @@ function warning() {
}
ReactDOM.render(
<div>
<Space>
<Button onClick={info}>Info</Button>
<Button onClick={success}>Success</Button>
<Button onClick={error}>Error</Button>
<Button onClick={warning}>Warning</Button>
</div>,
</Space>,
mountNode,
);
```

View File

@ -91,12 +91,6 @@ modal.update({
modal.destroy();
```
<style>
.code-box-demo .ant-btn {
margin-right: 8px;
}
</style>
- `Modal.destroyAll`
`Modal.destroyAll()` could destroy all confirmation modal dialogs(Modal.info/Modal.success/Modal.error/Modal.warning/Modal.confirm). Usually, you can use it in router change event to destroy confirm modal dialog automatically without use modal reference to close( it's too complex to use for all modal dialogs)

View File

@ -93,12 +93,6 @@ modal.update({
modal.destroy();
```
<style>
.code-box-demo .ant-btn {
margin-right: 8px;
}
</style>
- `Modal.destroyAll`
使用 `Modal.destroyAll()` 可以销毁弹出的确认窗(即上述的 Modal.info、Modal.success、Modal.error、Modal.warning、Modal.confirm。通常用于路由监听当中处理路由前进、后退不能销毁确认对话框的问题而不用各处去使用实例的返回值进行关闭modal.destroy() 适用于主动关闭,而不是路由这样被动关闭)

View File

@ -46,6 +46,13 @@ exports[`renders ./components/notification/demo/duration.md correctly 1`] = `
exports[`renders ./components/notification/demo/hooks.md correctly 1`] = `
Array [
<div
class="ant-space ant-space-horizontal ant-space-align-center"
>
<div
class="ant-space-item"
style="margin-right:8px"
>
<button
class="ant-btn ant-btn-primary"
type="button"
@ -73,7 +80,11 @@ Array [
<span>
topLeft
</span>
</button>,
</button>
</div>
<div
class="ant-space-item"
>
<button
class="ant-btn ant-btn-primary"
type="button"
@ -101,11 +112,20 @@ Array [
<span>
topRight
</span>
</button>,
</button>
</div>
</div>,
<div
class="ant-divider ant-divider-horizontal"
role="separator"
/>,
<div
class="ant-space ant-space-horizontal ant-space-align-center"
>
<div
class="ant-space-item"
style="margin-right:8px"
>
<button
class="ant-btn ant-btn-primary"
type="button"
@ -133,7 +153,11 @@ Array [
<span>
bottomLeft
</span>
</button>,
</button>
</div>
<div
class="ant-space-item"
>
<button
class="ant-btn ant-btn-primary"
type="button"
@ -161,12 +185,21 @@ Array [
<span>
bottomRight
</span>
</button>,
</button>
</div>
</div>,
]
`;
exports[`renders ./components/notification/demo/placement.md correctly 1`] = `
<div>
<div
class="ant-space ant-space-horizontal ant-space-align-center"
>
<div
class="ant-space-item"
style="margin-right:8px"
>
<button
class="ant-btn ant-btn-primary"
type="button"
@ -195,6 +228,10 @@ exports[`renders ./components/notification/demo/placement.md correctly 1`] = `
topLeft
</span>
</button>
</div>
<div
class="ant-space-item"
>
<button
class="ant-btn ant-btn-primary"
type="button"
@ -223,10 +260,19 @@ exports[`renders ./components/notification/demo/placement.md correctly 1`] = `
topRight
</span>
</button>
</div>
</div>
<div
class="ant-divider ant-divider-horizontal"
role="separator"
/>
<div
class="ant-space ant-space-horizontal ant-space-align-center"
>
<div
class="ant-space-item"
style="margin-right:8px"
>
<button
class="ant-btn ant-btn-primary"
type="button"
@ -255,6 +301,10 @@ exports[`renders ./components/notification/demo/placement.md correctly 1`] = `
bottomLeft
</span>
</button>
</div>
<div
class="ant-space-item"
>
<button
class="ant-btn ant-btn-primary"
type="button"
@ -283,6 +333,8 @@ exports[`renders ./components/notification/demo/placement.md correctly 1`] = `
bottomRight
</span>
</button>
</div>
</div>
</div>
`;
@ -309,7 +361,13 @@ exports[`renders ./components/notification/demo/with-btn.md correctly 1`] = `
`;
exports[`renders ./components/notification/demo/with-icon.md correctly 1`] = `
<div>
<div
class="ant-space ant-space-horizontal ant-space-align-center"
>
<div
class="ant-space-item"
style="margin-right:8px"
>
<button
class="ant-btn"
type="button"
@ -318,6 +376,11 @@ exports[`renders ./components/notification/demo/with-icon.md correctly 1`] = `
Success
</span>
</button>
</div>
<div
class="ant-space-item"
style="margin-right:8px"
>
<button
class="ant-btn"
type="button"
@ -326,6 +389,11 @@ exports[`renders ./components/notification/demo/with-icon.md correctly 1`] = `
Info
</span>
</button>
</div>
<div
class="ant-space-item"
style="margin-right:8px"
>
<button
class="ant-btn"
type="button"
@ -334,6 +402,10 @@ exports[`renders ./components/notification/demo/with-icon.md correctly 1`] = `
Warning
</span>
</button>
</div>
<div
class="ant-space-item"
>
<button
class="ant-btn"
type="button"
@ -342,5 +414,6 @@ exports[`renders ./components/notification/demo/with-icon.md correctly 1`] = `
Error
</span>
</button>
</div>
</div>
`;

View File

@ -14,7 +14,7 @@ title:
Use `notification.useNotification` to get `contextHolder` with context accessible issue.
```jsx
import { Button, notification, Divider } from 'antd';
import { Button, notification, Divider, Space } from 'antd';
import {
RadiusUpleftOutlined,
RadiusUprightOutlined,
@ -38,6 +38,7 @@ const Demo = () => {
return (
<Context.Provider value={{ name: 'Ant Design' }}>
{contextHolder}
<Space>
<Button type="primary" onClick={() => openNotification('topLeft')}>
<RadiusUpleftOutlined />
topLeft
@ -46,7 +47,9 @@ const Demo = () => {
<RadiusUprightOutlined />
topRight
</Button>
</Space>
<Divider />
<Space>
<Button type="primary" onClick={() => openNotification('bottomLeft')}>
<RadiusBottomleftOutlined />
bottomLeft
@ -55,6 +58,7 @@ const Demo = () => {
<RadiusBottomrightOutlined />
bottomRight
</Button>
</Space>
</Context.Provider>
);
};

View File

@ -14,7 +14,7 @@ title:
A notification box can appear from the `topRight`, `bottomRight`, `bottomLeft` or `topLeft` of the viewport.
```jsx
import { Button, notification, Divider } from 'antd';
import { Button, notification, Divider, Space } from 'antd';
import {
RadiusUpleftOutlined,
RadiusUprightOutlined,
@ -33,6 +33,7 @@ const openNotification = placement => {
ReactDOM.render(
<div>
<Space>
<Button type="primary" onClick={() => openNotification('topLeft')}>
<RadiusUpleftOutlined />
topLeft
@ -41,7 +42,9 @@ ReactDOM.render(
<RadiusUprightOutlined />
topRight
</Button>
</Space>
<Divider />
<Space>
<Button type="primary" onClick={() => openNotification('bottomLeft')}>
<RadiusBottomleftOutlined />
bottomLeft
@ -50,6 +53,7 @@ ReactDOM.render(
<RadiusBottomrightOutlined />
bottomRight
</Button>
</Space>
</div>,
mountNode,
);

View File

@ -14,7 +14,7 @@ title:
A notification box with a icon at the left side.
```jsx
import { Button, notification } from 'antd';
import { Button, notification, Space } from 'antd';
const openNotificationWithIcon = type => {
notification[type]({
@ -25,18 +25,12 @@ const openNotificationWithIcon = type => {
};
ReactDOM.render(
<div>
<Space>
<Button onClick={() => openNotificationWithIcon('success')}>Success</Button>
<Button onClick={() => openNotificationWithIcon('info')}>Info</Button>
<Button onClick={() => openNotificationWithIcon('warning')}>Warning</Button>
<Button onClick={() => openNotificationWithIcon('error')}>Error</Button>
</div>,
</Space>,
mountNode,
);
```
<style>
.code-box-demo .ant-btn {
margin-right: 1em;
}
</style>

View File

@ -85,6 +85,11 @@ ReactDOM.render(
margin-right: 8px;
margin-bottom: 8px;
}
.code-box-demo .ant-btn-rtl {
margin-right: 0;
margin-left: 8px;
margin-bottom: 8px;
}
#components-popover-demo-placement .ant-btn {
width: 70px;
text-align: center;

View File

@ -17,7 +17,7 @@
&:last-child {
.@{result-prefix-cls}-rtl & {
margin-left: 8px;
margin-left: 0;
}
}
}

View File

@ -25,14 +25,14 @@ To input a value in a range.
| range | dual thumb mode | boolean | false | |
| reverse | reverse the component | boolean | false | |
| step | The granularity the slider can step through values. Must greater than 0, and be divided by (max - min) . When `marks` no null, `step` can be `null`. | number\|null | 1 | |
| tipFormatter | Slider will pass its value to `tipFormatter`, and display its value in Tooltip, and hide Tooltip when return value is null. | Function\|null | IDENTITY | |
| tipFormatter | Slider will pass its value to `tipFormatter`, and display its value in Tooltip, and hide Tooltip when return value is null. | value => ReactNode\|null | IDENTITY | |
| value | The value of slider. When `range` is `false`, use `number`, otherwise, use `[number, number]` | number\|\[number, number] | |
| vertical | If true, the slider will be vertical. | Boolean | false | |
| onAfterChange | Fire when `onmouseup` is fired. | Function(value) | NOOP | |
| onChange | Callback function that is fired when the user changes the slider's value. | Function(value) | NOOP | |
| onAfterChange | Fire when `onmouseup` is fired. | (value) => void | NOOP | |
| onChange | Callback function that is fired when the user changes the slider's value. | (value) => void | NOOP | |
| tooltipPlacement | Set Tooltip display position. Ref [`Tooltip`](/components/tooltip/). | string | | |
| tooltipVisible | If true, Tooltip will show always, or it will not show anyway, even if dragging or hovering. | Boolean | | |
| getTooltipPopupContainer | The DOM container of the Tooltip, the default behavior is to create a div element in body. | Function | () => document.body | |
| getTooltipPopupContainer | The DOM container of the Tooltip, the default behavior is to create a div element in body. | (triggerNode) => HTMLElement | () => document.body | |
## Methods

View File

@ -26,14 +26,14 @@ title: Slider
| range | 双滑块模式 | boolean | false | |
| reverse | 反向坐标轴 | boolean | false | |
| step | 步长,取值必须大于 0并且可被 (max - min) 整除。当 `marks` 不为空对象时,可以设置 `step``null`,此时 Slider 的可选值仅有 marks 标出来的部分。 | number\|null | 1 | |
| tipFormatter | Slider 会把当前值传给 `tipFormatter`,并在 Tooltip 中显示 `tipFormatter` 的返回值,若为 null则隐藏 Tooltip。 | Function\|null | IDENTITY | |
| tipFormatter | Slider 会把当前值传给 `tipFormatter`,并在 Tooltip 中显示 `tipFormatter` 的返回值,若为 null则隐藏 Tooltip。 | value => ReactNode\|null | IDENTITY | |
| value | 设置当前取值。当 `range``false` 时,使用 `number`,否则用 `[number, number]` | number\|\[number, number] | | |
| vertical | 值为 `true`Slider 为垂直方向 | Boolean | false | |
| onAfterChange | 与 `onmouseup` 触发时机一致,把当前值作为参数传入。 | Function(value) | NOOP | |
| onChange | 当 Slider 的值发生改变时,会触发 onChange 事件,并把改变后的值作为参数传入。 | Function(value) | NOOP | |
| onAfterChange | 与 `onmouseup` 触发时机一致,把当前值作为参数传入。 | (value) => void | NOOP | |
| onChange | 当 Slider 的值发生改变时,会触发 onChange 事件,并把改变后的值作为参数传入。 | (value) => void | NOOP | |
| tooltipPlacement | 设置 Tooltip 展示位置。参考 [`Tooltip`](/components/tooltip/)。 | string | | |
| tooltipVisible | 值为`true`时Tooltip 将会始终显示;否则始终不显示,哪怕在拖拽及移入时。 | Boolean | | |
| getTooltipPopupContainer | Tooltip 渲染父节点,默认渲染到 body 上。 | Function | () => document.body | |
| getTooltipPopupContainer | Tooltip 渲染父节点,默认渲染到 body 上。 | (triggerNode) => HTMLElement | () => document.body | |
## 方法

View File

@ -92,6 +92,7 @@
text-align: center;
word-break: keep-all;
cursor: pointer;
user-select: none;
&-active {
color: @text-color;

View File

@ -165,7 +165,13 @@ exports[`renders ./components/spin/demo/nested.md correctly 1`] = `
`;
exports[`renders ./components/spin/demo/size.md correctly 1`] = `
<div>
<div
class="ant-space ant-space-horizontal ant-space-align-center"
>
<div
class="ant-space-item"
style="margin-right:16px"
>
<div
class="ant-spin ant-spin-sm ant-spin-spinning"
>
@ -186,6 +192,11 @@ exports[`renders ./components/spin/demo/size.md correctly 1`] = `
/>
</span>
</div>
</div>
<div
class="ant-space-item"
style="margin-right:16px"
>
<div
class="ant-spin ant-spin-spinning"
>
@ -206,6 +217,10 @@ exports[`renders ./components/spin/demo/size.md correctly 1`] = `
/>
</span>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-spin ant-spin-lg ant-spin-spinning"
>
@ -226,6 +241,7 @@ exports[`renders ./components/spin/demo/size.md correctly 1`] = `
/>
</span>
</div>
</div>
</div>
`;

View File

@ -14,20 +14,14 @@ title:
A small `Spin` is used for loading text, default sized `Spin` for loading a card-level block, and large `Spin` used for loading a **page**.
```jsx
import { Spin } from 'antd';
import { Spin, Space } from 'antd';
ReactDOM.render(
<div>
<Space size="middle">
<Spin size="small" />
<Spin />
<Spin size="large" />
</div>,
</Space>,
mountNode,
);
```
<style>
.ant-spin.ant-spin-spinning {
margin-right: 16px;
}
</style>

View File

@ -93,7 +93,8 @@
@switch-sm-height: 14px;
@switch-min-width: 40px;
@switch-sm-min-width: 24px;
@switch-inner-margin: 0 22px 0 4px;
@switch-inner-margin-min: 4px;
@switch-inner-margin-max: 22px;
// Slider
// ---

View File

@ -729,7 +729,8 @@
@switch-color: @primary-color;
@switch-bg: @component-background;
@switch-shadow-color: fade(#00230b, 20%);
@switch-inner-margin: 0 24px 0 6px;
@switch-inner-margin-min: 6px;
@switch-inner-margin-max: 24px;
// Pagination
// ---

View File

@ -23,7 +23,8 @@
&-inner {
display: block;
margin: @switch-inner-margin;
margin-right: @switch-inner-margin-min;
margin-left: @switch-inner-margin-max;
color: @text-color-inverse;
font-size: @font-size-sm;
}
@ -135,7 +136,8 @@
background-color: @switch-color;
.@{switch-prefix-cls}-inner {
margin: @switch-inner-margin;
margin-right: @switch-inner-margin-max;
margin-left: @switch-inner-margin-min;
}
&::after {

View File

@ -10,8 +10,8 @@
&-inner {
.@{switch-prefix-cls}-rtl & {
margin-right: 24px;
margin-left: 6px;
margin-right: @switch-inner-margin-max;
margin-left: @switch-inner-margin-min;
}
}
@ -51,8 +51,8 @@
&-checked {
.@{switch-prefix-cls}-inner {
.@{switch-prefix-cls}-rtl& {
margin-right: 6px;
margin-left: 24px;
margin-right: @switch-inner-margin-min;
margin-left: @switch-inner-margin-max;
}
}

View File

@ -403,18 +403,19 @@ function Table<RecordType extends object = any>(props: TableProps<RecordType>) {
paginationSize = mergedSize === 'small' || mergedSize === 'middle' ? 'small' : undefined;
}
const renderPagination = (position: string = 'right') => (
const renderPagination = (position: string) => (
<Pagination
className={`${prefixCls}-pagination ${prefixCls}-pagination-${position}`}
{...mergedPagination}
size={paginationSize}
/>
);
const defaultPosition = direction === 'rtl' ? 'left' : 'right';
if (mergedPagination.position !== null && Array.isArray(mergedPagination.position)) {
const topPos = mergedPagination.position.find(p => p.indexOf('top') !== -1);
const bottomPos = mergedPagination.position.find(p => p.indexOf('bottom') !== -1);
if (!topPos && !bottomPos) {
bottomPaginationNode = renderPagination();
bottomPaginationNode = renderPagination(defaultPosition);
} else {
if (topPos) {
topPaginationNode = renderPagination(topPos!.toLowerCase().replace('top', ''));
@ -424,7 +425,7 @@ function Table<RecordType extends object = any>(props: TableProps<RecordType>) {
}
}
} else {
bottomPaginationNode = renderPagination();
bottomPaginationNode = renderPagination(defaultPosition);
}
}

View File

@ -27,6 +27,9 @@ exports[`Table.rowSelection fix expand on th left when selection column fixed on
<col
class="ant-table-expand-icon-col"
/>
<col
class="ant-table-selection-column"
/>
</colgroup>
<thead
class="ant-table-thead"
@ -354,7 +357,11 @@ exports[`Table.rowSelection fix selection column on the left 1`] = `
<table
style="width:903px;min-width:100%;table-layout:fixed"
>
<colgroup />
<colgroup>
<col
class="ant-table-selection-column"
/>
</colgroup>
<thead
class="ant-table-thead"
>
@ -634,7 +641,11 @@ exports[`Table.rowSelection fix selection column on the left when any other colu
<table
style="width:903px;min-width:100%;table-layout:fixed"
>
<colgroup />
<colgroup>
<col
class="ant-table-selection-column"
/>
</colgroup>
<thead
class="ant-table-thead"
>
@ -945,7 +956,11 @@ exports[`Table.rowSelection should support getPopupContainer 1`] = `
<table
style="table-layout: auto;"
>
<colgroup />
<colgroup>
<col
class="ant-table-selection-column"
/>
</colgroup>
<thead
class="ant-table-thead"
>
@ -1268,7 +1283,11 @@ exports[`Table.rowSelection should support getPopupContainer from ConfigProvider
<table
style="table-layout: auto;"
>
<colgroup />
<colgroup>
<col
class="ant-table-selection-column"
/>
</colgroup>
<thead
class="ant-table-thead"
>
@ -1591,7 +1610,11 @@ exports[`Table.rowSelection use column as selection column when key is \`selecti
<table
style="table-layout:auto"
>
<colgroup />
<colgroup>
<col
class="ant-table-selection-column"
/>
</colgroup>
<thead
class="ant-table-thead"
>

View File

@ -2378,6 +2378,9 @@ exports[`renders ./components/table/demo/dynamic-settings.md correctly 1`] = `
<col
class="ant-table-expand-icon-col"
/>
<col
class="ant-table-selection-column"
/>
</colgroup>
<thead
class="ant-table-thead"
@ -4932,7 +4935,9 @@ exports[`renders ./components/table/demo/expand-children.md correctly 1`] = `
style="table-layout:auto"
>
<colgroup>
<col />
<col
class="ant-table-selection-column"
/>
<col />
<col
style="width:12%;min-width:12%"
@ -10821,9 +10826,14 @@ exports[`renders ./components/table/demo/pagination.md correctly 1`] = `
`;
exports[`renders ./components/table/demo/reset-filter.md correctly 1`] = `
<div>
Array [
<div
class="table-operations"
class="ant-space ant-space-horizontal ant-space-align-center"
style="margin-bottom:16px"
>
<div
class="ant-space-item"
style="margin-right:8px"
>
<button
class="ant-btn"
@ -10833,6 +10843,11 @@ exports[`renders ./components/table/demo/reset-filter.md correctly 1`] = `
Sort age
</span>
</button>
</div>
<div
class="ant-space-item"
style="margin-right:8px"
>
<button
class="ant-btn"
type="button"
@ -10841,6 +10856,10 @@ exports[`renders ./components/table/demo/reset-filter.md correctly 1`] = `
Clear filters
</span>
</button>
</div>
<div
class="ant-space-item"
>
<button
class="ant-btn"
type="button"
@ -10850,6 +10869,7 @@ exports[`renders ./components/table/demo/reset-filter.md correctly 1`] = `
</span>
</button>
</div>
</div>,
<div
class="ant-table-wrapper"
>
@ -11317,8 +11337,8 @@ exports[`renders ./components/table/demo/reset-filter.md correctly 1`] = `
</ul>
</div>
</div>
</div>
</div>
</div>,
]
`;
exports[`renders ./components/table/demo/resizable-column.md correctly 1`] = `
@ -11852,7 +11872,11 @@ exports[`renders ./components/table/demo/row-selection.md correctly 1`] = `
<table
style="table-layout:auto"
>
<colgroup />
<colgroup>
<col
class="ant-table-selection-column"
/>
</colgroup>
<thead
class="ant-table-thead"
>
@ -12195,7 +12219,11 @@ exports[`renders ./components/table/demo/row-selection-and-operation.md correctl
<table
style="table-layout:auto"
>
<colgroup />
<colgroup>
<col
class="ant-table-selection-column"
/>
</colgroup>
<thead
class="ant-table-thead"
>
@ -12778,7 +12806,11 @@ exports[`renders ./components/table/demo/row-selection-custom.md correctly 1`] =
<table
style="table-layout:auto"
>
<colgroup />
<colgroup>
<col
class="ant-table-selection-column"
/>
</colgroup>
<thead
class="ant-table-thead"
>
@ -13388,7 +13420,11 @@ exports[`renders ./components/table/demo/row-selection-custom-debug.md correctly
<table
style="table-layout:auto"
>
<colgroup />
<colgroup>
<col
class="ant-table-selection-column"
/>
</colgroup>
<thead
class="ant-table-thead"
>

View File

@ -14,7 +14,7 @@ title:
Implement a customized column search example via `filterDropdown`.
```jsx
import { Table, Input, Button } from 'antd';
import { Table, Input, Button, Space } from 'antd';
import Highlighter from 'react-highlight-words';
import { SearchOutlined } from '@ant-design/icons';
@ -64,26 +64,25 @@ class App extends React.Component {
onPressEnter={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
style={{ width: 188, marginBottom: 8, display: 'block' }}
/>
<Space>
<Button
type="primary"
onClick={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
icon={<SearchOutlined />}
size="small"
style={{ width: 90, marginRight: 8 }}
style={{ width: 90 }}
>
Search
</Button>
<Button onClick={() => this.handleReset(clearFilters)} size="small" style={{ width: 90 }}>
Reset
</Button>
</Space>
</div>
),
filterIcon: filtered => <SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} />,
onFilter: (value, record) =>
record[dataIndex]
.toString()
.toLowerCase()
.includes(value.toLowerCase()),
record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()),
onFilterDropdownVisibleChange: visible => {
if (visible) {
setTimeout(() => this.searchInput.select());

View File

@ -22,7 +22,7 @@ Control filters and sorters by `filteredValue` and `sortOrder`.
> 3. `column.key` is required.
```jsx
import { Table, Button } from 'antd';
import { Table, Button, Space } from 'antd';
const data = [
{
@ -128,27 +128,17 @@ class App extends React.Component {
},
];
return (
<div>
<div className="table-operations">
<>
<Space style={{ marginBottom: 16 }}>
<Button onClick={this.setAgeSort}>Sort age</Button>
<Button onClick={this.clearFilters}>Clear filters</Button>
<Button onClick={this.clearAll}>Clear filters and sorters</Button>
</div>
</Space>
<Table columns={columns} dataSource={data} onChange={this.handleChange} />
</div>
</>
);
}
}
ReactDOM.render(<App />, mountNode);
```
```css
.table-operations {
margin-bottom: 16px;
}
.table-operations > button {
margin-right: 8px;
}
```

View File

@ -1,5 +1,6 @@
import * as React from 'react';
import DownOutlined from '@ant-design/icons/DownOutlined';
import { INTERNAL_COL_DEFINE } from 'rc-table';
import { FixedType } from 'rc-table/lib/interface';
import Checkbox, { CheckboxProps } from '../../checkbox';
import Dropdown from '../../dropdown';
@ -440,6 +441,9 @@ export default function useSelection<RecordType>(
className: `${prefixCls}-selection-column`,
title: rowSelection.columnTitle || title,
render: renderSelectionCell,
[INTERNAL_COL_DEFINE]: {
className: `${prefixCls}-selection-column`,
},
};
if (expandType === 'row' && columns.length && !expandIconColumnIndex) {

View File

@ -360,6 +360,7 @@
}
}
&-selection-column,
table tr th&-selection-column,
table tr td&-selection-column {
width: @table-selection-column-width;

View File

@ -2526,9 +2526,18 @@ exports[`renders ./components/tabs/demo/nest.md correctly 1`] = `
exports[`renders ./components/tabs/demo/position.md correctly 1`] = `
<div>
<div
class="ant-space ant-space-horizontal ant-space-align-center"
style="margin-bottom:16px"
>
<div
class="ant-space-item"
style="margin-right:8px"
>
Tab position
</div>
<div
class="ant-space-item"
>
<div
class="ant-select ant-select-single ant-select-show-arrow"
>
@ -2587,6 +2596,7 @@ exports[`renders ./components/tabs/demo/position.md correctly 1`] = `
</span>
</div>
</div>
</div>
<div
class="ant-tabs ant-tabs-top ant-tabs-line"
>

View File

@ -14,7 +14,7 @@ title:
Tab's position: left, right, top or bottom.
```jsx
import { Tabs, Select } from 'antd';
import { Tabs, Select, Space } from 'antd';
const { TabPane } = Tabs;
const { Option } = Select;
@ -31,7 +31,7 @@ class Demo extends React.Component {
render() {
return (
<div>
<div style={{ marginBottom: 16 }}>
<Space style={{ marginBottom: 16 }}>
Tab position
<Select
value={this.state.tabPosition}
@ -43,7 +43,7 @@ class Demo extends React.Component {
<Option value="left">left</Option>
<Option value="right">right</Option>
</Select>
</div>
</Space>
<Tabs tabPosition={this.state.tabPosition}>
<TabPane tab="Tab 1" key="1">
Content of Tab 1

View File

@ -204,19 +204,29 @@ exports[`renders ./components/tag/demo/basic.md correctly 1`] = `
exports[`renders ./components/tag/demo/checkable.md correctly 1`] = `
Array [
<span
class="ant-tag ant-tag-checkable"
style="margin-right:8px"
>
Tag1
Categories:
</span>,
<span
class="ant-tag ant-tag-checkable"
>
Tag2
Movies
</span>,
<span
class="ant-tag ant-tag-checkable ant-tag-checkable-checked"
>
Books
</span>,
<span
class="ant-tag ant-tag-checkable"
>
Tag3
Music
</span>,
<span
class="ant-tag ant-tag-checkable"
>
Sports
</span>,
]
`;
@ -456,36 +466,6 @@ exports[`renders ./components/tag/demo/controlled.md correctly 1`] = `
</div>
`;
exports[`renders ./components/tag/demo/hot-tags.md correctly 1`] = `
Array [
<span
style="margin-right:8px"
>
Categories:
</span>,
<span
class="ant-tag ant-tag-checkable"
>
Movies
</span>,
<span
class="ant-tag ant-tag-checkable ant-tag-checkable-checked"
>
Books
</span>,
<span
class="ant-tag ant-tag-checkable"
>
Music
</span>,
<span
class="ant-tag ant-tag-checkable"
>
Sports
</span>,
]
`;
exports[`renders ./components/tag/demo/icon.md correctly 1`] = `
<div>
<span

View File

@ -1,7 +1,7 @@
---
order: 3
title:
zh-CN: 可选择
zh-CN: 可选择标签
en-US: Checkable
---
@ -22,12 +22,38 @@ import { Tag } from 'antd';
const { CheckableTag } = Tag;
ReactDOM.render(
const tagsData = ['Movies', 'Books', 'Music', 'Sports'];
class HotTags extends React.Component {
state = {
selectedTags: ['Books'],
};
handleChange(tag, checked) {
const { selectedTags } = this.state;
const nextSelectedTags = checked ? [...selectedTags, tag] : selectedTags.filter(t => t !== tag);
console.log('You are interested in: ', nextSelectedTags);
this.setState({ selectedTags: nextSelectedTags });
}
render() {
const { selectedTags } = this.state;
return (
<>
<CheckableTag>Tag1</CheckableTag>
<CheckableTag>Tag2</CheckableTag>
<CheckableTag>Tag3</CheckableTag>
</>,
mountNode,
);
<span style={{ marginRight: 8 }}>Categories:</span>
{tagsData.map(tag => (
<CheckableTag
key={tag}
checked={selectedTags.indexOf(tag) > -1}
onChange={checked => this.handleChange(tag, checked)}
>
{tag}
</CheckableTag>
))}
</>
);
}
}
ReactDOM.render(<HotTags />, mountNode);
```

View File

@ -44,12 +44,6 @@ ReactDOM.render(
);
```
```css
.ant-tag {
margin-bottom: 8px;
}
```
<style>
.code-box-demo .ant-tag {
margin-bottom: 8px;

View File

@ -1,55 +0,0 @@
---
order: 4
title:
zh-CN: 热门标签
en-US: Hot Tags
---
## zh-CN
选择你感兴趣的话题。
## en-US
Select your favourite topics.
```jsx
import { Tag } from 'antd';
const { CheckableTag } = Tag;
const tagsData = ['Movies', 'Books', 'Music', 'Sports'];
class HotTags extends React.Component {
state = {
selectedTags: ['Books'],
};
handleChange(tag, checked) {
const { selectedTags } = this.state;
const nextSelectedTags = checked ? [...selectedTags, tag] : selectedTags.filter(t => t !== tag);
console.log('You are interested in: ', nextSelectedTags);
this.setState({ selectedTags: nextSelectedTags });
}
render() {
const { selectedTags } = this.state;
return (
<>
<span style={{ marginRight: 8 }}>Categories:</span>
{tagsData.map(tag => (
<CheckableTag
key={tag}
checked={selectedTags.indexOf(tag) > -1}
onChange={checked => this.handleChange(tag, checked)}
>
{tag}
</CheckableTag>
))}
</>
);
}
}
ReactDOM.render(<HotTags />, mountNode);
```

View File

@ -297,4 +297,22 @@ describe('Tooltip', () => {
await sleep(500);
expect(wrapper.instance().getPopupDomNode().className).toContain('placement-topRight');
});
it('should works for mismatch placement', async () => {
const wrapper = mount(
<Tooltip
title="xxxxx"
align={{
points: ['bc', 'tl'],
}}
mouseEnterDelay={0}
>
<span>Hello world!</span>
</Tooltip>,
);
const button = wrapper.find('span').at(0);
button.simulate('mouseenter');
await sleep(600);
expect(wrapper.instance().getPopupDomNode().className).toContain('ant-tooltip');
});
});

View File

@ -28,10 +28,3 @@ ReactDOM.render(
mountNode,
);
```
<style>
.code-box-demo .ant-btn {
margin-right: 1em;
margin-bottom: 1em;
}
</style>

View File

@ -46,10 +46,3 @@ ReactDOM.render(
mountNode,
);
```
<style>
.code-box-demo .ant-btn {
margin-right: 1em;
margin-bottom: 1em;
}
</style>

View File

@ -79,6 +79,11 @@ ReactDOM.render(
margin-right: 8px;
margin-bottom: 8px;
}
.code-box-demo .ant-btn-rtl {
margin-right: 0;
margin-left: 8px;
margin-bottom: 8px;
}
#components-tooltip-demo-placement .ant-btn {
width: 70px;
text-align: center;

View File

@ -186,6 +186,9 @@ class Tooltip extends React.Component<TooltipProps, any> {
placements[key].points[0] === align.points[0] &&
placements[key].points[1] === align.points[1],
)[0];
if (!placement) {
return;
}
// 根据当前坐标设置动画点
const rect = domNode.getBoundingClientRect();
const transformOrigin = {

View File

@ -2243,7 +2243,11 @@ exports[`renders ./components/transfer/demo/table-transfer.md correctly 1`] = `
<table
style="table-layout:auto"
>
<colgroup />
<colgroup>
<col
class="ant-table-selection-column"
/>
</colgroup>
<thead
class="ant-table-thead"
>
@ -2920,7 +2924,11 @@ exports[`renders ./components/transfer/demo/table-transfer.md correctly 1`] = `
<table
style="table-layout:auto"
>
<colgroup />
<colgroup>
<col
class="ant-table-selection-column"
/>
</colgroup>
<thead
class="ant-table-thead"
>

View File

@ -33,7 +33,7 @@ Uploading is the process of publishing information (web pages, text, pictures, v
| multiple | Whether to support selected multiple file. `IE10+` supported. You can select multiple files with CTRL holding down while multiple is set to be true | boolean | false | |
| name | The name of uploading file | string | 'file' | |
| previewFile | Customize preview file logic | (file: File \| Blob) => Promise<dataURL: string> | - | |
| isImageUrl | Customize if render <img /> in thumbnail | (file: UploadFile) => boolean | [inside implementation](https://github.com/ant-design/ant-design/blob/4ad5830eecfb87471cd8ac588c5d992862b70770/components/upload/utils.tsx#L47-L68) | |
| isImageUrl | Customize if render `<img />` in thumbnail | (file: UploadFile) => boolean | [inside implementation](https://github.com/ant-design/ant-design/blob/4ad5830eecfb87471cd8ac588c5d992862b70770/components/upload/utils.tsx#L47-L68) | |
| showUploadList | Whether to show default upload list, could be an object to specify `showPreviewIcon`, `showRemoveIcon`, `showDownloadIcon`, `removeIcon` and `downloadIcon` individually | Boolean or { showPreviewIcon?: boolean, showDownloadIcon?: boolean, showRemoveIcon?: boolean, removeIcon?: React.ReactNode, downloadIcon?: React.ReactNode } | true | |
| supportServerRender | Need to be turned on while the server side is rendering | boolean | false | |
| withCredentials | ajax upload with cookie sent | boolean | false | |

View File

@ -34,7 +34,7 @@ title: Upload
| multiple | 是否支持多选文件,`ie10+` 支持。开启后按住 ctrl 可选择多个文件 | boolean | false | |
| name | 发到后台的文件参数名 | string | 'file' | |
| previewFile | 自定义文件预览逻辑 | (file: File \| Blob) => Promise<dataURL: string> | 无 | |
| isImageUrl | 自定义缩略图是否使用 img 标签进行显示 | (file: UploadFile) => boolean | [内部实现](https://github.com/ant-design/ant-design/blob/4ad5830eecfb87471cd8ac588c5d992862b70770/components/upload/utils.tsx#L47-L68) | |
| isImageUrl | 自定义缩略图是否使用 `<img />` 标签进行显示 | (file: UploadFile) => boolean | [内部实现](https://github.com/ant-design/ant-design/blob/4ad5830eecfb87471cd8ac588c5d992862b70770/components/upload/utils.tsx#L47-L68) | |
| showUploadList | 是否展示文件列表, 可设为一个对象,用于单独设定 `showPreviewIcon`, `showRemoveIcon`, `showDownloadIcon`, `removeIcon``downloadIcon` | Boolean or { showPreviewIcon?: boolean, showRemoveIcon?: boolean, showDownloadIcon?: boolean, removeIcon?: React.ReactNode, downloadIcon?: React.ReactNode } | true | |
| supportServerRender | 服务端渲染时需要打开这个 | boolean | false | |
| withCredentials | 上传请求时是否携带 cookie | boolean | false | |

View File

@ -47,45 +47,34 @@ const LinksList = () => (
</li>
<li>
<a href="http://ng.ant.design" target="_blank">
NG-ZORRO - Ant Design of Angular<LinkIcon />
NG-ZORRO - Ant Design of Angular
</a>
</li>
<li>
<a href="http://ng.mobile.ant.design" target="_blank">
NG-ZORRO-MOBILE - Ant Design Mobile of Angular<LinkIcon />
NG-ZORRO-MOBILE - Ant Design Mobile of Angular
</a>
</li>
<li>
<a href="http://vue.ant.design" target="_blank">Ant Design of Vue<LinkIcon /></a>
<a href="http://vue.ant.design" target="_blank">Ant Design of Vue</a>
</li>
<li>
<a href="https://ecomfe.github.io/santd" target="_blank">
San UI Toolkit for Ant Design<LinkIcon />
</a>
</li>
<li>
<a href="https://github.com/FE-Driver/vue-beauty" target="_blank">
vue-beauty (vue)<LinkIcon />
San UI Toolkit for Ant Design
</a>
</li>
<li>
<a href="https://github.com/priornix/antizer" target="_blank">
antizer (ClojureScript)<LinkIcon />
antizer (ClojureScript)
</a>
</li>
<li>
<a href="https://github.com/idcos/antd-ember" target="_blank">
antd-ember<LinkIcon />
<a href="https://ant-design-blazor.github.io/" target="_blank">
ant-design-blazor/ant-design-blazor
</a>
</li>
<li>
<a href="https://github.com/zzuu666/antue" target="_blank">
antue (vue)<LinkIcon />
</a>
</li>
<li>
<span class="ant-divider ant-divider-vertical" />
<a href="https://append-it.github.io/ant-design-blazor/" target="_blank">
Ant Design of Blazor<LinkIcon />
append-it/ant-design-blazor
</a>
</li>
</ul>

View File

@ -47,45 +47,36 @@ const LinksList = () => (
</li>
<li>
<a href="http://ng.ant.design" target="_blank">
NG-ZORRO - Ant Design of Angular<LinkIcon />
NG-ZORRO - Ant Design of Angular
</a>
</li>
<li>
<a href="http://ng.mobile.ant.design" target="_blank">
NG-ZORRO-MOBILE - Ant Design Mobile of Angular<LinkIcon />
NG-ZORRO-MOBILE - Ant Design Mobile of Angular
</a>
</li>
<li>
<a href="http://vue.ant.design" target="_blank">Ant Design of Vue<LinkIcon /></a>
<a href="http://vue.ant.design" target="_blank">Ant Design of Vue</a>
</li>
<li>
<a href="https://ecomfe.github.io/santd" target="_blank">
San UI Toolkit for Ant Design<LinkIcon />
</a>
</li>
<li>
<a href="https://github.com/FE-Driver/vue-beauty" target="_blank">
vue-beauty (vue)<LinkIcon />
San UI Toolkit for Ant Design
</a>
</li>
<li>
<a href="https://github.com/priornix/antizer" target="_blank">
antizer (ClojureScript)<LinkIcon />
antizer (ClojureScript)
</a>
</li>
<li>
<a href="https://github.com/idcos/antd-ember" target="_blank">
antd-ember<LinkIcon />
<a href="https://ant-design-blazor.github.io/" target="_blank">
ant-design-blazor/ant-design-blazor
</a>
</li>
<li>
<a href="https://github.com/zzuu666/antue" target="_blank">
antue (vue)<LinkIcon />
<span class="ant-divider ant-divider-vertical" />
<a href="https://append-it.github.io/ant-design-blazor/" target="_blank">
append-it/ant-design-blazor
</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

@ -1,6 +1,6 @@
{
"name": "antd",
"version": "4.1.5",
"version": "4.2.0",
"description": "An enterprise-class UI design language and React components implementation",
"keywords": [
"ant",
@ -71,7 +71,7 @@
"prettier": "prettier -c --write '**/*'",
"pretty-quick": "pretty-quick",
"pub": "antd-tools run pub",
"prepublish": "antd-tools run guard",
"prepublish": "npm run snyk-protect && antd-tools run guard",
"site": "cross-env NODE_ICU_DATA=node_modules/full-icu concurrently \"bisheng build --ssr -c ./site/bisheng.config.js\" \"node ./scripts/generateColorLess.js\"",
"sort": "npx sort-package-json",
"sort-api": "antd-tools run sort-api-table",
@ -82,7 +82,8 @@
"test-all": "./scripts/test-all.sh",
"test-node": "jest --config .jest.node.js --no-cache",
"tsc": "tsc",
"site:test": "jest --config .jest.site.js --cache=false"
"site:test": "jest --config .jest.site.js --cache=false",
"snyk-protect": "snyk protect"
},
"husky": {
"hooks": {
@ -103,7 +104,7 @@
"copy-to-clipboard": "^3.2.0",
"css-animation": "^1.5.0",
"lodash": "^4.17.13",
"moment": "^2.24.0",
"moment": "~2.24.0",
"omit.js": "^1.0.2",
"prop-types": "^15.7.2",
"raf": "^3.4.1",
@ -128,7 +129,7 @@
"rc-slider": "~9.2.3",
"rc-steps": "~3.5.0",
"rc-switch": "~1.9.0",
"rc-table": "~7.5.2",
"rc-table": "~7.5.3",
"rc-tabs": "~10.1.1",
"rc-tooltip": "~4.0.2",
"rc-tree": "~3.1.0",
@ -139,7 +140,8 @@
"rc-virtual-list": "^1.1.0",
"resize-observer-polyfill": "^1.5.1",
"scroll-into-view-if-needed": "^2.2.20",
"warning": "~4.0.3"
"warning": "~4.0.3",
"snyk": "^1.316.1"
},
"devDependencies": {
"@ant-design/bisheng-plugin": "^2.3.0",
@ -290,5 +292,6 @@
"tnpm": {
"mode": "npm"
},
"title": "Ant Design"
"title": "Ant Design",
"snyk": true
}

View File

@ -2,7 +2,7 @@
@import './colors';
.rc-footer {
z-index: 9;
z-index: 8;
&-container {
max-width: unset;

View File

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

View File

@ -79,11 +79,6 @@ 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,16 +37,6 @@ 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