Browse Source

Merge pull request #38577 from ant-design/master

chore: sync 4.x
pull/38623/head
MadCcc 2 years ago
committed by GitHub
parent
commit
17c7526728
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      CHANGELOG.zh-CN.md
  2. 18
      components/breadcrumb/Breadcrumb.tsx
  3. 3
      components/breadcrumb/BreadcrumbItem.tsx
  4. 20
      components/breadcrumb/__tests__/Breadcrumb.test.tsx
  5. 6
      components/dropdown/__tests__/dropdown-button.test.tsx
  6. 2
      components/input/Search.tsx
  7. 9
      components/input/__tests__/Search.test.tsx
  8. 7
      components/modal/Modal.tsx
  9. 15
      components/modal/__tests__/Modal.test.tsx
  10. 11
      components/modal/__tests__/__snapshots__/demo-extend.test.ts.snap
  11. 11
      components/modal/__tests__/__snapshots__/demo.test.ts.snap
  12. 57
      components/modal/demo/custom-mouse-position.md
  13. 2
      components/table/__tests__/Table.pagination.test.tsx
  14. 34
      components/table/__tests__/Table.sorter.test.tsx
  15. 10
      components/table/hooks/useFilter/FilterWrapper.tsx
  16. 3
      components/typography/Base/index.tsx
  17. 20
      components/typography/__tests__/prefixCls.test.tsx
  18. 6
      package.json

6
CHANGELOG.zh-CN.md

