mirror of
https://github.com/ant-design/ant-design.git
synced 2025-06-07 09:26:06 +08:00
chore: sync master into feature
This commit is contained in:
commit
2542d2424e
16
.github/dependabot.yml
vendored
16
.github/dependabot.yml
vendored
@ -9,26 +9,22 @@ updates:
|
||||
directory: /
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "05:00"
|
||||
timezone: Asia/Shanghai
|
||||
groups:
|
||||
rc-component-patch:
|
||||
dependency-type: production
|
||||
patterns:
|
||||
- "rc-*"
|
||||
- "@rc-component*"
|
||||
update-types: [patch]
|
||||
dependencies:
|
||||
dependency-type: production
|
||||
exclude-patterns:
|
||||
- "rc-*"
|
||||
- "@rc-component*"
|
||||
update-types: [major, minor]
|
||||
dev-dependencies:
|
||||
dependency-type: development
|
||||
update-types: [major]
|
||||
ignore:
|
||||
- dependency-name: "@ant-design/cssinjs"
|
||||
- dependency-name: "rc-*"
|
||||
- dependency-name: "@rc-component*"
|
||||
- dependency-name: "@ant-design*"
|
||||
- dependency-name: dayjs
|
||||
versions: [1.x]
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
|
@ -75,5 +75,6 @@
|
||||
"https://github.com/ant-design/ant-design/issues/51420",
|
||||
"https://github.com/ant-design/ant-design/issues/51430"
|
||||
],
|
||||
"5.22.6": ["https://github.com/ant-design/ant-design/issues/52124"]
|
||||
"5.22.6": ["https://github.com/ant-design/ant-design/issues/52124"],
|
||||
"5.24.8": ["https://github.com/ant-design/ant-design/issues/53652"]
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
[![CI status][github-action-image]][github-action-url] [![codecov][codecov-image]][codecov-url] [![NPM version][npm-image]][npm-url] [![NPM downloads][download-image]][download-url]
|
||||
|
||||
[![][bundlephobia-image]][bundlephobia-url] [![][jsdelivr-image]][jsdelivr-url] [![FOSSA Status][fossa-image]][fossa-url]
|
||||
[![][bundlephobia-image]][bundlephobia-url] [![][jsdelivr-image]][jsdelivr-url] [![FOSSA Status][fossa-image]][fossa-url] [![DeepWiki][deepwiki-image]][deepwiki-url]
|
||||
|
||||
[![Follow Twitter][twitter-image]][twitter-url] [![Renovate status][renovate-image]][renovate-dashboard-url] [![][issues-helper-image]][issues-helper-url] [![dumi][dumi-image]][dumi-url] [![Issues need help][help-wanted-image]][help-wanted-url]
|
||||
|
||||
@ -43,6 +43,8 @@
|
||||
[dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square
|
||||
[dumi-url]: https://github.com/umijs/dumi
|
||||
[github-issues-url]: https://new-issue.ant.design
|
||||
[deepwiki-url]: https://deepwiki.com/ant-design/ant-design
|
||||
[deepwiki-image]: https://img.shields.io/badge/Chat%20with-DeepWiki%20🤖-20B2AA?style=flat-square
|
||||
|
||||
</div>
|
||||
|
||||
@ -191,8 +193,7 @@ $ npm start
|
||||
|
||||
<a href="https://openomy.app/github/ant-design/ant-design" target="_blank" style="display: block; width: 100%;" align="center">
|
||||
<img src="https://openomy.app/svg?repo=ant-design/ant-design&chart=bubble&latestMonth=3" target="_blank" alt="Contribution Leaderboard" style="display: block; width: 100%;" />
|
||||
</a>
|
||||
|
||||
</a>
|
||||
|
||||
请参考[贡献指南](https://ant.design/docs/react/contributing-cn).
|
||||
|
||||
@ -214,8 +215,6 @@ $ npm start
|
||||
|
||||
## Issue 赞助
|
||||
|
||||
我们使用 [Polar.sh](https://polar.sh/ant-design) 和 [Issuehunt](https://issuehunt.io/repos/3452688) 来推动您希望看到的针对 antd 的修复和改进,请查看我们的赞助列表:
|
||||
|
||||
<a href="https://polar.sh/ant-design"><img src="https://polar.sh/embed/fund-our-backlog.svg?org=ant-design" /></a>
|
||||
我们使用 [Issuehunt](https://issuehunt.io/repos/3452688) 来推动您希望看到的针对 antd 的修复和改进,请查看我们的赞助列表:
|
||||
|
||||
[](https://issuehunt.io/repos/34526884)
|
||||
|
11
README.md
11
README.md
@ -8,7 +8,7 @@ An enterprise-class UI design language and React UI library.
|
||||
|
||||
[![CI status][github-action-image]][github-action-url] [![codecov][codecov-image]][codecov-url] [![NPM version][npm-image]][npm-url] [![NPM downloads][download-image]][download-url]
|
||||
|
||||
[![][bundlephobia-image]][bundlephobia-url] [![][jsdelivr-image]][jsdelivr-url] [![FOSSA Status][fossa-image]][fossa-url]
|
||||
[![][bundlephobia-image]][bundlephobia-url] [![][jsdelivr-image]][jsdelivr-url] [![FOSSA Status][fossa-image]][fossa-url] [![DeepWiki][deepwiki-image]][deepwiki-url]
|
||||
|
||||
[![Follow Twitter][twitter-image]][twitter-url] [![Renovate status][renovate-image]][renovate-dashboard-url] [![][issues-helper-image]][issues-helper-url] [![dumi][dumi-image]][dumi-url] [![Issues need help][help-wanted-image]][help-wanted-url]
|
||||
|
||||
@ -43,6 +43,8 @@ An enterprise-class UI design language and React UI library.
|
||||
[dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square
|
||||
[dumi-url]: https://github.com/umijs/dumi
|
||||
[github-issues-url]: https://new-issue.ant.design
|
||||
[deepwiki-url]: https://deepwiki.com/ant-design/ant-design
|
||||
[deepwiki-image]: https://img.shields.io/badge/Chat%20with-DeepWiki%20🤖-20B2AA?style=flat-square
|
||||
|
||||
</div>
|
||||
|
||||
@ -173,8 +175,7 @@ Open your browser and visit http://127.0.0.1:8001, see more at [Development](htt
|
||||
|
||||
<a href="https://openomy.app/github/ant-design/ant-design" target="_blank" style="display: block; width: 100%;" align="center">
|
||||
<img src="https://openomy.app/svg?repo=ant-design/ant-design&chart=bubble&latestMonth=3" target="_blank" alt="Contribution Leaderboard" style="display: block; width: 100%;" />
|
||||
</a>
|
||||
|
||||
</a>
|
||||
|
||||
Let's build a better antd together.
|
||||
|
||||
@ -184,8 +185,6 @@ For collaborators, adhere to our [Pull Request Principle](https://github.com/ant
|
||||
|
||||
## Issue funding
|
||||
|
||||
We use [Polar.sh](https://polar.sh/ant-design) and [Issuehunt](https://issuehunt.io/repos/3452688) to up-vote and promote specific features that you would like to see and implement. Check our backlog and help us:
|
||||
|
||||
<a href="https://polar.sh/ant-design"><img src="https://polar.sh/embed/fund-our-backlog.svg?org=ant-design" /></a>
|
||||
We use [Issuehunt](https://issuehunt.io/repos/3452688) to up-vote and promote specific features that you would like to see and implement. Check our backlog and help us:
|
||||
|
||||
[](https://issuehunt.io/repos/34526884)
|
||||
|
@ -238,7 +238,8 @@ const Affix = React.forwardRef<AffixRef, InternalAffixProps>((props, ref) => {
|
||||
|
||||
React.useEffect(() => {
|
||||
addListeners();
|
||||
}, [target, affixStyle, lastAffix]);
|
||||
return () => removeListeners();
|
||||
}, [target, affixStyle, lastAffix, offsetTop, offsetBottom]);
|
||||
|
||||
React.useEffect(() => {
|
||||
updatePosition();
|
||||
|
@ -16,6 +16,11 @@ Before using icons, you need to install the [@ant-design/icons](https://github.c
|
||||
|
||||
<InstallDependencies npm='npm install @ant-design/icons@5.x --save' yarn='yarn add @ant-design/icons@5.x' pnpm='pnpm install @ant-design/icons@5.x --save' bun='bun add @ant-design/icons@5.x'></InstallDependencies>
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
:::warning{title=Tips}
|
||||
Remember to use @ant-design/icons v5 with antd v5. See: [#53275](https://github.com/ant-design/ant-design/issues/53275#issuecomment-2747448317)
|
||||
:::
|
||||
|
||||
## List of icons
|
||||
|
||||
<IconSearch></IconSearch>
|
||||
|
@ -11,21 +11,26 @@ demo:
|
||||
cols: 2
|
||||
---
|
||||
|
||||
## 使用方法
|
||||
## 使用方法 {#how-to-use}
|
||||
|
||||
使用图标组件,你需要安装 [@ant-design/icons](https://github.com/ant-design/ant-design-icons) 图标组件包:
|
||||
|
||||
<InstallDependencies npm='npm install @ant-design/icons@5.x --save' yarn='yarn add @ant-design/icons@5.x' pnpm='pnpm install @ant-design/icons@5.x --save' bun='bun add @ant-design/icons@5.x'></InstallDependencies>
|
||||
|
||||
## 设计师专属
|
||||
<!-- prettier-ignore -->
|
||||
:::warning{title=温馨提示}
|
||||
使用 antd v5 时, 请确保安装配套的 @ant-design/icons v5 版本。详见 [#53275](https://github.com/ant-design/ant-design/issues/53275#issuecomment-2747448317)
|
||||
:::
|
||||
|
||||
## 设计师专属 {#designers-exclusive}
|
||||
|
||||
安装 [Kitchen Sketch 插件 💎](https://kitchen.alipay.com),就可以一键拖拽使用 Ant Design 和 Iconfont 的海量图标,还可以关联自有项目。
|
||||
|
||||
## 图标列表
|
||||
## 图标列表 {#list-of-icons}
|
||||
|
||||
<IconSearch></IconSearch>
|
||||
|
||||
## 代码演示
|
||||
## 代码演示 {#examples}
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
<code src="./demo/basic.tsx">基本用法</code>
|
||||
@ -38,7 +43,7 @@ demo:
|
||||
|
||||
从 4.0 开始,antd 不再内置 Icon 组件,请使用独立的包 `@ant-design/icons`。
|
||||
|
||||
### 通用图标
|
||||
### 通用图标 {#common-icon}
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
@ -58,7 +63,7 @@ import { StarOutlined, StarFilled, StarTwoTone } from '@ant-design/icons';
|
||||
<StarTwoTone twoToneColor="#eb2f96" />
|
||||
```
|
||||
|
||||
### 自定义 Icon
|
||||
### 自定义 Icon {#custom-icon}
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
@ -67,7 +72,7 @@ import { StarOutlined, StarFilled, StarTwoTone } from '@ant-design/icons';
|
||||
| spin | 是否有旋转动画 | boolean | false | |
|
||||
| style | 设置图标的样式,例如 `fontSize` 和 `color` | CSSProperties | - | |
|
||||
|
||||
### 关于 SVG 图标
|
||||
### 关于 SVG 图标 {#about-svg-icons}
|
||||
|
||||
在 `3.9.0` 之后,我们使用了 SVG 图标替换了原先的 font 图标,从而带来了以下优势:
|
||||
|
||||
@ -86,7 +91,7 @@ import { MessageOutlined } from '@ant-design/icons';
|
||||
<MessageOutlined style={{ fontSize: '16px', color: '#08c' }} />;
|
||||
```
|
||||
|
||||
### 双色图标主色
|
||||
### 双色图标主色 {#set-two-tone-color}
|
||||
|
||||
对于双色图标,可以通过使用 `getTwoToneColor()` 和 `setTwoToneColor(colorString)` 来全局设置图标主色。
|
||||
|
||||
@ -97,7 +102,7 @@ setTwoToneColor('#eb2f96');
|
||||
getTwoToneColor(); // #eb2f96
|
||||
```
|
||||
|
||||
### 自定义 font 图标
|
||||
### 自定义 font 图标 {#custom-font-icon}
|
||||
|
||||
在 `3.9.0` 之后,我们提供了一个 `createFromIconfontCN` 方法,方便开发者调用在 [iconfont.cn](http://iconfont.cn/) 上自行管理的图标。
|
||||
|
||||
@ -126,7 +131,7 @@ options 的配置项如下:
|
||||
|
||||
见 [iconfont.cn 使用帮助](http://iconfont.cn/help/detail?spm=a313x.7781069.1998910419.15&helptype=code) 查看如何生成 js 地址。
|
||||
|
||||
### 自定义 SVG 图标
|
||||
### 自定义 SVG 图标 {#custom-svg-icon}
|
||||
|
||||
如果使用 `webpack`,可以通过配置 [@svgr/webpack](https://www.npmjs.com/package/@svgr/webpack) 来将 `svg` 图标作为 `React` 组件导入。`@svgr/webpack` 的 `options` 选项请参阅 [svgr 文档](https://github.com/smooth-code/svgr#options)。
|
||||
|
||||
@ -184,6 +189,6 @@ ReactDOM.createRoot(mountNode).render(<Icon component={MessageSvg} />);
|
||||
| style | 计算后的 `svg` 元素样式 | CSSProperties | - | |
|
||||
| width | `svg` 元素宽度 | string \| number | `1em` | |
|
||||
|
||||
## 主题变量(Design Token)
|
||||
## 主题变量(Design Token){#design-token}
|
||||
|
||||
<ComponentTokenTable component="Icon"></ComponentTokenTable>
|
||||
|
@ -3,35 +3,30 @@ import { Avatar, Divider, List, Skeleton } from 'antd';
|
||||
import InfiniteScroll from 'react-infinite-scroll-component';
|
||||
|
||||
interface DataType {
|
||||
gender: string;
|
||||
name: {
|
||||
title: string;
|
||||
first: string;
|
||||
last: string;
|
||||
};
|
||||
email: string;
|
||||
picture: {
|
||||
large: string;
|
||||
medium: string;
|
||||
thumbnail: string;
|
||||
};
|
||||
nat: string;
|
||||
gender?: string;
|
||||
name?: string;
|
||||
email?: string;
|
||||
avatar?: string;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState<DataType[]>([]);
|
||||
const [page, setPage] = useState(1);
|
||||
|
||||
const loadMoreData = () => {
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
fetch('https://randomuser.me/api/?results=10&inc=name,gender,email,nat,picture&noinfo')
|
||||
fetch(`https://660d2bd96ddfa2943b33731c.mockapi.io/api/users/?page=${page}&limit=10`)
|
||||
.then((res) => res.json())
|
||||
.then((body) => {
|
||||
setData([...data, ...body.results]);
|
||||
.then((res) => {
|
||||
const results = Array.isArray(res) ? res : [];
|
||||
setData([...data, ...results]);
|
||||
setLoading(false);
|
||||
setPage(page + 1);
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false);
|
||||
@ -65,8 +60,8 @@ const App: React.FC = () => {
|
||||
renderItem={(item) => (
|
||||
<List.Item key={item.email}>
|
||||
<List.Item.Meta
|
||||
avatar={<Avatar src={item.picture.large} />}
|
||||
title={<a href="https://ant.design">{item.name.last}</a>}
|
||||
avatar={<Avatar src={item.avatar} />}
|
||||
title={<a href="https://ant.design">{item.name}</a>}
|
||||
description={item.email}
|
||||
/>
|
||||
<div>Content</div>
|
||||
|
@ -3,59 +3,51 @@ import { Avatar, Button, List, Skeleton } from 'antd';
|
||||
|
||||
interface DataType {
|
||||
gender?: string;
|
||||
name: {
|
||||
title?: string;
|
||||
first?: string;
|
||||
last?: string;
|
||||
};
|
||||
name?: string;
|
||||
email?: string;
|
||||
picture: {
|
||||
large?: string;
|
||||
medium?: string;
|
||||
thumbnail?: string;
|
||||
};
|
||||
nat?: string;
|
||||
avatar?: string;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
const count = 3;
|
||||
const fakeDataUrl = `https://randomuser.me/api/?results=${count}&inc=name,gender,email,nat,picture&noinfo`;
|
||||
const PAGE_SIZE = 3;
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [initLoading, setInitLoading] = useState(true);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState<DataType[]>([]);
|
||||
const [list, setList] = useState<DataType[]>([]);
|
||||
const [page, setPage] = useState(1);
|
||||
|
||||
const fetchData = (currentPage: number) => {
|
||||
const fakeDataUrl = `https://660d2bd96ddfa2943b33731c.mockapi.io/api/users?page=${currentPage}&limit=${PAGE_SIZE}`;
|
||||
return fetch(fakeDataUrl).then((res) => res.json());
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetch(fakeDataUrl)
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
setInitLoading(false);
|
||||
setData(res.results);
|
||||
setList(res.results);
|
||||
});
|
||||
fetchData(page).then((res) => {
|
||||
const results = Array.isArray(res) ? res : [];
|
||||
setInitLoading(false);
|
||||
setData(results);
|
||||
setList(results);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onLoadMore = () => {
|
||||
setLoading(true);
|
||||
setList(
|
||||
data.concat(
|
||||
Array.from({ length: count }).map(() => ({ loading: true, name: {}, picture: {} })),
|
||||
),
|
||||
);
|
||||
fetch(fakeDataUrl)
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
const newData = data.concat(res.results);
|
||||
setData(newData);
|
||||
setList(newData);
|
||||
setLoading(false);
|
||||
// Resetting window's offsetTop so as to display react-virtualized demo underfloor.
|
||||
// In real scene, you can using public method of react-virtualized:
|
||||
// https://stackoverflow.com/questions/46700726/how-to-use-public-method-updateposition-of-react-virtualized
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
});
|
||||
setList(data.concat(Array.from({ length: PAGE_SIZE }).map(() => ({ loading: true }))));
|
||||
const nextPage = page + 1;
|
||||
setPage(nextPage);
|
||||
fetchData(nextPage).then((res) => {
|
||||
const results = Array.isArray(res) ? res : [];
|
||||
const newData = data.concat(results);
|
||||
setData(newData);
|
||||
setList(newData);
|
||||
setLoading(false);
|
||||
// Resetting window's offsetTop so as to display react-virtualized demo underfloor.
|
||||
// In real scene, you can using public method of react-virtualized:
|
||||
// https://stackoverflow.com/questions/46700726/how-to-use-public-method-updateposition-of-react-virtualized
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
});
|
||||
};
|
||||
|
||||
const loadMore =
|
||||
@ -85,8 +77,8 @@ const App: React.FC = () => {
|
||||
>
|
||||
<Skeleton avatar title={false} loading={item.loading} active>
|
||||
<List.Item.Meta
|
||||
avatar={<Avatar src={item.picture.large} />}
|
||||
title={<a href="https://ant.design">{item.name?.last}</a>}
|
||||
avatar={<Avatar src={item.avatar} />}
|
||||
title={<a href="https://ant.design">{item.name}</a>}
|
||||
description="Ant Design, a design language for background applications, is refined by Ant UED Team"
|
||||
/>
|
||||
<div>content</div>
|
||||
|
@ -5,32 +5,26 @@ import VirtualList from 'rc-virtual-list';
|
||||
interface UserItem {
|
||||
email: string;
|
||||
gender: string;
|
||||
name: {
|
||||
first: string;
|
||||
last: string;
|
||||
title: string;
|
||||
};
|
||||
nat: string;
|
||||
picture: {
|
||||
large: string;
|
||||
medium: string;
|
||||
thumbnail: string;
|
||||
};
|
||||
name: string;
|
||||
avatar: string;
|
||||
}
|
||||
|
||||
const fakeDataUrl =
|
||||
'https://randomuser.me/api/?results=20&inc=name,gender,email,nat,picture&noinfo';
|
||||
const ContainerHeight = 400;
|
||||
const CONTAINER_HEIGHT = 400;
|
||||
const PAGE_SIZE = 20;
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [data, setData] = useState<UserItem[]>([]);
|
||||
const [page, setPage] = useState(1);
|
||||
|
||||
const appendData = (showMessage = true) => {
|
||||
const fakeDataUrl = `https://660d2bd96ddfa2943b33731c.mockapi.io/api/users/?page=${page}&limit=${PAGE_SIZE}`;
|
||||
fetch(fakeDataUrl)
|
||||
.then((res) => res.json())
|
||||
.then((body) => {
|
||||
setData(data.concat(body.results));
|
||||
showMessage && message.success(`${body.results.length} more items loaded!`);
|
||||
const results = Array.isArray(body) ? body : [];
|
||||
setData(data.concat(results));
|
||||
setPage(page + 1);
|
||||
showMessage && message.success(`${results.length} more items loaded!`);
|
||||
});
|
||||
};
|
||||
|
||||
@ -40,7 +34,9 @@ const App: React.FC = () => {
|
||||
|
||||
const onScroll = (e: React.UIEvent<HTMLElement, UIEvent>) => {
|
||||
// Refer to: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#problems_and_solutions
|
||||
if (Math.abs(e.currentTarget.scrollHeight - e.currentTarget.scrollTop - ContainerHeight) <= 1) {
|
||||
if (
|
||||
Math.abs(e.currentTarget.scrollHeight - e.currentTarget.scrollTop - CONTAINER_HEIGHT) <= 1
|
||||
) {
|
||||
appendData();
|
||||
}
|
||||
};
|
||||
@ -49,7 +45,7 @@ const App: React.FC = () => {
|
||||
<List>
|
||||
<VirtualList
|
||||
data={data}
|
||||
height={ContainerHeight}
|
||||
height={CONTAINER_HEIGHT}
|
||||
itemHeight={47}
|
||||
itemKey="email"
|
||||
onScroll={onScroll}
|
||||
@ -57,8 +53,8 @@ const App: React.FC = () => {
|
||||
{(item: UserItem) => (
|
||||
<List.Item key={item.email}>
|
||||
<List.Item.Meta
|
||||
avatar={<Avatar src={item.picture.large} />}
|
||||
title={<a href="https://ant.design">{item.name.last}</a>}
|
||||
avatar={<Avatar src={item.avatar} />}
|
||||
title={<a href="https://ant.design">{item.name}</a>}
|
||||
description={item.email}
|
||||
/>
|
||||
<div>Content</div>
|
||||
|
@ -14429,7 +14429,9 @@ exports[`renders components/select/demo/select-users.tsx extend context correctl
|
||||
class="ant-select-item-empty"
|
||||
id="rc_select_TEST_OR_SSR_list"
|
||||
role="listbox"
|
||||
/>
|
||||
>
|
||||
No results found
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { imageDemoTest } from '../../../tests/shared/imageTest';
|
||||
|
||||
describe('Select image', () => {
|
||||
imageDemoTest('select');
|
||||
imageDemoTest('select', {
|
||||
mobile: ['basic.tsx'],
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useMemo, useRef, useState } from 'react';
|
||||
import { Select, Spin } from 'antd';
|
||||
import { Select, Spin, Avatar } from 'antd';
|
||||
import type { SelectProps } from 'antd';
|
||||
import debounce from 'lodash/debounce';
|
||||
|
||||
@ -10,8 +10,13 @@ export interface DebounceSelectProps<ValueType = any>
|
||||
}
|
||||
|
||||
function DebounceSelect<
|
||||
ValueType extends { key?: string; label: React.ReactNode; value: string | number } = any,
|
||||
>({ fetchOptions, debounceTimeout = 800, ...props }: DebounceSelectProps<ValueType>) {
|
||||
ValueType extends {
|
||||
key?: string;
|
||||
label: React.ReactNode;
|
||||
value: string | number;
|
||||
avatar?: string;
|
||||
} = any,
|
||||
>({ fetchOptions, debounceTimeout = 300, ...props }: DebounceSelectProps<ValueType>) {
|
||||
const [fetching, setFetching] = useState(false);
|
||||
const [options, setOptions] = useState<ValueType[]>([]);
|
||||
const fetchRef = useRef(0);
|
||||
@ -42,9 +47,15 @@ function DebounceSelect<
|
||||
labelInValue
|
||||
filterOption={false}
|
||||
onSearch={debounceFetcher}
|
||||
notFoundContent={fetching ? <Spin size="small" /> : null}
|
||||
notFoundContent={fetching ? <Spin size="small" /> : 'No results found'}
|
||||
{...props}
|
||||
options={options}
|
||||
optionRender={(option) => (
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
{option.data.avatar && <Avatar src={option.data.avatar} style={{ marginRight: 8 }} />}
|
||||
{option.label}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -53,21 +64,21 @@ function DebounceSelect<
|
||||
interface UserValue {
|
||||
label: string;
|
||||
value: string;
|
||||
avatar?: string;
|
||||
}
|
||||
|
||||
async function fetchUserList(username: string): Promise<UserValue[]> {
|
||||
console.log('fetching user', username);
|
||||
|
||||
return fetch('https://randomuser.me/api/?results=5')
|
||||
.then((response) => response.json())
|
||||
.then((body) =>
|
||||
body.results.map(
|
||||
(user: { name: { first: string; last: string }; login: { username: string } }) => ({
|
||||
label: `${user.name.first} ${user.name.last}`,
|
||||
value: user.login.username,
|
||||
}),
|
||||
),
|
||||
);
|
||||
return fetch(`https://660d2bd96ddfa2943b33731c.mockapi.io/api/users/?search=${username}`)
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
const results = Array.isArray(res) ? res : [];
|
||||
return results.map((user) => ({
|
||||
label: user.name,
|
||||
value: user.id,
|
||||
avatar: user.avatar,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
const App: React.FC = () => {
|
||||
|
@ -72,6 +72,14 @@ const getSearchInputWithoutBorderStyle: GenerateStyle<SelectToken, CSSObject> =
|
||||
const genBaseStyle: GenerateStyle<SelectToken> = (token) => {
|
||||
const { antCls, componentCls, inputPaddingHorizontalBase, iconCls } = token;
|
||||
|
||||
const hoverShowClearStyle: CSSObject = {
|
||||
[`${componentCls}-clear`]: {
|
||||
opacity: 1,
|
||||
background: token.colorBgBase,
|
||||
borderRadius: '50%',
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
[componentCls]: {
|
||||
...resetComponent(token),
|
||||
@ -198,11 +206,8 @@ const genBaseStyle: GenerateStyle<SelectToken> = (token) => {
|
||||
},
|
||||
},
|
||||
|
||||
[`&:hover ${componentCls}-clear`]: {
|
||||
opacity: 1,
|
||||
background: token.colorBgBase,
|
||||
borderRadius: '50%',
|
||||
},
|
||||
'@media(hover:none)': hoverShowClearStyle,
|
||||
'&:hover': hoverShowClearStyle,
|
||||
},
|
||||
|
||||
// ========================= Feedback ==========================
|
||||
|
@ -95,6 +95,7 @@ const SplitBar: React.FC<SplitBarProps> = (props) => {
|
||||
const handleLazyEnd = useEvent(() => {
|
||||
onOffsetUpdate(index, constrainedOffsetX, constrainedOffsetY, true);
|
||||
setConstrainedOffset(0);
|
||||
onOffsetEnd();
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
|
@ -122,6 +122,9 @@ describe('Splitter lazy', () => {
|
||||
onResize.mockReset();
|
||||
mockDrag(container.querySelector('.ant-splitter-bar-dragger')!, onResize, -1000);
|
||||
expect(onResizeEnd).toHaveBeenCalledWith([30, 70]);
|
||||
|
||||
// mask should hide
|
||||
expect(container.querySelector('.ant-splitter-mask')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should work with touch events when lazy', async () => {
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
当使用 `rowSelection` 时,请设置 `rowSelection.preserveSelectedRowKeys` 属性以保留 `key`。
|
||||
|
||||
**注意,此示例使用 [模拟接口](https://randomuser.me),展示数据可能不准确,请打开网络面板查看请求。**
|
||||
**注意,此示例使用 [模拟接口](https://mocky.io),展示数据可能不准确,请打开网络面板查看请求。**
|
||||
|
||||
> 🛎️ 想要 3 分钟实现?试试 [ProTable](https://procomponents.ant.design/components/table)!
|
||||
|
||||
@ -16,4 +16,4 @@ This example shows how to fetch and present data from a remote server, and how t
|
||||
|
||||
Setting `rowSelection.preserveSelectedRowKeys` to keep the `key` when enable selection.
|
||||
|
||||
**Note, this example use [Mock API](https://randomuser.me) that you can look up in Network Console.**
|
||||
**Note, this example use [Mock API](https://mocky.io) that you can look up in Network Console.**
|
||||
|
@ -9,15 +9,10 @@ type ColumnsType<T extends object = object> = TableProps<T>['columns'];
|
||||
type TablePaginationConfig = Exclude<GetProp<TableProps, 'pagination'>, boolean>;
|
||||
|
||||
interface DataType {
|
||||
name: {
|
||||
first: string;
|
||||
last: string;
|
||||
};
|
||||
name: string;
|
||||
gender: string;
|
||||
email: string;
|
||||
login: {
|
||||
uuid: string;
|
||||
};
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface TableParams {
|
||||
@ -32,7 +27,6 @@ const columns: ColumnsType<DataType> = [
|
||||
title: 'Name',
|
||||
dataIndex: 'name',
|
||||
sorter: true,
|
||||
render: (name) => `${name.first} ${name.last}`,
|
||||
width: '20%',
|
||||
},
|
||||
{
|
||||
@ -58,11 +52,38 @@ const toURLSearchParams = <T extends AnyObject>(record: T) => {
|
||||
return params;
|
||||
};
|
||||
|
||||
const getRandomuserParams = (params: TableParams) => ({
|
||||
results: params.pagination?.pageSize,
|
||||
page: params.pagination?.current,
|
||||
...params,
|
||||
});
|
||||
const getRandomuserParams = (params: TableParams) => {
|
||||
const { pagination, filters, sortField, sortOrder, ...restParams } = params;
|
||||
const result: Record<string, any> = {};
|
||||
|
||||
// https://github.com/mockapi-io/docs/wiki/Code-examples#pagination
|
||||
result.limit = pagination?.pageSize;
|
||||
result.page = pagination?.current;
|
||||
|
||||
// https://github.com/mockapi-io/docs/wiki/Code-examples#filtering
|
||||
if (filters) {
|
||||
Object.entries(filters).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null) {
|
||||
result[key] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// https://github.com/mockapi-io/docs/wiki/Code-examples#sorting
|
||||
if (sortField) {
|
||||
result.orderby = sortField;
|
||||
result.order = sortOrder === 'ascend' ? 'asc' : 'desc';
|
||||
}
|
||||
|
||||
// 处理其他参数
|
||||
Object.entries(restParams).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null) {
|
||||
result[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [data, setData] = useState<DataType[]>();
|
||||
@ -78,17 +99,17 @@ const App: React.FC = () => {
|
||||
|
||||
const fetchData = () => {
|
||||
setLoading(true);
|
||||
fetch(`https://randomuser.me/api?${params.toString()}`)
|
||||
fetch(`https://660d2bd96ddfa2943b33731c.mockapi.io/api/users?${params.toString()}`)
|
||||
.then((res) => res.json())
|
||||
.then(({ results }) => {
|
||||
setData(results);
|
||||
.then((res) => {
|
||||
setData(Array.isArray(res) ? res : []);
|
||||
setLoading(false);
|
||||
setTableParams({
|
||||
...tableParams,
|
||||
pagination: {
|
||||
...tableParams.pagination,
|
||||
total: 200,
|
||||
// 200 is mock data, you should read it from server
|
||||
total: 100,
|
||||
// 100 is mock data, you should read it from server
|
||||
// total: data.totalCount,
|
||||
},
|
||||
});
|
||||
@ -120,7 +141,7 @@ const App: React.FC = () => {
|
||||
return (
|
||||
<Table<DataType>
|
||||
columns={columns}
|
||||
rowKey={(record) => record.login.uuid}
|
||||
rowKey={(record) => record.id}
|
||||
dataSource={data}
|
||||
pagination={tableParams.pagination}
|
||||
loading={loading}
|
||||
|
@ -1,6 +1,7 @@
|
||||
// jest-puppeteer.config.js
|
||||
module.exports = {
|
||||
launch: {
|
||||
headless: 'new',
|
||||
ignoreDefaultArgs: ['--disable-extensions'],
|
||||
args: [
|
||||
// Required for Docker version of Puppeteer
|
||||
|
@ -289,7 +289,7 @@
|
||||
"prettier": "^3.4.1",
|
||||
"pretty-format": "^29.7.0",
|
||||
"prismjs": "^1.29.0",
|
||||
"puppeteer": "^24.0.0",
|
||||
"puppeteer": "^24.7.1",
|
||||
"rc-footer": "^0.6.8",
|
||||
"rc-tween-one": "^3.0.6",
|
||||
"rc-virtual-list": "^3.17.0",
|
||||
@ -356,16 +356,13 @@
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"countup.js": "2.8.0",
|
||||
"nwsapi": "2.2.20"
|
||||
}
|
||||
},
|
||||
"overrides": {
|
||||
"countup.js": "2.8.0",
|
||||
"nwsapi": "2.2.20"
|
||||
},
|
||||
"resolutions": {
|
||||
"countup.js": "2.8.0",
|
||||
"nwsapi": "2.2.20"
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import fse from 'fs-extra';
|
||||
import { globSync } from 'glob';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import MockDate from 'mockdate';
|
||||
import type { HTTPRequest } from 'puppeteer';
|
||||
import type { HTTPRequest, Viewport } from 'puppeteer';
|
||||
import rcWarning from 'rc-util/lib/warning';
|
||||
import ReactDOMServer from 'react-dom/server';
|
||||
|
||||
@ -32,6 +32,7 @@ interface ImageTestOptions {
|
||||
onlyViewport?: boolean;
|
||||
ssr?: boolean;
|
||||
openTriggerClassName?: string;
|
||||
mobile?: boolean;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line jest/no-export
|
||||
@ -109,9 +110,14 @@ export default function imageTest(
|
||||
container = doc.querySelector<HTMLDivElement>('#root')!;
|
||||
});
|
||||
|
||||
function test(name: string, suffix: string, themedComponent: React.ReactElement) {
|
||||
function test(name: string, suffix: string, themedComponent: React.ReactElement, mobile = false) {
|
||||
it(name, async () => {
|
||||
await page.setViewport({ width: 800, height: 600 });
|
||||
const sharedViewportConfig: Partial<Viewport> = {
|
||||
isMobile: mobile,
|
||||
hasTouch: mobile,
|
||||
};
|
||||
|
||||
await page.setViewport({ width: 800, height: 600, ...sharedViewportConfig });
|
||||
|
||||
const onRequestHandle = (request: HTTPRequest) => {
|
||||
if (['image'].includes(request.resourceType())) {
|
||||
@ -166,6 +172,11 @@ export default function imageTest(
|
||||
unmount();
|
||||
}
|
||||
|
||||
// Remove mobile css for hardcode since CI will always think as mobile
|
||||
if (!mobile) {
|
||||
styleStr = styleStr.replace(/@media\(hover:\s*none\)/g, '@media(hover:not-valid)');
|
||||
}
|
||||
|
||||
if (openTriggerClassName) {
|
||||
styleStr += `<style>
|
||||
.${openTriggerClassName} {
|
||||
@ -211,7 +222,7 @@ export default function imageTest(
|
||||
Please consider using \`onlyViewport: ["filename.tsx"]\`, read more: https://github.com/ant-design/ant-design/pull/52053`,
|
||||
);
|
||||
|
||||
await page.setViewport({ width: 800, height: bodyHeight });
|
||||
await page.setViewport({ width: 800, height: bodyHeight, ...sharedViewportConfig });
|
||||
}
|
||||
|
||||
const image = await page.screenshot({
|
||||
@ -225,29 +236,35 @@ export default function imageTest(
|
||||
});
|
||||
}
|
||||
|
||||
Object.entries(themes).forEach(([key, algorithm]) => {
|
||||
const configTheme = {
|
||||
algorithm,
|
||||
token: {
|
||||
fontFamily: 'Arial',
|
||||
},
|
||||
};
|
||||
if (!options.mobile) {
|
||||
Object.entries(themes).forEach(([key, algorithm]) => {
|
||||
const configTheme = {
|
||||
algorithm,
|
||||
token: {
|
||||
fontFamily: 'Arial',
|
||||
},
|
||||
};
|
||||
|
||||
test(
|
||||
`component image screenshot should correct ${key}`,
|
||||
`.${key}`,
|
||||
<div style={{ background: key === 'dark' ? '#000' : '', padding: `24px 12px` }} key={key}>
|
||||
<ConfigProvider theme={configTheme}>{component}</ConfigProvider>
|
||||
</div>,
|
||||
);
|
||||
test(
|
||||
`[CSS Var] component image screenshot should correct ${key}`,
|
||||
`.${key}.css-var`,
|
||||
<div style={{ background: key === 'dark' ? '#000' : '', padding: `24px 12px` }} key={key}>
|
||||
<ConfigProvider theme={{ ...configTheme, cssVar: true }}>{component}</ConfigProvider>
|
||||
</div>,
|
||||
);
|
||||
});
|
||||
test(
|
||||
`component image screenshot should correct ${key}`,
|
||||
`.${key}`,
|
||||
<div style={{ background: key === 'dark' ? '#000' : '', padding: `24px 12px` }} key={key}>
|
||||
<ConfigProvider theme={configTheme}>{component}</ConfigProvider>
|
||||
</div>,
|
||||
);
|
||||
test(
|
||||
`[CSS Var] component image screenshot should correct ${key}`,
|
||||
`.${key}.css-var`,
|
||||
<div style={{ background: key === 'dark' ? '#000' : '', padding: `24px 12px` }} key={key}>
|
||||
<ConfigProvider theme={{ ...configTheme, cssVar: true }}>{component}</ConfigProvider>
|
||||
</div>,
|
||||
);
|
||||
});
|
||||
|
||||
// Mobile Snapshot
|
||||
} else {
|
||||
test(identifier, `.mobile`, component, true);
|
||||
}
|
||||
}
|
||||
|
||||
type Options = {
|
||||
@ -257,6 +274,7 @@ type Options = {
|
||||
ssr?: boolean;
|
||||
/** Open Trigger to check the popup render */
|
||||
openTriggerClassName?: string;
|
||||
mobile?: string[];
|
||||
};
|
||||
|
||||
// eslint-disable-next-line jest/no-export
|
||||
@ -266,25 +284,49 @@ export function imageDemoTest(component: string, options: Options = {}) {
|
||||
(file) => !file.includes('_semantic'),
|
||||
);
|
||||
|
||||
const mobileDemos: [file: string, node: any][] = [];
|
||||
|
||||
const getTestOption = (file: string) => ({
|
||||
onlyViewport:
|
||||
options.onlyViewport === true ||
|
||||
(Array.isArray(options.onlyViewport) && options.onlyViewport.some((c) => file.endsWith(c))),
|
||||
ssr: options.ssr,
|
||||
openTriggerClassName: options.openTriggerClassName,
|
||||
});
|
||||
|
||||
files.forEach((file) => {
|
||||
if (Array.isArray(options.skip) && options.skip.some((c) => file.endsWith(c))) {
|
||||
describeMethod = describe.skip;
|
||||
} else {
|
||||
describeMethod = describe;
|
||||
}
|
||||
|
||||
describeMethod(`Test ${file} image`, () => {
|
||||
let Demo = require(`../../${file}`).default;
|
||||
if (typeof Demo === 'function') {
|
||||
Demo = <Demo />;
|
||||
}
|
||||
imageTest(Demo, `${component}-${path.basename(file, '.tsx')}`, {
|
||||
onlyViewport:
|
||||
options.onlyViewport === true ||
|
||||
(Array.isArray(options.onlyViewport) &&
|
||||
options.onlyViewport.some((c) => file.endsWith(c))),
|
||||
ssr: options.ssr,
|
||||
openTriggerClassName: options.openTriggerClassName,
|
||||
});
|
||||
imageTest(Demo, `${component}-${path.basename(file, '.tsx')}`, getTestOption(file));
|
||||
|
||||
// Check if need mobile test
|
||||
if ((options.mobile || []).some((c) => file.endsWith(c))) {
|
||||
mobileDemos.push([file, Demo]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (mobileDemos.length) {
|
||||
describeMethod(`Test mobile image`, () => {
|
||||
beforeAll(async () => {
|
||||
await jestPuppeteer.resetPage();
|
||||
});
|
||||
|
||||
mobileDemos.forEach(([file, Demo]) => {
|
||||
imageTest(Demo, `${component}-${path.basename(file, '.tsx')}`, {
|
||||
...getTestOption(file),
|
||||
mobile: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user