chore: auto merge branches (#36782)

chore: Next merge master
This commit is contained in:
github-actions[bot] 2022-07-29 11:47:41 +00:00 committed by GitHub
commit 92fbfd1b93
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
97 changed files with 7770 additions and 2698 deletions

View File

@ -1,9 +1,14 @@
<!--
First of all, thank you for your contribution! 😄
New feature please send a pull request to feature branch, and rest to master branch.
Pull requests will be merged after one of the collaborators approve.
Please makes sure that these forms are filled before submitting your pull request, thank you!
For requesting to pull a new feature or bugfix, please send it from a feature/bugfix branch based on the `master` branch.
Before submitting your pull request, please make sure the checklist below is confirmed.
Your pull requests will be merged after one of the collaborators approve.
Thank you!
-->
[[中文版模板 / Chinese template](https://github.com/ant-design/ant-design/blob/master/.github/PULL_REQUEST_TEMPLATE/pr_cn.md)]
@ -29,7 +34,7 @@ Please makes sure that these forms are filled before submitting your pull reques
### 🔗 Related issue link
<!--
1. Describe the source of requirement, like related issue link.
1. Put the related issue or discussion links here.
-->
### 💡 Background and solution
@ -37,7 +42,7 @@ Please makes sure that these forms are filled before submitting your pull reques
<!--
1. Describe the problem and the scenario.
2. GIF or snapshot should be provided if includes UI/interactive modification.
3. How to fix the problem, and list final API implementation and usage sample if that is a new feature.
3. How to fix the problem, and list the final API implementation and usage sample if that is a new feature.
-->
### 📝 Changelog
@ -51,7 +56,7 @@ Describe changes from the user side, and list all potential break changes or oth
| 🇺🇸 English | |
| 🇨🇳 Chinese | |
### ☑️ Self Check before Merge
### ☑️ Self-Check before Merge
⚠️ Please check all items below before review. ⚠️

View File

@ -28,7 +28,6 @@ jobs:
branch: 'master'
dingding-token: ${{ secrets.DINGDING_BOT_TOKEN }}
dingding-msg: 'CHANGELOG.zh-CN.md'
dingding-delay-minute: 10
msg-title: '# Ant Design {{v}} 发布日志'
msg-poster: 'https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*zx7LTI_ECSAAAAAAAAAAAABkARQnAQ'
msg-footer: '💬 前往 [**Ant Design Releases**]({{url}}) 查看更新日志'
@ -44,6 +43,7 @@ jobs:
dingding-token: ${{ secrets.DINGDING_BOT_BIGFISH_TOKEN }}
dingding-msg: 'CHANGELOG.zh-CN.md'
dingding-delay-minute: 10
release: false
antd-conch-msg: '🐟 当前 Bigfish 内嵌 antd 版本:'
msg-title: '# Ant Design {{v}} 发布日志'
msg-poster: 'https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*zx7LTI_ECSAAAAAAAAAAAABkARQnAQ'

31
.github/workflows/size-limit.yml vendored Normal file
View File

@ -0,0 +1,31 @@
name: 📦 Compressed Size(size-limit)
on:
pull_request:
types: [opened, synchronize]
# Cancel prev CI if new commit come
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
compressed-size:
permissions:
contents: read # for actions/checkout to fetch code
pull-requests: write # for preactjs/compressed-size-action to create PR comments
runs-on: ubuntu-latest
env:
CI_JOB_NUMBER: 1
steps:
- uses: actions/checkout@v3
- uses: andresz1/size-limit-action@v1
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
pattern: "./dist/**/*.min.{js,css}"
build-script: "dist:esbuild-no-dup-check"
clean-script: "clean-lockfiles"

View File

@ -15,6 +15,45 @@ timeline: true
---
## 4.22.2
`2022-07-28`
- 💄 Adjust Collapse title click region which will be fully width when `collapsible=default` now. [#36761](https://github.com/ant-design/ant-design/pull/36761)
- Drawer
- 🐞 Fix Drawer not work in 360 browser. [#36748](https://github.com/ant-design/ant-design/pull/36748)
- 🐞 Revert back panel style into wrapper dom node in case developer use `contentWrapperStyle` for overwrite. [#36748](https://github.com/ant-design/ant-design/pull/36748)
- 🐞 Fix for the string type as `width/height` value and warning which should use number type instead. [#284](https://github.com/react-component/drawer/pull/284)
## 4.22.1
`2022-07-27`
- 🐞 Fix Drawer with percentage width display issue. [#36729](https://github.com/ant-design/ant-design/pull/36729)
## 4.22.0
`2022-07-26`
- Form
- 🔥 Form support `Form.Item.useStatus` for custom components to get Form.Item validate status. [#36486](https://github.com/ant-design/ant-design/pull/36486)
- 🆕 Form support `setFieldValue` to simplify config array index value. [#36058](https://github.com/ant-design/ant-design/pull/36058)
- 🐞 Fix Form.Item shaking when trigger validate so fast. [#36575](https://github.com/ant-design/ant-design/pull/36575)
- 🆕 Radio.Group support `onBlur` and `onFocus` props. [#36041](https://github.com/ant-design/ant-design/pull/36041)
- 🆕 Typography `ellipsis.tooltip` supports a object for Tooltip props. [#36099](https://github.com/ant-design/ant-design/pull/36099)
- 🛠 Refactor Drawer to remove directly style control which helps more React way. [#36672](https://github.com/ant-design/ant-design/pull/36672)
- 🛠 Refactor Sketelon.Button square shape style that its width is equal to height, and old become the default. [#36123](https://github.com/ant-design/ant-design/pull/36123) [@alanhaledc](https://github.com/alanhaledc)
- 🐞 Fix Modal.confirm `onCancel` argument close is not a function sometimes. [#36600](https://github.com/ant-design/ant-design/pull/36600) [@Wxh16144](https://github.com/Wxh16144)
- 🐞 Revert [#36439](https://github.com/ant-design/ant-design/pull/36439) to fix the problem of incorrect status when uploading and deleting files, and fix the status color change when Upload removes files again change problem. [#36706](https://github.com/ant-design/ant-design/pull/36706)
- Tree
- 🛠 Tree/TreeSelect `switcherIcon` argument support more parameters from `{ expanded: boolean }` to `AntTreeNodeProps`. [#36651](https://github.com/ant-design/ant-design/pull/36651) [@alanhaledc](https://github.com/alanhaledc)
- 🐞 Fix Tree `draggable` Fn params type from AntTreeNode to DataNode. [#36648](https://github.com/ant-design/ant-design/pull/36648) [@tianyuan233](https://github.com/tianyuan233)
- Table
- 💄 Fix Table extra shadow and scrollbar when all columns are fixed. [#36606](https://github.com/ant-design/ant-design/pull/36606) [@dashaowang](https://github.com/dashaowang)
- 💄 Fix Table tree data ellipsis style problem. [#36608](https://github.com/ant-design/ant-design/pull/36608)
- 🌐 Localization
- 🇱🇰 Add Sri Lanka locale. [#36149](https://github.com/ant-design/ant-design/pull/36149) [@sayuri-gi](https://github.com/sayuri-gi)
## 4.21.7
`2022-07-18`

View File

@ -15,6 +15,45 @@ timeline: true
---
## 4.22.2
`2022-07-28`
- 💄 调整 Collapse 标题文本在 `collapsible=default` 时为完整宽度点击区域。[#36761](https://github.com/ant-design/ant-design/pull/36761)
- Drawer
- 🐞 修复 Drawer 在 360 浏览器不生效的问题。[#36748](https://github.com/ant-design/ant-design/pull/36748)
- 🐞 回滚将样式恢复至包裹层以防止原本通过 `contentWrapperStyle` 覆盖样式的用法。[#36748](https://github.com/ant-design/ant-design/pull/36748)
- 🐞 修复兼容以 string 类型作为 `width/height` 的用法,并且警告用户应当使用 number 类型。[#284](https://github.com/react-component/drawer/pull/284)
## 4.22.1
`2022-07-27`
- 🐞 修复 Drawer 使用百分比宽度时的展示问题。[#36729](https://github.com/ant-design/ant-design/pull/36729)
## 4.22.0
`2022-07-26`
- Form
- 🔥 Form 新增 `Form.Item.useStatus` 用于获取 Form.Item 的校验状态。[#36486](https://github.com/ant-design/ant-design/pull/36486)
- 🆕 Form 支持 `setFieldValue` 以简化设置数字单个值的操作流程。[#36058](https://github.com/ant-design/ant-design/pull/36058)
- 🐞 修复 Form.Item 在快速切换校验状态时高度抖动的问题。[#36575](https://github.com/ant-design/ant-design/pull/36575)
- 🆕 Radio.Group 支持 `onBlur``onFocus` 属性。[#36041](https://github.com/ant-design/ant-design/pull/36041)
- 🆕 Typography `ellipsis.tooltip` 属性支持传入一个对象。[#36099](https://github.com/ant-design/ant-design/pull/36099)
- 🛠 重构 Drawer 移除直接的 dom 操作以使其更符合 React 运作方式。[#36672](https://github.com/ant-design/ant-design/pull/36672)
- 🛠 重构 Sketelon.Button square shape 样式为宽高相等,之前的 square 改为默认样式。[#36123](https://github.com/ant-design/ant-design/pull/36123) [@alanhaledc](https://github.com/alanhaledc)
- 🐞 修复 Modal.confirm 中 `onCancel(close)` 参数有时候不是 function 的问题。[#36600](https://github.com/ant-design/ant-design/pull/36600) [@Wxh16144](https://github.com/Wxh16144)
- 🐞 回滚 [#36439](https://github.com/ant-design/ant-design/pull/36439) 以修复上传和删除文件时状态不对的问题,并再次修复 Upload 移除文件时状态色会变化的问题。[#36706](https://github.com/ant-design/ant-design/pull/36706)
- Tree
- 🛠 Tree/TreeSelect `switcherIcon` 参数现在支持完整 TreeNode 属性,从 `{ expanded: boolean }` 变为 `AntTreeNodeProps`。[#36651](https://github.com/ant-design/ant-design/pull/36651) [@alanhaledc](https://github.com/alanhaledc)
- 🐞 修改 Tree `draggable` 函数的参数类型由 AntTreeNode 改为 DataNode。[#36648](https://github.com/ant-design/ant-design/pull/36648) [@tianyuan233](https://github.com/tianyuan233)
- Table
- 💄 修复 Table 固定列额外阴影和滚动条样式的问题。[#36606](https://github.com/ant-design/ant-design/pull/36606) [@dashaowang](https://github.com/dashaowang)
- 💄 修复 Table 树形数据固定列的省略样式错位的问题。[#36608](https://github.com/ant-design/ant-design/pull/36608)
- 🌐 国际化
- 🇱🇰 添加斯里兰卡语言。[#36149](https://github.com/ant-design/ant-design/pull/36149) [@sayuri-gi](https://github.com/sayuri-gi)
## 4.21.7
`2022-07-18`

View File

@ -1,4 +1,4 @@
export function isWindow(obj: any) {
export function isWindow(obj: any): obj is Window {
return obj !== null && obj !== undefined && obj === obj.window;
}
@ -12,16 +12,22 @@ export default function getScroll(
const method = top ? 'scrollTop' : 'scrollLeft';
let result = 0;
if (isWindow(target)) {
result = (target as Window)[top ? 'pageYOffset' : 'pageXOffset'];
result = target[top ? 'pageYOffset' : 'pageXOffset'];
} else if (target instanceof Document) {
result = target.documentElement[method];
} else if (target instanceof HTMLElement) {
result = target[method];
} else if (target) {
result = (target as HTMLElement)[method];
// According to the type inference, the `target` is `never` type.
// Since we configured the loose mode type checking, and supports mocking the target with such shape below::
// `{ documentElement: { scrollLeft: 200, scrollTop: 400 } }`,
// the program may falls into this branch.
// Check the corresponding tests for details. Don't sure what is the real scenario this happens.
result = target[method];
}
if (target && !isWindow(target) && typeof result !== 'number') {
result = ((target as HTMLElement).ownerDocument || (target as Document)).documentElement?.[
method
];
result = (target.ownerDocument ?? target).documentElement?.[method];
}
return result;
}

View File

@ -1619,130 +1619,9 @@ exports[`Cascader rtl render component should be rendered correctly in RTL direc
</div>
`;
exports[`Cascader should highlight keyword and filter when search in Cascader 1`] = `
<div
class="ant-select-dropdown ant-cascader-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up"
style="opacity: 0; min-width: 0;"
>
<div>
<div
class="ant-cascader-menus"
>
<ul
class="ant-cascader-menu"
role="menu"
>
<li
aria-checked="false"
class="ant-cascader-menu-item"
data-path-key="zhejiang__RC_CASCADER_SPLIT__hangzhou__RC_CASCADER_SPLIT__xihu"
role="menuitemcheckbox"
>
<div
class="ant-cascader-menu-item-content"
>
<span
class="ant-cascader-menu-item-keyword"
>
Z
</span>
hejiang / Hang
<span
class="ant-cascader-menu-item-keyword"
>
z
</span>
hou / West Lake
</div>
</li>
<li
aria-checked="false"
class="ant-cascader-menu-item"
data-path-key="jiangsu__RC_CASCADER_SPLIT__nanjing__RC_CASCADER_SPLIT__zhonghuamen"
role="menuitemcheckbox"
>
<div
class="ant-cascader-menu-item-content"
>
Jiangsu / Nanjing /
<span
class="ant-cascader-menu-item-keyword"
>
Z
</span>
hong Hua Men
</div>
</li>
</ul>
</div>
</div>
</div>
`;
exports[`Cascader should highlight keyword and filter when search in Cascader 1`] = `"<div><div class=\\"ant-cascader-menus\\"><ul class=\\"ant-cascader-menu\\" role=\\"menu\\"><li class=\\"ant-cascader-menu-item\\" role=\\"menuitemcheckbox\\" aria-checked=\\"false\\" data-path-key=\\"zhejiang__RC_CASCADER_SPLIT__hangzhou__RC_CASCADER_SPLIT__xihu\\"><div class=\\"ant-cascader-menu-item-content\\"><span class=\\"ant-cascader-menu-item-keyword\\">Z</span>hejiang / Hang<span class=\\"ant-cascader-menu-item-keyword\\">z</span>hou / West Lake</div></li><li class=\\"ant-cascader-menu-item\\" role=\\"menuitemcheckbox\\" aria-checked=\\"false\\" data-path-key=\\"jiangsu__RC_CASCADER_SPLIT__nanjing__RC_CASCADER_SPLIT__zhonghuamen\\"><div class=\\"ant-cascader-menu-item-content\\">Jiangsu / Nanjing / <span class=\\"ant-cascader-menu-item-keyword\\">Z</span>hong Hua Men</div></li></ul></div></div>"`;
exports[`Cascader should highlight keyword and filter when search in Cascader with same field name of label and value 1`] = `
<div
class="ant-select-dropdown ant-cascader-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up"
style="opacity: 0; min-width: 0;"
>
<div>
<div
class="ant-cascader-menus"
>
<ul
class="ant-cascader-menu"
role="menu"
>
<li
aria-checked="false"
class="ant-cascader-menu-item"
data-path-key="Zhejiang__RC_CASCADER_SPLIT__Hangzhou__RC_CASCADER_SPLIT__West Lake"
role="menuitemcheckbox"
>
<div
class="ant-cascader-menu-item-content"
>
<span
class="ant-cascader-menu-item-keyword"
>
Z
</span>
hejiang / Hang
<span
class="ant-cascader-menu-item-keyword"
>
z
</span>
hou / West Lake
</div>
</li>
<li
aria-checked="false"
class="ant-cascader-menu-item ant-cascader-menu-item-disabled"
data-path-key="Zhejiang__RC_CASCADER_SPLIT__Hangzhou__RC_CASCADER_SPLIT__Xia Sha"
role="menuitemcheckbox"
>
<div
class="ant-cascader-menu-item-content"
>
<span
class="ant-cascader-menu-item-keyword"
>
Z
</span>
hejiang / Hang
<span
class="ant-cascader-menu-item-keyword"
>
z
</span>
hou / Xia Sha
</div>
</li>
</ul>
</div>
</div>
</div>
`;
exports[`Cascader should highlight keyword and filter when search in Cascader with same field name of label and value 1`] = `"<div><div class=\\"ant-cascader-menus\\"><ul class=\\"ant-cascader-menu\\" role=\\"menu\\"><li class=\\"ant-cascader-menu-item\\" role=\\"menuitemcheckbox\\" aria-checked=\\"false\\" data-path-key=\\"Zhejiang__RC_CASCADER_SPLIT__Hangzhou__RC_CASCADER_SPLIT__West Lake\\"><div class=\\"ant-cascader-menu-item-content\\"><span class=\\"ant-cascader-menu-item-keyword\\">Z</span>hejiang / Hang<span class=\\"ant-cascader-menu-item-keyword\\">z</span>hou / West Lake</div></li><li class=\\"ant-cascader-menu-item ant-cascader-menu-item-disabled\\" role=\\"menuitemcheckbox\\" aria-checked=\\"false\\" data-path-key=\\"Zhejiang__RC_CASCADER_SPLIT__Hangzhou__RC_CASCADER_SPLIT__Xia Sha\\"><div class=\\"ant-cascader-menu-item-content\\"><span class=\\"ant-cascader-menu-item-keyword\\">Z</span>hejiang / Hang<span class=\\"ant-cascader-menu-item-keyword\\">z</span>hou / Xia Sha</div></li></ul></div></div>"`;
exports[`Cascader should render not found content 1`] = `
<div

View File

@ -1,5 +1,3 @@
import { mount } from 'enzyme';
import KeyCode from 'rc-util/lib/KeyCode';
import React from 'react';
import Cascader from '..';
import excludeAllWarning from '../../../tests/shared/excludeWarning';
@ -7,26 +5,26 @@ import focusTest from '../../../tests/shared/focusTest';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import ConfigProvider from '../../config-provider';
import { fireEvent, render } from '../../../tests/utils';
const { SHOW_CHILD, SHOW_PARENT } = Cascader;
function toggleOpen(wrapper) {
wrapper.find('.ant-select-selector').simulate('mousedown');
function toggleOpen(container) {
fireEvent.mouseDown(container.querySelector('.ant-select-selector'));
}
function isOpen(wrapper) {
return !!wrapper.find('Trigger').props().popupVisible;
function isOpen(container) {
return container.querySelector('.ant-cascader').className.includes('ant-select-open');
}
function getDropdown(wrapper) {
return wrapper.find('.ant-select-dropdown');
function getDropdown(container) {
return container.querySelector('.ant-select-dropdown');
}
function clickOption(wrapper, menuIndex, itemIndex, type = 'click') {
const menu = wrapper.find('ul.ant-cascader-menu').at(menuIndex);
const itemList = menu.find('li.ant-cascader-menu-item');
itemList.at(itemIndex).simulate(type);
function clickOption(container, menuIndex, itemIndex, type = 'click') {
const menu = container.querySelectorAll('ul.ant-cascader-menu')[menuIndex];
const itemList = menu.querySelectorAll('li.ant-cascader-menu-item');
fireEvent[type](itemList[itemIndex]);
}
const options = [
@ -76,80 +74,90 @@ describe('Cascader', () => {
rtlTest(Cascader);
it('popup correctly when panel is hidden', () => {
const wrapper = mount(<Cascader options={options} />);
expect(isOpen(wrapper)).toBeFalsy();
const { container } = render(<Cascader options={options} />);
expect(isOpen(container)).toBeFalsy();
});
it('popup correctly when panel is open', () => {
const onPopupVisibleChange = jest.fn();
const wrapper = mount(
const { container } = render(
<Cascader options={options} onPopupVisibleChange={onPopupVisibleChange} />,
);
toggleOpen(wrapper);
expect(isOpen(wrapper)).toBeTruthy();
toggleOpen(container);
expect(isOpen(container)).toBeTruthy();
expect(onPopupVisibleChange).toHaveBeenCalledWith(true);
});
it('support controlled mode', () => {
const wrapper = mount(<Cascader options={options} />);
wrapper.setProps({
value: ['zhejiang', 'hangzhou', 'xihu'],
});
expect(wrapper.render()).toMatchSnapshot();
const { rerender, asFragment } = render(<Cascader options={options} />);
rerender(<Cascader options={options} value={['zhejiang', 'hangzhou', 'xihu']} />);
expect(asFragment().firstChild).toMatchSnapshot();
});
it('popup correctly with defaultValue', () => {
const wrapper = mount(<Cascader options={options} defaultValue={['zhejiang', 'hangzhou']} />);
toggleOpen(wrapper);
expect(getDropdown(wrapper).render()).toMatchSnapshot();
const { container } = render(
<Cascader options={options} defaultValue={['zhejiang', 'hangzhou']} />,
);
toggleOpen(container);
expect(getDropdown(container)).toMatchSnapshot();
});
it('should support popupVisible', () => {
const wrapper = mount(<Cascader options={options} defaultValue={['zhejiang', 'hangzhou']} />);
expect(isOpen(wrapper)).toBeFalsy();
wrapper.setProps({ popupVisible: true });
expect(isOpen(wrapper)).toBeTruthy();
const { container, rerender } = render(
<Cascader options={options} defaultValue={['zhejiang', 'hangzhou']} />,
);
expect(isOpen(container)).toBeFalsy();
rerender(<Cascader options={options} defaultValue={['zhejiang', 'hangzhou']} popupVisible />);
expect(isOpen(container)).toBeTruthy();
});
it('can be selected', () => {
const onChange = jest.fn();
const wrapper = mount(<Cascader options={options} onChange={onChange} />);
const { container } = render(<Cascader options={options} onChange={onChange} />);
toggleOpen(wrapper);
expect(isOpen(wrapper)).toBeTruthy();
toggleOpen(container);
expect(isOpen(container)).toBeTruthy();
clickOption(wrapper, 0, 0);
expect(getDropdown(wrapper).render()).toMatchSnapshot();
clickOption(container, 0, 0);
expect(getDropdown(container)).toMatchSnapshot();
clickOption(wrapper, 1, 0);
expect(getDropdown(wrapper).render()).toMatchSnapshot();
clickOption(container, 1, 0);
expect(getDropdown(container)).toMatchSnapshot();
clickOption(wrapper, 2, 0);
expect(getDropdown(wrapper).render()).toMatchSnapshot();
clickOption(container, 2, 0);
expect(getDropdown(container)).toMatchSnapshot();
expect(onChange).toHaveBeenCalledTimes(1);
expect(onChange).toHaveBeenCalledWith(['zhejiang', 'hangzhou', 'xihu'], expect.anything());
});
it('backspace should work with `Cascader[showSearch]`', () => {
const wrapper = mount(<Cascader options={options} showSearch />);
wrapper.find('input').simulate('change', { target: { value: '123' } });
expect(isOpen(wrapper)).toBeTruthy();
const { container } = render(<Cascader options={options} showSearch />);
fireEvent.change(container.querySelector('input'), { target: { value: '123' } });
expect(isOpen(container)).toBeTruthy();
wrapper.find('input').simulate('keydown', { which: KeyCode.BACKSPACE });
expect(isOpen(wrapper)).toBeTruthy();
fireEvent.keyDown(container.querySelector('input'), {
key: 'Backspace',
keyCode: 8,
});
expect(isOpen(container)).toBeTruthy();
wrapper.find('input').simulate('change', { target: { value: '' } });
expect(isOpen(wrapper)).toBeTruthy();
fireEvent.change(container.querySelector('input'), { target: { value: '' } });
expect(isOpen(container)).toBeTruthy();
wrapper.find('input').simulate('keydown', { which: KeyCode.BACKSPACE });
expect(isOpen(wrapper)).toBeFalsy();
fireEvent.keyDown(container.querySelector('input'), {
key: 'Backspace',
keyCode: 8,
});
expect(isOpen(container)).toBeFalsy();
});
it('should highlight keyword and filter when search in Cascader', () => {
const wrapper = mount(<Cascader options={options} showSearch={{ filter }} />);
wrapper.find('input').simulate('change', { target: { value: 'z' } });
expect(getDropdown(wrapper).render()).toMatchSnapshot();
const { container } = render(<Cascader options={options} showSearch={{ filter }} />);
fireEvent.change(container.querySelector('input'), { target: { value: 'z' } });
// React 18 with testing lib will have additional space. We have to compare innerHTML. Sad.
expect(getDropdown(container).innerHTML).toMatchSnapshot();
});
it('should highlight keyword and filter when search in Cascader with same field name of label and value', () => {
@ -175,58 +183,67 @@ describe('Cascader', () => {
function customFilter(inputValue, path) {
return path.some(option => option.name.toLowerCase().indexOf(inputValue.toLowerCase()) > -1);
}
const wrapper = mount(
const { container } = render(
<Cascader
options={customOptions}
fieldNames={{ label: 'name', value: 'name' }}
showSearch={{ filter: customFilter }}
/>,
);
wrapper.find('input').simulate('change', { target: { value: 'z' } });
expect(getDropdown(wrapper).render()).toMatchSnapshot();
fireEvent.change(container.querySelector('input'), { target: { value: 'z' } });
// React 18 with testing lib will have additional space. We have to compare innerHTML. Sad.
expect(getDropdown(container).innerHTML).toMatchSnapshot();
});
it('should render not found content', () => {
const wrapper = mount(<Cascader options={options} showSearch={{ filter }} />);
wrapper.find('input').simulate('change', { target: { value: '__notfoundkeyword__' } });
expect(getDropdown(wrapper).render()).toMatchSnapshot();
const { container } = render(<Cascader options={options} showSearch={{ filter }} />);
fireEvent.change(container.querySelector('input'), {
target: { value: '__notfoundkeyword__' },
});
expect(getDropdown(container)).toMatchSnapshot();
});
it('should support to clear selection', () => {
const wrapper = mount(<Cascader options={options} defaultValue={['zhejiang', 'hangzhou']} />);
expect(wrapper.find('.ant-select-selection-item').text()).toEqual('Zhejiang / Hangzhou');
wrapper.find('.ant-select-clear').at(0).simulate('mouseDown');
expect(wrapper.exists('.ant-select-selection-item')).toBeFalsy();
const { container } = render(
<Cascader options={options} defaultValue={['zhejiang', 'hangzhou']} />,
);
expect(container.querySelector('.ant-select-selection-item').textContent).toEqual(
'Zhejiang / Hangzhou',
);
fireEvent.mouseDown(container.querySelector('.ant-select-clear'));
expect(container.querySelector('.ant-select-selection-item')).toBeFalsy();
});
it('should clear search input when clear selection', () => {
const wrapper = mount(
const { container } = render(
<Cascader options={options} defaultValue={['zhejiang', 'hangzhou']} showSearch />,
);
wrapper.find('input').simulate('change', { target: { value: 'xxx' } });
wrapper.find('.ant-select-clear').at(0).simulate('mouseDown');
expect(wrapper.find('input').props().value).toEqual('');
fireEvent.change(container.querySelector('input'), { target: { value: 'xxx' } });
fireEvent.mouseDown(container.querySelector('.ant-select-clear'));
expect(container.querySelector('input').value).toEqual('');
});
it('should change filtered item when options are changed', () => {
const wrapper = mount(<Cascader options={options} showSearch={{ filter }} />);
wrapper.find('input').simulate('change', { target: { value: 'a' } });
expect(wrapper.find('.ant-cascader-menu-item').length).toBe(2);
wrapper.setProps({ options: [options[0]] });
expect(wrapper.find('.ant-cascader-menu-item').length).toBe(1);
const { container, rerender } = render(<Cascader options={options} showSearch={{ filter }} />);
fireEvent.change(container.querySelector('input'), { target: { value: 'a' } });
expect(container.querySelectorAll('.ant-cascader-menu-item').length).toBe(2);
rerender(<Cascader options={[options[0]]} showSearch={{ filter }} />);
expect(container.querySelectorAll('.ant-cascader-menu-item').length).toBe(1);
});
it('should select item immediately when searching and pressing down arrow key', () => {
const wrapper = mount(<Cascader options={options} showSearch={{ filter }} />);
wrapper.find('input').simulate('change', { target: { value: 'a' } });
expect(wrapper.find('.ant-cascader-menu-item').length).toBe(2);
expect(wrapper.find('.ant-cascader-menu-item-active').length).toBe(0);
const { container } = render(<Cascader options={options} showSearch={{ filter }} />);
fireEvent.change(container.querySelector('input'), { target: { value: 'a' } });
wrapper.find('input').simulate('keyDown', {
which: KeyCode.DOWN,
expect(container.querySelectorAll('.ant-cascader-menu-item').length).toBe(2);
expect(container.querySelectorAll('.ant-cascader-menu-item-active').length).toBe(0);
fireEvent.keyDown(container.querySelector('input'), {
key: 'Down',
keyCode: 40,
});
expect(wrapper.find('.ant-cascader-menu-item-active').length).toBe(1);
expect(container.querySelectorAll('.ant-cascader-menu-item-active').length).toBe(1);
});
it('can use fieldNames', () => {
@ -267,7 +284,7 @@ describe('Cascader', () => {
const onChange = jest.fn();
const wrapper = mount(
const { container } = render(
<Cascader
options={customerOptions}
onChange={onChange}
@ -280,10 +297,10 @@ describe('Cascader', () => {
/>,
);
clickOption(wrapper, 0, 0);
clickOption(wrapper, 1, 0);
clickOption(wrapper, 2, 0);
expect(wrapper.find('.ant-select-selection-item').text()).toEqual(
clickOption(container, 0, 0);
clickOption(container, 1, 0);
clickOption(container, 2, 0);
expect(container.querySelector('.ant-select-selection-item').textContent).toEqual(
'Zhejiang / Hangzhou / West Lake',
);
expect(onChange).toHaveBeenCalledWith(['zhejiang', 'hangzhou', 'xihu'], expect.anything());
@ -291,14 +308,14 @@ describe('Cascader', () => {
it('should show not found content when options.length is 0', () => {
const customerOptions = [];
const wrapper = mount(<Cascader options={customerOptions} />);
toggleOpen(wrapper);
expect(getDropdown(wrapper).render()).toMatchSnapshot();
const { container } = render(<Cascader options={customerOptions} />);
toggleOpen(container);
expect(getDropdown(container)).toMatchSnapshot();
});
it('not found content should be disabled', () => {
const wrapper = mount(<Cascader options={[]} open />);
expect(wrapper.find('.ant-cascader-menu-item-disabled').length).toBe(1);
const { container } = render(<Cascader options={[]} open />);
expect(container.querySelectorAll('.ant-cascader-menu-item-disabled').length).toBe(1);
});
describe('limit filtered item count', () => {
@ -309,22 +326,28 @@ describe('Cascader', () => {
});
it('limit with positive number', () => {
const wrapper = mount(<Cascader options={options} showSearch={{ filter, limit: 1 }} />);
wrapper.find('input').simulate('change', { target: { value: 'a' } });
expect(wrapper.find('.ant-cascader-menu-item')).toHaveLength(1);
const { container } = render(
<Cascader options={options} showSearch={{ filter, limit: 1 }} />,
);
fireEvent.change(container.querySelector('input'), { target: { value: 'a' } });
expect(container.querySelectorAll('.ant-cascader-menu-item')).toHaveLength(1);
});
it('not limit', () => {
const wrapper = mount(<Cascader options={options} showSearch={{ filter, limit: false }} />);
wrapper.find('input').simulate('change', { target: { value: 'a' } });
expect(wrapper.find('.ant-cascader-menu-item')).toHaveLength(2);
const { container } = render(
<Cascader options={options} showSearch={{ filter, limit: false }} />,
);
fireEvent.change(container.querySelector('input'), { target: { value: 'a' } });
expect(container.querySelectorAll('.ant-cascader-menu-item')).toHaveLength(2);
});
it('negative limit', () => {
const wrapper = mount(<Cascader options={options} showSearch={{ filter, limit: -1 }} />);
wrapper.find('input').simulate('click');
wrapper.find('input').simulate('change', { target: { value: 'a' } });
expect(wrapper.find('.ant-cascader-menu-item')).toHaveLength(2);
const { container } = render(
<Cascader options={options} showSearch={{ filter, limit: -1 }} />,
);
fireEvent.click(container.querySelector('input'));
fireEvent.change(container.querySelector('input'), { target: { value: 'a' } });
expect(container.querySelectorAll('.ant-cascader-menu-item')).toHaveLength(2);
});
});
@ -332,7 +355,7 @@ describe('Cascader', () => {
// eslint-disable-next-line jest/no-disabled-tests
it.skip('should warning if not find `value` in `options`', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
mount(<Cascader options={[{ label: 'a', value: 'a', children: [{ label: 'b' }] }]} />);
render(<Cascader options={[{ label: 'a', value: 'a', children: [{ label: 'b' }] }]} />);
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: Cascader] Not found `value` in `options`.',
);
@ -355,22 +378,22 @@ describe('Cascader', () => {
},
];
expect(() => {
mount(<Cascader options={optionsWithChildrenNull} />);
render(<Cascader options={optionsWithChildrenNull} />);
}).not.toThrow();
});
it('placeholder works correctly', () => {
const wrapper = mount(<Cascader options={[]} />);
expect(wrapper.find('.ant-select-selection-placeholder').text()).toEqual('');
const { container, rerender } = render(<Cascader options={[]} />);
expect(container.querySelector('.ant-select-selection-placeholder').textContent).toEqual('');
const customPlaceholder = 'Custom placeholder';
wrapper.setProps({
placeholder: customPlaceholder,
});
expect(wrapper.find('.ant-select-selection-placeholder').text()).toEqual(customPlaceholder);
rerender(<Cascader options={[]} placeholder={customPlaceholder} />);
expect(container.querySelector('.ant-select-selection-placeholder').textContent).toEqual(
customPlaceholder,
);
});
it('placement work correctly', () => {
it('placement work correctly', async () => {
const customerOptions = [
{
value: 'zhejiang',
@ -383,17 +406,20 @@ describe('Cascader', () => {
],
},
];
const wrapper = mount(<Cascader options={customerOptions} placement="topRight" />);
expect(wrapper.find('Trigger').prop('popupPlacement')).toEqual('topRight');
const { container } = render(<Cascader options={customerOptions} placement="topRight" />);
toggleOpen(container);
// Inject in tests/__mocks__/rc-trigger.js
expect(global.triggerProps.popupPlacement).toEqual('topRight');
});
it('popup correctly with defaultValue RTL', () => {
const wrapper = mount(
const { asFragment } = render(
<ConfigProvider direction="rtl">
<Cascader options={options} defaultValue={['zhejiang', 'hangzhou']} open />
</ConfigProvider>,
);
expect(wrapper.render()).toMatchSnapshot();
expect(asFragment().firstChild).toMatchSnapshot();
});
it('can be selected in RTL direction', () => {
@ -432,7 +458,7 @@ describe('Cascader', () => {
},
];
const onChange = jest.fn();
const wrapper = mount(
const { container } = render(
<ConfigProvider direction="rtl">
<Cascader
options={options2}
@ -443,72 +469,84 @@ describe('Cascader', () => {
</ConfigProvider>,
);
toggleOpen(wrapper);
clickOption(wrapper, 0, 0);
expect(getDropdown(wrapper).render()).toMatchSnapshot();
toggleOpen(container);
clickOption(container, 0, 0);
expect(getDropdown(container)).toMatchSnapshot();
toggleOpen(wrapper);
clickOption(wrapper, 1, 0);
expect(getDropdown(wrapper).render()).toMatchSnapshot();
toggleOpen(container);
clickOption(container, 1, 0);
expect(getDropdown(container)).toMatchSnapshot();
toggleOpen(wrapper);
clickOption(wrapper, 2, 0);
expect(getDropdown(wrapper).render()).toMatchSnapshot();
toggleOpen(container);
clickOption(container, 2, 0);
expect(getDropdown(container)).toMatchSnapshot();
expect(onChange).toHaveBeenCalledTimes(1);
expect(onChange).toHaveBeenCalledWith(['zhejiang', 'hangzhou', 'xihu'], expect.anything());
});
it('defaultValue works correctly when no match options', () => {
const wrapper = mount(<Cascader options={options} defaultValue={['options1', 'options2']} />);
expect(wrapper.find('.ant-select-selection-item').text()).toEqual('options1 / options2');
const { container } = render(
<Cascader options={options} defaultValue={['options1', 'options2']} />,
);
expect(container.querySelector('.ant-select-selection-item').textContent).toEqual(
'options1 / options2',
);
});
it('can be selected when showSearch', () => {
const onChange = jest.fn();
const wrapper = mount(<Cascader options={options} onChange={onChange} showSearch />);
wrapper.find('input').simulate('change', { target: { value: 'Zh' } });
expect(wrapper.find('.ant-cascader-menu').length).toBe(1);
clickOption(wrapper, 0, 0);
const { container } = render(<Cascader options={options} onChange={onChange} showSearch />);
fireEvent.change(container.querySelector('input'), { target: { value: 'Zh' } });
expect(container.querySelectorAll('.ant-cascader-menu').length).toBe(1);
clickOption(container, 0, 0);
expect(onChange).toHaveBeenCalledWith(['zhejiang', 'hangzhou', 'xihu'], expect.anything());
});
it('options should open after press esc and then search', () => {
const wrapper = mount(<Cascader options={options} showSearch />);
wrapper.find('input').simulate('change', { target: { value: 'jin' } });
expect(isOpen(wrapper)).toBeTruthy();
wrapper.find('input').simulate('keydown', { which: KeyCode.ESC });
expect(isOpen(wrapper)).toBeFalsy();
wrapper.find('input').simulate('change', { target: { value: 'jin' } });
expect(isOpen(wrapper)).toBeTruthy();
const { container } = render(<Cascader options={options} showSearch />);
fireEvent.change(container.querySelector('input'), { target: { value: 'jin' } });
expect(isOpen(container)).toBeTruthy();
fireEvent.keyDown(container.querySelector('input'), {
key: 'Esc',
keyCode: 27,
});
expect(isOpen(container)).toBeFalsy();
fireEvent.change(container.querySelector('input'), { target: { value: 'jin' } });
expect(isOpen(container)).toBeTruthy();
});
it('onChange works correctly when the label of fieldNames is the same as value', () => {
const onChange = jest.fn();
const sameNames = { label: 'label', value: 'label' };
const wrapper = mount(
const { container } = render(
<Cascader options={options} onChange={onChange} showSearch fieldNames={sameNames} />,
);
wrapper.find('input').simulate('change', { target: { value: 'est' } });
clickOption(wrapper, 0, 0);
fireEvent.change(container.querySelector('input'), { target: { value: 'est' } });
clickOption(container, 0, 0);
expect(onChange).toHaveBeenCalledWith(['Zhejiang', 'Hangzhou', 'West Lake'], expect.anything());
});
it('rtl should work well with placement', () => {
const wrapper = mount(<Cascader options={options} direction="rtl" />);
const { container } = render(<Cascader options={options} direction="rtl" />);
toggleOpen(container);
expect(wrapper.find('Trigger').prop('popupPlacement')).toEqual('bottomRight');
// Inject in tests/__mocks__/rc-trigger.js
expect(global.triggerProps.popupPlacement).toEqual('bottomRight');
});
describe('legacy props', () => {
it('popupClassName', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const wrapper = mount(
const { container } = render(
<Cascader open popupPlacement="bottomLeft" popupClassName="mock-cls" />,
);
expect(wrapper.exists('.mock-cls')).toBeTruthy();
expect(wrapper.find('Trigger').prop('popupPlacement')).toEqual('bottomLeft');
expect(container.querySelector('.mock-cls')).toBeTruthy();
// Inject in tests/__mocks__/rc-trigger.js
expect(global.triggerProps.popupPlacement).toEqual('bottomLeft');
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: Cascader] `popupClassName` is deprecated. Please use `dropdownClassName` instead.',
@ -562,7 +600,7 @@ describe('Cascader', () => {
selectedValue = value;
};
const wrapper = mount(
const { container, asFragment } = render(
<Cascader
options={multipleOptions}
onChange={onChange}
@ -570,13 +608,13 @@ describe('Cascader', () => {
showCheckedStrategy={SHOW_CHILD}
/>,
);
toggleOpen(wrapper);
expect(wrapper.render()).toMatchSnapshot();
toggleOpen(container);
expect(asFragment().firstChild).toMatchSnapshot();
clickOption(wrapper, 0, 0);
clickOption(wrapper, 1, 0);
clickOption(wrapper, 2, 0);
clickOption(wrapper, 2, 1);
clickOption(container, 0, 0);
clickOption(container, 1, 0);
clickOption(container, 2, 0);
clickOption(container, 2, 1);
expect(selectedValue[0].join(',')).toBe('zhejiang,hangzhou,xihu');
expect(selectedValue[1].join(',')).toBe('zhejiang,hangzhou,donghu');
expect(selectedValue.join(',')).toBe('zhejiang,hangzhou,xihu,zhejiang,hangzhou,donghu');
@ -627,7 +665,7 @@ describe('Cascader', () => {
selectedValue = value;
};
const wrapper = mount(
const { container, asFragment } = render(
<Cascader
options={multipleOptions}
onChange={onChange}
@ -635,12 +673,12 @@ describe('Cascader', () => {
showCheckedStrategy={SHOW_PARENT}
/>,
);
toggleOpen(wrapper);
expect(wrapper.render()).toMatchSnapshot();
clickOption(wrapper, 0, 0);
clickOption(wrapper, 1, 0);
clickOption(wrapper, 2, 0);
clickOption(wrapper, 2, 1);
toggleOpen(container);
expect(asFragment().firstChild).toMatchSnapshot();
clickOption(container, 0, 0);
clickOption(container, 1, 0);
clickOption(container, 2, 0);
clickOption(container, 2, 1);
expect(selectedValue.length).toBe(1);
expect(selectedValue.join(',')).toBe('zhejiang');

View File

@ -1,7 +1,7 @@
import { mount } from 'enzyme';
import * as React from 'react';
import type { BaseOptionType } from '..';
import Cascader from '..';
import { render } from '../../../tests/utils';
describe('Cascader.typescript', () => {
it('options value', () => {
@ -46,8 +46,10 @@ describe('Cascader.typescript', () => {
});
it('suffixIcon', () => {
const wrapper = mount(<Cascader suffixIcon={<span />} />);
expect(wrapper).toBeTruthy();
const { container } = render(<Cascader suffixIcon={<span />} />);
expect(
container.querySelector('.ant-select-arrow')?.querySelector('span')?.className,
).toBeFalsy();
});
it('Generic', () => {
@ -57,7 +59,7 @@ describe('Cascader.typescript', () => {
customizeChildren?: MyOptionData[];
}
const wrapper = mount(
const { container } = render(
<Cascader<MyOptionData>
options={[
{
@ -73,20 +75,20 @@ describe('Cascader.typescript', () => {
]}
/>,
);
expect(wrapper).toBeTruthy();
expect(container).toBeTruthy();
});
it('single onChange', () => {
const wrapper = mount(
const { container } = render(
<Cascader multiple={false} onChange={(values: (string | number)[]) => values} />,
);
expect(wrapper).toBeTruthy();
expect(container).toBeTruthy();
});
it('multiple onChange', () => {
const wrapper = mount(
const { container } = render(
<Cascader multiple onChange={(values: (string | number)[][]) => values} />,
);
expect(wrapper).toBeTruthy();
expect(container).toBeTruthy();
});
});

View File

@ -1,7 +1,6 @@
import { mount } from 'enzyme';
import React from 'react';
import { act } from 'react-dom/test-utils';
import { render, sleep } from '../../../tests/utils';
import { sleep, render, fireEvent } from '../../../tests/utils';
import { resetWarned } from '../../_util/warning';
describe('Collapse', () => {
@ -10,6 +9,15 @@ describe('Collapse', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
// fix React concurrent
function triggerAllTimer() {
for (let i = 0; i < 10; i += 1) {
act(() => {
jest.runAllTimers();
});
}
}
beforeEach(() => {
resetWarned();
});
@ -23,16 +31,16 @@ describe('Collapse', () => {
});
it('should support remove expandIcon', () => {
const wrapper = mount(
const { asFragment } = render(
<Collapse expandIcon={() => null}>
<Collapse.Panel header="header" />
</Collapse>,
);
expect(wrapper.render()).toMatchSnapshot();
expect(asFragment().firstChild).toMatchSnapshot();
});
it('should keep the className of the expandIcon', () => {
const wrapper = mount(
const { container } = render(
<Collapse
expandIcon={() => (
<button type="button" className="custom-expandicon-classname">
@ -44,49 +52,51 @@ describe('Collapse', () => {
</Collapse>,
);
expect(wrapper.find('.custom-expandicon-classname').exists()).toBe(true);
expect(container.querySelectorAll('.custom-expandicon-classname').length).toBe(1);
});
it('should render extra node of panel', () => {
const wrapper = mount(
const { asFragment } = render(
<Collapse>
<Collapse.Panel header="header" extra={<button type="button">action</button>} />
<Collapse.Panel header="header" extra={<button type="button">action</button>} />
</Collapse>,
);
expect(wrapper.render()).toMatchSnapshot();
expect(asFragment().firstChild).toMatchSnapshot();
});
it('could be expand and collapse', async () => {
const wrapper = mount(
const { container } = render(
<Collapse>
<Collapse.Panel header="This is panel header 1" key="1">
content
</Collapse.Panel>
</Collapse>,
);
expect(wrapper.find('.ant-collapse-item').hasClass('ant-collapse-item-active')).toBe(false);
wrapper.find('.ant-collapse-header').at(0).simulate('click');
wrapper.update();
expect(
container.querySelector('.ant-collapse-item')?.classList.contains('ant-collapse-item-active'),
).toBe(false);
fireEvent.click(container.querySelector('.ant-collapse-header')!);
await sleep(400);
wrapper.update();
expect(wrapper.find('.ant-collapse-item').hasClass('ant-collapse-item-active')).toBe(true);
expect(
container.querySelector('.ant-collapse-item')?.classList.contains('ant-collapse-item-active'),
).toBe(true);
});
it('could override default openMotion', () => {
const wrapper = mount(
const { container, asFragment } = render(
<Collapse openMotion={{}}>
<Collapse.Panel header="This is panel header 1" key="1">
content
</Collapse.Panel>
</Collapse>,
);
wrapper.find('.ant-collapse-header').at(0).simulate('click');
expect(wrapper.render()).toMatchSnapshot();
fireEvent.click(container.querySelector('.ant-collapse-header')!);
expect(asFragment().firstChild).toMatchSnapshot();
});
it('should trigger warning and keep compatibility when using disabled in Panel', () => {
const wrapper = mount(
const { container } = render(
<Collapse>
<Collapse.Panel disabled header="This is panel header 1" key="1">
content
@ -98,19 +108,19 @@ describe('Collapse', () => {
'Warning: [antd: Collapse.Panel] `disabled` is deprecated. Please use `collapsible="disabled"` instead.',
);
expect(wrapper.find('.ant-collapse-item-disabled').length).toBe(1);
expect(container.querySelectorAll('.ant-collapse-item-disabled').length).toBe(1);
wrapper.find('.ant-collapse-header').simulate('click');
expect(wrapper.find('.ant-collapse-item-active').length).toBe(0);
fireEvent.click(container.querySelector('.ant-collapse-header')!);
expect(container.querySelectorAll('.ant-collapse-item-active').length).toBe(0);
});
it('should end motion when set activeKey while hiding', async () => {
jest.useFakeTimers();
jest.spyOn(window, 'requestAnimationFrame').mockImplementation(cb => {
setTimeout(cb, 16.66);
});
const spiedRAF = jest
.spyOn(window, 'requestAnimationFrame')
.mockImplementation(cb => setTimeout(cb, 16.66));
let setActiveKeyOuter;
let setActiveKeyOuter: React.Dispatch<React.SetStateAction<React.Key | undefined>>;
const Test = () => {
const [activeKey, setActiveKey] = React.useState();
setActiveKeyOuter = setActiveKey;
@ -125,17 +135,18 @@ describe('Collapse', () => {
);
};
const wrapper = mount(<Test />);
const { container } = render(<Test />);
await act(async () => {
setActiveKeyOuter('1');
await Promise.resolve();
jest.runAllTimers();
});
expect(wrapper.render().find('.ant-motion-collapse').length).toBe(0);
triggerAllTimer();
window.requestAnimationFrame.mockRestore();
expect(container.querySelectorAll('.ant-motion-collapse').length).toBe(0);
spiedRAF.mockRestore();
jest.useRealTimers();
});

View File

@ -69,6 +69,10 @@ export const genBaseStyle: GenerateStyle<CollapseToken> = token => {
cursor: 'pointer',
transition: `all ${motionDurationSlow}, visibility 0s`,
[`> ${componentCls}-header-text`]: {
flex: 'auto',
},
'&:focus': {
outline: 'none',
},
@ -100,6 +104,7 @@ export const genBaseStyle: GenerateStyle<CollapseToken> = token => {
cursor: 'default',
[`${componentCls}-header-text`]: {
flex: 'none',
cursor: 'pointer',
},
},

View File

@ -14130,449 +14130,519 @@ exports[`ConfigProvider components Divider prefixCls 1`] = `
exports[`ConfigProvider components Drawer configProvider 1`] = `
<div
class=""
class="config-drawer config-drawer-right config-drawer-open config-drawer-inline"
tabindex="-1"
>
<div
class="config-drawer config-drawer-right"
tabindex="-1"
class="config-drawer-mask"
/>
<div
aria-hidden="true"
data-sentinel="start"
style="width:0;height:0;overflow:hidden;outline:none;position:absolute"
tabindex="0"
/>
<div
class="config-drawer-content-wrapper"
style="width:378px"
>
<div
class="config-drawer-mask"
/>
<div
class="config-drawer-content-wrapper"
style="transform:translateX(100%);-ms-transform:translateX(100%);width:378px"
aria-modal="true"
class="config-drawer-content"
role="dialog"
>
<div
class="config-drawer-content"
class="config-drawer-wrapper-body"
>
<div
class="config-drawer-wrapper-body"
class="config-drawer-header config-drawer-header-close-only"
>
<div
class="config-drawer-header config-drawer-header-close-only"
class="config-drawer-header-title"
>
<div
class="config-drawer-header-title"
<button
aria-label="Close"
class="config-drawer-close"
type="button"
>
<button
aria-label="Close"
class="config-drawer-close"
type="button"
<span
aria-label="close"
class="anticon anticon-close"
role="img"
>
<span
aria-label="close"
class="anticon anticon-close"
role="img"
<svg
aria-hidden="true"
data-icon="close"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<svg
aria-hidden="true"
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>
<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="config-drawer-body"
/>
</div>
<div
class="config-drawer-body"
/>
</div>
</div>
</div>
<div
aria-hidden="true"
data-sentinel="end"
style="width:0;height:0;overflow:hidden;outline:none;position:absolute"
tabindex="0"
/>
</div>
`;
exports[`ConfigProvider components Drawer configProvider componentDisabled 1`] = `
<div
class=""
class="config-drawer config-drawer-right config-drawer-open config-drawer-inline"
tabindex="-1"
>
<div
class="config-drawer config-drawer-right"
tabindex="-1"
class="config-drawer-mask"
/>
<div
aria-hidden="true"
data-sentinel="start"
style="width:0;height:0;overflow:hidden;outline:none;position:absolute"
tabindex="0"
/>
<div
class="config-drawer-content-wrapper"
style="width:378px"
>
<div
class="config-drawer-mask"
/>
<div
class="config-drawer-content-wrapper"
style="transform:translateX(100%);-ms-transform:translateX(100%);width:378px"
aria-modal="true"
class="config-drawer-content"
role="dialog"
>
<div
class="config-drawer-content"
class="config-drawer-wrapper-body"
>
<div
class="config-drawer-wrapper-body"
class="config-drawer-header config-drawer-header-close-only"
>
<div
class="config-drawer-header config-drawer-header-close-only"
class="config-drawer-header-title"
>
<div
class="config-drawer-header-title"
<button
aria-label="Close"
class="config-drawer-close"
type="button"
>
<button
aria-label="Close"
class="config-drawer-close"
type="button"
<span
aria-label="close"
class="anticon anticon-close"
role="img"
>
<span
aria-label="close"
class="anticon anticon-close"
role="img"
<svg
aria-hidden="true"
data-icon="close"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<svg
aria-hidden="true"
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>
<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="config-drawer-body"
/>
</div>
<div
class="config-drawer-body"
/>
</div>
</div>
</div>
<div
aria-hidden="true"
data-sentinel="end"
style="width:0;height:0;overflow:hidden;outline:none;position:absolute"
tabindex="0"
/>
</div>
`;
exports[`ConfigProvider components Drawer configProvider componentSize large 1`] = `
<div
class=""
class="config-drawer config-drawer-right config-drawer-open config-drawer-inline"
tabindex="-1"
>
<div
class="config-drawer config-drawer-right"
tabindex="-1"
class="config-drawer-mask"
/>
<div
aria-hidden="true"
data-sentinel="start"
style="width:0;height:0;overflow:hidden;outline:none;position:absolute"
tabindex="0"
/>
<div
class="config-drawer-content-wrapper"
style="width:378px"
>
<div
class="config-drawer-mask"
/>
<div
class="config-drawer-content-wrapper"
style="transform:translateX(100%);-ms-transform:translateX(100%);width:378px"
aria-modal="true"
class="config-drawer-content"
role="dialog"
>
<div
class="config-drawer-content"
class="config-drawer-wrapper-body"
>
<div
class="config-drawer-wrapper-body"
class="config-drawer-header config-drawer-header-close-only"
>
<div
class="config-drawer-header config-drawer-header-close-only"
class="config-drawer-header-title"
>
<div
class="config-drawer-header-title"
<button
aria-label="Close"
class="config-drawer-close"
type="button"
>
<button
aria-label="Close"
class="config-drawer-close"
type="button"
<span
aria-label="close"
class="anticon anticon-close"
role="img"
>
<span
aria-label="close"
class="anticon anticon-close"
role="img"
<svg
aria-hidden="true"
data-icon="close"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<svg
aria-hidden="true"
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>
<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="config-drawer-body"
/>
</div>
<div
class="config-drawer-body"
/>
</div>
</div>
</div>
<div
aria-hidden="true"
data-sentinel="end"
style="width:0;height:0;overflow:hidden;outline:none;position:absolute"
tabindex="0"
/>
</div>
`;
exports[`ConfigProvider components Drawer configProvider componentSize middle 1`] = `
<div
class=""
class="config-drawer config-drawer-right config-drawer-open config-drawer-inline"
tabindex="-1"
>
<div
class="config-drawer config-drawer-right"
tabindex="-1"
class="config-drawer-mask"
/>
<div
aria-hidden="true"
data-sentinel="start"
style="width:0;height:0;overflow:hidden;outline:none;position:absolute"
tabindex="0"
/>
<div
class="config-drawer-content-wrapper"
style="width:378px"
>
<div
class="config-drawer-mask"
/>
<div
class="config-drawer-content-wrapper"
style="transform:translateX(100%);-ms-transform:translateX(100%);width:378px"
aria-modal="true"
class="config-drawer-content"
role="dialog"
>
<div
class="config-drawer-content"
class="config-drawer-wrapper-body"
>
<div
class="config-drawer-wrapper-body"
class="config-drawer-header config-drawer-header-close-only"
>
<div
class="config-drawer-header config-drawer-header-close-only"
class="config-drawer-header-title"
>
<div
class="config-drawer-header-title"
<button
aria-label="Close"
class="config-drawer-close"
type="button"
>
<button
aria-label="Close"
class="config-drawer-close"
type="button"
<span
aria-label="close"
class="anticon anticon-close"
role="img"
>
<span
aria-label="close"
class="anticon anticon-close"
role="img"
<svg
aria-hidden="true"
data-icon="close"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<svg
aria-hidden="true"
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>
<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="config-drawer-body"
/>
</div>
<div
class="config-drawer-body"
/>
</div>
</div>
</div>
<div
aria-hidden="true"
data-sentinel="end"
style="width:0;height:0;overflow:hidden;outline:none;position:absolute"
tabindex="0"
/>
</div>
`;
exports[`ConfigProvider components Drawer configProvider virtual and dropdownMatchSelectWidth 1`] = `
<div
class=""
class="ant-drawer ant-drawer-right ant-drawer-open ant-drawer-inline"
tabindex="-1"
>
<div
class="ant-drawer ant-drawer-right"
tabindex="-1"
class="ant-drawer-mask"
/>
<div
aria-hidden="true"
data-sentinel="start"
style="width:0;height:0;overflow:hidden;outline:none;position:absolute"
tabindex="0"
/>
<div
class="ant-drawer-content-wrapper"
style="width:378px"
>
<div
class="ant-drawer-mask"
/>
<div
class="ant-drawer-content-wrapper"
style="transform:translateX(100%);-ms-transform:translateX(100%);width:378px"
aria-modal="true"
class="ant-drawer-content"
role="dialog"
>
<div
class="ant-drawer-content"
class="ant-drawer-wrapper-body"
>
<div
class="ant-drawer-wrapper-body"
class="ant-drawer-header ant-drawer-header-close-only"
>
<div
class="ant-drawer-header ant-drawer-header-close-only"
class="ant-drawer-header-title"
>
<div
class="ant-drawer-header-title"
<button
aria-label="Close"
class="ant-drawer-close"
type="button"
>
<button
aria-label="Close"
class="ant-drawer-close"
type="button"
<span
aria-label="close"
class="anticon anticon-close"
role="img"
>
<span
aria-label="close"
class="anticon anticon-close"
role="img"
<svg
aria-hidden="true"
data-icon="close"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<svg
aria-hidden="true"
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>
<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-drawer-body"
/>
</div>
<div
class="ant-drawer-body"
/>
</div>
</div>
</div>
<div
aria-hidden="true"
data-sentinel="end"
style="width:0;height:0;overflow:hidden;outline:none;position:absolute"
tabindex="0"
/>
</div>
`;
exports[`ConfigProvider components Drawer normal 1`] = `
<div
class=""
class="ant-drawer ant-drawer-right ant-drawer-open ant-drawer-inline"
tabindex="-1"
>
<div
class="ant-drawer ant-drawer-right"
tabindex="-1"
class="ant-drawer-mask"
/>
<div
aria-hidden="true"
data-sentinel="start"
style="width:0;height:0;overflow:hidden;outline:none;position:absolute"
tabindex="0"
/>
<div
class="ant-drawer-content-wrapper"
style="width:378px"
>
<div
class="ant-drawer-mask"
/>
<div
class="ant-drawer-content-wrapper"
style="transform:translateX(100%);-ms-transform:translateX(100%);width:378px"
aria-modal="true"
class="ant-drawer-content"
role="dialog"
>
<div
class="ant-drawer-content"
class="ant-drawer-wrapper-body"
>
<div
class="ant-drawer-wrapper-body"
class="ant-drawer-header ant-drawer-header-close-only"
>
<div
class="ant-drawer-header ant-drawer-header-close-only"
class="ant-drawer-header-title"
>
<div
class="ant-drawer-header-title"
<button
aria-label="Close"
class="ant-drawer-close"
type="button"
>
<button
aria-label="Close"
class="ant-drawer-close"
type="button"
<span
aria-label="close"
class="anticon anticon-close"
role="img"
>
<span
aria-label="close"
class="anticon anticon-close"
role="img"
<svg
aria-hidden="true"
data-icon="close"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<svg
aria-hidden="true"
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>
<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-drawer-body"
/>
</div>
<div
class="ant-drawer-body"
/>
</div>
</div>
</div>
<div
aria-hidden="true"
data-sentinel="end"
style="width:0;height:0;overflow:hidden;outline:none;position:absolute"
tabindex="0"
/>
</div>
`;
exports[`ConfigProvider components Drawer prefixCls 1`] = `
<div
class=""
class="prefix-Drawer prefix-Drawer-right prefix-Drawer-open prefix-Drawer-inline"
tabindex="-1"
>
<div
class="prefix-Drawer prefix-Drawer-right"
tabindex="-1"
class="prefix-Drawer-mask"
/>
<div
aria-hidden="true"
data-sentinel="start"
style="width:0;height:0;overflow:hidden;outline:none;position:absolute"
tabindex="0"
/>
<div
class="prefix-Drawer-content-wrapper"
style="width:378px"
>
<div
class="prefix-Drawer-mask"
/>
<div
class="prefix-Drawer-content-wrapper"
style="transform:translateX(100%);-ms-transform:translateX(100%);width:378px"
aria-modal="true"
class="prefix-Drawer-content"
role="dialog"
>
<div
class="prefix-Drawer-content"
class="prefix-Drawer-wrapper-body"
>
<div
class="prefix-Drawer-wrapper-body"
class="prefix-Drawer-header prefix-Drawer-header-close-only"
>
<div
class="prefix-Drawer-header prefix-Drawer-header-close-only"
class="prefix-Drawer-header-title"
>
<div
class="prefix-Drawer-header-title"
<button
aria-label="Close"
class="prefix-Drawer-close"
type="button"
>
<button
aria-label="Close"
class="prefix-Drawer-close"
type="button"
<span
aria-label="close"
class="anticon anticon-close"
role="img"
>
<span
aria-label="close"
class="anticon anticon-close"
role="img"
<svg
aria-hidden="true"
data-icon="close"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<svg
aria-hidden="true"
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>
<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="prefix-Drawer-body"
/>
</div>
<div
class="prefix-Drawer-body"
/>
</div>
</div>
</div>
<div
aria-hidden="true"
data-sentinel="end"
style="width:0;height:0;overflow:hidden;outline:none;position:absolute"
tabindex="0"
/>
</div>
`;

View File

@ -1,8 +1,9 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import Drawer from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { render } from '../../../tests/utils';
import { fireEvent, render } from '../../../tests/utils';
import ConfigProvider from '../../config-provider';
const DrawerTest = ({ getContainer }) => (
@ -17,19 +18,53 @@ describe('Drawer', () => {
mountTest(Drawer);
rtlTest(Drawer);
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
function triggerMotion() {
act(() => {
jest.runAllTimers();
});
const mask = document.querySelector('.ant-drawer-mask');
if (mask) {
fireEvent.animationEnd(mask);
}
const panel = document.querySelector('.ant-drawer-content');
if (panel) {
fireEvent.animationEnd(panel);
}
act(() => {
jest.runAllTimers();
});
}
it('render correctly', () => {
const { container: wrapper } = render(
<Drawer visible width={400} getContainer={false}>
Here is content of Drawer
</Drawer>,
);
triggerMotion();
expect(wrapper.firstChild).toMatchSnapshot();
});
it('getContainer return undefined', () => {
const { container: wrapper, rerender } = render(<DrawerTest getContainer={() => undefined} />);
triggerMotion();
expect(wrapper.firstChild).toMatchSnapshot();
rerender(<DrawerTest getContainer={false} />);
triggerMotion();
expect(wrapper.firstChild).toMatchSnapshot();
});
@ -39,6 +74,8 @@ describe('Drawer', () => {
Here is content of Drawer
</Drawer>,
);
triggerMotion();
expect(wrapper.firstChild).toMatchSnapshot();
});
@ -48,6 +85,8 @@ describe('Drawer', () => {
Here is content of Drawer
</Drawer>,
);
triggerMotion();
expect(wrapper.firstChild).toMatchSnapshot();
});
@ -57,6 +96,8 @@ describe('Drawer', () => {
Here is content of Drawer
</Drawer>,
);
triggerMotion();
expect(wrapper.firstChild).toMatchSnapshot();
});
@ -66,15 +107,19 @@ describe('Drawer', () => {
Here is content of Drawer
</Drawer>,
);
triggerMotion();
expect(wrapper.firstChild).toMatchSnapshot();
});
it('className is test_drawer', () => {
const { container: wrapper } = render(
<Drawer destroyOnClose visible={false} className="test_drawer" getContainer={false}>
<Drawer destroyOnClose visible className="test_drawer" getContainer={false}>
Here is content of Drawer
</Drawer>,
);
triggerMotion();
expect(wrapper.firstChild).toMatchSnapshot();
});
@ -94,6 +139,8 @@ describe('Drawer', () => {
Here is content of Drawer
</Drawer>,
);
triggerMotion();
expect(wrapper.firstChild).toMatchSnapshot();
});
@ -103,6 +150,8 @@ describe('Drawer', () => {
Here is content of Drawer
</Drawer>,
);
triggerMotion();
expect(wrapper.firstChild).toMatchSnapshot();
});
@ -131,6 +180,8 @@ describe('Drawer', () => {
Here is content of Drawer
</Drawer>,
);
triggerMotion();
expect(wrapper.firstChild).toMatchSnapshot();
});
@ -147,15 +198,4 @@ describe('Drawer', () => {
errorSpy.mockRestore();
});
it('should support ref', () => {
const ref = React.createRef();
render(
<Drawer visible ref={ref} width={400}>
Here is content of Drawer
</Drawer>,
);
expect(typeof ref.current.push).toBe('function');
expect(typeof ref.current.pull).toBe('function');
});
});

View File

@ -1,6 +1,6 @@
import React from 'react';
import Drawer from '..';
import { fireEvent, render } from '../../../tests/utils';
import { act, fireEvent, render } from '../../../tests/utils';
describe('Drawer', () => {
const getDrawer = props => (
@ -9,6 +9,14 @@ describe('Drawer', () => {
</Drawer>
);
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
it('render correctly', () => {
const { container, asFragment, rerender } = render(getDrawer());
expect(container.querySelector('.ant-drawer-body')).toBeTruthy();
@ -48,54 +56,68 @@ describe('Drawer', () => {
it('dom should be removed after close when destroyOnClose is true', () => {
const { container, rerender } = render(getDrawer({ destroyOnClose: true }));
expect(container.querySelector('.ant-drawer')).toBeTruthy();
rerender(getDrawer({ destroyOnClose: true, visible: false }));
const ev = new TransitionEvent('transitionend', { bubbles: true });
ev.propertyName = 'transform';
fireEvent(document.querySelector('.ant-drawer-content-wrapper'), ev);
act(() => {
jest.runAllTimers();
});
expect(container.querySelector('.ant-drawer-wrapper-body')).toBeFalsy();
expect(container.querySelector('.ant-drawer')).toBeFalsy();
});
it('dom should be existed after close when destroyOnClose is false', () => {
const { container, rerender } = render(getDrawer());
expect(container.querySelector('.ant-drawer-wrapper-body')).toBeTruthy();
expect(container.querySelector('.ant-drawer')).toBeTruthy();
rerender(getDrawer({ visible: false }));
const ev = new TransitionEvent('transitionend', { bubbles: true });
ev.propertyName = 'transform';
fireEvent(document.querySelector('.ant-drawer-content-wrapper'), ev);
act(() => {
jest.runAllTimers();
});
fireEvent.animationEnd(container.querySelector('.ant-drawer-content'));
expect(container.querySelector('.ant-drawer-wrapper-body')).toBeTruthy();
expect(container.querySelector('.ant-drawer')).toBeTruthy();
});
it('dom should be existed after close twice when getContainer is false', () => {
const { container, rerender } = render(getDrawer({ visible: true, getContainer: false }));
rerender(getDrawer({ visible: false, getContainer: false }));
const ev = new TransitionEvent('transitionend', { bubbles: true });
ev.propertyName = 'transform';
fireEvent(document.querySelector('.ant-drawer-content-wrapper'), ev);
expect(container.querySelector('.ant-drawer-content')).toBeTruthy();
// Hide
rerender(getDrawer({ visible: false, getContainer: false }));
act(() => {
jest.runAllTimers();
});
fireEvent.animationEnd(container.querySelector('.ant-drawer-content-wrapper'));
expect(container.querySelector('.ant-drawer-content-wrapper-hidden')).toBeTruthy();
// Show
rerender(getDrawer({ visible: true, getContainer: false }));
const ev2 = new TransitionEvent('transitionend', { bubbles: true });
ev2.propertyName = 'transform';
fireEvent(document.querySelector('.ant-drawer-content-wrapper'), ev2);
expect(container.querySelector('.ant-drawer-content-wrapper')).toBeTruthy();
expect(container.querySelector('.ant-drawer-content-wrapper-hidden')).toBeFalsy();
// Hide
rerender(getDrawer({ visible: false, getContainer: false }));
const ev3 = new TransitionEvent('transitionend', { bubbles: true });
ev3.propertyName = 'transform';
fireEvent(document.querySelector('.ant-drawer-content-wrapper'), ev3);
expect(container.querySelector('.ant-drawer-wrapper-body')).toBeTruthy();
act(() => {
jest.runAllTimers();
});
fireEvent.animationEnd(container.querySelector('.ant-drawer-content-wrapper'));
expect(container.querySelector('.ant-drawer-content-wrapper-hidden')).toBeTruthy();
});
it('test afterVisibleChange', async () => {
const afterVisibleChange = jest.fn();
const { rerender } = render(getDrawer({ afterVisibleChange, visible: true }));
const { container, rerender } = render(getDrawer({ afterVisibleChange, visible: true }));
rerender(getDrawer({ afterVisibleChange, visible: false }));
const ev = new TransitionEvent('transitionend', { bubbles: true });
ev.propertyName = 'transform';
fireEvent(document.querySelector('.ant-drawer-content-wrapper'), ev);
act(() => {
jest.runAllTimers();
});
fireEvent.animationEnd(container.querySelector('.ant-drawer-content-wrapper'));
expect(afterVisibleChange).toBeCalledTimes(1);
});
it('should support children ref', () => {
const fn = jest.fn();

View File

@ -113,8 +113,10 @@ describe('Drawer', () => {
const { container: wrapper } = render(<MultiDrawer placement="right" />);
fireEvent.click(wrapper.querySelector('button#open_drawer'));
fireEvent.click(wrapper.querySelector('button#open_two_drawer'));
const translateX = wrapper.querySelectorAll('.ant-drawer.test_drawer')[0].style.transform;
expect(translateX).toEqual('translateX(-180px)');
expect(wrapper.querySelector('.ant-drawer-content-wrapper')).toHaveStyle({
transform: 'translateX(-180px)',
});
expect(wrapper.querySelectorAll('#two_drawer_text').length).toBe(1);
});
@ -122,8 +124,10 @@ describe('Drawer', () => {
const { container: wrapper } = render(<MultiDrawer placement="left" />);
fireEvent.click(wrapper.querySelector('button#open_drawer'));
fireEvent.click(wrapper.querySelector('button#open_two_drawer'));
const translateX = wrapper.querySelectorAll('.ant-drawer.test_drawer')[0].style.transform;
expect(translateX).toEqual('translateX(180px)');
expect(wrapper.querySelector('.ant-drawer-content-wrapper')).toHaveStyle({
transform: 'translateX(180px)',
});
expect(wrapper.querySelectorAll('#two_drawer_text').length).toBe(1);
fireEvent.click(wrapper.querySelector('.Two-level .ant-drawer-close'));
expect(wrapper.querySelector('.childrenDrawer').innerHTML).toEqual('false');
@ -133,8 +137,9 @@ describe('Drawer', () => {
const { container: wrapper } = render(<MultiDrawer placement="top" />);
fireEvent.click(wrapper.querySelector('button#open_drawer'));
fireEvent.click(wrapper.querySelector('button#open_two_drawer'));
const translateX = wrapper.querySelectorAll('.ant-drawer.test_drawer')[0].style.transform;
expect(translateX).toEqual('translateY(180px)');
expect(wrapper.querySelector('.ant-drawer-content-wrapper')).toHaveStyle({
transform: 'translateY(180px)',
});
expect(wrapper.querySelectorAll('#two_drawer_text').length).toBe(1);
});
@ -143,11 +148,18 @@ describe('Drawer', () => {
fireEvent.click(wrapper.querySelector('button#open_drawer'));
fireEvent.click(wrapper.querySelector('button#open_two_drawer'));
fireEvent.click(wrapper.querySelector('button#remove_drawer'));
let translateX = wrapper.querySelectorAll('.ant-drawer.test_drawer')[0].style.transform;
expect(translateX).toEqual('');
// Strange, testing-lib get wrong style in next branch.
expect(wrapper.querySelector('.ant-drawer-content-wrapper').style).toEqual(
expect.objectContaining({
transform: '',
}),
);
fireEvent.click(wrapper.querySelector('button#open_two_drawer'));
translateX = wrapper.querySelectorAll('.ant-drawer.test_drawer')[0].style.transform;
expect(translateX).toEqual('translateY(180px)');
expect(wrapper.querySelector('.ant-drawer-content-wrapper')).toHaveStyle({
transform: 'translateY(180px)',
});
expect(wrapper.querySelectorAll('#two_drawer_text').length).toBe(1);
});
@ -155,23 +167,28 @@ describe('Drawer', () => {
const { container: wrapper } = render(<MultiDrawer push={{ distance: 256 }} />);
fireEvent.click(wrapper.querySelector('button#open_drawer'));
fireEvent.click(wrapper.querySelector('button#open_two_drawer'));
const translateX = wrapper.querySelectorAll('.ant-drawer.test_drawer')[0].style.transform;
expect(translateX).toEqual('translateX(-256px)');
expect(wrapper.querySelector('.ant-drawer-content-wrapper')).toHaveStyle({
transform: 'translateX(-256px)',
});
});
it('custom MultiDrawer push with true', () => {
const { container: wrapper } = render(<MultiDrawer push />);
fireEvent.click(wrapper.querySelector('button#open_drawer'));
fireEvent.click(wrapper.querySelector('button#open_two_drawer'));
const translateX = wrapper.querySelectorAll('.ant-drawer.test_drawer')[0].style.transform;
expect(translateX).toEqual('translateX(-180px)');
expect(wrapper.querySelector('.ant-drawer-content-wrapper')).toHaveStyle({
transform: 'translateX(-180px)',
});
});
it('custom MultiDrawer push with false', () => {
const { container: wrapper } = render(<MultiDrawer push={false} />);
fireEvent.click(wrapper.querySelector('button#open_drawer'));
fireEvent.click(wrapper.querySelector('button#open_two_drawer'));
const translateX = wrapper.querySelectorAll('.ant-drawer.test_drawer')[0].style.transform;
expect(translateX).toEqual('');
expect(wrapper.querySelector('.ant-drawer-content-wrapper').style).toEqual(
expect.objectContaining({
transform: '',
}),
);
});
});

File diff suppressed because it is too large Load Diff

View File

@ -2,66 +2,76 @@
exports[`Drawer render correctly 1`] = `
<div
class=""
class="ant-drawer ant-drawer-right ant-drawer-inline"
tabindex="-1"
>
<div
class="ant-drawer ant-drawer-right"
tabindex="-1"
class="ant-drawer-mask ant-drawer-mask-motion-leave ant-drawer-mask-motion-leave-start ant-drawer-mask-motion"
/>
<div
aria-hidden="true"
data-sentinel="start"
style="width: 0px; height: 0px; overflow: hidden; outline: none; position: absolute;"
tabindex="0"
/>
<div
class="ant-drawer-content-wrapper ant-drawer-panel-motion-right-leave ant-drawer-panel-motion-right-leave-start ant-drawer-panel-motion-right"
style="width: 378px;"
>
<div
class="ant-drawer-mask"
/>
<div
class="ant-drawer-content-wrapper"
style="width: 378px; transform: translateX(100%);"
aria-modal="true"
class="ant-drawer-content"
role="dialog"
>
<div
class="ant-drawer-content"
class="ant-drawer-wrapper-body"
>
<div
class="ant-drawer-wrapper-body"
class="ant-drawer-header ant-drawer-header-close-only"
>
<div
class="ant-drawer-header ant-drawer-header-close-only"
class="ant-drawer-header-title"
>
<div
class="ant-drawer-header-title"
<button
aria-label="Close"
class="ant-drawer-close"
type="button"
>
<button
aria-label="Close"
class="ant-drawer-close"
type="button"
<span
aria-label="close"
class="anticon anticon-close"
role="img"
>
<span
aria-label="close"
class="anticon anticon-close"
role="img"
<svg
aria-hidden="true"
data-icon="close"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<svg
aria-hidden="true"
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>
<div
class="ant-drawer-body"
>
Here is content of Drawer
<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>
<div
class="ant-drawer-body"
>
Here is content of Drawer
</div>
</div>
</div>
</div>
<div
aria-hidden="true"
data-sentinel="end"
style="width: 0px; height: 0px; overflow: hidden; outline: none; position: absolute;"
tabindex="0"
/>
</div>
`;

View File

@ -1,502 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders ./components/drawer/demo/basic-right.md extend context correctly 1`] = `
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Open
</span>
</button>
`;
exports[`renders ./components/drawer/demo/config-provider.md extend context correctly 1`] = `
<div
class="site-drawer-render-in-current-wrapper"
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Open
</span>
</button>
</div>
`;
exports[`renders ./components/drawer/demo/extra.md extend context correctly 1`] = `
<div
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"
>
<span
class="ant-radio"
>
<input
class="ant-radio-input"
type="radio"
value="top"
/>
<span
class="ant-radio-inner"
/>
</span>
<span>
top
</span>
</label>
<label
class="ant-radio-wrapper ant-radio-wrapper-checked"
>
<span
class="ant-radio ant-radio-checked"
>
<input
checked=""
class="ant-radio-input"
type="radio"
value="right"
/>
<span
class="ant-radio-inner"
/>
</span>
<span>
right
</span>
</label>
<label
class="ant-radio-wrapper"
>
<span
class="ant-radio"
>
<input
class="ant-radio-input"
type="radio"
value="bottom"
/>
<span
class="ant-radio-inner"
/>
</span>
<span>
bottom
</span>
</label>
<label
class="ant-radio-wrapper"
>
<span
class="ant-radio"
>
<input
class="ant-radio-input"
type="radio"
value="left"
/>
<span
class="ant-radio-inner"
/>
</span>
<span>
left
</span>
</label>
</div>
</div>
<div
class="ant-space-item"
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Open
</span>
</button>
</div>
</div>
`;
exports[`renders ./components/drawer/demo/form-in-drawer.md extend context correctly 1`] = `
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span
aria-label="plus"
class="anticon anticon-plus"
role="img"
>
<svg
aria-hidden="true"
data-icon="plus"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<defs />
<path
d="M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z"
/>
<path
d="M176 474h672q8 0 8 8v60q0 8-8 8H176q-8 0-8-8v-60q0-8 8-8z"
/>
</svg>
</span>
<span>
New account
</span>
</button>
`;
exports[`renders ./components/drawer/demo/multi-level-drawer.md extend context correctly 1`] = `
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Open drawer
</span>
</button>
`;
exports[`renders ./components/drawer/demo/no-mask.md extend context correctly 1`] = `
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Open
</span>
</button>
`;
exports[`renders ./components/drawer/demo/placement.md extend context correctly 1`] = `
<div
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"
>
<span
class="ant-radio"
>
<input
class="ant-radio-input"
type="radio"
value="top"
/>
<span
class="ant-radio-inner"
/>
</span>
<span>
top
</span>
</label>
<label
class="ant-radio-wrapper"
>
<span
class="ant-radio"
>
<input
class="ant-radio-input"
type="radio"
value="right"
/>
<span
class="ant-radio-inner"
/>
</span>
<span>
right
</span>
</label>
<label
class="ant-radio-wrapper"
>
<span
class="ant-radio"
>
<input
class="ant-radio-input"
type="radio"
value="bottom"
/>
<span
class="ant-radio-inner"
/>
</span>
<span>
bottom
</span>
</label>
<label
class="ant-radio-wrapper ant-radio-wrapper-checked"
>
<span
class="ant-radio ant-radio-checked"
>
<input
checked=""
class="ant-radio-input"
type="radio"
value="left"
/>
<span
class="ant-radio-inner"
/>
</span>
<span>
left
</span>
</label>
</div>
</div>
<div
class="ant-space-item"
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Open
</span>
</button>
</div>
</div>
`;
exports[`renders ./components/drawer/demo/render-in-current.md extend context correctly 1`] = `
<div
class="site-drawer-render-in-current-wrapper"
>
Render in this
<div
style="margin-top:16px"
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Open
</span>
</button>
</div>
<div
class=""
>
<div
class="ant-drawer ant-drawer-right"
style="position:absolute"
tabindex="-1"
>
<div
class="ant-drawer-mask"
/>
<div
class="ant-drawer-content-wrapper"
style="transform:translateX(100%);-ms-transform:translateX(100%);width:378px"
>
<div
class="ant-drawer-content"
>
<div
class="ant-drawer-wrapper-body"
>
<div
class="ant-drawer-header"
>
<div
class="ant-drawer-header-title"
>
<div
class="ant-drawer-title"
>
Basic Drawer
</div>
</div>
</div>
<div
class="ant-drawer-body"
>
<p>
Some contents...
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`renders ./components/drawer/demo/size.md extend context correctly 1`] = `
<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"
>
<span>
Open Default Size (378px)
</span>
</button>
</div>
<div
class="ant-space-item"
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Open Large Size (736px)
</span>
</button>
</div>
</div>
`;
exports[`renders ./components/drawer/demo/user-profile.md extend context correctly 1`] = `
<div
class="ant-list ant-list-split ant-list-bordered"
>
<div
class="ant-spin-nested-loading"
>
<div
class="ant-spin-container"
>
<ul
class="ant-list-items"
>
<li
class="ant-list-item"
>
<div
class="ant-list-item-meta"
>
<div
class="ant-list-item-meta-avatar"
>
<span
class="ant-avatar ant-avatar-circle ant-avatar-image"
>
<img
src="https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png"
/>
</span>
</div>
<div
class="ant-list-item-meta-content"
>
<h4
class="ant-list-item-meta-title"
>
<a
href="https://ant.design/index-cn"
>
Lily
</a>
</h4>
<div
class="ant-list-item-meta-description"
>
Progresser XTech
</div>
</div>
</div>
<ul
class="ant-list-item-action"
>
<li>
<a>
View Profile
</a>
</li>
</ul>
</li>
<li
class="ant-list-item"
>
<div
class="ant-list-item-meta"
>
<div
class="ant-list-item-meta-avatar"
>
<span
class="ant-avatar ant-avatar-circle ant-avatar-image"
>
<img
src="https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png"
/>
</span>
</div>
<div
class="ant-list-item-meta-content"
>
<h4
class="ant-list-item-meta-title"
>
<a
href="https://ant.design/index-cn"
>
Lily
</a>
</h4>
<div
class="ant-list-item-meta-description"
>
Progresser XTech
</div>
</div>
</div>
<ul
class="ant-list-item-action"
>
<li>
<a>
View Profile
</a>
</li>
</ul>
</li>
</ul>
</div>
</div>
</div>
`;

File diff suppressed because it is too large Load Diff

View File

@ -310,50 +310,22 @@ exports[`renders ./components/drawer/demo/render-in-current.md correctly 1`] = `
</button>
</div>
<div
class=""
class="ant-drawer ant-drawer-right ant-drawer-inline"
style="position:absolute"
tabindex="-1"
>
<div
class="ant-drawer ant-drawer-right"
style="position:absolute"
tabindex="-1"
>
<div
class="ant-drawer-mask"
/>
<div
class="ant-drawer-content-wrapper"
style="transform:translateX(100%);-ms-transform:translateX(100%);width:378px"
>
<div
class="ant-drawer-content"
>
<div
class="ant-drawer-wrapper-body"
>
<div
class="ant-drawer-header"
>
<div
class="ant-drawer-header-title"
>
<div
class="ant-drawer-title"
>
Basic Drawer
</div>
</div>
</div>
<div
class="ant-drawer-body"
>
<p>
Some contents...
</p>
</div>
</div>
</div>
</div>
</div>
aria-hidden="true"
data-sentinel="start"
style="width:0;height:0;overflow:hidden;outline:none;position:absolute"
tabindex="0"
/>
<div
aria-hidden="true"
data-sentinel="end"
style="width:0;height:0;overflow:hidden;outline:none;position:absolute"
tabindex="0"
/>
</div>
</div>
`;

View File

@ -1,3 +0,0 @@
import { extendTest } from '../../../tests/shared/demoTest';
extendTest('drawer');

View File

@ -0,0 +1,19 @@
import * as React from 'react';
import { extendTest } from '../../../tests/shared/demoTest';
jest.mock('rc-drawer', () => {
const Drawer = jest.requireActual('rc-drawer');
const MockDrawer = Drawer.default;
return (props: any) => {
const newProps = {
...props,
open: true,
getContainer: false,
};
return <MockDrawer {...newProps} />;
};
});
extendTest('drawer', {
testingLib: true,
});

View File

@ -40,6 +40,13 @@ const App: React.FC = () => {
mask={false}
onClose={onClose}
visible={visible}
contentWrapperStyle={{
width: 333,
background: 'red',
borderRadius: 20,
boxShadow: '-5px 0 5px green',
overflow: 'hidden',
}}
>
<p>Some contents...</p>
<p>Some contents...</p>

View File

@ -1,20 +1,17 @@
import CloseOutlined from '@ant-design/icons/CloseOutlined';
import classNames from 'classnames';
import RcDrawer from 'rc-drawer';
import type { DrawerProps as RcDrawerProps } from 'rc-drawer';
import type { CSSMotionProps } from 'rc-motion';
import * as React from 'react';
import { ConfigContext } from '../config-provider';
import { NoFormStyle } from '../form/context';
import { getTransitionName } from '../_util/motion';
import { tuple } from '../_util/type';
// CSSINJS
import useStyle from './style';
type DrawerRef = {
push(): void;
pull(): void;
};
const DrawerContext = React.createContext<DrawerRef | null>(null);
type EventType =
| React.KeyboardEvent<HTMLDivElement>
| React.MouseEvent<HTMLDivElement | HTMLButtonElement>;
@ -72,212 +69,136 @@ export interface DrawerProps {
const defaultPushState: PushState = { distance: 180 };
const Drawer = React.forwardRef<DrawerRef, DrawerProps>(
(
function Drawer({
width,
height,
size = 'default',
closable = true,
mask = true,
push = defaultPushState,
closeIcon = <CloseOutlined />,
bodyStyle,
drawerStyle,
className,
visible,
children,
zIndex,
style,
title,
headerStyle,
onClose,
footer,
footerStyle,
prefixCls: customizePrefixCls,
getContainer: customizeGetContainer,
extra,
afterVisibleChange,
...rest
}: DrawerProps) {
const { getPopupContainer, getPrefixCls, direction } = React.useContext(ConfigContext);
const prefixCls = getPrefixCls('drawer', customizePrefixCls);
// Style
const [wrapSSR, hashId] = useStyle(prefixCls);
const getContainer =
// 有可能为 false所以不能直接判断
customizeGetContainer === undefined && getPopupContainer
? () => getPopupContainer(document.body)
: customizeGetContainer;
const closeIconNode = closable && (
<button type="button" onClick={onClose} aria-label="Close" className={`${prefixCls}-close`}>
{closeIcon}
</button>
);
function renderHeader() {
if (!title && !closable) {
return null;
}
return (
<div
className={classNames(`${prefixCls}-header`, {
[`${prefixCls}-header-close-only`]: closable && !title && !extra,
})}
style={headerStyle}
>
<div className={`${prefixCls}-header-title`}>
{closeIconNode}
{title && <div className={`${prefixCls}-title`}>{title}</div>}
</div>
{extra && <div className={`${prefixCls}-extra`}>{extra}</div>}
</div>
);
}
function renderFooter() {
if (!footer) {
return null;
}
const footerClassName = `${prefixCls}-footer`;
return (
<div className={footerClassName} style={footerStyle}>
{footer}
</div>
);
}
const drawerClassName = classNames(
{
width,
height,
size = 'default',
closable = true,
placement = 'right' as placementType,
maskClosable = true,
mask = true,
level = null,
keyboard = true,
push = defaultPushState,
closeIcon = <CloseOutlined />,
bodyStyle,
drawerStyle,
className,
visible: propsVisible,
forceRender,
children,
zIndex,
destroyOnClose,
style,
title,
headerStyle,
onClose,
footer,
footerStyle,
prefixCls: customizePrefixCls,
getContainer: customizeGetContainer,
extra,
afterVisibleChange,
...rest
'no-mask': !mask,
[`${prefixCls}-rtl`]: direction === 'rtl',
},
ref,
) => {
const [internalPush, setPush] = React.useState(false);
const parentDrawer = React.useContext(DrawerContext);
const destroyCloseRef = React.useRef<boolean>(false);
className,
hashId,
);
const [load, setLoad] = React.useState(false);
const [visible, setVisible] = React.useState(false);
// ============================ Size ============================
const mergedWidth = React.useMemo(() => width ?? (size === 'large' ? 736 : 378), [width, size]);
const mergedHeight = React.useMemo(
() => height ?? (size === 'large' ? 736 : 378),
[height, size],
);
React.useEffect(() => {
if (propsVisible) {
setLoad(true);
} else {
setVisible(false);
}
}, [propsVisible]);
// =========================== Motion ===========================
const maskMotion: CSSMotionProps = {
motionName: getTransitionName(prefixCls, 'mask-motion'),
motionAppear: true,
motionEnter: true,
motionLeave: true,
};
React.useEffect(() => {
if (load && propsVisible) {
setVisible(true);
}
}, [load, propsVisible]);
const panelMotion: RcDrawerProps['motion'] = motionPlacement => ({
motionName: getTransitionName(prefixCls, `panel-motion-${motionPlacement}`),
motionAppear: true,
motionEnter: true,
motionLeave: true,
});
const { getPopupContainer, getPrefixCls, direction } = React.useContext(ConfigContext);
const prefixCls = getPrefixCls('drawer', customizePrefixCls);
// Style
const [wrapSSR, hashId] = useStyle(prefixCls);
const getContainer =
// 有可能为 false所以不能直接判断
customizeGetContainer === undefined && getPopupContainer
? () => getPopupContainer(document.body)
: customizeGetContainer;
React.useEffect(() => {
// fix: delete drawer in child and re-render, no push started.
// <Drawer>{show && <Drawer />}</Drawer>
if (propsVisible && parentDrawer) {
parentDrawer.push();
}
return () => {
if (parentDrawer) {
parentDrawer.pull();
// parentDrawer = null;
}
};
}, []);
React.useEffect(() => {
if (parentDrawer) {
if (visible) {
parentDrawer.push();
} else {
parentDrawer.pull();
}
}
}, [visible]);
const operations = React.useMemo(
() => ({
push() {
if (push) {
setPush(true);
}
},
pull() {
if (push) {
setPush(false);
}
},
}),
[push],
);
React.useImperativeHandle(ref, () => operations, [operations]);
const getOffsetStyle = () => {
// https://github.com/ant-design/ant-design/issues/24287
if (!visible && !mask) {
return {};
}
const offsetStyle: any = {};
if (placement === 'left' || placement === 'right') {
const defaultWidth = size === 'large' ? 736 : 378;
offsetStyle.width = typeof width === 'undefined' ? defaultWidth : width;
} else {
const defaultHeight = size === 'large' ? 736 : 378;
offsetStyle.height = typeof height === 'undefined' ? defaultHeight : height;
}
return offsetStyle;
};
const getRcDrawerStyle = () => {
// get drawer push width or height
const getPushTransform = (_placement?: placementType) => {
let distance: number | string;
if (typeof push === 'boolean') {
distance = push ? defaultPushState.distance : 0;
} else {
distance = push!.distance;
}
distance = parseFloat(String(distance || 0));
if (_placement === 'left' || _placement === 'right') {
return `translateX(${_placement === 'left' ? distance : -distance}px)`;
}
if (_placement === 'top' || _placement === 'bottom') {
return `translateY(${_placement === 'top' ? distance : -distance}px)`;
}
};
// 当无 mask 时,将 width 应用到外层容器上
// 解决 https://github.com/ant-design/ant-design/issues/12401 的问题
const offsetStyle = mask ? {} : getOffsetStyle();
return {
zIndex,
transform: internalPush ? getPushTransform(placement) : undefined,
...offsetStyle,
...style,
};
};
const closeIconNode = closable && (
<button type="button" onClick={onClose} aria-label="Close" className={`${prefixCls}-close`}>
{closeIcon}
</button>
);
function renderHeader() {
if (!title && !closable) {
return null;
}
return (
<div
className={classNames(`${prefixCls}-header`, {
[`${prefixCls}-header-close-only`]: closable && !title && !extra,
})}
style={headerStyle}
>
<div className={`${prefixCls}-header-title`}>
{closeIconNode}
{title && <div className={`${prefixCls}-title`}>{title}</div>}
</div>
{extra && <div className={`${prefixCls}-extra`}>{extra}</div>}
</div>
);
}
function renderFooter() {
if (!footer) {
return null;
}
const footerClassName = `${prefixCls}-footer`;
return (
<div className={footerClassName} style={footerStyle}>
{footer}
</div>
);
}
// render drawer body dom
const renderBody = () => {
// destroyCloseRef.current =false Load the body only once by default
if (destroyCloseRef.current && !forceRender && !propsVisible) {
return null;
}
return (
// =========================== Render ===========================
return wrapSSR(
<NoFormStyle status override>
<RcDrawer
prefixCls={prefixCls}
onClose={onClose}
{...rest}
open={visible}
mask={mask}
push={push}
width={mergedWidth}
height={mergedHeight}
rootClassName={drawerClassName}
getContainer={getContainer}
afterOpenChange={open => {
afterVisibleChange?.(open);
}}
maskMotion={maskMotion}
motion={panelMotion}
rootStyle={style}
>
<div className={`${prefixCls}-wrapper-body`} style={{ ...drawerStyle }}>
{renderHeader()}
<div className={`${prefixCls}-body`} style={bodyStyle}>
@ -285,58 +206,11 @@ const Drawer = React.forwardRef<DrawerRef, DrawerProps>(
</div>
{renderFooter()}
</div>
);
};
</RcDrawer>
</NoFormStyle>,
);
}
const drawerClassName = classNames(
{
'no-mask': !mask,
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
hashId,
);
const offsetStyle = mask ? getOffsetStyle() : {};
return wrapSSR(
<DrawerContext.Provider value={operations}>
<NoFormStyle status override>
<RcDrawer
handler={false}
{...{
placement,
prefixCls,
maskClosable,
level,
keyboard,
children,
onClose,
forceRender,
...rest,
}}
{...offsetStyle}
open={visible || propsVisible}
showMask={mask}
style={getRcDrawerStyle()}
className={drawerClassName}
getContainer={getContainer}
afterVisibleChange={open => {
if (open) {
destroyCloseRef.current = false;
} else if (destroyOnClose) {
destroyCloseRef.current = true;
setLoad(false);
}
afterVisibleChange?.(open);
}}
>
{renderBody()}
</RcDrawer>
</NoFormStyle>
</DrawerContext.Provider>,
);
},
);
if (process.env.NODE_ENV !== 'production') {
Drawer.displayName = 'Drawer';
}

View File

@ -1,254 +1,211 @@
import type { CSSObject } from '@ant-design/cssinjs';
import { Keyframes } from '@ant-design/cssinjs';
import type { FullToken, GenerateStyle } from '../../theme';
import { genComponentStyleHook, mergeToken } from '../../theme';
import genMotionStyle from './motion';
export interface ComponentToken {
zIndexPopup: number;
}
export interface DrawerToken extends FullToken<'Drawer'> {
drawerFooterPaddingVertical: number;
drawerFooterPaddingHorizontal: number;
}
const antdDrawerFadeIn = new Keyframes('antDrawerFadeIn', {
'0%': { opacity: 0 },
'100%': { opacity: 1 },
});
// =============================== Base ===============================
const genBaseStyle: GenerateStyle<DrawerToken> = (token: DrawerToken): CSSObject => {
const genDrawerStyle: GenerateStyle<DrawerToken> = (token: DrawerToken) => {
const {
componentCls,
motionEaseOut,
zIndexPopup,
colorBgMask,
colorBgElevated,
motionDurationSlow,
fontSizeLG,
paddingLG,
lineWidth,
radiusBase,
fontSize,
lineHeight,
drawerFooterPaddingVertical,
drawerFooterPaddingHorizontal,
zIndexPopupBase,
colorText,
padding,
paddingLG,
fontSizeLG,
lineHeightLG,
lineWidth,
lineType,
colorSplit,
marginSM,
colorIcon,
colorIconHover,
colorText,
fontWeightStrong,
drawerFooterPaddingVertical,
drawerFooterPaddingHorizontal,
} = token;
const wrapperCls = `${componentCls}-content-wrapper`;
return {
[`${componentCls}`]: {
[componentCls]: {
position: 'fixed',
zIndex: zIndexPopupBase,
width: 0,
height: '100%',
transition: `width 0s ease ${motionDurationSlow}, height 0s ease ${motionDurationSlow}`,
[`${componentCls}-content-wrapper`]: {
inset: 0,
zIndex: zIndexPopup,
pointerEvents: 'none',
'&-inline': {
position: 'absolute',
width: '100%',
height: '100%',
transition: `transform ${motionDurationSlow} ${motionEaseOut},box-shadow ${motionDurationSlow} ${motionEaseOut}`,
[`${componentCls}-content`]: {
width: '100%',
height: '100%',
position: 'relative',
zIndex: 1,
overflow: 'auto',
backgroundColor: token.colorBgElevated,
backgroundClip: `padding-box`,
border: 0,
[`${componentCls}-wrapper-body`]: {
display: 'flex',
flexFlow: 'column nowrap',
width: '100%',
height: '100%',
[`${componentCls}-header`]: {
position: 'relative',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: `${padding}px ${paddingLG}px`,
color: colorText,
background: token.colorBgElevated,
borderBottom: `${lineWidth}px ${lineType} ${colorSplit}`,
borderRadius: `${radiusBase}px ${radiusBase}px 0 0`,
[`${componentCls}-header-title`]: {
display: 'flex',
flex: 1,
alignItems: 'center',
justifyContent: 'space-between',
[`${componentCls}-title`]: {
flex: 1,
margin: 0,
color: colorText,
fontWeight: token.fontWeightStrong,
fontSize: fontSizeLG,
lineHeight: token.lineHeightLG,
},
[`${componentCls}-close`]: {
display: 'inline-block',
marginInlineEnd: token.marginSM,
color: token.colorIcon,
fontWeight: token.fontWeightStrong,
fontSize: fontSizeLG,
fontStyle: 'normal',
lineHeight: 1,
textAlign: 'center',
textTransform: 'none',
textDecoration: 'none',
background: 'transparent',
border: 0,
outline: 0,
cursor: 'pointer',
transition: `color ${motionDurationSlow}`,
textRendering: 'auto',
[`&:focus, &:hover`]: {
color: token.colorIconHover,
textDecoration: 'none',
},
},
},
[`${componentCls}-header-close-only`]: {
paddingBottom: 0,
border: 'none',
},
},
[`${componentCls}-body`]: {
flexGrow: 1,
padding: paddingLG,
overflow: 'auto',
fontSize,
lineHeight,
wordWrap: 'break-word',
},
[`${componentCls}-footer`]: {
flexShrink: 0,
padding: `${drawerFooterPaddingVertical}px ${drawerFooterPaddingHorizontal}px`,
borderTop: `${lineWidth}px ${lineType} ${colorSplit}`,
},
},
},
},
// ====================== Mask ======================
[`${componentCls}-mask`]: {
position: 'absolute',
insetBlockStart: 0,
insetInlineStart: 0,
width: '100%',
height: 0,
backgroundColor: token.colorBgMask,
opacity: 0,
transition: `opacity ${motionDurationSlow} linear, height 0s ease ${motionDurationSlow}`,
pointerEvents: 'none',
inset: 0,
zIndex: zIndexPopup,
background: colorBgMask,
pointerEvents: 'auto',
},
},
[`${componentCls}${componentCls}-open ${componentCls}-mask`]: {
height: '100%',
opacity: 1,
transition: 'none',
animationName: antdDrawerFadeIn,
animationDuration: token.motionDurationSlow,
animationTimingFunction: motionEaseOut,
pointerEvents: 'auto',
},
};
};
const genDrawerStyle: GenerateStyle<DrawerToken> = (token: DrawerToken) => {
const { componentCls, motionDurationSlow, lineWidth, motionEaseOut } = token;
// ==================== Content =====================
[wrapperCls]: {
position: 'absolute',
zIndex: zIndexPopup,
transition: `all ${motionDurationSlow}`,
return {
// =================== left,right ===================
[`${componentCls}-left`]: {
insetInlineStart: 0,
insetBlockStart: 0,
width: 0,
height: '100%',
[`${componentCls}-content-wrapper`]: {
height: '100%',
insetInlineStart: 0,
'&-hidden': {
display: 'none',
},
},
},
[`${componentCls}-left${componentCls}-open`]: {
width: '100%',
transition: `transform ${motionDurationSlow} ${motionEaseOut}`,
[`${componentCls}-content-wrapper`]: {
// Placement
[`&-left ${wrapperCls}`]: {
top: 0,
bottom: 0,
left: {
_skip_check_: true,
value: 0,
},
boxShadow: token.boxShadowDrawerRight,
},
},
[`${componentCls}-right`]: {
insetInlineEnd: 0,
insetBlockStart: 0,
width: 0,
height: '100%',
[`${componentCls}-content-wrapper`]: {
height: '100%',
insetInlineEnd: 0,
},
},
[`${componentCls}-right${componentCls}-open`]: {
width: '100%',
transition: `transform ${motionDurationSlow} ${motionEaseOut}`,
[`${componentCls}-content-wrapper`]: {
[`&-right ${wrapperCls}`]: {
top: 0,
right: {
_skip_check_: true,
value: 0,
},
bottom: 0,
boxShadow: token.boxShadowDrawerLeft,
},
},
// https://github.com/ant-design/ant-design/issues/18607, Avoid edge alignment bug.
[`${componentCls}-right${componentCls}-open.no-mask`]: {
insetInlineEnd: lineWidth,
transform: `translateX(${lineWidth})`,
},
// =================== top,bottom ===================
[`${componentCls}-top,${componentCls}-bottom`]: {
insetInlineStart: 0,
width: '100%',
height: 0,
[`${componentCls}-content-wrapper`]: {
width: '100%',
},
},
[`${componentCls}-top${componentCls}-open,${componentCls}-bottom${componentCls}-open`]: {
height: '100%',
transition: `transform ${motionDurationSlow} ${motionEaseOut}`,
},
[`${componentCls}-top`]: {
insetBlockStart: 0,
},
[`${componentCls}-top${componentCls}-open`]: {
[`${componentCls}-content-wrapper`]: {
[`&-top ${wrapperCls}`]: {
top: 0,
insetInline: 0,
boxShadow: token.boxShadowDrawerDown,
},
},
[`${componentCls}-bottom`]: {
bottom: 0,
[`${componentCls}-content-wrapper`]: {
[`&-bottom ${wrapperCls}`]: {
bottom: 0,
},
},
[`${componentCls}-bottom${componentCls}-bottom-open`]: {
[`${componentCls}-content-wrapper`]: {
insetInline: 0,
boxShadow: token.boxShadowDrawerUp,
},
},
[`${componentCls}-bottom${componentCls}-bottom-open.no-mask`]: {
insetBlockEnd: lineWidth,
transform: `translateY(${lineWidth})`,
[`${componentCls}-content`]: {
width: '100%',
height: '100%',
overflow: 'auto',
background: colorBgElevated,
pointerEvents: 'auto',
},
// ===================== Panel ======================
[`${componentCls}-wrapper-body`]: {
display: 'flex',
flexDirection: 'column',
width: '100%',
height: '100%',
},
// Header
[`${componentCls}-header`]: {
display: 'flex',
flex: 0,
alignItems: 'center',
padding: `${padding}px ${paddingLG}px`,
fontSize: fontSizeLG,
lineHeight: lineHeightLG,
borderBottom: `${lineWidth}px ${lineType} ${colorSplit}`,
'&-title': {
display: 'flex',
flex: 1,
alignItems: 'center',
minWidth: 0,
minHeight: 0,
},
},
[`${componentCls}-extra`]: {
flex: 0,
},
[`${componentCls}-close`]: {
display: 'inline-block',
marginInlineEnd: marginSM,
color: colorIcon,
fontWeight: fontWeightStrong,
fontSize: fontSizeLG,
fontStyle: 'normal',
lineHeight: 1,
textAlign: 'center',
textTransform: 'none',
textDecoration: 'none',
background: 'transparent',
border: 0,
outline: 0,
cursor: 'pointer',
transition: `color ${motionDurationSlow}`,
textRendering: 'auto',
'&:focus, &:hover': {
color: colorIconHover,
textDecoration: 'none',
},
},
[`${componentCls}-title`]: {
flex: 1,
margin: 0,
color: colorText,
fontWeight: token.fontWeightStrong,
fontSize: fontSizeLG,
lineHeight: lineHeightLG,
},
// Body
[`${componentCls}-body`]: {
flex: 1,
minWidth: 0,
minHeight: 0,
padding: paddingLG,
overflow: 'auto',
},
// Footer
[`${componentCls}-footer`]: {
flexShrink: 0,
padding: `${drawerFooterPaddingVertical}px ${drawerFooterPaddingHorizontal}px`,
borderTop: `${lineWidth}px ${lineType} ${colorSplit}`,
},
// ====================== RTL =======================
'&-rtl': {
direction: 'rtl',
},
},
};
};
// ============================== Export ==============================
export default genComponentStyleHook('Drawer', token => {
const drawerToken = mergeToken<DrawerToken>(token, {
drawerFooterPaddingVertical: token.paddingXS,
drawerFooterPaddingHorizontal: token.padding,
});
export default genComponentStyleHook(
'Drawer',
token => {
const drawerToken = mergeToken<DrawerToken>(token, {
drawerFooterPaddingVertical: token.paddingXS,
drawerFooterPaddingHorizontal: token.padding,
});
return [genBaseStyle(drawerToken), genDrawerStyle(drawerToken)];
});
return [genDrawerStyle(drawerToken), genMotionStyle(drawerToken)];
},
token => ({
zIndexPopup: token.zIndexPopupBase,
}),
);

View File

@ -0,0 +1,126 @@
import type { DrawerToken } from '.';
import type { GenerateStyle } from '../../theme';
const genMotionStyle: GenerateStyle<DrawerToken> = (token: DrawerToken) => {
const { componentCls, motionDurationSlow } = token;
const sharedPanelMotion = {
'&-enter, &-appear, &-leave': {
'&-start': {
transition: 'none',
},
'&-active': {
transition: `all ${motionDurationSlow}`,
},
},
};
return {
[componentCls]: {
// ======================== Mask ========================
[`${componentCls}-mask-motion`]: {
'&-enter, &-appear, &-leave': {
'&-active': {
transition: `all ${motionDurationSlow}`,
},
},
'&-enter, &-appear': {
opacity: 0,
'&-active': {
opacity: 1,
},
},
'&-leave': {
opacity: 1,
'&-active': {
opacity: 0,
},
},
},
// ======================= Panel ========================
[`${componentCls}-panel-motion`]: {
// Left
'&-left': [
sharedPanelMotion,
{
'&-enter, &-appear': {
transform: 'translateX(-100%)',
'&-active': {
transform: 'translateX(0)',
},
},
'&-leave': {
transform: 'translateX(0)',
'&-active': {
transform: 'translateX(-100%)',
},
},
},
],
// Right
'&-right': [
sharedPanelMotion,
{
'&-enter, &-appear': {
transform: 'translateX(100%)',
'&-active': {
transform: 'translateX(0)',
},
},
'&-leave': {
transform: 'translateX(0)',
'&-active': {
transform: 'translateX(100%)',
},
},
},
],
// Top
'&-top': [
sharedPanelMotion,
{
'&-enter, &-appear': {
transform: 'translateY(-100%)',
'&-active': {
transform: 'translateY(0)',
},
},
'&-leave': {
transform: 'translateY(0)',
'&-active': {
transform: 'translateY(-100%)',
},
},
},
],
// Bottom
'&-bottom': [
sharedPanelMotion,
{
'&-enter, &-appear': {
transform: 'translateY(100%)',
'&-active': {
transform: 'translateY(0)',
},
},
'&-leave': {
transform: 'translateY(0)',
'&-active': {
transform: 'translateY(100%)',
},
},
},
],
},
},
};
};
export default genMotionStyle;

View File

@ -17,6 +17,7 @@ const genGridRowStyle: GenerateStyle<GridRowToken> = (token): CSSObject => {
[componentCls]: {
display: 'flex',
flexFlow: 'row wrap',
minWidth: 0,
'&::before, &::after': {
display: 'flex',

View File

@ -1,10 +1,10 @@
import { ArrowDownOutlined, ArrowUpOutlined } from '@ant-design/icons';
import { mount } from 'enzyme';
import React from 'react';
import { ArrowDownOutlined, ArrowUpOutlined } from '@ant-design/icons';
import InputNumber from '..';
import focusTest from '../../../tests/shared/focusTest';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { fireEvent, render } from '../../../tests/utils';
describe('InputNumber', () => {
focusTest(InputNumber, { refFocus: true });
@ -14,32 +14,35 @@ describe('InputNumber', () => {
// https://github.com/ant-design/ant-design/issues/13896
it('should return null when blur a empty input number', () => {
const onChange = jest.fn();
const wrapper = mount(<InputNumber defaultValue="1" onChange={onChange} />);
wrapper.find('input').simulate('change', { target: { value: '' } });
const { container } = render(<InputNumber defaultValue="1" onChange={onChange} />);
fireEvent.change(container.querySelector('input'), { target: { value: '' } });
expect(onChange).toHaveBeenLastCalledWith(null);
});
it('should call onStep when press up or down button', () => {
const onStep = jest.fn();
const wrapper = mount(<InputNumber defaultValue={1} onStep={onStep} />);
wrapper.find('.ant-input-number-handler-up').simulate('mousedown');
const { container } = render(<InputNumber defaultValue={1} onStep={onStep} />);
fireEvent.mouseDown(container.querySelector('.ant-input-number-handler-up'));
expect(onStep).toBeCalledTimes(1);
expect(onStep).toHaveBeenLastCalledWith(2, { offset: 1, type: 'up' });
wrapper.find('.ant-input-number-handler-down').simulate('mousedown');
fireEvent.mouseDown(container.querySelector('.ant-input-number-handler-down'));
expect(onStep).toBeCalledTimes(2);
expect(onStep).toHaveBeenLastCalledWith(1, { offset: 1, type: 'down' });
});
it('renders correctly when controls is boolean', () => {
expect(mount(<InputNumber controls={false} />).render()).toMatchSnapshot();
const { asFragment } = render(<InputNumber controls={false} />);
expect(asFragment().firstChild).toMatchSnapshot();
});
it('renders correctly when controls is {}', () => {
expect(mount(<InputNumber controls={{}} />).render()).toMatchSnapshot();
const { asFragment } = render(<InputNumber controls={{}} />);
expect(asFragment().firstChild).toMatchSnapshot();
});
it('renders correctly when controls has custom upIcon and downIcon', () => {
const wrapper = mount(
const { asFragment } = render(
<InputNumber
controls={{
upIcon: <ArrowUpOutlined />,
@ -47,11 +50,11 @@ describe('InputNumber', () => {
}}
/>,
);
expect(wrapper.render()).toMatchSnapshot();
expect(asFragment().firstChild).toMatchSnapshot();
});
it('should support className', () => {
const wrapper = mount(
const { container } = render(
<InputNumber
controls={{
upIcon: <ArrowUpOutlined className="my-class-name" />,
@ -59,11 +62,11 @@ describe('InputNumber', () => {
}}
/>,
);
expect(wrapper.find('.anticon-arrow-up').getDOMNode().className.includes('my-class-name')).toBe(
expect(container.querySelector('.anticon-arrow-up')?.className.includes('my-class-name')).toBe(
true,
);
expect(
wrapper.find('.anticon-arrow-down').getDOMNode().className.includes('my-class-name'),
container.querySelector('.anticon-arrow-down')?.className.includes('my-class-name'),
).toBe(true);
});
});

View File

@ -1,25 +1,24 @@
import { render } from '@testing-library/react';
import { mount } from 'enzyme';
import React from 'react';
import React, { forwardRef } from 'react';
import InputNumber from '..';
import focusTest from '../../../tests/shared/focusTest';
import { fireEvent, render } from '../../../tests/utils';
describe('prefix', () => {
focusTest(
React.forwardRef((props, ref) => <InputNumber {...props} prefix="A" ref={ref} />),
forwardRef((props, ref) => <InputNumber {...props} prefix="A" ref={ref} />),
{ refFocus: true },
);
it('should support className when has prefix', () => {
const { container } = render(<InputNumber prefix="suffix" className="my-class-name" />);
expect(container.firstChild.className.includes('my-class-name')).toBe(true);
expect(container.firstChild?.className.includes('my-class-name')).toBe(true);
expect(container.querySelector('input')?.className.includes('my-class-name')).toBe(false);
});
it('should trigger focus when prefix is clicked', () => {
const wrapper = mount(<InputNumber prefix={<i>123</i>} />);
const { container } = render(<InputNumber prefix={<i>123</i>} />);
const mockFocus = jest.spyOn(wrapper.find('input').getDOMNode(), 'focus');
wrapper.find('i').simulate('mouseUp');
const mockFocus = jest.spyOn(container.querySelector('input'), 'focus');
fireEvent.mouseUp(container.querySelector('i'));
expect(mockFocus).toBeCalled();
});
});

View File

@ -42,7 +42,7 @@ export function resolveOnChange<E extends HTMLInputElement | HTMLTextAreaElement
if (!onChange) {
return;
}
let event = e;
let event = e as React.ChangeEvent<E>;
if (e.type === 'click') {
// Clone a new target for event.
@ -66,7 +66,7 @@ export function resolveOnChange<E extends HTMLInputElement | HTMLTextAreaElement
});
currentTarget.value = '';
onChange(event as React.ChangeEvent<E>);
onChange(event);
return;
}
@ -78,10 +78,10 @@ export function resolveOnChange<E extends HTMLInputElement | HTMLTextAreaElement
});
target.value = targetValue;
onChange(event as React.ChangeEvent<E>);
onChange(event);
return;
}
onChange(event as React.ChangeEvent<E>);
onChange(event);
}
export function triggerFocus(

View File

@ -74,13 +74,13 @@ const Password = React.forwardRef<InputRef, PasswordProps>((props, ref) => {
[`${prefixCls}-${size}`]: !!size,
});
const omittedProps = {
const omittedProps: InputProps = {
...omit(restProps, ['suffix', 'iconRender']),
type: visible ? 'text' : 'password',
className: inputClassName,
prefixCls: inputPrefixCls,
suffix: suffixIcon,
} as InputProps;
};
if (size) {
omittedProps.size = size;

View File

@ -166,3 +166,14 @@ const dividerItem = {
### Why will Menu's children be rendered twice?
Menu collects structure info with [twice-render](https://github.com/react-component/menu/blob/f4684514096d6b7123339cbe72e7b0f68db0bce2/src/Menu.tsx#L543) to support HOC usage. Merging into one render may cause the logic to become much more complex. Contributions to help improve the collection logic are welcomed.
### Why Menu do not responsive collapse in Flex layout?
Menu will render fully item in flex layout and then collapse it. You need tell flex not consider Menu width to enable responsive ([online demo](https://codesandbox.io/s/ding-bu-dao-hang-antd-4-21-7-forked-5e3imy?file=/demo.js)):
```jsx
<div style={{ flex }}>
<div style={{ ... }}>Some Content</div>
<Menu style={{ minWidth: 0, flex: "auto" }} />
</div>
```

View File

@ -120,7 +120,7 @@ return <Menu items={items} />;
#### SubMenuType
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- | --- |
| --- | --- | --- | --- | --- |
| children | 子菜单的菜单项 | [ItemType\[\]](#ItemType) | - | |
| disabled | 是否禁用 | boolean | false | |
| icon | 菜单图标 | ReactNode | - | |
@ -129,7 +129,7 @@ return <Menu items={items} />;
| popupClassName | 子菜单样式,`mode="inline"` 时无效 | string | - | |
| popupOffset | 子菜单偏移量,`mode="inline"` 时无效 | \[number, number] | - | |
| onTitleClick | 点击子菜单标题 | function({ key, domEvent }) | - | |
| theme | 设置子菜单的主题,默认从 Menu 上继承 | | `light` \| `dark` | - | |
| theme | 设置子菜单的主题,默认从 Menu 上继承 | `light` \| `dark` | - | |
#### MenuItemGroupType
@ -167,3 +167,14 @@ const dividerItem = {
### 为何 Menu 的子元素会渲染两次?
Menu 通过[二次渲染](https://github.com/react-component/menu/blob/f4684514096d6b7123339cbe72e7b0f68db0bce2/src/Menu.tsx#L543)收集嵌套结构信息以支持 HOC 的结构。合并成一个推导结构会使得逻辑变得十分复杂,欢迎 PR 以协助改进该设计。
### 在 Flex 布局中Menu 没有按照预期响应式省略菜单?
Menu 初始化时会先全部渲染,然后根据宽度裁剪内容。当处于 Flex 布局中,你需要告知其预期宽度为响应式宽度([在线 Demo](https://codesandbox.io/s/ding-bu-dao-hang-antd-4-21-7-forked-5e3imy?file=/demo.js)
```jsx
<div style={{ flex }}>
<div style={{ ... }}>Some Content</div>
<Menu style={{ minWidth: 0, flex: "auto" }} />
</div>
```

View File

@ -305,7 +305,7 @@ describe('Modal.confirm triggers callbacks correctly', () => {
});
describe('should not close modals when click confirm button when onOk has argument', () => {
['info', 'success', 'warning', 'error'].forEach(type => {
['confirm', 'info', 'success', 'warning', 'error'].forEach(type => {
it(type, async () => {
jest.useFakeTimers();
Modal[type]({
@ -318,7 +318,7 @@ describe('Modal.confirm triggers callbacks correctly', () => {
await sleep();
});
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
$$('.ant-btn')[0].click();
$$('.ant-btn-primary')[0].click();
await act(async () => {
jest.runAllTimers();
@ -674,4 +674,149 @@ describe('Modal.confirm triggers callbacks correctly', () => {
const { width } = $$('.ant-modal-body')[0].style;
expect(width).toBe('500px');
});
describe('the callback close should be a method when onCancel has a close parameter', () => {
['confirm', 'info', 'success', 'warning', 'error'].forEach(type => {
it(`click the close icon to trigger ${type} onCancel`, async () => {
jest.useFakeTimers();
const mock = jest.fn();
Modal[type]({
closable: true,
onCancel: close => mock(close),
});
await act(async () => {
jest.runAllTimers();
await sleep();
});
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
$$('.ant-modal-close')[0].click();
await act(async () => {
jest.runAllTimers();
await sleep();
});
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(0);
expect(mock).toBeCalledWith(expect.any(Function));
jest.useRealTimers();
});
});
['confirm', 'info', 'success', 'warning', 'error'].forEach(type => {
it(`press ESC to trigger ${type} onCancel`, async () => {
jest.useFakeTimers();
const mock = jest.fn();
Modal[type]({
keyboard: true,
onCancel: close => mock(close),
});
jest.runAllTimers();
await sleep();
jest.runAllTimers();
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
TestUtils.Simulate.keyDown($$('.ant-modal')[0], {
keyCode: KeyCode.ESC,
});
jest.runAllTimers();
await sleep(0);
jest.runAllTimers();
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(0);
expect(mock).toBeCalledWith(expect.any(Function));
jest.useRealTimers();
});
});
['confirm', 'info', 'success', 'warning', 'error'].forEach(type => {
it(`click the mask to trigger ${type} onCancel`, async () => {
jest.useFakeTimers();
const mock = jest.fn();
Modal[type]({
maskClosable: true,
onCancel: close => mock(close),
});
await act(async () => {
jest.runAllTimers();
await sleep();
});
expect($$('.ant-modal-mask')).toHaveLength(1);
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
$$('.ant-modal-wrap')[0].click();
await act(async () => {
jest.runAllTimers();
await sleep();
});
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(0);
expect(mock).toBeCalledWith(expect.any(Function));
jest.useRealTimers();
});
});
});
it('confirm modal click Cancel button close callback is a function', async () => {
jest.useFakeTimers();
const mock = jest.fn();
Modal.confirm({
onCancel: close => mock(close),
});
await act(async () => {
jest.runAllTimers();
await sleep();
});
$$('.ant-modal-confirm-btns > .ant-btn')[0].click();
await act(async () => {
jest.runAllTimers();
await sleep();
});
expect(mock).toBeCalledWith(expect.any(Function));
jest.useRealTimers();
});
it('close can close modal when onCancel has a close parameter', async () => {
jest.useFakeTimers();
Modal.confirm({
onCancel: close => close(),
});
await act(async () => {
jest.runAllTimers();
await sleep();
});
expect($$('.ant-modal-confirm-confirm')).toHaveLength(1);
$$('.ant-modal-confirm-btns > .ant-btn')[0].click();
await act(async () => {
jest.runAllTimers();
await sleep();
});
expect($$('.ant-modal-confirm-confirm')).toHaveLength(0);
jest.useRealTimers();
});
});

View File

@ -1,9 +1,11 @@
import CSSMotion from 'rc-motion';
import { genCSSMotion } from 'rc-motion/lib/CSSMotion';
import KeyCode from 'rc-util/lib/KeyCode';
import React from 'react';
import { act } from 'react-dom/test-utils';
import TestUtils, { act } from 'react-dom/test-utils';
import Modal from '..';
import { fireEvent, render } from '../../../tests/utils';
import { fireEvent, render, sleep } from '../../../tests/utils';
import Button from '../../button';
import ConfigProvider from '../../config-provider';
import Input from '../../input';
@ -193,4 +195,110 @@ describe('Modal.hook', () => {
fireEvent.click(container.querySelectorAll('.open-hook-modal-btn')[0]);
expect(document.body.classList.contains('ant-modal-confirm-title')).toBeFalsy();
});
it('the callback close should be a method when onCancel has a close parameter', async () => {
jest.useFakeTimers();
const clear = async function clear() {
await act(async () => {
jest.runAllTimers();
await sleep();
});
};
const mockFn = jest.fn();
const Demo = () => {
const [modal, contextHolder] = Modal.useModal();
const openBrokenModal = React.useCallback(() => {
modal.confirm({
closable: true,
keyboard: true,
maskClosable: true,
onCancel: close => mockFn(close),
});
}, [modal]);
return (
<div className="App">
{contextHolder}
<div className="open-hook-modal-btn" onClick={openBrokenModal}>
Test hook modal
</div>
</div>
);
};
const { container } = render(<Demo />);
await clear();
expect(document.body.querySelectorAll('.ant-modal-confirm-confirm')).toHaveLength(0);
// First open
fireEvent.click(container.querySelectorAll('.open-hook-modal-btn')[0]);
await clear();
expect(document.body.querySelectorAll('.ant-modal-confirm-confirm')).toHaveLength(1);
// Click mask to close
fireEvent.click(document.body.querySelectorAll('.ant-modal-wrap')[0]);
await clear();
expect(document.body.querySelectorAll('.ant-modal-confirm-confirm')).toHaveLength(0);
// Second open
fireEvent.click(container.querySelectorAll('.open-hook-modal-btn')[0]);
await clear();
expect(document.body.querySelectorAll('.ant-modal-confirm-confirm')).toHaveLength(1);
// Press ESC to turn off
TestUtils.Simulate.keyDown(document.body.querySelectorAll('.ant-modal')[0], {
keyCode: KeyCode.ESC,
});
await clear();
expect(document.body.querySelectorAll('.ant-modal-confirm-confirm')).toHaveLength(0);
// Third open
fireEvent.click(container.querySelectorAll('.open-hook-modal-btn')[0]);
await clear();
expect(document.body.querySelectorAll('.ant-modal-confirm-confirm')).toHaveLength(1);
// Click the close icon to close
fireEvent.click(document.body.querySelectorAll('.ant-modal-close')[0]);
await clear();
expect(document.body.querySelectorAll('.ant-modal-confirm-confirm')).toHaveLength(0);
// Last open
fireEvent.click(container.querySelectorAll('.open-hook-modal-btn')[0]);
await clear();
expect(document.body.querySelectorAll('.ant-modal-confirm-confirm')).toHaveLength(1);
// Click the Cancel button to close (invalid)
fireEvent.click(document.body.querySelectorAll('.ant-modal-confirm-btns > .ant-btn')[0]);
await clear();
expect(document.body.querySelectorAll('.ant-modal-confirm-confirm')).toHaveLength(1);
mockFn.mockImplementation(close => close());
// Click the Cancel button to close (valid)
fireEvent.click(document.body.querySelectorAll('.ant-modal-confirm-btns > .ant-btn')[0]);
await clear();
expect(document.body.querySelectorAll('.ant-modal-confirm-confirm')).toHaveLength(0);
// Close called 5 times
expect(mockFn).toHaveBeenCalledTimes(5);
expect(mockFn.mock.calls).toEqual(Array.from({ length: 5 }, () => [expect.any(Function)]));
jest.useRealTimers();
});
});

View File

@ -30,7 +30,7 @@ export default function confirm(config: ModalFuncProps) {
function destroy(...args: any[]) {
const triggerCancel = args.some(param => param && param.triggerCancel);
if (config.onCancel && triggerCancel) {
config.onCancel(...args);
config.onCancel(() => {}, ...args.slice(1));
}
for (let i = 0; i < destroyFns.length; i++) {
const fn = destroyFns[i];

View File

@ -1,136 +0,0 @@
@dialog-prefix-cls: ~'@{ant-prefix}-modal';
.@{dialog-prefix-cls} {
.reset-component();
.modal-mask();
position: relative;
top: 100px;
width: auto;
max-width: calc(100vw - 32px);
margin: 0 auto;
padding-bottom: 24px;
&-wrap {
z-index: @zindex-modal;
}
&-title {
margin: 0;
color: @modal-heading-color;
font-weight: 500;
font-size: @modal-header-title-font-size;
line-height: @modal-header-title-line-height;
word-wrap: break-word;
}
&-content {
position: relative;
background-color: @modal-content-bg;
background-clip: padding-box;
border: 0;
border-radius: @modal-border-radius;
box-shadow: @shadow-2;
pointer-events: auto;
}
&-close {
position: absolute;
top: 0;
right: 0;
z-index: @zindex-popup-close;
padding: 0;
color: @modal-close-color;
font-weight: 700;
line-height: 1;
text-decoration: none;
background: transparent;
border: 0;
outline: 0;
cursor: pointer;
transition: color 0.3s;
&-x {
display: block;
width: @modal-header-close-size;
height: @modal-header-close-size;
font-size: @font-size-lg;
font-style: normal;
line-height: @modal-header-close-size;
text-align: center;
text-transform: none;
text-rendering: auto;
}
&:focus,
&:hover {
color: @icon-color-hover;
text-decoration: none;
}
}
&-header {
padding: @modal-header-padding;
color: @text-color;
background: @modal-header-bg;
border-bottom: @modal-header-border-width @modal-header-border-style
@modal-header-border-color-split;
border-radius: @modal-border-radius @modal-border-radius 0 0;
}
&-body {
padding: @modal-body-padding;
font-size: @font-size-base;
line-height: @line-height-base;
word-wrap: break-word;
}
&-footer {
padding: @modal-footer-padding-vertical @modal-footer-padding-horizontal;
text-align: right;
background: @modal-footer-bg;
border-top: @modal-footer-border-width @modal-footer-border-style
@modal-footer-border-color-split;
border-radius: 0 0 @modal-border-radius @modal-border-radius;
.@{ant-prefix}-btn + .@{ant-prefix}-btn:not(.@{ant-prefix}-dropdown-trigger) {
margin-bottom: 0;
margin-left: 8px;
}
}
&-open {
overflow: hidden;
}
}
.@{dialog-prefix-cls}-centered {
text-align: center;
&::before {
display: inline-block;
width: 0;
height: 100%;
vertical-align: middle;
content: '';
}
.@{dialog-prefix-cls} {
top: 0;
display: inline-block;
padding-bottom: 0;
text-align: left;
vertical-align: middle;
}
}
@media (max-width: @screen-sm-max) {
.@{dialog-prefix-cls} {
max-width: calc(100vw - 16px);
margin: 8px auto;
}
.@{dialog-prefix-cls}-centered {
.@{dialog-prefix-cls} {
flex: 1;
}
}
}

View File

@ -36,7 +36,7 @@ const HookModal: React.ForwardRefRenderFunction<HookModalRef, HookModalProps> =
setVisible(false);
const triggerCancel = args.some(param => param && param.triggerCancel);
if (innerConfig.onCancel && triggerCancel) {
innerConfig.onCancel();
innerConfig.onCancel(() => {}, ...args.slice(1));
}
};

View File

@ -25,7 +25,7 @@ exports[`Radio Group passes prefixCls down to radio 1`] = `
</label>
<label
class="my-radio-wrapper"
style="font-size:12px"
style="font-size: 12px;"
>
<span
class="my-radio"

View File

@ -29,6 +29,7 @@ exports[`Radio Button should render correctly 1`] = `
<input
class="ant-radio-button-input"
type="radio"
value=""
/>
<span
class="ant-radio-button-inner"
@ -65,7 +66,7 @@ exports[`Radio Group passes prefixCls down to radio 1`] = `
</label>
<label
class="my-radio-wrapper"
style="font-size:12px"
style="font-size: 12px;"
>
<span
class="my-radio"

View File

@ -54,6 +54,7 @@ exports[`Radio should render correctly 1`] = `
<input
class="ant-radio-input"
type="radio"
value=""
/>
<span
class="ant-radio-inner"

View File

@ -1,8 +1,8 @@
import React from 'react';
import { mount, render } from 'enzyme';
import { fireEvent, render as testLibRender } from '@testing-library/react';
import Radio from '..';
import { render, fireEvent } from '../../../tests/utils';
describe('Radio Group', () => {
function createRadioGroup(props) {
return (
@ -28,32 +28,37 @@ describe('Radio Group', () => {
const onMouseEnter = jest.fn();
const onMouseLeave = jest.fn();
const wrapper = mount(
const { container } = render(
<Radio.Group onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
<Radio />
</Radio.Group>,
);
wrapper.find('div').at(0).simulate('mouseenter');
fireEvent.mouseEnter(container.querySelector('div'));
expect(onMouseEnter).toHaveBeenCalled();
wrapper.find('div').at(0).simulate('mouseleave');
fireEvent.mouseLeave(container.querySelector('div'));
expect(onMouseLeave).toHaveBeenCalled();
});
it('fire change events when value changes', () => {
const onChange = jest.fn();
const wrapper = mount(
const { container, rerender } = render(
createRadioGroup({
onChange,
}),
);
const radios = wrapper.find('input');
const radios = container.querySelectorAll('input');
// controlled component
wrapper.setProps({ value: 'A' });
radios.at(1).simulate('change');
rerender(
createRadioGroup({
onChange,
value: 'A',
}),
);
fireEvent.click(radios[1]);
expect(onChange.mock.calls.length).toBe(1);
});
@ -61,83 +66,95 @@ describe('Radio Group', () => {
const onChange = jest.fn();
const onChangeRadioGroup = jest.fn();
const wrapper = mount(
<Radio.Group onChange={onChangeRadioGroup}>
<Radio value="A" onChange={onChange}>
const RadioGroup = props => (
<Radio.Group onChange={props.onChangeRadioGroup}>
<Radio value="A" onChange={props.onChange}>
A
</Radio>
<Radio value="B" onChange={onChange}>
<Radio value="B" onChange={props.onChange}>
B
</Radio>
<Radio value="C" onChange={onChange}>
<Radio value="C" onChange={props.onChange}>
C
</Radio>
</Radio.Group>,
</Radio.Group>
);
const radios = wrapper.find('input');
const { container, rerender } = render(
<RadioGroup onChangeRadioGroup={onChangeRadioGroup} onChange={onChange} />,
);
const radios = container.querySelectorAll('input');
// controlled component
wrapper.setProps({ value: 'A' });
radios.at(1).simulate('change');
rerender(<RadioGroup value="A" onChangeRadioGroup={onChangeRadioGroup} onChange={onChange} />);
fireEvent.click(radios[1]);
expect(onChange.mock.calls.length).toBe(1);
expect(onChangeRadioGroup.mock.calls.length).toBe(1);
});
it('Trigger onChange when both of radioButton and radioGroup exists', () => {
const onChange = jest.fn();
const wrapper = mount(
<Radio.Group onChange={onChange}>
const RadioGroup = props => (
<Radio.Group {...props}>
<Radio.Button value="A">A</Radio.Button>
<Radio.Button value="B">B</Radio.Button>
<Radio.Button value="C">C</Radio.Button>
</Radio.Group>,
</Radio.Group>
);
const radios = wrapper.find('input');
const { container, rerender } = render(<RadioGroup onChange={onChange} />);
const radios = container.querySelectorAll('input');
// controlled component
wrapper.setProps({ value: 'A' });
radios.at(1).simulate('change');
rerender(<RadioGroup value="A" onChange={onChange} />);
fireEvent.click(radios[1]);
expect(onChange.mock.calls.length).toBe(1);
});
it('should only trigger once when in group with options', () => {
const onChange = jest.fn();
const options = [{ label: 'Bamboo', value: 'Bamboo' }];
const wrapper = mount(<Radio.Group options={options} onChange={onChange} />);
const { container } = render(<Radio.Group options={options} onChange={onChange} />);
wrapper.find('input').simulate('change');
fireEvent.click(container.querySelector('input'));
expect(onChange).toHaveBeenCalledTimes(1);
});
it("won't fire change events when value not changes", () => {
const onChange = jest.fn();
const wrapper = mount(
const { container, rerender } = render(
createRadioGroup({
onChange,
}),
);
const radios = wrapper.find('input');
const radios = container.querySelectorAll('input');
// controlled component
wrapper.setProps({ value: 'A' });
radios.at(0).simulate('change');
rerender(
createRadioGroup({
onChange,
value: 'A',
}),
);
fireEvent.click(radios[0]);
expect(onChange.mock.calls.length).toBe(0);
});
it('optional should correct render', () => {
const wrapper = mount(createRadioGroupByOption());
const radios = wrapper.find('input');
const { container } = render(createRadioGroupByOption());
const radios = container.querySelectorAll('input');
expect(radios.length).toBe(3);
});
it('all children should have a name property', () => {
const GROUP_NAME = 'radiogroup';
const wrapper = mount(createRadioGroup({ name: GROUP_NAME }));
const GROUP_NAME = 'GROUP_NAME';
const { container } = render(createRadioGroup({ name: GROUP_NAME }));
wrapper.find('input[type="radio"]').forEach(el => {
expect(el.props().name).toEqual(GROUP_NAME);
container.querySelectorAll('input[type="radio"]').forEach(el => {
expect(el.name).toEqual(GROUP_NAME);
});
});
@ -146,13 +163,13 @@ describe('Radio Group', () => {
{ label: 'Apple', value: 'Apple' },
{ label: 'Orange', value: 'Orange', style: { fontSize: 12 } },
];
const wrapper = render(<Radio.Group prefixCls="my-radio" options={options} />);
expect(wrapper).toMatchSnapshot();
const { container } = render(<Radio.Group prefixCls="my-radio" options={options} />);
expect(container.firstChild).toMatchSnapshot();
});
it('should forward ref', () => {
let radioGroupRef;
const { container } = testLibRender(
const { container } = render(
createRadioGroupByOption({
ref: ref => {
radioGroupRef = ref;
@ -164,7 +181,7 @@ describe('Radio Group', () => {
});
it('should support data-* or aria-* props', () => {
const { container } = testLibRender(
const { container } = render(
createRadioGroup({
'data-radio-group-id': 'radio-group-id',
'aria-label': 'radio-group',
@ -176,7 +193,7 @@ describe('Radio Group', () => {
it('Radio type should not be override', () => {
const onChange = jest.fn();
const wrapper = mount(
const { container } = render(
<Radio.Group onChange={onChange}>
<Radio value={1} type="1">
A
@ -192,35 +209,28 @@ describe('Radio Group', () => {
</Radio>
</Radio.Group>,
);
const radios = wrapper.find('input');
radios.at(1).simulate('change');
const radios = container.querySelectorAll('input');
fireEvent.click(radios[0]);
expect(onChange).toHaveBeenCalled();
expect(radios.at(1).getDOMNode().type).toBe('radio');
expect(radios[1].type).toBe('radio');
});
describe('value is null or undefined', () => {
it('use `defaultValue` when `value` is undefined', () => {
const options = [{ label: 'Bamboo', value: 'bamboo' }];
const wrapper = mount(
const { container } = render(
<Radio.Group defaultValue="bamboo" value={undefined} options={options} />,
);
expect(wrapper.find('.ant-radio-wrapper').at(0).hasClass('ant-radio-wrapper-checked')).toBe(
true,
);
expect(container.querySelectorAll('.ant-radio-wrapper-checked').length).toBe(1);
});
[undefined, null].forEach(newValue => {
it(`should set value back when value change back to ${newValue}`, () => {
const options = [{ label: 'Bamboo', value: 'bamboo' }];
const wrapper = mount(<Radio.Group value="bamboo" options={options} />);
expect(wrapper.find('.ant-radio-wrapper').at(0).hasClass('ant-radio-wrapper-checked')).toBe(
true,
);
wrapper.setProps({ value: newValue });
wrapper.update();
expect(wrapper.find('.ant-radio-wrapper').at(0).hasClass('ant-radio-wrapper-checked')).toBe(
false,
);
const { container, rerender } = render(<Radio.Group value="bamboo" options={options} />);
expect(container.querySelectorAll('.ant-radio-wrapper-checked').length).toBe(1);
rerender(<Radio.Group value={newValue} options={options} />);
expect(container.querySelectorAll('.ant-radio-wrapper-checked').length).toBe(0);
});
});
});
@ -228,7 +238,7 @@ describe('Radio Group', () => {
it('onBlur & onFocus should work', () => {
const handleBlur = jest.fn();
const handleFocus = jest.fn();
const { container } = testLibRender(
const { container } = render(
<Radio.Group options={['1', '2', '3']} onBlur={handleBlur} onFocus={handleFocus} />,
);
fireEvent.focus(container.firstChild);

View File

@ -1,11 +1,11 @@
import { render as testLibRender } from '@testing-library/react';
import { mount, render } from 'enzyme';
import React from 'react';
import Radio, { Button } from '..';
import focusTest from '../../../tests/shared/focusTest';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { render, fireEvent } from '../../../tests/utils';
describe('Radio Button', () => {
focusTest(Button, { refFocus: true });
mountTest(Button);
@ -13,20 +13,22 @@ describe('Radio Button', () => {
rtlTest(Button);
it('should render correctly', () => {
const wrapper = render(<Button className="customized">Test</Button>);
expect(wrapper).toMatchSnapshot();
const { container } = render(<Button className="customized">Test</Button>);
expect(container.firstChild).toMatchSnapshot();
});
it('responses hover events', () => {
const onMouseEnter = jest.fn();
const onMouseLeave = jest.fn();
const wrapper = mount(<Button onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} />);
const { container } = render(
<Button onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} />,
);
wrapper.find('label').simulate('mouseenter');
fireEvent.mouseEnter(container.querySelector('label'));
expect(onMouseEnter).toHaveBeenCalled();
wrapper.find('label').simulate('mouseleave');
fireEvent.mouseLeave(container.querySelector('label'));
expect(onMouseLeave).toHaveBeenCalled();
});
});
@ -46,32 +48,29 @@ describe('Radio Group', () => {
const onMouseEnter = jest.fn();
const onMouseLeave = jest.fn();
const wrapper = mount(
const { container } = render(
<Radio.Group onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
<Radio />
</Radio.Group>,
);
wrapper.find('div').at(0).simulate('mouseenter');
fireEvent.mouseEnter(container.querySelectorAll('div')[0]);
expect(onMouseEnter).toHaveBeenCalled();
wrapper.find('div').at(0).simulate('mouseleave');
fireEvent.mouseLeave(container.querySelectorAll('div')[0]);
expect(onMouseLeave).toHaveBeenCalled();
});
it('fire change events when value changes', () => {
const onChange = jest.fn();
const wrapper = mount(
createRadioGroup({
onChange,
}),
);
const radios = wrapper.find('input');
const { container, rerender } = render(createRadioGroup({ onChange }));
const radios = container.querySelectorAll('input');
// controlled component
wrapper.setProps({ value: 'A' });
radios.at(1).simulate('change');
rerender(createRadioGroup({ onChange, value: 'A' }));
fireEvent.click(radios[1]);
expect(onChange.mock.calls.length).toBe(1);
});
@ -79,7 +78,7 @@ describe('Radio Group', () => {
const onChange = jest.fn();
const onChangeRadioGroup = jest.fn();
const wrapper = mount(
const { container } = render(
<Radio.Group onChange={onChangeRadioGroup}>
<Radio value="A" onChange={onChange}>
A
@ -92,63 +91,69 @@ describe('Radio Group', () => {
</Radio>
</Radio.Group>,
);
const radios = wrapper.find('input');
const radios = container.querySelectorAll('input');
// controlled component
wrapper.setProps({ value: 'A' });
radios.at(1).simulate('change');
fireEvent.click(radios[1]);
expect(onChange.mock.calls.length).toBe(1);
expect(onChangeRadioGroup.mock.calls.length).toBe(1);
});
it('Trigger onChange when both of Button and radioGroup exists', () => {
const onChange = jest.fn();
const wrapper = mount(
const { container, rerender } = render(
<Radio.Group onChange={onChange}>
<Button value="A">A</Button>
<Button value="B">B</Button>
<Button value="C">C</Button>
</Radio.Group>,
);
const radios = wrapper.find('input');
const radios = container.querySelectorAll('input');
// controlled component
wrapper.setProps({ value: 'A' });
radios.at(1).simulate('change');
rerender(
<Radio.Group value="A" onChange={onChange}>
<Button value="A">A</Button>
<Button value="B">B</Button>
<Button value="C">C</Button>
</Radio.Group>,
);
fireEvent.click(radios[1]);
expect(onChange.mock.calls.length).toBe(1);
});
it('should only trigger once when in group with options', () => {
const onChange = jest.fn();
const options = [{ label: 'Bamboo', value: 'Bamboo' }];
const wrapper = mount(<Radio.Group options={options} onChange={onChange} />);
const { container } = render(<Radio.Group options={options} onChange={onChange} />);
wrapper.find('input').simulate('change');
fireEvent.click(container.querySelector('input'));
expect(onChange).toHaveBeenCalledTimes(1);
});
it("won't fire change events when value not changes", () => {
const onChange = jest.fn();
const wrapper = mount(
const { container, rerender } = render(
createRadioGroup({
onChange,
}),
);
const radios = wrapper.find('input');
const radios = container.querySelectorAll('input');
// controlled component
wrapper.setProps({ value: 'A' });
radios.at(0).simulate('change');
rerender(createRadioGroup({ onChange, value: 'A' }));
fireEvent.click(radios[0]);
expect(onChange.mock.calls.length).toBe(0);
});
it('all children should have a name property', () => {
const GROUP_NAME = 'radiogroup';
const wrapper = mount(createRadioGroup({ name: GROUP_NAME }));
const GROUP_NAME = 'GROUP_NAME';
const { container } = render(createRadioGroup({ name: GROUP_NAME }));
wrapper.find('input[type="radio"]').forEach(el => {
expect(el.props().name).toEqual(GROUP_NAME);
container.querySelectorAll('input[type="radio"]').forEach(el => {
expect(el.name).toEqual(GROUP_NAME);
});
});
@ -157,13 +162,13 @@ describe('Radio Group', () => {
{ label: 'Apple', value: 'Apple' },
{ label: 'Orange', value: 'Orange', style: { fontSize: 12 } },
];
const wrapper = render(<Radio.Group prefixCls="my-radio" options={options} />);
expect(wrapper).toMatchSnapshot();
const { container } = render(<Radio.Group prefixCls="my-radio" options={options} />);
expect(container.firstChild).toMatchSnapshot();
});
it('should forward ref', () => {
let radioGroupRef;
const { container } = testLibRender(
const { container } = render(
createRadioGroup({
ref: ref => {
radioGroupRef = ref;
@ -175,7 +180,7 @@ describe('Radio Group', () => {
});
it('should support data-* or aria-* props', () => {
const { container } = testLibRender(
const { container } = render(
createRadioGroup({
'data-radio-group-id': 'radio-group-id',
'aria-label': 'radio-group',
@ -187,7 +192,7 @@ describe('Radio Group', () => {
it('Radio type should not be override', () => {
const onChange = jest.fn();
const wrapper = mount(
const { container } = render(
<Radio.Group onChange={onChange}>
<Radio value={1} type="1">
A
@ -203,48 +208,36 @@ describe('Radio Group', () => {
</Radio>
</Radio.Group>,
);
const radios = wrapper.find('input');
radios.at(1).simulate('change');
const radios = container.querySelectorAll('input');
fireEvent.click(radios[0]);
expect(onChange).toHaveBeenCalled();
expect(radios.at(1).getDOMNode().type).toBe('radio');
expect(radios[1].type).toBe('radio');
});
describe('value is null or undefined', () => {
it('use `defaultValue` when `value` is undefined', () => {
const wrapper = mount(
const { container } = render(
<Radio.Group defaultValue="bamboo" value={undefined}>
<Button value="bamboo">Bamboo</Button>
</Radio.Group>,
);
expect(
wrapper
.find('.ant-radio-button-wrapper')
.at(0)
.hasClass('ant-radio-button-wrapper-checked'),
).toBe(true);
expect(container.querySelectorAll('.ant-radio-button-wrapper-checked').length).toBe(1);
});
[undefined, null].forEach(newValue => {
it(`should set value back when value change back to ${newValue}`, () => {
const wrapper = mount(
const { container, rerender } = render(
<Radio.Group value="bamboo">
<Button value="bamboo">Bamboo</Button>
</Radio.Group>,
);
expect(
wrapper
.find('.ant-radio-button-wrapper')
.at(0)
.hasClass('ant-radio-button-wrapper-checked'),
).toBe(true);
wrapper.setProps({ value: newValue });
wrapper.update();
expect(
wrapper
.find('.ant-radio-button-wrapper')
.at(0)
.hasClass('ant-radio-button-wrapper-checked'),
).toBe(false);
expect(container.querySelectorAll('.ant-radio-button-wrapper-checked').length).toBe(1);
rerender(
<Radio.Group value={newValue}>
<Button value="bamboo">Bamboo</Button>
</Radio.Group>,
);
expect(container.querySelectorAll('.ant-radio-button-wrapper-checked').length).toBe(0);
});
});
});

View File

@ -1,10 +1,11 @@
import { mount, render } from 'enzyme';
import React from 'react';
import Radio, { Button, Group } from '..';
import focusTest from '../../../tests/shared/focusTest';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { render, fireEvent } from '../../../tests/utils';
describe('Radio', () => {
focusTest(Radio, { refFocus: true });
mountTest(Radio);
@ -16,20 +17,20 @@ describe('Radio', () => {
rtlTest(Button);
it('should render correctly', () => {
const wrapper = render(<Radio className="customized">Test</Radio>);
expect(wrapper).toMatchSnapshot();
const { container } = render(<Radio className="customized">Test</Radio>);
expect(container.firstChild).toMatchSnapshot();
});
it('responses hover events', () => {
const onMouseEnter = jest.fn();
const onMouseLeave = jest.fn();
const wrapper = mount(<Radio onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} />);
const { container } = render(<Radio onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} />);
wrapper.find('label').simulate('mouseenter');
fireEvent.mouseEnter(container.querySelector('label'));
expect(onMouseEnter).toHaveBeenCalled();
wrapper.find('label').simulate('mouseleave');
fireEvent.mouseLeave(container.querySelector('label'));
expect(onMouseLeave).toHaveBeenCalled();
});
});

View File

@ -1665,9 +1665,9 @@ exports[`renders ./components/select/demo/custom-dropdown-menu.md extend context
<div
class="ant-space-item"
>
<a
class="ant-typography"
style="white-space:nowrap"
<button
class="ant-btn ant-btn-text"
type="button"
>
<span
aria-label="plus"
@ -1692,8 +1692,10 @@ exports[`renders ./components/select/demo/custom-dropdown-menu.md extend context
/>
</svg>
</span>
Add item
</a>
<span>
Add item
</span>
</button>
</div>
</div>
</div>

View File

@ -7,16 +7,17 @@ title:
## zh-CN
使用 `dropdownRender` 对下拉菜单进行自由扩展。自定义内容点击时会关闭浮层,如果不喜欢关闭,可以添加 `onMouseDown={e => e.preventDefault()}` 进行阻止(更多详情见 [#13448](https://github.com/ant-design/ant-design/issues/13448))。
使用 `open` 对下拉菜单进行自由扩展。如果希望点击自定义内容后关闭浮层,你需要使用受控模式自行控制([codesandbox](https://codesandbox.io/s/ji-ben-shi-yong-antd-4-21-7-forked-gnp4cy?file=/demo.js))。
## en-US
Customize the dropdown menu via `dropdownRender`. Dropdown menu will be closed if click `dropdownRender` area, you can prevent it by wrapping `onMouseDown={e => e.preventDefault()}` (see more at [#13448](https://github.com/ant-design/ant-design/issues/13448)).
Customize the dropdown menu via `dropdownRender`. If you want to close the dropdown after clicking the custom content, you need to control `open` prop, here is an [codesandbox](https://codesandbox.io/s/ji-ben-shi-yong-antd-4-21-7-forked-gnp4cy?file=/demo.js).
```tsx
import { PlusOutlined } from '@ant-design/icons';
import { Divider, Input, Select, Space, Typography } from 'antd';
import React, { useState } from 'react';
import { Divider, Input, Select, Space, Button } from 'antd';
import type { InputRef } from 'antd';
import React, { useState, useRef } from 'react';
const { Option } = Select;
@ -25,6 +26,7 @@ let index = 0;
const App: React.FC = () => {
const [items, setItems] = useState(['jack', 'lucy']);
const [name, setName] = useState('');
const inputRef = useRef<InputRef>(null);
const onNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setName(event.target.value);
@ -34,6 +36,9 @@ const App: React.FC = () => {
e.preventDefault();
setItems([...items, name || `New item ${index++}`]);
setName('');
setTimeout(() => {
inputRef.current?.focus();
}, 0);
};
return (
@ -44,11 +49,16 @@ const App: React.FC = () => {
<>
{menu}
<Divider style={{ margin: '8px 0' }} />
<Space align="center" style={{ padding: '0 8px 4px' }}>
<Input placeholder="Please enter item" value={name} onChange={onNameChange} />
<Typography.Link onClick={addItem} style={{ whiteSpace: 'nowrap' }}>
<PlusOutlined /> Add item
</Typography.Link>
<Space style={{ padding: '0 8px 4px' }}>
<Input
placeholder="Please enter item"
ref={inputRef}
value={name}
onChange={onNameChange}
/>
<Button type="text" icon={<PlusOutlined />} onClick={addItem}>
Add item
</Button>
</Space>
</>
)}

View File

@ -111,15 +111,15 @@ Select component to select value from options.
It's caused by option with different `label` and `value`. You can use `optionFilterProp="label"` to change filter logic instead.
### The dropdown is closed when click `dropdownRender` area?
### When I click elements in dropdownRender, the select dropdown will not be closed?
Dropdown menu will be closed if click `dropdownRender` area, you can prevent it by wrapping `onMouseDown={e => e.preventDefault()}` (see more at [#13448](https://github.com/ant-design/ant-design/issues/13448)).
You can control it by `open` prop: [codesandbox](https://codesandbox.io/s/ji-ben-shi-yong-antd-4-21-7-forked-gnp4cy?file=/demo.js).
### Why sometime customize Option cause scroll break?
Virtual scroll internal set item height as `32px`. You need to adjust `listItemHeight` when your option height is less and `listHeight` config list container height:
```tsx
```jsx
<Select listItemHeight={10} listHeight={250} />
```

View File

@ -112,9 +112,9 @@ cover: https://gw.alipayobjects.com/zos/alicdn/_0XzgOis7/Select.svg
这一般是 `options` 中的 `label``value` 不同导致的,你可以通过 `optionFilterProp="label"` 将过滤设置为展示值以避免这种情况。
### 点击 `dropdownRender` 里的内容浮层关闭怎么办
### 点击 `dropdownRender` 里的元素,下拉菜单不会自动消失
自定义内容点击时会关闭浮层,如果不喜欢关闭,可以添加 `onMouseDown={e => e.preventDefault()}` 进行阻止(更多详情见 [#13448](https://github.com/ant-design/ant-design/issues/13448)
你可以使用受控模式,手动设置 `open` 属性:[codesandbox](https://codesandbox.io/s/ji-ben-shi-yong-antd-4-21-7-forked-gnp4cy?file=/demo.js)
### 自定义 Option 样式导致滚动异常怎么办?

View File

@ -148,42 +148,6 @@ exports[`renders ./components/space/demo/align.md extend context correctly 1`] =
</div>
</div>
</div>
<div
class="space-align-block"
>
<div
class="ant-space ant-space-horizontal ant-space-align-stretch"
>
<div
class="ant-space-item"
style="margin-right:8px"
>
stretch
</div>
<div
class="ant-space-item"
style="margin-right:8px"
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Primary
</span>
</button>
</div>
<div
class="ant-space-item"
>
<span
class="mock-block"
>
Block
</span>
</div>
</div>
</div>
</div>
`;

View File

@ -148,42 +148,6 @@ exports[`renders ./components/space/demo/align.md correctly 1`] = `
</div>
</div>
</div>
<div
class="space-align-block"
>
<div
class="ant-space ant-space-horizontal ant-space-align-stretch"
>
<div
class="ant-space-item"
style="margin-right:8px"
>
stretch
</div>
<div
class="ant-space-item"
style="margin-right:8px"
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Primary
</span>
</button>
</div>
<div
class="ant-space-item"
>
<span
class="mock-block"
>
Block
</span>
</div>
</div>
</div>
</div>
`;

View File

@ -47,13 +47,6 @@ const App: React.FC = () => (
<span className="mock-block">Block</span>
</Space>
</div>
<div className="space-align-block">
<Space align="stretch">
stretch
<Button type="primary">Primary</Button>
<span className="mock-block">Block</span>
</Space>
</div>
</div>
);

View File

@ -16,7 +16,7 @@ Avoid components clinging together and set a unified space.
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| align | Align items | `start` \| `end` \|`center` \|`baseline` \|`stretch` | - | 4.2.0 |
| align | Align items | `start` \| `end` \|`center` \|`baseline` | - | 4.2.0 |
| direction | The space direction | `vertical` \| `horizontal` | `horizontal` | 4.1.0 |
| size | The space size | [Size](#Size) \| [Size\[\]](#Size) | `small` | 4.1.0 \| Array: 4.9.0 |
| split | Set split | ReactNode | - | 4.7.0 |

View File

@ -24,7 +24,7 @@ export interface SpaceProps extends React.HTMLAttributes<HTMLDivElement> {
size?: SpaceSize | [SpaceSize, SpaceSize];
direction?: 'horizontal' | 'vertical';
// No `stretch` since many components do not support that.
align?: 'start' | 'end' | 'center' | 'baseline' | 'stretch';
align?: 'start' | 'end' | 'center' | 'baseline';
split?: React.ReactNode;
wrap?: boolean;
}

View File

@ -20,7 +20,7 @@ cover: https://gw.alipayobjects.com/zos/antfincdn/wc6%263gJ0Y8/Space.svg
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| align | 对齐方式 | `start` \| `end` \|`center` \|`baseline` \|`stretch` | - | 4.2.0 |
| align | 对齐方式 | `start` \| `end` \|`center` \|`baseline` | - | 4.2.0 |
| direction | 间距方向 | `vertical` \| `horizontal` | `horizontal` | 4.1.0 |
| size | 间距大小 | [Size](#Size) \| [Size\[\]](#Size) | `small` | 4.1.0 \| Array: 4.9.0 |
| split | 设置拆分 | ReactNode | - | 4.7.0 |

View File

@ -36,9 +36,6 @@ const genSpaceStyle: GenerateStyle<SpaceToken> = token => {
'&-baseline': {
alignItems: 'flex-baseline',
},
'&-stretch': {
alignItems: 'stretch',
},
},
[`${componentCls}-space-item`]: {
'&:empty': {

View File

@ -26636,6 +26636,709 @@ Array [
]
`;
exports[`renders ./components/table/demo/tree-table-ellipsis.md extend context correctly 1`] = `
Array [
<div
class="ant-space ant-space-horizontal ant-space-align-center"
style="margin-bottom:16px"
>
<div
class="ant-space-item"
style="margin-right:8px"
>
Fixed first column:
</div>
<div
class="ant-space-item"
>
<button
aria-checked="true"
class="ant-switch ant-switch-checked"
role="switch"
type="button"
>
<div
class="ant-switch-handle"
/>
<span
class="ant-switch-inner"
/>
</button>
</div>
</div>,
<div
class="ant-table-wrapper"
>
<div
class="ant-spin-nested-loading"
>
<div
class="ant-spin-container"
>
<div
class="ant-table ant-table-has-fix-left"
>
<div
class="ant-table-container"
>
<div
class="ant-table-content"
>
<table
style="table-layout:fixed"
>
<colgroup>
<col
class="ant-table-selection-col"
style="width:100px"
/>
<col
style="width:30%"
/>
<col
style="width:12%"
/>
</colgroup>
<thead
class="ant-table-thead"
>
<tr>
<th
class="ant-table-cell ant-table-selection-column ant-table-cell-fix-left"
style="position:sticky;left:0"
>
<div
class="ant-table-selection"
>
<label
class="ant-checkbox-wrapper"
>
<span
class="ant-checkbox"
>
<input
class="ant-checkbox-input"
type="checkbox"
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
</div>
</th>
<th
class="ant-table-cell ant-table-cell-fix-left ant-table-cell-fix-left-last ant-table-cell-ellipsis"
style="position:sticky;left:0"
title="Name"
>
<span
class="ant-table-cell-content"
>
Name
</span>
</th>
<th
class="ant-table-cell"
>
Age
</th>
<th
class="ant-table-cell"
>
Address
</th>
</tr>
</thead>
<tbody
class="ant-table-tbody"
>
<tr
class="ant-table-row ant-table-row-level-0"
data-row-key="1"
>
<td
class="ant-table-cell ant-table-selection-column ant-table-cell-fix-left"
style="position:sticky;left:0"
>
<label
class="ant-checkbox-wrapper"
>
<span
class="ant-checkbox"
>
<input
class="ant-checkbox-input"
type="checkbox"
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
</td>
<td
class="ant-table-cell ant-table-cell-fix-left ant-table-cell-fix-left-last ant-table-cell-ellipsis ant-table-cell-with-append"
style="position:sticky;left:0"
title="John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr."
>
<span
class="ant-table-row-indent indent-level-0"
style="padding-left:0px"
/>
<button
aria-label="Collapse row"
class="ant-table-row-expand-icon ant-table-row-expand-icon-expanded"
type="button"
/>
<span
class="ant-table-cell-content"
>
John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr.
</span>
</td>
<td
class="ant-table-cell"
>
60
</td>
<td
class="ant-table-cell"
>
New York No. 1 Lake Park
</td>
</tr>
<tr
class="ant-table-row ant-table-row-level-1"
data-row-key="11"
>
<td
class="ant-table-cell ant-table-selection-column ant-table-cell-fix-left"
style="position:sticky;left:0"
>
<label
class="ant-checkbox-wrapper"
>
<span
class="ant-checkbox"
>
<input
class="ant-checkbox-input"
type="checkbox"
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
</td>
<td
class="ant-table-cell ant-table-cell-fix-left ant-table-cell-fix-left-last ant-table-cell-ellipsis ant-table-cell-with-append"
style="position:sticky;left:0"
title="John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr."
>
<span
class="ant-table-row-indent indent-level-1"
style="padding-left:15px"
/>
<button
aria-label="Collapse row"
class="ant-table-row-expand-icon ant-table-row-expand-icon-spaced"
type="button"
/>
<span
class="ant-table-cell-content"
>
John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr.
</span>
</td>
<td
class="ant-table-cell"
>
42
</td>
<td
class="ant-table-cell"
>
New York No. 2 Lake Park
</td>
</tr>
<tr
class="ant-table-row ant-table-row-level-1"
data-row-key="12"
>
<td
class="ant-table-cell ant-table-selection-column ant-table-cell-fix-left"
style="position:sticky;left:0"
>
<label
class="ant-checkbox-wrapper"
>
<span
class="ant-checkbox"
>
<input
class="ant-checkbox-input"
type="checkbox"
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
</td>
<td
class="ant-table-cell ant-table-cell-fix-left ant-table-cell-fix-left-last ant-table-cell-ellipsis ant-table-cell-with-append"
style="position:sticky;left:0"
title="John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr."
>
<span
class="ant-table-row-indent indent-level-1"
style="padding-left:15px"
/>
<button
aria-label="Collapse row"
class="ant-table-row-expand-icon ant-table-row-expand-icon-expanded"
type="button"
/>
<span
class="ant-table-cell-content"
>
John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr.
</span>
</td>
<td
class="ant-table-cell"
>
30
</td>
<td
class="ant-table-cell"
>
New York No. 3 Lake Park
</td>
</tr>
<tr
class="ant-table-row ant-table-row-level-2"
data-row-key="121"
>
<td
class="ant-table-cell ant-table-selection-column ant-table-cell-fix-left"
style="position:sticky;left:0"
>
<label
class="ant-checkbox-wrapper"
>
<span
class="ant-checkbox"
>
<input
class="ant-checkbox-input"
type="checkbox"
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
</td>
<td
class="ant-table-cell ant-table-cell-fix-left ant-table-cell-fix-left-last ant-table-cell-ellipsis ant-table-cell-with-append"
style="position:sticky;left:0"
title="John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr."
>
<span
class="ant-table-row-indent indent-level-2"
style="padding-left:30px"
/>
<button
aria-label="Collapse row"
class="ant-table-row-expand-icon ant-table-row-expand-icon-spaced"
type="button"
/>
<span
class="ant-table-cell-content"
>
John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr.
</span>
</td>
<td
class="ant-table-cell"
>
16
</td>
<td
class="ant-table-cell"
>
New York No. 3 Lake Park
</td>
</tr>
<tr
class="ant-table-row ant-table-row-level-1"
data-row-key="13"
>
<td
class="ant-table-cell ant-table-selection-column ant-table-cell-fix-left"
style="position:sticky;left:0"
>
<label
class="ant-checkbox-wrapper"
>
<span
class="ant-checkbox"
>
<input
class="ant-checkbox-input"
type="checkbox"
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
</td>
<td
class="ant-table-cell ant-table-cell-fix-left ant-table-cell-fix-left-last ant-table-cell-ellipsis ant-table-cell-with-append"
style="position:sticky;left:0"
title="Jim Green sr. Jim Green sr. Jim Green sr. Jim Green sr."
>
<span
class="ant-table-row-indent indent-level-1"
style="padding-left:15px"
/>
<button
aria-label="Collapse row"
class="ant-table-row-expand-icon ant-table-row-expand-icon-expanded"
type="button"
/>
<span
class="ant-table-cell-content"
>
Jim Green sr. Jim Green sr. Jim Green sr. Jim Green sr.
</span>
</td>
<td
class="ant-table-cell"
>
72
</td>
<td
class="ant-table-cell"
>
London No. 1 Lake Park
</td>
</tr>
<tr
class="ant-table-row ant-table-row-level-2"
data-row-key="131"
>
<td
class="ant-table-cell ant-table-selection-column ant-table-cell-fix-left"
style="position:sticky;left:0"
>
<label
class="ant-checkbox-wrapper"
>
<span
class="ant-checkbox"
>
<input
class="ant-checkbox-input"
type="checkbox"
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
</td>
<td
class="ant-table-cell ant-table-cell-fix-left ant-table-cell-fix-left-last ant-table-cell-ellipsis ant-table-cell-with-append"
style="position:sticky;left:0"
title="Jim Green. Jim Green. Jim Green. Jim Green. Jim Green. Jim Green."
>
<span
class="ant-table-row-indent indent-level-2"
style="padding-left:30px"
/>
<button
aria-label="Collapse row"
class="ant-table-row-expand-icon ant-table-row-expand-icon-expanded"
type="button"
/>
<span
class="ant-table-cell-content"
>
Jim Green. Jim Green. Jim Green. Jim Green. Jim Green. Jim Green.
</span>
</td>
<td
class="ant-table-cell"
>
42
</td>
<td
class="ant-table-cell"
>
London No. 2 Lake Park
</td>
</tr>
<tr
class="ant-table-row ant-table-row-level-3"
data-row-key="1311"
>
<td
class="ant-table-cell ant-table-selection-column ant-table-cell-fix-left"
style="position:sticky;left:0"
>
<label
class="ant-checkbox-wrapper"
>
<span
class="ant-checkbox"
>
<input
class="ant-checkbox-input"
type="checkbox"
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
</td>
<td
class="ant-table-cell ant-table-cell-fix-left ant-table-cell-fix-left-last ant-table-cell-ellipsis ant-table-cell-with-append"
style="position:sticky;left:0"
title="Jim Green jr. Jim Green jr. Jim Green jr. Jim Green jr."
>
<span
class="ant-table-row-indent indent-level-3"
style="padding-left:45px"
/>
<button
aria-label="Collapse row"
class="ant-table-row-expand-icon ant-table-row-expand-icon-spaced"
type="button"
/>
<span
class="ant-table-cell-content"
>
Jim Green jr. Jim Green jr. Jim Green jr. Jim Green jr.
</span>
</td>
<td
class="ant-table-cell"
>
25
</td>
<td
class="ant-table-cell"
>
London No. 3 Lake Park
</td>
</tr>
<tr
class="ant-table-row ant-table-row-level-3"
data-row-key="1312"
>
<td
class="ant-table-cell ant-table-selection-column ant-table-cell-fix-left"
style="position:sticky;left:0"
>
<label
class="ant-checkbox-wrapper"
>
<span
class="ant-checkbox"
>
<input
class="ant-checkbox-input"
type="checkbox"
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
</td>
<td
class="ant-table-cell ant-table-cell-fix-left ant-table-cell-fix-left-last ant-table-cell-ellipsis ant-table-cell-with-append"
style="position:sticky;left:0"
title="Jimmy Green sr. Jimmy Green sr. Jimmy Green sr."
>
<span
class="ant-table-row-indent indent-level-3"
style="padding-left:45px"
/>
<button
aria-label="Collapse row"
class="ant-table-row-expand-icon ant-table-row-expand-icon-spaced"
type="button"
/>
<span
class="ant-table-cell-content"
>
Jimmy Green sr. Jimmy Green sr. Jimmy Green sr.
</span>
</td>
<td
class="ant-table-cell"
>
18
</td>
<td
class="ant-table-cell"
>
London No. 4 Lake Park
</td>
</tr>
<tr
class="ant-table-row ant-table-row-level-0"
data-row-key="2"
>
<td
class="ant-table-cell ant-table-selection-column ant-table-cell-fix-left"
style="position:sticky;left:0"
>
<label
class="ant-checkbox-wrapper"
>
<span
class="ant-checkbox"
>
<input
class="ant-checkbox-input"
type="checkbox"
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
</td>
<td
class="ant-table-cell ant-table-cell-fix-left ant-table-cell-fix-left-last ant-table-cell-ellipsis ant-table-cell-with-append"
style="position:sticky;left:0"
title="Joe Black"
>
<span
class="ant-table-row-indent indent-level-0"
style="padding-left:0px"
/>
<button
aria-label="Collapse row"
class="ant-table-row-expand-icon ant-table-row-expand-icon-spaced"
type="button"
/>
<span
class="ant-table-cell-content"
>
Joe Black
</span>
</td>
<td
class="ant-table-cell"
>
32
</td>
<td
class="ant-table-cell"
>
Sidney No. 1 Lake Park
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<ul
class="ant-pagination ant-table-pagination ant-table-pagination-right"
unselectable="unselectable"
>
<li
aria-disabled="true"
class="ant-pagination-prev ant-pagination-disabled"
title="Previous Page"
>
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="left"
class="anticon anticon-left"
role="img"
>
<svg
aria-hidden="true"
data-icon="left"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"
/>
</svg>
</span>
</button>
</li>
<li
class="ant-pagination-item ant-pagination-item-1 ant-pagination-item-active"
tabindex="0"
title="1"
>
<a
rel="nofollow"
>
1
</a>
</li>
<li
aria-disabled="true"
class="ant-pagination-next ant-pagination-disabled"
title="Next Page"
>
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="right"
class="anticon anticon-right"
role="img"
>
<svg
aria-hidden="true"
data-icon="right"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"
/>
</svg>
</span>
</button>
</li>
</ul>
</div>
</div>
</div>,
]
`;
exports[`renders ./components/table/demo/virtual-list.md extend context correctly 1`] = `
<div
class="ant-table-wrapper virtual-table"

View File

@ -20815,6 +20815,709 @@ Array [
]
`;
exports[`renders ./components/table/demo/tree-table-ellipsis.md correctly 1`] = `
Array [
<div
class="ant-space ant-space-horizontal ant-space-align-center"
style="margin-bottom:16px"
>
<div
class="ant-space-item"
style="margin-right:8px"
>
Fixed first column:
</div>
<div
class="ant-space-item"
>
<button
aria-checked="true"
class="ant-switch ant-switch-checked"
role="switch"
type="button"
>
<div
class="ant-switch-handle"
/>
<span
class="ant-switch-inner"
/>
</button>
</div>
</div>,
<div
class="ant-table-wrapper"
>
<div
class="ant-spin-nested-loading"
>
<div
class="ant-spin-container"
>
<div
class="ant-table ant-table-has-fix-left"
>
<div
class="ant-table-container"
>
<div
class="ant-table-content"
>
<table
style="table-layout:fixed"
>
<colgroup>
<col
class="ant-table-selection-col"
style="width:100px"
/>
<col
style="width:30%"
/>
<col
style="width:12%"
/>
</colgroup>
<thead
class="ant-table-thead"
>
<tr>
<th
class="ant-table-cell ant-table-selection-column ant-table-cell-fix-left"
style="position:sticky;left:0"
>
<div
class="ant-table-selection"
>
<label
class="ant-checkbox-wrapper"
>
<span
class="ant-checkbox"
>
<input
class="ant-checkbox-input"
type="checkbox"
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
</div>
</th>
<th
class="ant-table-cell ant-table-cell-fix-left ant-table-cell-fix-left-last ant-table-cell-ellipsis"
style="position:sticky;left:0"
title="Name"
>
<span
class="ant-table-cell-content"
>
Name
</span>
</th>
<th
class="ant-table-cell"
>
Age
</th>
<th
class="ant-table-cell"
>
Address
</th>
</tr>
</thead>
<tbody
class="ant-table-tbody"
>
<tr
class="ant-table-row ant-table-row-level-0"
data-row-key="1"
>
<td
class="ant-table-cell ant-table-selection-column ant-table-cell-fix-left"
style="position:sticky;left:0"
>
<label
class="ant-checkbox-wrapper"
>
<span
class="ant-checkbox"
>
<input
class="ant-checkbox-input"
type="checkbox"
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
</td>
<td
class="ant-table-cell ant-table-cell-fix-left ant-table-cell-fix-left-last ant-table-cell-ellipsis ant-table-cell-with-append"
style="position:sticky;left:0"
title="John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr."
>
<span
class="ant-table-row-indent indent-level-0"
style="padding-left:0px"
/>
<button
aria-label="Collapse row"
class="ant-table-row-expand-icon ant-table-row-expand-icon-expanded"
type="button"
/>
<span
class="ant-table-cell-content"
>
John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr.
</span>
</td>
<td
class="ant-table-cell"
>
60
</td>
<td
class="ant-table-cell"
>
New York No. 1 Lake Park
</td>
</tr>
<tr
class="ant-table-row ant-table-row-level-1"
data-row-key="11"
>
<td
class="ant-table-cell ant-table-selection-column ant-table-cell-fix-left"
style="position:sticky;left:0"
>
<label
class="ant-checkbox-wrapper"
>
<span
class="ant-checkbox"
>
<input
class="ant-checkbox-input"
type="checkbox"
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
</td>
<td
class="ant-table-cell ant-table-cell-fix-left ant-table-cell-fix-left-last ant-table-cell-ellipsis ant-table-cell-with-append"
style="position:sticky;left:0"
title="John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr."
>
<span
class="ant-table-row-indent indent-level-1"
style="padding-left:15px"
/>
<button
aria-label="Collapse row"
class="ant-table-row-expand-icon ant-table-row-expand-icon-spaced"
type="button"
/>
<span
class="ant-table-cell-content"
>
John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr.
</span>
</td>
<td
class="ant-table-cell"
>
42
</td>
<td
class="ant-table-cell"
>
New York No. 2 Lake Park
</td>
</tr>
<tr
class="ant-table-row ant-table-row-level-1"
data-row-key="12"
>
<td
class="ant-table-cell ant-table-selection-column ant-table-cell-fix-left"
style="position:sticky;left:0"
>
<label
class="ant-checkbox-wrapper"
>
<span
class="ant-checkbox"
>
<input
class="ant-checkbox-input"
type="checkbox"
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
</td>
<td
class="ant-table-cell ant-table-cell-fix-left ant-table-cell-fix-left-last ant-table-cell-ellipsis ant-table-cell-with-append"
style="position:sticky;left:0"
title="John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr."
>
<span
class="ant-table-row-indent indent-level-1"
style="padding-left:15px"
/>
<button
aria-label="Collapse row"
class="ant-table-row-expand-icon ant-table-row-expand-icon-expanded"
type="button"
/>
<span
class="ant-table-cell-content"
>
John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr.
</span>
</td>
<td
class="ant-table-cell"
>
30
</td>
<td
class="ant-table-cell"
>
New York No. 3 Lake Park
</td>
</tr>
<tr
class="ant-table-row ant-table-row-level-2"
data-row-key="121"
>
<td
class="ant-table-cell ant-table-selection-column ant-table-cell-fix-left"
style="position:sticky;left:0"
>
<label
class="ant-checkbox-wrapper"
>
<span
class="ant-checkbox"
>
<input
class="ant-checkbox-input"
type="checkbox"
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
</td>
<td
class="ant-table-cell ant-table-cell-fix-left ant-table-cell-fix-left-last ant-table-cell-ellipsis ant-table-cell-with-append"
style="position:sticky;left:0"
title="John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr."
>
<span
class="ant-table-row-indent indent-level-2"
style="padding-left:30px"
/>
<button
aria-label="Collapse row"
class="ant-table-row-expand-icon ant-table-row-expand-icon-spaced"
type="button"
/>
<span
class="ant-table-cell-content"
>
John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr.
</span>
</td>
<td
class="ant-table-cell"
>
16
</td>
<td
class="ant-table-cell"
>
New York No. 3 Lake Park
</td>
</tr>
<tr
class="ant-table-row ant-table-row-level-1"
data-row-key="13"
>
<td
class="ant-table-cell ant-table-selection-column ant-table-cell-fix-left"
style="position:sticky;left:0"
>
<label
class="ant-checkbox-wrapper"
>
<span
class="ant-checkbox"
>
<input
class="ant-checkbox-input"
type="checkbox"
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
</td>
<td
class="ant-table-cell ant-table-cell-fix-left ant-table-cell-fix-left-last ant-table-cell-ellipsis ant-table-cell-with-append"
style="position:sticky;left:0"
title="Jim Green sr. Jim Green sr. Jim Green sr. Jim Green sr."
>
<span
class="ant-table-row-indent indent-level-1"
style="padding-left:15px"
/>
<button
aria-label="Collapse row"
class="ant-table-row-expand-icon ant-table-row-expand-icon-expanded"
type="button"
/>
<span
class="ant-table-cell-content"
>
Jim Green sr. Jim Green sr. Jim Green sr. Jim Green sr.
</span>
</td>
<td
class="ant-table-cell"
>
72
</td>
<td
class="ant-table-cell"
>
London No. 1 Lake Park
</td>
</tr>
<tr
class="ant-table-row ant-table-row-level-2"
data-row-key="131"
>
<td
class="ant-table-cell ant-table-selection-column ant-table-cell-fix-left"
style="position:sticky;left:0"
>
<label
class="ant-checkbox-wrapper"
>
<span
class="ant-checkbox"
>
<input
class="ant-checkbox-input"
type="checkbox"
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
</td>
<td
class="ant-table-cell ant-table-cell-fix-left ant-table-cell-fix-left-last ant-table-cell-ellipsis ant-table-cell-with-append"
style="position:sticky;left:0"
title="Jim Green. Jim Green. Jim Green. Jim Green. Jim Green. Jim Green."
>
<span
class="ant-table-row-indent indent-level-2"
style="padding-left:30px"
/>
<button
aria-label="Collapse row"
class="ant-table-row-expand-icon ant-table-row-expand-icon-expanded"
type="button"
/>
<span
class="ant-table-cell-content"
>
Jim Green. Jim Green. Jim Green. Jim Green. Jim Green. Jim Green.
</span>
</td>
<td
class="ant-table-cell"
>
42
</td>
<td
class="ant-table-cell"
>
London No. 2 Lake Park
</td>
</tr>
<tr
class="ant-table-row ant-table-row-level-3"
data-row-key="1311"
>
<td
class="ant-table-cell ant-table-selection-column ant-table-cell-fix-left"
style="position:sticky;left:0"
>
<label
class="ant-checkbox-wrapper"
>
<span
class="ant-checkbox"
>
<input
class="ant-checkbox-input"
type="checkbox"
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
</td>
<td
class="ant-table-cell ant-table-cell-fix-left ant-table-cell-fix-left-last ant-table-cell-ellipsis ant-table-cell-with-append"
style="position:sticky;left:0"
title="Jim Green jr. Jim Green jr. Jim Green jr. Jim Green jr."
>
<span
class="ant-table-row-indent indent-level-3"
style="padding-left:45px"
/>
<button
aria-label="Collapse row"
class="ant-table-row-expand-icon ant-table-row-expand-icon-spaced"
type="button"
/>
<span
class="ant-table-cell-content"
>
Jim Green jr. Jim Green jr. Jim Green jr. Jim Green jr.
</span>
</td>
<td
class="ant-table-cell"
>
25
</td>
<td
class="ant-table-cell"
>
London No. 3 Lake Park
</td>
</tr>
<tr
class="ant-table-row ant-table-row-level-3"
data-row-key="1312"
>
<td
class="ant-table-cell ant-table-selection-column ant-table-cell-fix-left"
style="position:sticky;left:0"
>
<label
class="ant-checkbox-wrapper"
>
<span
class="ant-checkbox"
>
<input
class="ant-checkbox-input"
type="checkbox"
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
</td>
<td
class="ant-table-cell ant-table-cell-fix-left ant-table-cell-fix-left-last ant-table-cell-ellipsis ant-table-cell-with-append"
style="position:sticky;left:0"
title="Jimmy Green sr. Jimmy Green sr. Jimmy Green sr."
>
<span
class="ant-table-row-indent indent-level-3"
style="padding-left:45px"
/>
<button
aria-label="Collapse row"
class="ant-table-row-expand-icon ant-table-row-expand-icon-spaced"
type="button"
/>
<span
class="ant-table-cell-content"
>
Jimmy Green sr. Jimmy Green sr. Jimmy Green sr.
</span>
</td>
<td
class="ant-table-cell"
>
18
</td>
<td
class="ant-table-cell"
>
London No. 4 Lake Park
</td>
</tr>
<tr
class="ant-table-row ant-table-row-level-0"
data-row-key="2"
>
<td
class="ant-table-cell ant-table-selection-column ant-table-cell-fix-left"
style="position:sticky;left:0"
>
<label
class="ant-checkbox-wrapper"
>
<span
class="ant-checkbox"
>
<input
class="ant-checkbox-input"
type="checkbox"
/>
<span
class="ant-checkbox-inner"
/>
</span>
</label>
</td>
<td
class="ant-table-cell ant-table-cell-fix-left ant-table-cell-fix-left-last ant-table-cell-ellipsis ant-table-cell-with-append"
style="position:sticky;left:0"
title="Joe Black"
>
<span
class="ant-table-row-indent indent-level-0"
style="padding-left:0px"
/>
<button
aria-label="Collapse row"
class="ant-table-row-expand-icon ant-table-row-expand-icon-spaced"
type="button"
/>
<span
class="ant-table-cell-content"
>
Joe Black
</span>
</td>
<td
class="ant-table-cell"
>
32
</td>
<td
class="ant-table-cell"
>
Sidney No. 1 Lake Park
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<ul
class="ant-pagination ant-table-pagination ant-table-pagination-right"
unselectable="unselectable"
>
<li
aria-disabled="true"
class="ant-pagination-prev ant-pagination-disabled"
title="Previous Page"
>
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="left"
class="anticon anticon-left"
role="img"
>
<svg
aria-hidden="true"
data-icon="left"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 000 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"
/>
</svg>
</span>
</button>
</li>
<li
class="ant-pagination-item ant-pagination-item-1 ant-pagination-item-active"
tabindex="0"
title="1"
>
<a
rel="nofollow"
>
1
</a>
</li>
<li
aria-disabled="true"
class="ant-pagination-next ant-pagination-disabled"
title="Next Page"
>
<button
class="ant-pagination-item-link"
disabled=""
tabindex="-1"
type="button"
>
<span
aria-label="right"
class="anticon anticon-right"
role="img"
>
<svg
aria-hidden="true"
data-icon="right"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"
/>
</svg>
</span>
</button>
</li>
</ul>
</div>
</div>
</div>,
]
`;
exports[`renders ./components/table/demo/virtual-list.md correctly 1`] = `
<div
class="ant-table-wrapper virtual-table"

View File

@ -0,0 +1,136 @@
---
order: 17.1
title:
en-US: Tree data ellipsis debug demo
zh-CN: 树形数据省略情况测试
debug: true
---
## zh-CN
https://github.com/ant-design/ant-design/issues/36583
## en-US
https://github.com/ant-design/ant-design/issues/36583
```tsx
import { Space, Switch, Table } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import React, { useState } from 'react';
interface DataType {
key: React.ReactNode;
name: string;
age: number;
address: string;
children?: DataType[];
}
const data: DataType[] = [
{
key: 1,
name: 'John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr.',
age: 60,
address: 'New York No. 1 Lake Park',
children: [
{
key: 11,
name: 'John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr.',
age: 42,
address: 'New York No. 2 Lake Park',
},
{
key: 12,
name: 'John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr.',
age: 30,
address: 'New York No. 3 Lake Park',
children: [
{
key: 121,
name: 'John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr. John Brown sr.',
age: 16,
address: 'New York No. 3 Lake Park',
},
],
},
{
key: 13,
name: 'Jim Green sr. Jim Green sr. Jim Green sr. Jim Green sr.',
age: 72,
address: 'London No. 1 Lake Park',
children: [
{
key: 131,
name: 'Jim Green. Jim Green. Jim Green. Jim Green. Jim Green. Jim Green.',
age: 42,
address: 'London No. 2 Lake Park',
children: [
{
key: 1311,
name: 'Jim Green jr. Jim Green jr. Jim Green jr. Jim Green jr.',
age: 25,
address: 'London No. 3 Lake Park',
},
{
key: 1312,
name: 'Jimmy Green sr. Jimmy Green sr. Jimmy Green sr.',
age: 18,
address: 'London No. 4 Lake Park',
},
],
},
],
},
],
},
{
key: 2,
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
},
];
const App: React.FC = () => {
const [fixed, setFixed] = useState(true);
const columns: ColumnsType<DataType> = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
width: '30%',
ellipsis: true,
fixed,
},
{
title: 'Age',
dataIndex: 'age',
key: 'age',
width: '12%',
},
{
title: 'Address',
dataIndex: 'address',
key: 'address',
},
];
return (
<>
<Space align="center" style={{ marginBottom: 16 }}>
Fixed first column: <Switch checked={fixed} onChange={setFixed} />
</Space>
<Table
columns={columns}
rowSelection={{ columnWidth: 100 }}
expandable={{ defaultExpandAllRows: true }}
dataSource={data}
/>
</>
);
};
export default App;
```

View File

@ -17,6 +17,10 @@ const genExpandStyle: GenerateStyle<TableToken, CSSObject> = token => {
tableExpandIconBg,
tableExpandColumnWidth,
radiusBase,
fontSize,
fontSizeSM,
lineHeight,
lineWidth,
tablePaddingVertical,
tablePaddingHorizontal,
tableExpandedRowBg,
@ -35,6 +39,12 @@ const genExpandStyle: GenerateStyle<TableToken, CSSObject> = token => {
[`${componentCls}-row-expand-icon-cell`]: {
textAlign: 'center',
[`${componentCls}-row-expand-icon`]: {
display: 'inline-flex',
float: 'none',
verticalAlign: 'sub',
},
},
[`${componentCls}-row-indent`]: {
@ -44,8 +54,7 @@ const genExpandStyle: GenerateStyle<TableToken, CSSObject> = token => {
[`${componentCls}-row-expand-icon`]: {
...operationUnit(token),
position: 'relative',
display: 'inline-flex',
verticalAlign: 'text-top',
float: 'left',
boxSizing: 'border-box',
width: expandIconSize,
height: expandIconSize,
@ -104,15 +113,12 @@ const genExpandStyle: GenerateStyle<TableToken, CSSObject> = token => {
border: 0,
visibility: 'hidden',
},
[`+ ${componentCls}-cell-content`]: {
display: 'inline-block !important',
width: `calc(100% - (${expandIconSize}px + ${paddingXS}px))`,
verticalAlign: 'top',
},
},
[`${componentCls}-row-indent + ${componentCls}-row-expand-icon`]: {
marginTop:
(fontSize * lineHeight - lineWidth * 3) / 2 -
Math.ceil((fontSizeSM * 1.4 - lineWidth * 3) / 2),
marginInlineEnd: paddingXS,
},

View File

@ -38,6 +38,10 @@ const genFixedStyle: GenerateStyle<TableToken, CSSObject> = token => {
pointerEvents: 'none',
},
[`${componentCls}-cell-fix-left-all::after`]: {
display: 'none',
},
[`
${componentCls}-cell-fix-right-first::after,
${componentCls}-cell-fix-right-last::after

View File

@ -1,49 +0,0 @@
// ================================================================
// = Border Radius =
// ================================================================
.@{table-prefix-cls} {
/* title + table */
&-title {
border-radius: @table-border-radius-base @table-border-radius-base 0 0;
}
&-title + &-container {
border-top-left-radius: 0;
border-top-right-radius: 0;
table {
border-radius: 0;
> thead > tr:first-child {
th:first-child {
border-radius: 0;
}
th:last-child {
border-radius: 0;
}
}
}
}
/* table */
&-container {
border-top-left-radius: @table-border-radius-base;
border-top-right-radius: @table-border-radius-base;
table > thead > tr:first-child {
th:first-child {
border-top-left-radius: @table-border-radius-base;
}
th:last-child {
border-top-right-radius: @table-border-radius-base;
}
}
}
/* table + footer */
&-footer {
border-radius: 0 0 @table-border-radius-base @table-border-radius-base;
}
}

View File

@ -15,13 +15,17 @@ const genRadiusStyle: GenerateStyle<TableToken, CSSObject> = token => {
borderStartStartRadius: 0,
borderStartEndRadius: 0,
'table > thead > tr:first-child': {
'th:first-child': {
borderRadius: 0,
},
table: {
borderRadius: 0,
'th:last-child': {
borderRadius: 0,
'> thead > tr:first-child': {
'th:first-child': {
borderRadius: 0,
},
'th:last-child': {
borderRadius: 0,
},
},
},
},

View File

@ -21,7 +21,7 @@ Ant Design has 3 types of Tabs for different situations.
### Tabs
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| --- | --- | --- | --- | --- | --- |
| activeKey | Current TabPane's key | string | - | |
| addIcon | Customize add icon | ReactNode | - | 4.4.0 |
| animated | Whether to change tabs with animation. Only works while `tabPosition="top"` | boolean \| { inkBar: boolean, tabPane: boolean } | { inkBar: true, tabPane: false } | |
@ -35,7 +35,7 @@ Ant Design has 3 types of Tabs for different situations.
| tabBarExtraContent | Extra content in tab bar | ReactNode \| {left?: ReactNode, right?: ReactNode} | - | object: 4.6.0 |
| tabBarGutter | The gap between tabs | number | - | |
| tabBarStyle | Tab bar style object | CSSProperties | - | |
| tabPosition | Position of tabs | `top` \| `right` \| `bottom` \| `left` | `top` | |
| tabPosition | Position of tabs | `top` \| `right` \| `bottom` \ | `left` | `top` | |
| destroyInactiveTabPane | Whether destroy inactive TabPane when change tab | boolean | false | |
| type | Basic style of tabs | `line` \| `card` \| `editable-card` | `line` | |
| onChange | Callback executed when active tab is changed | function(activeKey) {} | - | |
@ -50,6 +50,7 @@ More option at [rc-tabs option](https://github.com/react-component/tabs#tabs)
| Property | Description | Type | Default |
| --- | --- | --- | --- |
| closeIcon | Customize close icon in TabPane's head. Only works while `type="editable-card"` | ReactNode | - |
| disabled | Set TabPane disabled | boolean | false |
| forceRender | Forced render of content in tabs, not lazy render after clicking on tabs | boolean | false |
| key | TabPane's key | string | - |
| tab | Show text in TabPane's head | ReactNode | - |

View File

@ -27,7 +27,7 @@ Ant Design 依次提供了三级选项卡,分别用于不同的场景。
| --- | --- | --- | --- | --- |
| activeKey | 当前激活 tab 面板的 key | string | - | |
| addIcon | 自定义添加按钮 | ReactNode | - | 4.4.0 |
| animated | 是否使用动画切换 Tabs, 仅生效于 `tabPosition="top"` | boolean \| { inkBar: boolean, tabPane: boolean } | { inkBar: true, tabPane: false } | |
| animated | 是否使用动画切换 Tabs, 仅生效于 `tabPosition="top"` | boolean\| { inkBar: boolean, tabPane: boolean } | { inkBar: true, tabPane: false } | |
| centered | 标签居中展示 | boolean | false | 4.4.0 |
| defaultActiveKey | 初始化选中面板的 key如果没有设置 activeKey | string | `第一个面板` | |
| hideAdd | 是否隐藏加号图标,在 `type="editable-card"` 时有效 | boolean | false | |
@ -46,11 +46,16 @@ Ant Design 依次提供了三级选项卡,分别用于不同的场景。
| onTabClick | tab 被点击的回调 | function(key: string, event: MouseEvent) | - | |
| onTabScroll | tab 滚动时触发 | function({ direction: `left` \| `right` \| `top` \| `bottom` }) | - | 4.3.0 |
> 更多属性查看 [rc-tabs tabs](https://github.com/react-component/tabs#tabs)
### Tabs.TabPane
| 参数 | 说明 | 类型 | 默认值 |
| ----------- | ----------------------------------------------- | --------- | ------ |
| closeIcon | 自定义关闭图标,`在 type="editable-card"`时有效 | ReactNode | - |
| disabled | 禁用某一项 | boolean | false |
| forceRender | 被隐藏时是否渲染 DOM 结构 | boolean | false |
| key | 对应 activeKey | string | - |
| tab | 选项卡头显示文字 | ReactNode | - |
> 更多属性查看 [rc-tabs tabpane](https://github.com/react-component/tabs#tabpane)

View File

@ -11,6 +11,7 @@ import type { ComponentToken as CheckboxComponentToken } from '../checkbox/style
import type { ComponentToken as DatePickerComponentToken } from '../date-picker/style';
import type { ComponentToken as DividerComponentToken } from '../divider/style';
import type { ComponentToken as DropdownComponentToken } from '../dropdown/style';
import type { ComponentToken as DrawerComponentToken } from '../drawer/style';
import type { ComponentToken as EmptyComponentToken } from '../empty/style';
import type { ComponentToken as ImageComponentToken } from '../image/style';
import type { ComponentToken as InputNumberComponentToken } from '../input-number/style';
@ -84,7 +85,7 @@ export interface ComponentTokenMap {
DatePicker?: DatePickerComponentToken;
Descriptions?: {};
Divider?: DividerComponentToken;
Drawer?: {};
Drawer?: DrawerComponentToken;
Dropdown?: DropdownComponentToken;
Empty?: EmptyComponentToken;
Form?: {};

View File

@ -60,7 +60,7 @@ export type RenderFunction = () => React.ReactNode;
export interface TooltipPropsWithOverlay extends AbstractTooltipProps {
title?: React.ReactNode | RenderFunction;
overlay: React.ReactNode | RenderFunction;
overlay?: React.ReactNode | RenderFunction;
}
export interface TooltipPropsWithTitle extends AbstractTooltipProps {

View File

@ -45,7 +45,7 @@ Tree selection control.
| size | To set the size of the select input | `large` \| `middle` \| `small` | - | |
| status | Set validation status | 'error' \| 'warning' | - | 4.19.0 |
| suffixIcon | The custom suffix icon,you must set `showArrow` to `true` manually in multiple selection mode | ReactNode | - | |
| switcherIcon | customize collapse \| expand icon of tree node | ReactNode | - | |
| switcherIcon | Customize collapse/expand icon of tree node | ReactNode \| ((props: AntTreeNodeProps) => ReactNode) | - | renderProps: 4.20.0 |
| tagRender | Customize tag render when `multiple` | (props) => ReactNode | - | |
| treeCheckable | Whether to show checkbox on the treeNodes | boolean | false | |
| treeCheckStrictly | Whether to check nodes precisely (in the `checkable` mode), means parent and child nodes are not associated, and it will make `labelInValue` be true | boolean | false | |

View File

@ -46,7 +46,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/Ax4DA0njr/TreeSelect.svg
| size | 选择框大小 | `large` \| `middle` \| `small` | - | |
| status | 设置校验状态 | 'error' \| 'warning' | - | 4.19.0 |
| suffixIcon | 自定义的选择框后缀图标, 多选模式下必须同时设置 `showArrow` 为 true | ReactNode | - | |
| switcherIcon | 自定义树节点的展开/折叠图标 | ReactNode | - | |
| switcherIcon | 自定义树节点的展开/折叠图标 | ReactNode \| ((props: AntTreeNodeProps) => ReactNode) | - | renderProps: 4.20.0 |
| tagRender | 自定义 tag 内容,多选时生效 | (props) => ReactNode | - | |
| treeCheckable | 显示 Checkbox | boolean | false | |
| treeCheckStrictly | `checkable` 状态下节点选择完全受控(父子节点选中状态不再关联),会使得 `labelInValue` 强制为 true | boolean | false | |

View File

@ -12,7 +12,7 @@ import renderSwitcherIcon from './utils/iconUtil';
import useStyle from './style';
export type SwitcherIcon = React.ReactNode | ((props: { expanded: boolean }) => React.ReactNode);
export type SwitcherIcon = React.ReactNode | ((props: AntTreeNodeProps) => React.ReactNode);
export interface AntdTreeNodeAttribute {
eventKey: string;
@ -97,7 +97,8 @@ export interface AntTreeNodeDropEvent {
// [Legacy] Compatible for v3
export type TreeNodeNormal = DataNode;
type DraggableFn = (node: AntTreeNode) => boolean;
type DraggableFn = (node: DataNode) => boolean;
interface DraggableConfig {
icon?: React.ReactNode | false;
nodeDraggable?: DraggableFn;

View File

@ -76,7 +76,7 @@ describe('Tree', () => {
it('switcherIcon in Tree could be render prop function', () => {
const { container } = render(
<Tree
switcherIcon={expanded =>
switcherIcon={({ expanded }) =>
expanded ? <span className="open" /> : <span className="close" />
}
defaultExpandAll

View File

@ -1,6 +1,7 @@
import type { BasicDataNode } from 'rc-tree';
import * as React from 'react';
import { render } from '../../../tests/utils';
import type { DataNode } from '../index';
import Tree from '../index';
const { DirectoryTree } = Tree;
@ -74,4 +75,25 @@ describe('Tree.TypeScript', () => {
expect(container).toBeTruthy();
});
it('draggable params type', () => {
const { container } = render(
<Tree
treeData={[
{
title: 'Bamboo',
key: 'bamboo',
children: [
{
title: 'Little',
key: 'little',
},
],
},
]}
draggable={(node: DataNode) => node.title === 'Little'}
/>,
);
expect(container).toBeTruthy();
});
});

View File

@ -44,7 +44,7 @@ Almost anything can be represented in a tree structure. Examples include directo
| selectedKeys | (Controlled) Specifies the keys of the selected treeNodes | string\[] | - | |
| showIcon | Shows the icon before a TreeNode's title. There is no default style; you must set a custom style for it if set to true | boolean | false | |
| showLine | Shows a connecting line | boolean \| {showLeafIcon: boolean} | false | |
| switcherIcon | Customize collapse/expand icon of tree node | ReactNode \| (({ expanded: boolean }) => React.ReactNode) | - | renderProps: 4.20.0 |
| switcherIcon | Customize collapse/expand icon of tree node | ReactNode \| ((props: AntTreeNodeProps) => ReactNode) | - | renderProps: 4.20.0 |
| titleRender | Customize tree node title render | (nodeData) => ReactNode | - | 4.5.0 |
| treeData | The treeNodes data Array, if set it then you need not to construct children TreeNode. (key should be unique across the whole array) | array&lt;{ key, title, children, \[disabled, selectable] }> | - | |
| virtual | Disable virtual scroll when set to false | boolean | true | 4.1.0 |

View File

@ -45,7 +45,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/Xh-oWqg9k/Tree.svg
| selectedKeys | (受控)设置选中的树节点 | string\[] | - | |
| showIcon | 是否展示 TreeNode title 前的图标,没有默认样式,如设置为 true需要自行定义图标相关样式 | boolean | false | |
| showLine | 是否展示连接线 | boolean \| {showLeafIcon: boolean} | false | |
| switcherIcon | 自定义树节点的展开/折叠图标 | ReactNode \| (({ expanded: boolean }) => React.ReactNode) | - | renderProps: 4.20.0 |
| switcherIcon | 自定义树节点的展开/折叠图标 | ReactNode \| ((props: AntTreeNodeProps) => ReactNode) | - | renderProps: 4.20.0 |
| titleRender | 自定义渲染节点 | (nodeData) => ReactNode | - | 4.5.0 |
| treeData | treeNodes 数据,如果设置则不需要手动构造 TreeNode 节点key 在整个树范围内唯一) | array&lt;{key, title, children, \[disabled, selectable]}> | - | |
| virtual | 设置 false 时关闭虚拟滚动 | boolean | true | 4.1.0 |

View File

@ -35,8 +35,7 @@ export default function renderSwitcherIcon(
const switcherCls = `${prefixCls}-switcher-icon`;
const switcher =
typeof switcherIcon === 'function' ? switcherIcon({ expanded: !!expanded }) : switcherIcon;
const switcher = typeof switcherIcon === 'function' ? switcherIcon(treeNodeProps) : switcherIcon;
if (isValidElement(switcher)) {
return cloneElement(switcher, {

View File

@ -1,25 +1,26 @@
import * as React from 'react';
import Tooltip from '../../tooltip';
import type { TooltipProps } from '../../tooltip';
export interface EllipsisTooltipProps {
title?: React.ReactNode;
tooltipProps?: TooltipProps;
enabledEllipsis: boolean;
isEllipsis?: boolean;
children: React.ReactElement;
}
const EllipsisTooltip = ({
title,
enabledEllipsis,
isEllipsis,
children,
tooltipProps,
}: EllipsisTooltipProps) => {
if (!title || !enabledEllipsis) {
if (!tooltipProps?.title || !enabledEllipsis) {
return children;
}
return (
<Tooltip title={title} visible={isEllipsis ? undefined : false}>
<Tooltip visible={isEllipsis ? undefined : false} {...tooltipProps}>
{children}
</Tooltip>
);

View File

@ -13,9 +13,10 @@ import { composeRef } from 'rc-util/lib/ref';
import * as React from 'react';
import { ConfigContext } from '../../config-provider';
import { useLocaleReceiver } from '../../locale-provider/LocaleReceiver';
import Tooltip from '../../tooltip';
import { isStyleSupport } from '../../_util/styleChecker';
import TransButton from '../../_util/transButton';
import { isStyleSupport } from '../../_util/styleChecker';
import type { TooltipProps } from '../../tooltip';
import Tooltip from '../../tooltip';
import Editable from '../Editable';
import useMergedConfig from '../hooks/useMergedConfig';
import useUpdatedEffect from '../hooks/useUpdatedEffect';
@ -55,7 +56,7 @@ export interface EllipsisConfig {
symbol?: React.ReactNode;
onExpand?: React.MouseEventHandler<HTMLElement>;
onEllipsis?: (ellipsis: boolean) => void;
tooltip?: React.ReactNode;
tooltip?: React.ReactNode | TooltipProps;
}
export interface BlockProps extends TypographyProps {
@ -309,7 +310,16 @@ const Base = React.forwardRef((props: InternalBlockProps, ref: any) => {
}, [enableEllipsis, cssEllipsis, children, cssLineClamp]);
// ========================== Tooltip ===========================
const tooltipTitle = ellipsisConfig.tooltip === true ? children : ellipsisConfig.tooltip;
let tooltipProps: TooltipProps = {};
if (ellipsisConfig.tooltip === true) {
tooltipProps = { title: children };
} else if (React.isValidElement(ellipsisConfig.tooltip)) {
tooltipProps = { title: ellipsisConfig.tooltip };
} else if (typeof ellipsisConfig.tooltip === 'object') {
tooltipProps = { title: children, ...ellipsisConfig.tooltip };
} else {
tooltipProps = { title: ellipsisConfig.tooltip };
}
const topAriaLabel = React.useMemo(() => {
const isValid = (val: any) => ['string', 'number'].includes(typeof val);
@ -325,12 +335,12 @@ const Base = React.forwardRef((props: InternalBlockProps, ref: any) => {
return title;
}
if (isValid(tooltipTitle)) {
return tooltipTitle;
if (isValid(tooltipProps.title)) {
return tooltipProps.title;
}
return undefined;
}, [enableEllipsis, cssEllipsis, title, tooltipTitle, isMergedEllipsis]);
}, [enableEllipsis, cssEllipsis, title, tooltipProps.title, isMergedEllipsis]);
// =========================== Render ===========================
// >>>>>>>>>>> Editing input
@ -452,7 +462,7 @@ const Base = React.forwardRef((props: InternalBlockProps, ref: any) => {
<ResizeObserver onResize={onResize} disabled={!mergedEnableEllipsis || cssEllipsis}>
{resizeRef => (
<EllipsisTooltip
title={tooltipTitle}
tooltipProps={tooltipProps}
enabledEllipsis={mergedEnableEllipsis}
isEllipsis={isMergedEllipsis}
>

View File

@ -292,6 +292,38 @@ describe('Typography.Ellipsis', () => {
expect(baseElement.querySelector('.ant-tooltip-open')).not.toBeNull();
});
});
it('tooltip props', async () => {
const { container, baseElement } = await getWrapper({
title: 'This is tooltip',
className: 'tooltip-class-name',
});
fireEvent.mouseEnter(container.firstChild);
await waitFor(() => {
expect(container.querySelector('.tooltip-class-name')).toBeTruthy();
expect(baseElement.querySelector('.ant-tooltip-open')).not.toBeNull();
});
});
it('tooltip title true', async () => {
const { container, baseElement } = await getWrapper({
title: true,
className: 'tooltip-class-name',
});
fireEvent.mouseEnter(container.firstChild);
await waitFor(() => {
expect(container.querySelector('.tooltip-class-name')).toBeTruthy();
expect(baseElement.querySelector('.ant-tooltip-open')).not.toBeNull();
});
});
it('tooltip element', async () => {
const { container, baseElement } = await getWrapper(
<div className="tooltip-class-name">title</div>,
);
fireEvent.mouseEnter(container.firstChild);
await waitFor(() => {
expect(container.querySelector('.tooltip-class-name')).toBeTruthy();
expect(baseElement.querySelector('.ant-tooltip-open')).not.toBeNull();
});
});
});
it('js ellipsis should show aria-label', () => {

View File

@ -122,7 +122,7 @@ Basic text writing, including headings, body text, lists, and more.
expandable: boolean,
suffix: string,
symbol: ReactNode,
tooltip: boolean | ReactNode,
tooltip: boolean | ReactNode | TooltipProps,
onExpand: function(event),
onEllipsis: function(ellipsis),
}
@ -133,7 +133,7 @@ Basic text writing, including headings, body text, lists, and more.
| rows | Max rows of content | number | - | |
| suffix | Suffix of ellipsis content | string | - | |
| symbol | Custom description of ellipsis | ReactNode | `Expand` | |
| tooltip | Show tooltip when ellipsis | boolean \| ReactNode | - | 4.11.0 |
| tooltip | Show tooltip when ellipsis | boolean \| ReactNode \| [TooltipProps](/components/tooltip/#API) | - | 4.11.0 |
| onEllipsis | Called when enter or leave ellipsis state | function(ellipsis) | - | 4.2.0 |
| onExpand | Called when expand content | function(event) | - | |

View File

@ -123,20 +123,20 @@ cover: https://gw.alipayobjects.com/zos/alicdn/GOM1KQ24O/Typography.svg
expandable: boolean,
suffix: string,
symbol: ReactNode,
tooltip: boolean | ReactNode,
tooltip: boolean | ReactNode | TooltipProps,
onExpand: function(event),
onEllipsis: function(ellipsis),
}
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| ---------- | -------------------- | -------------------- | ------ | ------ |
| expandable | 是否可展开 | boolean | - | |
| rows | 最多显示的行数 | number | - | |
| suffix | 自定义省略内容后缀 | string | - | |
| symbol | 自定义展开描述文案 | ReactNode | `展开` | |
| tooltip | 省略时,展示提示信息 | boolean \| ReactNode | - | 4.11.0 |
| onEllipsis | 触发省略时的回调 | function(ellipsis) | - | 4.2.0 |
| onExpand | 点击展开时的回调 | function(event) | - | |
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| expandable | 是否可展开 | boolean | - | |
| rows | 最多显示的行数 | number | - | |
| suffix | 自定义省略内容后缀 | string | - | |
| symbol | 自定义展开描述文案 | ReactNode | `展开` | |
| tooltip | 省略时,展示提示信息 | boolean \| ReactNode \| [TooltipProps](/components/tooltip/#API) | - | 4.11.0 |
| onEllipsis | 触发省略时的回调 | function(ellipsis) | - | 4.2.0 |
| onExpand | 点击展开时的回调 | function(event) | - | |
## FAQ

View File

@ -269,8 +269,15 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
const removedFileList = removeFileItem(file, mergedFileList);
if (removedFileList) {
currentFile = { ...file };
currentFile = { ...file, status: 'removed' };
mergedFileList?.forEach(item => {
const matchKey = currentFile.uid !== undefined ? 'uid' : 'name';
if (item[matchKey] === currentFile[matchKey] && !Object.isFrozen(item)) {
item.status = 'removed';
}
});
upload.current?.abort(currentFile);
onInternalChange(currentFile, removedFileList);
}
});

View File

@ -72,6 +72,15 @@ const ListItem = React.forwardRef(
}: ListItemProps,
ref: React.Ref<HTMLDivElement>,
) => {
// Status: which will ignore `removed` status
const { status } = file;
const [mergedStatus, setMergedStatus] = React.useState(status);
React.useEffect(() => {
if (status !== 'removed') {
setMergedStatus(status);
}
}, [status]);
// Delay to show the progress bar
const [showProgress, setShowProgress] = React.useState(false);
const progressRafRef = React.useRef<any>();
@ -88,10 +97,10 @@ const ListItem = React.forwardRef(
const iconNode = iconRender(file);
let icon = <div className={`${prefixCls}-icon`}>{iconNode}</div>;
if (listType === 'picture' || listType === 'picture-card') {
if (file.status === 'uploading' || (!file.thumbUrl && !file.url)) {
if (mergedStatus === 'uploading' || (!file.thumbUrl && !file.url)) {
const uploadingClassName = classNames({
[`${prefixCls}-list-item-thumbnail`]: true,
[`${prefixCls}-list-item-file`]: file.status !== 'uploading',
[`${prefixCls}-list-item-file`]: mergedStatus !== 'uploading',
});
icon = <div className={uploadingClassName}>{iconNode}</div>;
} else {
@ -125,7 +134,7 @@ const ListItem = React.forwardRef(
const listItemClassName = classNames(
`${prefixCls}-list-item`,
`${prefixCls}-list-item-${file.status}`,
`${prefixCls}-list-item-${mergedStatus}`,
);
const linkProps =
typeof file.linkProps === 'string' ? JSON.parse(file.linkProps) : file.linkProps;
@ -142,7 +151,7 @@ const ListItem = React.forwardRef(
: null;
const downloadIcon =
showDownloadIcon && file.status === 'done'
showDownloadIcon && mergedStatus === 'done'
? actionIconRender(
(typeof customDownloadIcon === 'function'
? customDownloadIcon(file)
@ -211,10 +220,10 @@ const ListItem = React.forwardRef(
</a>
) : null;
const pictureCardActions = listType === 'picture-card' && file.status !== 'uploading' && (
const pictureCardActions = listType === 'picture-card' && mergedStatus !== 'uploading' && (
<span className={`${prefixCls}-list-item-actions`}>
{previewIcon}
{file.status === 'done' && downloadIcon}
{mergedStatus === 'done' && downloadIcon}
{removeIcon}
</span>
);
@ -230,7 +239,7 @@ const ListItem = React.forwardRef(
{showProgress && (
<CSSMotion
motionName={`${rootPrefixCls}-fade`}
visible={file.status === 'uploading'}
visible={mergedStatus === 'uploading'}
motionDeadline={2000}
>
{({ className: motionClassName }) => {
@ -256,7 +265,7 @@ const ListItem = React.forwardRef(
? file.response
: file.error?.statusText || file.error?.message || locale.uploadError;
const item =
file.status === 'error' ? (
mergedStatus === 'error' ? (
<Tooltip title={message} getPopupContainer={node => node.parentNode as HTMLElement}>
{dom}
</Tooltip>

View File

@ -110,7 +110,7 @@ exports[`Upload List itemRender 1`] = `
class="custom-item-render"
>
<span>
uid:-1 name: xxx.png status: done url: https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png 1/2
uid:-1 name: xxx.png status: removed url: https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png 1/2
</span>
<span
class="custom-item-render-action-remove"
@ -136,7 +136,7 @@ exports[`Upload List itemRender 1`] = `
class="custom-item-render"
>
<span>
uid:-2 name: yyy.png status: done url: https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png 2/2
uid:-2 name: yyy.png status: removed url: https://zos.alipayobjects.com/rmsportal/IQKRngzUuFzJzGzRJXUs.png 2/2
</span>
<span
class="custom-item-render-action-remove"

View File

@ -505,9 +505,6 @@ describe('Upload', () => {
<Upload fileList={[file]} onChange={onChange} onRemove={onRemove} />,
);
fireEvent.click(container.querySelector('div.ant-upload-list-item .anticon-delete'));
expect(container.querySelector('.ant-upload-list-item').className).toContain(
'ant-upload-list-item-uploading',
);
// uploadStart is a batch work which we need wait for react act
await act(async () => {
@ -520,13 +517,8 @@ describe('Upload', () => {
await removePromise(true);
});
// https://github.com/ant-design/ant-design/issues/36286
expect(container.querySelector('.ant-upload-list-item').className).toContain(
'ant-upload-list-item-uploading',
);
expect(onChange).toHaveBeenCalled();
expect(file.status).toBe('uploading');
expect(file.status).toBe('removed');
});
it('should not stop download when return use onDownload', done => {

View File

@ -1567,4 +1567,45 @@ describe('Upload List', () => {
unmount();
});
});
// https://github.com/ant-design/ant-design/issues/36286
it('remove should keep origin className', async () => {
jest.useFakeTimers();
const onChange = jest.fn();
const list = [
{
uid: '0',
name: 'xxx.png',
status: 'error',
},
];
const { container } = render(
<Upload fileList={list} listType="picture-card" onChange={onChange} />,
);
fireEvent.click(container.querySelector('.anticon-delete'));
// Wait for Upload sync
for (let i = 0; i < 10; i += 1) {
// eslint-disable-next-line no-await-in-loop
await Promise.resolve();
}
act(() => {
jest.runAllTimers();
});
expect(onChange).toHaveBeenCalledWith(
expect.objectContaining({
file: expect.objectContaining({
status: 'removed',
}),
}),
);
expect(container.querySelector('.ant-upload-list-item-error')).toBeTruthy();
jest.useRealTimers();
});
});

View File

@ -57,7 +57,7 @@ Extends File with additional props.
| crossOrigin | CORS settings attributes | `'anonymous'` \| `'use-credentials'` \| `''` | - | 4.20.0 |
| name | File name | string | - | - |
| percent | Upload progress percent | number | - | - |
| status | Upload status. Show different style when configured | `error` \| `success` \| `done` \| `uploading` | - | - |
| status | Upload status. Show different style when configured | `error` \| `success` \| `done` \| `uploading` \| `removed` | - | - |
| thumbUrl | Thumb image url | string | - | - |
| uid | unique id. Will auto generate when not provided | string | - | - |
| url | Download url | string | - | - |
@ -82,7 +82,7 @@ When uploading state change, it returns:
{
uid: 'uid', // unique identifier, negative is recommend, to prevent interference with internal generated id
name: 'xx.png', // file name
status: 'done', // optionsuploading, done, error. Intercepted file by beforeUpload don't have status field.
status: 'done', // optionsuploading, done, error, removed. Intercepted file by beforeUpload don't have status field.
response: '{"status": "success"}', // response from server
linkProps: '{"download": "image"}', // additional html props of file link
xhr: 'XMLHttpRequest{ ... }', // XMLHttpRequest Header

View File

@ -58,7 +58,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/QaeBt_ZMg/Upload.svg
| crossOrigin | CORS 属性设置 | `'anonymous'` \| `'use-credentials'` \| `''` | - | 4.20.0 |
| name | 文件名 | string | - | - |
| percent | 上传进度 | number | - | - |
| status | 上传状态,不同状态展示颜色也会有所不同 | `error` \| `success` \| `done` \| `uploading` | - | - |
| status | 上传状态,不同状态展示颜色也会有所不同 | `error` \| `success` \| `done` \| `uploading` \| `removed` | - | - |
| thumbUrl | 缩略图地址 | string | - | - |
| uid | 唯一标识符,不设置时会自动生成 | string | - | - |
| url | 下载地址 | string | - | - |
@ -83,7 +83,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/QaeBt_ZMg/Upload.svg
{
uid: 'uid', // 文件唯一标识,建议设置为负数,防止和内部产生的 id 冲突
name: 'xx.png' // 文件名
status: 'done', // 状态有uploading done errorbeforeUpload 拦截的文件没有 status 属性
status: 'done', // 状态有uploading done error removedbeforeUpload 拦截的文件没有 status 属性
response: '{"status": "success"}', // 服务端响应内容
linkProps: '{"download": "image"}', // 下载链接额外的 HTML 属性
}

View File

@ -10,7 +10,7 @@ export interface RcFile extends OriRcFile {
readonly lastModifiedDate: Date;
}
export type UploadFileStatus = 'error' | 'success' | 'done' | 'uploading';
export type UploadFileStatus = 'error' | 'success' | 'done' | 'uploading' | 'removed';
export interface HttpRequestHeader {
[key: string]: string;

View File

@ -21,6 +21,10 @@ But in antd, `undefined` is treated as uncontrolled, and `null` is used as an ex
Note: For `options` in `Select-like` components, it is **strongly recommended not** to use `undefined` and `null` as `value` in `option`. Please use `string | number` as a valid `value` in `option`.
## Can I use internal API which is not documented on the site?
NOT RECOMMEND. Internal API is not guaranteed to be compatible with future versions. It may be removed or changed in some versions. If you really need to use it, you should to make sure these API is still valid when upgrading to a new version or just lock version for usage.
## `Select Dropdown DatePicker TimePicker Popover Popconfirm` disappears when I click another popup component inside it. How do I resolve this?
This is an old bug that has been fixed since `v3.11.x`. If you're using an older version, you can use `<Select getPopupContainer={trigger => trigger.parentElement}>` to render a component inside Popover. (Or other `getXxxxContainer` props)

View File

@ -21,6 +21,10 @@ title: FAQ
注意:对于类 `Select` 组件的 `options`,我们**强烈不建议**使用 `undefined``null` 作为 `option` 中的 `value`,请使用 `string | number` 作为 `option``value`
## 官方文档中没有提供的隐藏 API 我可以使用吗?
不推荐。对内接口不保证兼容性,它很可能在某个版本中因重构而移除。如果你确实需要使用,需自行确保版本升级时隐藏接口仍旧可用,或者锁定版本。
## 当我点击 `Select Dropdown DatePicker TimePicker Popover Popconfirm` 内的另一个 popup 组件时它会消失,如何解决?
该问题在 `3.11.0` 后已经解决。如果你仍在使用旧版本,你可以通过 `<Select getPopupContainer={trigger => trigger.parentElement}>` 来在 Popover 中渲染组件,或者使用其他的 `getXxxxContainer` 参数。

View File

@ -49,6 +49,7 @@
"authors": "node ./scripts/generate-authors",
"build": "npm run compile && NODE_OPTIONS='--max-old-space-size=4096' npm run dist",
"bundlesize": "bundlesize",
"size-limit": "size-limit",
"check-commit": "node ./scripts/check-commit",
"check-ts-demo": "node ./scripts/check-ts-demo",
"clean": "antd-tools run clean && rm -rf es lib coverage dist report.html",
@ -123,12 +124,12 @@
"rc-checkbox": "~2.3.0",
"rc-collapse": "~3.3.0",
"rc-dialog": "~8.9.0",
"rc-drawer": "~4.4.2",
"rc-drawer": "~5.1.0-alpha.1",
"rc-dropdown": "~4.0.0",
"rc-field-form": "~1.27.0",
"rc-image": "~5.7.0",
"rc-input": "~0.0.1-alpha.5",
"rc-input-number": "~7.3.0",
"rc-input-number": "~7.3.5",
"rc-mentions": "~1.9.0",
"rc-menu": "~9.6.0",
"rc-motion": "^2.6.1",
@ -143,7 +144,7 @@
"rc-slider": "~10.0.0",
"rc-steps": "~4.1.0",
"rc-switch": "~3.2.0",
"rc-table": "~7.25.0",
"rc-table": "~7.25.3",
"rc-tabs": "~11.16.0",
"rc-textarea": "~0.3.0",
"rc-tooltip": "~5.2.0",
@ -161,6 +162,7 @@
"@ant-design/tools": "^15.0.4",
"@docsearch/css": "^3.0.0",
"@qixian.cs/github-contributors-list": "^1.0.3",
"@size-limit/file": "^7.0.8",
"@stackblitz/sdk": "^1.3.0",
"@testing-library/jest-dom": "^5.16.3",
"@testing-library/react": "^12.0.0",
@ -282,6 +284,7 @@
"scrollama": "^3.0.0",
"semver": "^7.3.5",
"simple-git": "^3.0.0",
"size-limit": "^7.0.8",
"stylelint": "^14.9.0",
"stylelint-config-prettier": "^9.0.2",
"stylelint-config-rational-order": "^0.1.2",
@ -302,6 +305,28 @@
"publishConfig": {
"registry": "https://registry.npmjs.org/"
},
"size-limit": [
{
"path": "./dist/antd.min.js",
"limit": "282 kB"
},
{
"path": "./dist/antd.min.css",
"limit": "66 kB"
},
{
"path": "./dist/antd.dark.min.css",
"limit": "67.5 kB"
},
{
"path": "./dist/antd.compact.min.css",
"limit": "66 kB"
},
{
"path": "./dist/antd.variable.min.css",
"limit": "67 kB"
}
],
"bundlesize": [
{
"path": "./dist/antd.min.js",

View File

@ -7,6 +7,23 @@ const chalk = require('chalk');
const { spawnSync } = require('child_process');
const packageJson = require('../package.json');
const DEPRECIATED_VERSION = {
'>= 4.21.6 < 4.22.0': ['https://github.com/ant-design/ant-design/pull/36682'],
};
function matchDeprecated(version) {
const match = Object.keys(DEPRECIATED_VERSION).find(depreciated =>
semver.satisfies(version, depreciated),
);
const reason = DEPRECIATED_VERSION[match] || [];
return {
match,
reason: Array.isArray(reason) ? reason : [reason],
};
}
const SAFE_DAYS_START = 1000 * 60 * 60 * 24 * 15; // 15 days
const SAFE_DAYS_DIFF = 1000 * 60 * 60 * 24 * 3; // 3 days not update seems to be stable
@ -35,16 +52,27 @@ const SAFE_DAYS_DIFF = 1000 * 60 * 60 * 24 * 3; // 3 days not update seems to be
});
// Slice for choosing the latest versions
const latestVersions = versionList.slice(0, 20).map(version => ({
publishTime: time[version],
timeDiff: dayjs().diff(dayjs(time[version])),
value: version,
}));
const latestVersions = versionList
// Cut off
.slice(0, 30)
// Formatter
.map(version => ({
publishTime: time[version],
timeDiff: dayjs().diff(dayjs(time[version])),
value: version,
depreciated: matchDeprecated(version).match,
}));
const startDefaultVersionIndex = latestVersions.findIndex(
const filteredLatestVersions = latestVersions
// Filter deprecated versions
.filter(({ depreciated }) => !depreciated);
const startDefaultVersionIndex = filteredLatestVersions.findIndex(
({ timeDiff }) => timeDiff >= SAFE_DAYS_START,
);
const defaultVersionList = latestVersions.slice(0, startDefaultVersionIndex + 1).reverse();
const defaultVersionList = filteredLatestVersions
.slice(0, startDefaultVersionIndex + 1)
.reverse();
// Find safe version
let defaultVersionObj;
@ -64,26 +92,52 @@ const SAFE_DAYS_DIFF = 1000 * 60 * 60 * 24 * 3; // 3 days not update seems to be
const defaultVersion = defaultVersionObj ? defaultVersionObj.value : null;
// Selection
const { conchVersion } = await inquirer.prompt([
let { conchVersion } = await inquirer.prompt([
{
type: 'list',
name: 'conchVersion',
default: defaultVersion,
message: 'Please select Conch Version:',
choices: latestVersions.map(info => {
const { value, publishTime } = info;
const { value, publishTime, depreciated } = info;
const desc = dayjs(publishTime).fromNow();
return {
...info,
name: `${value} (${desc}) ${value === defaultVersion ? '(default)' : ''}`,
name: `${depreciated ? '🚨' : '✅'} ${value} (${desc}) ${
value === defaultVersion ? '(default)' : ''
}`,
};
}),
},
]);
// Make sure it's not deprecated version
const deprecatedObj = matchDeprecated(conchVersion);
if (deprecatedObj.match) {
console.log('\n');
console.log(chalk.red('Deprecated For:'));
deprecatedObj.reason.forEach(reason => {
console.log(chalk.yellow(` * ${reason}`));
});
console.log('\n');
const { conchConfirm } = await inquirer.prompt([
{
type: 'confirm',
name: 'conchVersion',
default: false,
message: 'SURE to continue?!!',
},
]);
if (!conchConfirm) {
conchVersion = null;
}
}
// Check if need to update
if (distTags.conch === conchVersion) {
if (!conchVersion || distTags.conch === conchVersion) {
console.log(`🎃 Conch Version not change. Safe to ${chalk.green('ignore')}.`);
} else {
console.log('💾 Tagging Conch Version:', chalk.green(conchVersion));

View File

@ -1,8 +1,9 @@
import * as React from 'react';
import Trigger from 'rc-trigger/lib/mock';
import { TriggerMockContext } from '../shared/demoTest';
import { TriggerMockContext } from '../shared/demoTestContext';
export default React.forwardRef((props, ref) => {
const mergedPopupVisible = React.useContext(TriggerMockContext) ?? props.popupVisible;
global.triggerProps = props;
return <Trigger {...props} ref={ref} popupVisible={mergedPopupVisible} />;
});

View File

@ -1,16 +1,15 @@
/* eslint-disable react/jsx-no-constructed-context-values */
import * as React from 'react';
import glob from 'glob';
import { render } from 'enzyme';
import { render as enzymeRender } from 'enzyme';
import MockDate from 'mockdate';
import dayjs from 'dayjs';
import { StyleProvider, createCache } from '@ant-design/cssinjs';
import type { TriggerProps } from 'rc-trigger';
import { excludeWarning } from './excludeWarning';
import { render, act } from '../utils';
import { TriggerMockContext } from './demoTestContext';
export const TriggerMockContext = React.createContext<Partial<TriggerProps> | undefined>(undefined);
type CheerIO = ReturnType<typeof render>;
type CheerIO = ReturnType<typeof enzymeRender>;
type CheerIOElement = CheerIO[0];
// We should avoid use it in 4.0. Reopen if can not handle this.
const USE_REPLACEMENT = false;
@ -53,6 +52,7 @@ function ariaConvert(wrapper: CheerIO) {
type Options = {
skip?: boolean | string[];
testingLib?: boolean;
};
function baseText(doInject: boolean, component: string, options: Options = {}) {
@ -76,7 +76,7 @@ function baseText(doInject: boolean, component: string, options: Options = {}) {
// Inject cssinjs cache to avoid create <style /> element
Demo = <StyleProvider cache={createCache()}>{Demo}</StyleProvider>;
render(Demo);
enzymeRender(Demo);
expect(errSpy).not.toHaveBeenCalledWith(expect.stringContaining('[Ant Design CSS-in-JS]'));
MockDate.reset();
@ -90,8 +90,9 @@ function baseText(doInject: boolean, component: string, options: Options = {}) {
doInject ? `renders ${file} extend context correctly` : `renders ${file} correctly`,
() => {
const errSpy = excludeWarning();
const mockDate = dayjs('2016-11-22').valueOf();
MockDate.set(dayjs('2016-11-22').valueOf());
MockDate.set(mockDate);
let Demo = require(`../.${file}`).default; // eslint-disable-line global-require, import/no-dynamic-require
// Inject Trigger status unless skipped
Demo = typeof Demo === 'function' ? <Demo /> : Demo;
@ -110,14 +111,29 @@ function baseText(doInject: boolean, component: string, options: Options = {}) {
// Inject cssinjs cache to avoid create <style /> element
Demo = <StyleProvider cache={createCache()}>{Demo}</StyleProvider>;
const wrapper = render(Demo);
if (options?.testingLib) {
jest.useFakeTimers().setSystemTime(mockDate);
// Convert aria related content
ariaConvert(wrapper);
const { container } = render(Demo);
act(() => {
jest.runAllTimers();
});
const { children } = container;
const child = children.length > 1 ? children : children[0];
expect(child).toMatchSnapshot();
jest.useRealTimers();
} else {
const wrapper = enzymeRender(Demo);
// Convert aria related content
ariaConvert(wrapper);
expect(wrapper).toMatchSnapshot();
}
expect(wrapper).toMatchSnapshot();
MockDate.reset();
errSpy();
},
);

View File

@ -0,0 +1,7 @@
/* eslint-disable import/prefer-default-export */
import * as React from 'react';
import type { TriggerProps } from 'rc-trigger';
// We export context here is to avoid testing-lib inject `afterEach` in `tests/index.test.js`
// Which breaks the circle deps
export const TriggerMockContext = React.createContext<Partial<TriggerProps> | undefined>(undefined);