@ -110,7 +110,7 @@ timeline: true
- 💄 修复 RTL 下 Input.Search 边框样式问题。[#37980](https://github.com/ant-design/ant-design/pull/37980) [@foryuki](https://github.com/foryuki)
- 🐞 修复 AutoComplete 会报未使用的废弃属性 `dropdownClassName` 的问题。[#37974](https://github.com/ant-design/ant-design/pull/37974) [@heiyu4585](https://github.com/heiyu4585)
- 🐞 修复 Typography 省略算法在计算一些元素 fontSize 时为空字符串的情况[#37928](https://github.com/ant-design/ant-design/pull/37928) [@zheeeng](https://github.com/zheeeng)
- 🐞 Fix Tabs 添加按钮在某些边界情况下无法展示的问题。[#37937](https://github.com/ant-design/ant-design/pull/37937)
- 🐞 修复 Tabs 添加按钮在某些边界情况下无法展示的问题。[#37937](https://github.com/ant-design/ant-design/pull/37937)
- 🐞 修复 RangePicker 在某些情况下面板会闪烁的问题。[#439](https://github.com/react-component/picker/pull/439)
- 🛠 重构 Spin 为 Function Component。[#37969](https://github.com/ant-design/ant-design/pull/37969) [@li-jia-nan](https://github.com/li-jia-nan)
- 🛠 重构 Statistic.Countdown 为 Function Component.[#37938](https://github.com/ant-design/ant-design/pull/37938) [@li-jia-nan](https://github.com/li-jia-nan)
@ -1463,7 +1463,7 @@ timeline: true
- Dropdown
- 🐞 修复 Dropdown 带图标的菜单项禁用样式丢失的问题。[#29433](https://github.com/ant-design/ant-design/pull/29433)
- 🐞 修复 Dropdown 内 Menu 不支持 `expandIcon` 的问题。[#29338](https://github.com/ant-design/ant-design/pull/29338)
- 🐞 Fix 在本地开发时会报 tree-shaking 警告信息的问题。[#29378](https://github.com/ant-design/ant-design/pull/29378)
- 🐞 修复在本地开发时会报 tree-shaking 警告信息的问题。[#29378](https://github.com/ant-design/ant-design/pull/29378)
- 🇰🇷 修复 TimePicker 本地化。[#29540](https://github.com/ant-design/ant-design/pull/29540)
- TypeScript
- 🤖 修复 Form.Item 的泛型定义问题。[#29397](https://github.com/ant-design/ant-design/pull/29397) [@mumiao](https://github.com/mumiao)
@ -1860,7 +1860,7 @@ timeline: true
- Modal
- 🆕 `modal.update()` 支持函数式更新。[#27163](https://github.com/ant-design/ant-design/pull/27163) [@Mongkii](https://github.com/Mongkii)
- 🆕 Modal method 增加 `bodyStyle` 属性。[#27292](https://github.com/ant-design/ant-design/pull/27292)
- 🐞 Fix Modal missing `modalRender` prop。[#27272](https://github.com/ant-design/ant-design/pull/27272) [@jieny](https://github.com/jieny)
- 🐞 修复 Modal 丢失 `modalRender` 属性 TS 定义。[#27272](https://github.com/ant-design/ant-design/pull/27272) [@jieny](https://github.com/jieny)
- 🐞 `Modal.config` 中设置的 `rootPrefixCls` 可以对 `title``content` 下使用的 antd 组件生效。[#27376](https://github.com/ant-design/ant-design/pull/27376) [@Chersquwn](https://github.com/Chersquwn)
- Input
- 🆕 Input.Textarea 支持 `size` 属性。[#27110](https://github.com/ant-design/ant-design/pull/27110)

18
components/breadcrumb/Breadcrumb.tsx

@ -2,9 +2,11 @@ import classNames from 'classnames';
import toArray from 'rc-util/lib/Children/toArray';
import * as React from 'react';
import { ConfigContext } from '../config-provider';
import type { DropdownProps } from '../dropdown';
import Menu from '../menu';
import { cloneElement } from '../_util/reactNode';
import warning from '../_util/warning';
import type { BreadcrumbItemProps } from './BreadcrumbItem';
import BreadcrumbItem from './BreadcrumbItem';
import BreadcrumbSeparator from './BreadcrumbSeparator';
@ -50,7 +52,7 @@ function defaultItemRender(route: Route, params: any, routes: Route[], paths: st
const getPath = (path: string, params: any) => {
path = (path || '').replace(/^\//, '');
Object.keys(params).forEach(key => {
Object.keys(params).forEach((key) => {
path = path.replace(`:${key}`, params[key]);
});
return path;
@ -88,18 +90,18 @@ const Breadcrumb: BreadcrumbInterface = ({
if (routes && routes.length > 0) {
// generated by route
const paths: string[] = [];
crumbs = routes.map(route => {
crumbs = routes.map((route) => {
const path = getPath(route.path, params);
if (path) {
paths.push(path);
}
// generated overlay by route.children
let overlay;
let overlay: DropdownProps['overlay'];
if (route.children && route.children.length) {
overlay = (
<Menu
items={route.children.map(child => ({
items={route.children.map((child) => ({
key: child.path || child.breadcrumbName,
label: itemRender(child, params, routes, addChildPath(paths, child.path, params)),
}))}
@ -107,8 +109,14 @@ const Breadcrumb: BreadcrumbInterface = ({
);
}
const itemProps: BreadcrumbItemProps = { separator };
if (overlay) {
itemProps.overlay = overlay;
}
return (
<BreadcrumbItem overlay={overlay} separator={separator} key={path || route.breadcrumbName}>
<BreadcrumbItem {...itemProps} key={path || route.breadcrumbName}>
{itemRender(route, params, routes, paths)}
</BreadcrumbItem>
);

3
components/breadcrumb/BreadcrumbItem.tsx

@ -1,6 +1,5 @@
import DownOutlined from '@ant-design/icons/DownOutlined';
import * as React from 'react';
import warning from '../_util/warning';
import { ConfigContext } from '../config-provider';
import type { DropdownProps } from '../dropdown/dropdown';
@ -23,7 +22,7 @@ export interface BreadcrumbItemProps {
interface BreadcrumbItemInterface extends React.FC<BreadcrumbItemProps> {
__ANT_BREADCRUMB_ITEM: boolean;
}
const BreadcrumbItem: BreadcrumbItemInterface = props => {
const BreadcrumbItem: BreadcrumbItemInterface = (props) => {
const {
prefixCls: customizePrefixCls,
separator = '/',

20
components/breadcrumb/__tests__/Breadcrumb.test.tsx

@ -181,4 +181,24 @@ describe('Breadcrumb', () => {
expect(container.querySelectorAll('.ant-breadcrumb-link')[1].textContent).toBe('0');
expect(container.firstChild).toMatchSnapshot();
});
it('should console Error when `overlay` in props', () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
render(
<Breadcrumb>
<Breadcrumb.Item overlay={<div>test</div>} />
</Breadcrumb>,
);
expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Breadcrumb.Item] `overlay` is deprecated. Please use `menu` instead.',
);
errSpy.mockRestore();
});
it('should not console Error when `overlay` not in props', () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
render(<Breadcrumb routes={[{ path: '/', breadcrumbName: 'Test' }]} />);
expect(errSpy).not.toHaveBeenCalled();
errSpy.mockRestore();
});
});

6
components/dropdown/__tests__/dropdown-button.test.tsx

@ -14,7 +14,7 @@ jest.mock('../dropdown', () => {
const MockedDropdown: React.FC<DropdownProps> & {
Button: typeof ActualDropdownComponent.Button;
} = props => {
} = (props) => {
dropdownProps = props;
const { children, ...restProps } = props;
return h.createElement(ActualDropdownComponent, { ...restProps }, children);
@ -131,7 +131,7 @@ describe('DropdownButton', () => {
'ant-btn',
);
});
it('should console Error then `overlay` in props', () => {
it('should console Error when `overlay` in props', () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
render(<DropdownButton overlay={<div>test</div>} />);
expect(errSpy).toHaveBeenCalledWith(
@ -139,7 +139,7 @@ describe('DropdownButton', () => {
);
errSpy.mockRestore();
});
it('should not console Error then `overlay` not in props', () => {
it('should not console Error when `overlay` not in props', () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
render(<DropdownButton />);
expect(errSpy).not.toHaveBeenCalled();

2
components/input/Search.tsx

@ -75,7 +75,7 @@ const Search = React.forwardRef<InputRef, SearchProps>((props, ref) => {
};
const onPressEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (composedRef.current) {
if (composedRef.current || loading) {
return;
}
onSearch(e);

9
components/input/__tests__/Search.test.tsx

@ -161,6 +161,15 @@ describe('Input.Search', () => {
expect(asFragmentWithEnterButton().firstChild).toMatchSnapshot();
});
it('should not trigger onSearch when press enter while loading', () => {
const onSearch = jest.fn();
const { container } = render(
<Search loading onSearch={onSearch} />,
);
fireEvent.keyDown(container.querySelector('input')!, { key: 'Enter', keyCode: 13 });
expect(onSearch).not.toHaveBeenCalled();
});
it('should support addonAfter and suffix for loading', () => {
const { asFragment } = render(<Search loading suffix="suffix" addonAfter="addonAfter" />);
const { asFragment: asFragmentWithEnterButton } = render(

7
components/modal/Modal.tsx

@ -15,7 +15,9 @@ import { canUseDocElement } from '../_util/styleChecker';
import warning from '../_util/warning';
import { getConfirmLocale } from './locale';
let mousePosition: { x: number; y: number } | null;
type MousePosition = { x: number; y: number } | null;
let mousePosition: MousePosition;
// ref: https://github.com/ant-design/ant-design/issues/15795
const getClickPosition = (e: MouseEvent) => {
@ -91,6 +93,7 @@ export interface ModalProps {
modalRender?: (node: React.ReactNode) => React.ReactNode;
focusTriggerAfterClose?: boolean;
children?: React.ReactNode;
mousePosition?: MousePosition;
}
type getContainerFunc = () => HTMLElement;
@ -231,7 +234,7 @@ const Modal: React.FC<ModalProps> = props => {
wrapClassName={wrapClassNameExtended}
footer={footer === undefined ? defaultFooter : footer}
visible={open || visible}
mousePosition={mousePosition}
mousePosition={restProps.mousePosition ?? mousePosition}
onClose={handleCancel}
closeIcon={closeIconToRender}
focusTriggerAfterClose={focusTriggerAfterClose}

15
components/modal/__tests__/Modal.test.tsx

@ -92,6 +92,21 @@ describe('Modal', () => {
).toBeTruthy();
});
it('custom mouse position', () => {
const Demo = () => {
const containerRef = React.useRef<HTMLDivElement>(null);
return (
<div ref={containerRef}>
<Modal open getContainer={() => containerRef.current!} mousePosition={{x: 100, y: 100}} />
</div>
);
};
const { container } = render(<Demo />);
expect(
(container.querySelectorAll('.ant-modal')[0] as HTMLDivElement).style.transformOrigin,
).toBe('100px 100px');
});
it('deprecated warning', () => {
resetWarned();
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

11
components/modal/__tests__/__snapshots__/demo-extend.test.ts.snap

@ -104,6 +104,17 @@ exports[`renders ./components/modal/demo/confirm-router.md extend context correc
</button>
`;
exports[`renders ./components/modal/demo/custom-mouse-position.md extend context correctly 1`] = `
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Open Modal
</span>
</button>
`;
exports[`renders ./components/modal/demo/dark.md extend context correctly 1`] = `
<button
class="ant-btn ant-btn-primary"

11
components/modal/__tests__/__snapshots__/demo.test.ts.snap

@ -104,6 +104,17 @@ exports[`renders ./components/modal/demo/confirm-router.md correctly 1`] = `
</button>
`;
exports[`renders ./components/modal/demo/custom-mouse-position.md correctly 1`] = `
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Open Modal
</span>
</button>
`;
exports[`renders ./components/modal/demo/dark.md correctly 1`] = `
<button
class="ant-btn ant-btn-primary"

57
components/modal/demo/custom-mouse-position.md

@ -0,0 +1,57 @@
---
order: 999
title:
zh-CN: 控制弹框动画原点
en-US: control modal's animation origin position
debug: true
---
## zh-CN
通过 `mousePosition` 控制弹框动画原点.
## en-US
pass `mousePosition` to control modal's animation origin position
```tsx
import { Button, Modal } from 'antd';
import React, { useState } from 'react';
const App: React.FC = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const showModal = () => {
setIsModalOpen(true);
};
const handleOk = () => {
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
return (
<>
<Button type="primary" onClick={showModal}>
Open Modal
</Button>
<Modal
title="Basic Modal"
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
mousePosition={{ x: 300, y: 300 }}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
</>
);
};
export default App;
```

2
components/table/__tests__/Table.pagination.test.tsx

@ -418,7 +418,7 @@ describe('Table.pagination', () => {
* to `pagination`, since they misunderstand that `pagination` can accept a boolean value.
*/
it('Accepts pagination as true', () => {
const { asFragment } = render(createTable({ pagination: true } as TableProps<any>));
const { asFragment } = render(createTable({ pagination: true as any } as TableProps<any>));
expect(asFragment().firstChild).toMatchSnapshot();
});

34
components/table/__tests__/Table.sorter.test.tsx

@ -39,7 +39,7 @@ describe('Table.sorter', () => {
container
?.querySelector('.ant-table-tbody')
?.querySelectorAll('tr')
?.forEach(tr => {
?.forEach((tr) => {
namesList.push(tr.querySelector('td')?.textContent);
});
return namesList;
@ -129,7 +129,7 @@ describe('Table.sorter', () => {
expect(getNameColumn()?.getAttribute('aria-sort')).toEqual('descending');
});
it('sort records with keydown', () => {
it('sort records when press enter', () => {
const { container } = render(createTable());
// ascend
@ -141,6 +141,30 @@ describe('Table.sorter', () => {
expect(renderedNames(container)).toEqual(['Tom', 'Lucy', 'Jack', 'Jerry']);
});
// https://github.com/ant-design/ant-design/issues/38579
it('should not sort records when press enter in filter dropdown', () => {
const { container } = render(
createTable({
columns: [
{
...column,
filters: [{ text: 'J', value: 'J' }],
onFilter: (value: any, record: any) => record.name.includes(value),
filterDropdownOpen: true,
},
],
}),
);
// don't trigger ascend
fireEvent.keyDown(container.querySelector('.ant-table-filter-dropdown')!, { keyCode: 13 });
expect(renderedNames(container)).toEqual(['Jack', 'Lucy', 'Tom', 'Jerry']);
// don't trigger descend
fireEvent.keyDown(container.querySelector('.ant-table-filter-dropdown')!, { keyCode: 13 });
expect(renderedNames(container)).toEqual(['Jack', 'Lucy', 'Tom', 'Jerry']);
});
describe('can be controlled by sortOrder', () => {
it('single', () => {
const { container } = render(
@ -434,7 +458,7 @@ describe('Table.sorter', () => {
class TableTest extends React.Component {
state = { pagination: {} };
onChange: TableProps<any>['onChange'] = pagination => {
onChange: TableProps<any>['onChange'] = (pagination) => {
this.setState({ pagination });
};
@ -494,7 +518,7 @@ describe('Table.sorter', () => {
pagination: {},
};
onChange: TableProps<any>['onChange'] = pagination => {
onChange: TableProps<any>['onChange'] = (pagination) => {
this.setState({ pagination });
};
@ -561,7 +585,7 @@ describe('Table.sorter', () => {
pagination: {},
};
onChange: TableProps<any>['onChange'] = pagination => {
onChange: TableProps<any>['onChange'] = (pagination) => {
this.setState({ pagination });
};

10
components/table/hooks/useFilter/FilterWrapper.tsx

@ -1,12 +1,20 @@
import * as React from 'react';
import KeyCode from 'rc-util/lib/KeyCode';
export interface FilterDropdownMenuWrapperProps {
children?: React.ReactNode;
className?: string;
}
const onKeyDown: React.KeyboardEventHandler<HTMLDivElement> = (event) => {
const { keyCode } = event;
if (keyCode === KeyCode.ENTER) {
event.stopPropagation();
}
};
const FilterDropdownMenuWrapper = (props: FilterDropdownMenuWrapperProps) => (
<div className={props.className} onClick={e => e.stopPropagation()}>
<div className={props.className} onClick={(e) => e.stopPropagation()} onKeyDown={onKeyDown}>
{props.children}
</div>
);

3
components/typography/Base/index.tsx

@ -277,7 +277,7 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
const cssLineClamp = mergedEnableEllipsis && rows > 1 && cssEllipsis;
// >>>>> Expand
const onExpandClick: React.MouseEventHandler<HTMLElement> = e => {
const onExpandClick: React.MouseEventHandler<HTMLElement> = (e) => {
setExpanded(true);
ellipsisConfig.onExpand?.(e);
};
@ -510,6 +510,7 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
},
className,
)}
prefixCls={customizePrefixCls}
style={{
...style,
WebkitLineClamp: cssLineClamp ? rows : undefined,

20
components/typography/__tests__/prefixCls.test.tsx

@ -0,0 +1,20 @@
import React from 'react';
import { render } from '../../../tests/utils';
import Base from '../Base';
describe('Typography keep prefixCls', () => {
describe('Base', () => {
it('should support className when has prefix', () => {
const { container: wrapper } = render(
<Base component="p" prefixCls="custom-prefixCls" className="custom-class">
test prefixCls
</Base>,
);
expect(
(wrapper.firstChild as HTMLElement)?.className.includes('custom-prefixCls'),
).toBeTruthy();
expect((wrapper.firstChild as HTMLElement)?.className.includes('custom-class')).toBeTruthy();
});
});
});

6
package.json

@ -293,7 +293,7 @@
"stylelint-declaration-block-no-ignored-properties": "^2.1.0",
"stylelint-order": "^5.0.0",
"theme-switcher": "^1.0.2",
"typescript": "~4.8.4",
"typescript": "~4.9.3",
"webpack-bundle-analyzer": "^4.1.0",
"xhr-mock": "^2.4.1",
"yaml-front-matter": "^4.0.0"
@ -353,7 +353,7 @@
"mode": "npm"
},
"lint-staged": {
"*.{ts,tsx,js,json}": "rome format --write",
"*.{less,md}": "prettier --ignore-unknown --write"
"*.{ts,tsx,js}": "rome format --write",
"*.{json,less,md}": "prettier --ignore-unknown --write"
}
}

Loading…
Cancel
Save