+ Default
Square
Round
Circle
diff --git a/components/skeleton/index.en-US.md b/components/skeleton/index.en-US.md
index 3579190cfb37..ded6541a0e1e 100644
--- a/components/skeleton/index.en-US.md
+++ b/components/skeleton/index.en-US.md
@@ -55,7 +55,7 @@ Provide a placeholder while you wait for content to load, or to visualise conten
| --- | --- | --- | --- | --- |
| active | Show animation effect | boolean | false | |
| block | Option to fit button width to its parent width | boolean | false | 4.17.0 |
-| shape | Set the shape of button | `circle` \| `round` \| `default` | - | |
+| shape | Set the shape of button | `circle` \| `round` \| `square` \| `default` | - | |
| size | Set the size of button | `large` \| `small` \| `default` | - | |
### SkeletonInputProps
diff --git a/components/skeleton/index.zh-CN.md b/components/skeleton/index.zh-CN.md
index 844b30e9e02d..c67546b428fc 100644
--- a/components/skeleton/index.zh-CN.md
+++ b/components/skeleton/index.zh-CN.md
@@ -52,12 +52,12 @@ cover: https://gw.alipayobjects.com/zos/alicdn/KpcciCJgv/Skeleton.svg
### SkeletonButtonProps
-| 属性 | 说明 | 类型 | 默认值 | 版本 |
-| ------ | ------------------------------ | -------------------------------- | ------ | ------ |
-| active | 是否展示动画效果 | boolean | false | |
-| block | 将按钮宽度调整为其父宽度的选项 | boolean | false | 4.17.0 |
-| shape | 指定按钮的形状 | `circle` \| `round` \| `default` | - | |
-| size | 设置按钮的大小 | `large` \| `small` \| `default` | - | |
+| 属性 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| active | 是否展示动画效果 | boolean | false | |
+| block | 将按钮宽度调整为其父宽度的选项 | boolean | false | 4.17.0 |
+| shape | 指定按钮的形状 | `circle` \| `round` \| `square` \| `default` | - | |
+| size | 设置按钮的大小 | `large` \| `small` \| `default` | - | |
### SkeletonInputProps
diff --git a/components/skeleton/style/index.less b/components/skeleton/style/index.less
index c7ac8e451f77..606897c470b8 100644
--- a/components/skeleton/style/index.less
+++ b/components/skeleton/style/index.less
@@ -216,6 +216,11 @@
min-width: @size * 2;
.skeleton-element-common-size(@size);
+ &.@{skeleton-button-prefix-cls}-square {
+ width: @size;
+ min-width: @size;
+ }
+
&.@{skeleton-button-prefix-cls}-circle {
width: @size;
min-width: @size;
diff --git a/components/style/themes/default.less b/components/style/themes/default.less
index eb4d4c6f044a..1bc14dde35bd 100644
--- a/components/style/themes/default.less
+++ b/components/style/themes/default.less
@@ -890,7 +890,7 @@
@page-header-content-padding-vertical: @padding-sm;
@page-header-back-color: #000;
@page-header-ghost-bg: inherit;
-@page-header-heading-title: @heading-4-size;
+@page-header-heading-title: @heading-3-size;
@page-header-heading-sub-title: 14px;
@page-header-tabs-tab-font-size: 16px;
@@ -1026,11 +1026,13 @@
// ---
@drawer-header-padding: @padding-lg;
@drawer-body-padding: @padding-lg;
+@drawer-body-padding-top: @padding-xs;
+@drawer-body-padding-bottom: @padding-md + 2px;
@drawer-bg: @component-background;
@drawer-footer-padding-vertical: @modal-footer-padding-vertical;
@drawer-footer-padding-horizontal: @modal-footer-padding-horizontal;
@drawer-header-close-size: 56px;
-@drawer-title-font-size: @font-size-lg;
+@drawer-title-font-size: @font-size-lg + 4px;
@drawer-title-line-height: 22px;
// Timeline
diff --git a/components/style/themes/variable.less b/components/style/themes/variable.less
index 90c5aed3f402..9b36683d9c49 100644
--- a/components/style/themes/variable.less
+++ b/components/style/themes/variable.less
@@ -945,7 +945,7 @@
@page-header-content-padding-vertical: @padding-sm;
@page-header-back-color: #000;
@page-header-ghost-bg: inherit;
-@page-header-heading-title: @heading-4-size;
+@page-header-heading-title: @heading-3-size;
@page-header-heading-sub-title: 14px;
@page-header-tabs-tab-font-size: 16px;
@@ -1081,11 +1081,13 @@
// ---
@drawer-header-padding: @padding-lg;
@drawer-body-padding: @padding-lg;
+@drawer-body-padding-top: @padding-xs;
+@drawer-body-padding-bottom: @padding-md + 2px;
@drawer-bg: @component-background;
@drawer-footer-padding-vertical: @modal-footer-padding-vertical;
@drawer-footer-padding-horizontal: @modal-footer-padding-horizontal;
@drawer-header-close-size: 56px;
-@drawer-title-font-size: @font-size-lg;
+@drawer-title-font-size: @font-size-lg + 4px;
@drawer-title-line-height: 22px;
// Timeline
diff --git a/components/switch/index.tsx b/components/switch/index.tsx
index c519de18e001..813cba12da21 100755
--- a/components/switch/index.tsx
+++ b/components/switch/index.tsx
@@ -1,4 +1,4 @@
-import LoadingOutlined from '@ant-design/icons/LoadingOutlined';
+import { LoadingOutlined } from 'infra-design-icons';
import classNames from 'classnames';
import RcSwitch from 'rc-switch';
import * as React from 'react';
diff --git a/components/table/__tests__/__snapshots__/Table.rowSelection.test.js.snap b/components/table/__tests__/__snapshots__/Table.rowSelection.test.js.snap
index e773204a0049..6513d1a2c88c 100644
--- a/components/table/__tests__/__snapshots__/Table.rowSelection.test.js.snap
+++ b/components/table/__tests__/__snapshots__/Table.rowSelection.test.js.snap
@@ -724,7 +724,7 @@ exports[`Table.rowSelection fix selection column on the left when any other colu
Name
@@ -784,7 +784,7 @@ exports[`Table.rowSelection fix selection column on the left when any other colu
|
Jack
@@ -816,7 +816,7 @@ exports[`Table.rowSelection fix selection column on the left when any other colu
|
Lucy
@@ -848,7 +848,7 @@ exports[`Table.rowSelection fix selection column on the left when any other colu
|
Tom
@@ -880,7 +880,7 @@ exports[`Table.rowSelection fix selection column on the left when any other colu
|
Jerry
diff --git a/components/table/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/table/__tests__/__snapshots__/demo-extend.test.ts.snap
index 532ac643e5e5..07db210362b1 100644
--- a/components/table/__tests__/__snapshots__/demo-extend.test.ts.snap
+++ b/components/table/__tests__/__snapshots__/demo-extend.test.ts.snap
@@ -2294,847 +2294,907 @@ Array [
style="margin-bottom:16px"
>
@@ -15720,79 +15780,87 @@ Array [
style="margin-bottom:16px"
>
diff --git a/components/table/__tests__/__snapshots__/demo.test.js.snap b/components/table/__tests__/__snapshots__/demo.test.js.snap
index 9859c844deb0..92a174865466 100644
--- a/components/table/__tests__/__snapshots__/demo.test.js.snap
+++ b/components/table/__tests__/__snapshots__/demo.test.js.snap
@@ -1803,847 +1803,907 @@ Array [
style="margin-bottom:16px"
>
@@ -11942,79 +12002,87 @@ Array [
style="margin-bottom:16px"
>
diff --git a/components/table/demo/tree-table-ellipsis.md b/components/table/demo/tree-table-ellipsis.md
index 6894ed990d01..bd8c569f33ea 100644
--- a/components/table/demo/tree-table-ellipsis.md
+++ b/components/table/demo/tree-table-ellipsis.md
@@ -15,8 +15,8 @@ https://github.com/ant-design/ant-design/issues/36583
https://github.com/ant-design/ant-design/issues/36583
```tsx
-import { Space, Switch, Table } from 'antd';
-import type { ColumnsType } from 'antd/es/table';
+import { Space, Switch, Table } from 'infrad';
+import type { ColumnsType } from 'infrad/es/table';
import React, { useState } from 'react';
interface DataType {
diff --git a/components/time-picker/locale/si_LK.tsx b/components/time-picker/locale/si_LK.tsx
new file mode 100644
index 000000000000..f0737dbcf1d7
--- /dev/null
+++ b/components/time-picker/locale/si_LK.tsx
@@ -0,0 +1,8 @@
+import type { TimePickerLocale } from '../index';
+
+const locale: TimePickerLocale = {
+ placeholder: 'වේලාව තෝරන්න',
+ rangePlaceholder: ['ආරම්භක වේලාව', 'නිමවන වේලාව'],
+};
+
+export default locale;
diff --git a/components/tooltip/index.tsx b/components/tooltip/index.tsx
index b60f3f86813b..c959af9f3ac4 100644
--- a/components/tooltip/index.tsx
+++ b/components/tooltip/index.tsx
@@ -57,7 +57,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 {
diff --git a/components/tree-select/index.en-US.md b/components/tree-select/index.en-US.md
index 747190a4404a..a5bd260e1597 100644
--- a/components/tree-select/index.en-US.md
+++ b/components/tree-select/index.en-US.md
@@ -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 | |
diff --git a/components/tree-select/index.zh-CN.md b/components/tree-select/index.zh-CN.md
index 278cb1d66c1b..174dbb1de9f1 100644
--- a/components/tree-select/index.zh-CN.md
+++ b/components/tree-select/index.zh-CN.md
@@ -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 | |
diff --git a/components/tree/Tree.tsx b/components/tree/Tree.tsx
index d8a453c0df53..cb9225ccd37d 100644
--- a/components/tree/Tree.tsx
+++ b/components/tree/Tree.tsx
@@ -10,7 +10,7 @@ import DirectoryTree from './DirectoryTree';
import dropIndicatorRender from './utils/dropIndicator';
import renderSwitcherIcon from './utils/iconUtil';
-export type SwitcherIcon = React.ReactNode | ((props: { expanded: boolean }) => React.ReactNode);
+export type SwitcherIcon = React.ReactNode | ((props: AntTreeNodeProps) => React.ReactNode);
export interface AntdTreeNodeAttribute {
eventKey: string;
diff --git a/components/tree/__tests__/index.test.js b/components/tree/__tests__/index.test.js
index c5006f9ba4a9..104a1cebb25b 100644
--- a/components/tree/__tests__/index.test.js
+++ b/components/tree/__tests__/index.test.js
@@ -76,7 +76,7 @@ describe('Tree', () => {
it('switcherIcon in Tree could be render prop function', () => {
const { container } = render(
+ switcherIcon={({ expanded }) =>
expanded ? :
}
defaultExpandAll
diff --git a/components/tree/index.en-US.md b/components/tree/index.en-US.md
index 9b5fb5b27037..77aaab480e12 100644
--- a/components/tree/index.en-US.md
+++ b/components/tree/index.en-US.md
@@ -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<{ key, title, children, \[disabled, selectable] }> | - | |
| virtual | Disable virtual scroll when set to false | boolean | true | 4.1.0 |
diff --git a/components/tree/index.zh-CN.md b/components/tree/index.zh-CN.md
index 0b5c976cdc2a..cccc34db83d7 100644
--- a/components/tree/index.zh-CN.md
+++ b/components/tree/index.zh-CN.md
@@ -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<{key, title, children, \[disabled, selectable]}> | - | |
| virtual | 设置 false 时关闭虚拟滚动 | boolean | true | 4.1.0 |
diff --git a/components/tree/utils/iconUtil.tsx b/components/tree/utils/iconUtil.tsx
index 60803b1fa036..2c475ccb38c5 100644
--- a/components/tree/utils/iconUtil.tsx
+++ b/components/tree/utils/iconUtil.tsx
@@ -37,8 +37,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, {
diff --git a/components/typography/Base/EllipsisTooltip.tsx b/components/typography/Base/EllipsisTooltip.tsx
index 53632eb4b7f6..a2467208f82a 100644
--- a/components/typography/Base/EllipsisTooltip.tsx
+++ b/components/typography/Base/EllipsisTooltip.tsx
@@ -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 (
-
+
{children}
);
diff --git a/components/typography/Base/index.tsx b/components/typography/Base/index.tsx
index 5be1b2a30ebb..009053924207 100644
--- a/components/typography/Base/index.tsx
+++ b/components/typography/Base/index.tsx
@@ -11,9 +11,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';
@@ -53,7 +54,7 @@ export interface EllipsisConfig {
symbol?: React.ReactNode;
onExpand?: React.MouseEventHandler;
onEllipsis?: (ellipsis: boolean) => void;
- tooltip?: React.ReactNode;
+ tooltip?: React.ReactNode | TooltipProps;
}
export interface BlockProps extends TypographyProps {
@@ -307,7 +308,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);
@@ -323,12 +333,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
@@ -450,7 +460,7 @@ const Base = React.forwardRef((props: InternalBlockProps, ref: any) => {
{resizeRef => (
diff --git a/components/typography/__tests__/ellipsis.test.js b/components/typography/__tests__/ellipsis.test.js
index 0439f2d46634..69c94f1d9543 100644
--- a/components/typography/__tests__/ellipsis.test.js
+++ b/components/typography/__tests__/ellipsis.test.js
@@ -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(
+ title ,
+ );
+ 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', () => {
diff --git a/components/typography/index.en-US.md b/components/typography/index.en-US.md
index 32ceba3430c9..f2a92374bb6d 100644
--- a/components/typography/index.en-US.md
+++ b/components/typography/index.en-US.md
@@ -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) | - | |
diff --git a/components/typography/index.zh-CN.md b/components/typography/index.zh-CN.md
index 7966f0c1fe72..6aa06cd58d30 100644
--- a/components/typography/index.zh-CN.md
+++ b/components/typography/index.zh-CN.md
@@ -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
diff --git a/components/upload/Upload.tsx b/components/upload/Upload.tsx
index 426139c849c3..bae03963ac0a 100644
--- a/components/upload/Upload.tsx
+++ b/components/upload/Upload.tsx
@@ -267,8 +267,15 @@ const InternalUpload: React.ForwardRefRenderFunction = (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);
}
});
diff --git a/components/upload/UploadList/ListItem.tsx b/components/upload/UploadList/ListItem.tsx
index 2afb3bb8ed74..282971512905 100644
--- a/components/upload/UploadList/ListItem.tsx
+++ b/components/upload/UploadList/ListItem.tsx
@@ -70,6 +70,15 @@ const ListItem = React.forwardRef(
}: ListItemProps,
ref: React.Ref,
) => {
+ // 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();
@@ -90,10 +99,10 @@ const ListItem = React.forwardRef(
const iconNode = iconRender(file);
let icon = {iconNode} ;
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 = {iconNode} ;
} else {
@@ -127,7 +136,7 @@ const ListItem = React.forwardRef(
const infoUploadingClass = classNames({
[`${prefixCls}-list-item`]: true,
- [`${prefixCls}-list-item-${file.status}`]: true,
+ [`${prefixCls}-list-item-${mergedStatus}`]: true,
[`${prefixCls}-list-item-list-type-${listType}`]: true,
});
const trashClass = classNames({
@@ -148,7 +157,7 @@ const ListItem = React.forwardRef(
: null;
const downloadIcon =
- showDownloadIcon && file.status === 'done'
+ showDownloadIcon && mergedStatus === 'done'
? actionIconRender(
(typeof customDownloadIcon === 'function'
? customDownloadIcon(file)
@@ -219,10 +228,10 @@ const ListItem = React.forwardRef(
) : null;
- const actions = listType === 'picture-card' && file.status !== 'uploading' && (
+ const actions = listType === 'picture-card' && mergedStatus !== 'uploading' && (
{previewIcon}
- {file.status === 'done' && downloadIcon}
+ {mergedStatus === 'done' && downloadIcon}
{removeIcon}
);
@@ -249,7 +258,7 @@ const ListItem = React.forwardRef(
{showProgress && (
{({ className: motionClassName }) => {
@@ -271,7 +280,7 @@ const ListItem = React.forwardRef(
);
const listContainerNameClass = classNames(`${prefixCls}-list-${listType}-container`, className);
const item =
- file.status === 'error' ? (
+ mergedStatus === 'error' ? (
node.parentNode as HTMLElement}>
{dom}
diff --git a/components/upload/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/upload/__tests__/__snapshots__/demo-extend.test.ts.snap
index 41e21cae90c0..eee99be5f23f 100644
--- a/components/upload/__tests__/__snapshots__/demo-extend.test.ts.snap
+++ b/components/upload/__tests__/__snapshots__/demo-extend.test.ts.snap
@@ -3780,77 +3780,81 @@ exports[`renders ./components/upload/demo/upload-with-aliyun-oss.md extend conte
class="ant-form ant-form-horizontal"
>
diff --git a/components/upload/__tests__/__snapshots__/demo.test.js.snap b/components/upload/__tests__/__snapshots__/demo.test.js.snap
index 8b689b138d10..c722bd7dc67c 100644
--- a/components/upload/__tests__/__snapshots__/demo.test.js.snap
+++ b/components/upload/__tests__/__snapshots__/demo.test.js.snap
@@ -3564,77 +3564,81 @@ exports[`renders ./components/upload/demo/upload-with-aliyun-oss.md correctly 1`
class="ant-form ant-form-horizontal"
>
diff --git a/components/upload/__tests__/__snapshots__/uploadlist.test.js.snap b/components/upload/__tests__/__snapshots__/uploadlist.test.js.snap
index 07aabf511832..e1a9dc879a84 100644
--- a/components/upload/__tests__/__snapshots__/uploadlist.test.js.snap
+++ b/components/upload/__tests__/__snapshots__/uploadlist.test.js.snap
@@ -118,7 +118,7 @@ exports[`Upload List itemRender 1`] = `
class="custom-item-render"
>
- 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
- 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
{
,
);
fireEvent.click(container.querySelector('div.ant-upload-list-item .anticon-trash'));
- 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 => {
diff --git a/components/upload/__tests__/uploadlist.test.js b/components/upload/__tests__/uploadlist.test.js
index 9419b6465d6f..cf24812ac97e 100644
--- a/components/upload/__tests__/uploadlist.test.js
+++ b/components/upload/__tests__/uploadlist.test.js
@@ -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(
+ ,
+ );
+
+ fireEvent.click(container.querySelector('.anticon-trash'));
+
+ // 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();
+ });
});
diff --git a/components/upload/index.en-US.md b/components/upload/index.en-US.md
index 2064e345f42a..c7d5cf6bffd3 100644
--- a/components/upload/index.en-US.md
+++ b/components/upload/index.en-US.md
@@ -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', // options:uploading, done, error. Intercepted file by beforeUpload don't have status field.
+ status: 'done', // options:uploading, 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
diff --git a/components/upload/index.zh-CN.md b/components/upload/index.zh-CN.md
index b162738573c7..d0ebc67d9dda 100644
--- a/components/upload/index.zh-CN.md
+++ b/components/upload/index.zh-CN.md
@@ -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 error,beforeUpload 拦截的文件没有 status 属性
+ status: 'done', // 状态有:uploading done error removed,被 beforeUpload 拦截的文件没有 status 属性
response: '{"status": "success"}', // 服务端响应内容
linkProps: '{"download": "image"}', // 下载链接额外的 HTML 属性
}
diff --git a/components/upload/interface.tsx b/components/upload/interface.tsx
index 02c36d801717..1b1c21808529 100755
--- a/components/upload/interface.tsx
+++ b/components/upload/interface.tsx
@@ -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;
diff --git a/components/upload/style/index.less b/components/upload/style/index.less
index c207e4855d0e..8b2af425a426 100644
--- a/components/upload/style/index.less
+++ b/components/upload/style/index.less
@@ -558,6 +558,7 @@
.@{upload-prefix-cls}-animate-inline-leave {
animation-duration: @animation-duration-slow;
animation-timing-function: @ease-in-out-circ;
+ animation-fill-mode: forwards;
}
.@{upload-prefix-cls}-animate-inline-appear,
diff --git a/docs/react/faq.en-US.md b/docs/react/faq.en-US.md
index 70188717cf89..7fa33a268052 100644
--- a/docs/react/faq.en-US.md
+++ b/docs/react/faq.en-US.md
@@ -99,6 +99,19 @@ Yes, you can [import `antd` with script tag](https://ant.design/docs/react/intro
If you need some features which should not be included in antd, try to extend antd's component with [HOC](https://gist.github.com/sebmarkbage/ef0bf1f338a7182b6775). [more](https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750#.eeu8q01s1)
+## How to get the definition which is not export?
+
+antd 会透出组件定义,但是随着重构可能导致内部一些定义命名或者属性变化。因而更推荐直接使用 Typescript 原生能力获取: antd will export mainly definitions, but not export internal definitions which may be rename or changed. So we recommend you to use Typescript's native ability to get the definition if needed:
+
+```tsx
+import { Table } from 'antd';
+
+type Props any> = Parameters[0];
+
+type TableProps = Props>;
+type DataSource = TableProps['dataSource'];
+```
+
## Date-related components locale is not working?
Please check whether import moment locale correctly.
diff --git a/docs/react/faq.zh-CN.md b/docs/react/faq.zh-CN.md
index 9dbe474a1a20..26189f4ff673 100644
--- a/docs/react/faq.zh-CN.md
+++ b/docs/react/faq.zh-CN.md
@@ -113,6 +113,19 @@ antd 内部会对 props 进行浅比较实现性能优化。当状态变更,
如果你需要一些 antd 没有包含的功能,你可以尝试通过 [HOC](https://gist.github.com/sebmarkbage/ef0bf1f338a7182b6775) 拓展 antd 的组件。 [更多](https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750#.eeu8q01s1)
+## 如何获取未导出的属性定义?
+
+antd 会透出组件定义,但是随着重构可能导致内部一些定义命名或者属性变化。因而更推荐直接使用 Typescript 原生能力获取:
+
+```tsx
+import { Table } from 'antd';
+
+type Props any> = Parameters[0];
+
+type TableProps = Props>;
+type DataSource = TableProps['dataSource'];
+```
+
## 我的组件默认语言是英文的?如何切回中文的。
请尝试使用 [ConfigProvider](/components/config-provider/#components-config-provider-demo-locale) 组件来包裹你的应用。
diff --git a/docs/react/i18n.en-US.md b/docs/react/i18n.en-US.md
index 261af1e009de..d43f63886e72 100644
--- a/docs/react/i18n.en-US.md
+++ b/docs/react/i18n.en-US.md
@@ -81,6 +81,7 @@ The following languages are currently supported:
| Portuguese | pt_PT |
| Romanian | ro_RO |
| Russian | ru_RU |
+| Sinhalese / Sinhala | si_LK |
| Slovak | sk_SK |
| Serbian | sr_RS |
| Slovenian | sl_SI |
diff --git a/docs/react/i18n.zh-CN.md b/docs/react/i18n.zh-CN.md
index c1b99425c879..dec18174f506 100644
--- a/docs/react/i18n.zh-CN.md
+++ b/docs/react/i18n.zh-CN.md
@@ -78,6 +78,7 @@ return (
| 葡萄牙语 | pt_PT |
| 罗马尼亚语 | ro_RO |
| 俄罗斯语 | ru_RU |
+| 僧伽罗语 | si_LK |
| 斯洛伐克语 | sk_SK |
| 塞尔维亚语 | sr_RS |
| 斯洛文尼亚语 | sl_SI |
diff --git a/docs/react/migration-v4.en-US.md b/docs/react/migration-v4.en-US.md
index 15dc609c67bd..8ea558843e4f 100644
--- a/docs/react/migration-v4.en-US.md
+++ b/docs/react/migration-v4.en-US.md
@@ -192,6 +192,14 @@ For parts that cannot be modified automatically, codemod will prompt on the comm
`@ant-design/codemod-v4` will help you migrate to antd v4. Obsolete components will be kept running through @ant-design/compatible. Generally, you don't need to migrate manually. The following sections detail the overall migration and changes.
+#### Install compatible package
+
+Install `@ant-design/compatible` with `v4-compatible-v3` tag:
+
+```bash
+npm install --save @ant-design/compatible@v4-compatible-v3
+```
+
#### Import the obsolete Form and Mention components via @ant-design/compatible package
```diff
diff --git a/docs/react/migration-v4.zh-CN.md b/docs/react/migration-v4.zh-CN.md
index 6c6991bcaa7f..a76991744078 100644
--- a/docs/react/migration-v4.zh-CN.md
+++ b/docs/react/migration-v4.zh-CN.md
@@ -193,6 +193,14 @@ antd4-codemod src
`@ant-design/codemod-v4` 会帮你迁移到 antd v4, 废弃的组件则通过 `@ant-design/compatible` 保持运行, 一般来说你无需手动迁移。下方内容详细介绍了整体的迁移和变化,你也可以参照变动手动修改。
+#### 安装兼容包
+
+安装 `@ant-design/compatible` 通过指定 `v4-compatible-v3` tag 确认为 v4 兼容 v3 版本:
+
+```bash
+npm install --save @ant-design/compatible@v4-compatible-v3
+```
+
#### 将已废弃的 `Form` 和 `Mention` 组件通过 `@ant-design/compatible` 包引入
```diff
diff --git a/docs/react/replace-moment.en-US.md b/docs/react/replace-moment.en-US.md
index 5b113405d84d..e6145741abb2 100644
--- a/docs/react/replace-moment.en-US.md
+++ b/docs/react/replace-moment.en-US.md
@@ -21,7 +21,6 @@ For example:
import { Dayjs } from 'dayjs';
import dayjsGenerateConfig from 'rc-picker/lib/generate/dayjs';
import generatePicker from 'antd/es/date-picker/generatePicker';
-import 'antd/es/date-picker/style/index';
const DatePicker = generatePicker(dayjsGenerateConfig);
@@ -61,7 +60,6 @@ For example:
import { Dayjs } from 'dayjs';
import dayjsGenerateConfig from 'rc-picker/lib/generate/dayjs';
import generateCalendar from 'antd/es/calendar/generateCalendar';
-import 'antd/es/calendar/style';
const Calendar = generateCalendar(dayjsGenerateConfig);
diff --git a/docs/react/replace-moment.zh-CN.md b/docs/react/replace-moment.zh-CN.md
index 4d87a58df6cb..42c6eee68201 100644
--- a/docs/react/replace-moment.zh-CN.md
+++ b/docs/react/replace-moment.zh-CN.md
@@ -21,7 +21,6 @@ title: 替换 Moment.js
import { Dayjs } from 'dayjs';
import dayjsGenerateConfig from 'rc-picker/es/generate/dayjs';
import generatePicker from 'antd/es/date-picker/generatePicker';
-import 'antd/es/date-picker/style/index';
const DatePicker = generatePicker(dayjsGenerateConfig);
@@ -61,7 +60,6 @@ export default TimePicker;
import { Dayjs } from 'dayjs';
import dayjsGenerateConfig from 'rc-picker/es/generate/dayjs';
import generateCalendar from 'antd/es/calendar/generateCalendar';
-import 'antd/es/calendar/style';
const Calendar = generateCalendar(dayjsGenerateConfig);
diff --git a/package.json b/package.json
index 4780cff536db..6bfe759b4d1a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "infrad",
- "version": "4.21.7",
+ "version": "4.22.3",
"description": "based on AntD",
"title": "Infra Design",
"keywords": [
@@ -48,6 +48,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",
@@ -113,14 +114,12 @@
],
"dependencies": {
"@ant-design/colors": "^6.0.0",
- "@ant-design/icons": "^4.7.0",
"@ant-design/react-slick": "~0.29.1",
"@babel/runtime": "^7.18.3",
"@ctrl/tinycolor": "^3.4.0",
"axios": "^0.26.0",
"classnames": "^2.2.6",
"copy-to-clipboard": "^3.2.0",
- "eslint-import-resolver-webpack": "^0.13.2",
"infra-design-icons": "^4.7.15",
"lodash": "^4.17.21",
"memoize-one": "^6.0.0",
@@ -129,18 +128,18 @@
"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.26.1",
+ "rc-field-form": "~1.27.0",
"rc-image": "~5.7.0",
"rc-input": "~0.0.1-alpha.5",
"rc-input-number": "~7.3.5",
"rc-mentions": "~1.9.0",
"rc-menu": "~9.6.0",
- "rc-motion": "^2.5.1",
+ "rc-motion": "^2.6.1",
"rc-notification": "~4.6.0",
- "rc-pagination": "~3.1.16",
- "rc-picker": "~2.6.8",
+ "rc-pagination": "~3.1.17",
+ "rc-picker": "~2.6.10",
"rc-progress": "~3.3.2",
"rc-rate": "~2.9.0",
"rc-resize-observer": "^1.2.0",
@@ -149,7 +148,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",
@@ -166,6 +165,7 @@
"@docsearch/css": "^3.0.0",
"@infra-fe/tools": "^15.1.0",
"@qixian.cs/github-contributors-list": "^1.0.3",
+ "@size-limit/file": "^8.0.0",
"@stackblitz/sdk": "^1.3.0",
"@testing-library/jest-dom": "^5.16.3",
"@testing-library/react": "^12.0.0",
@@ -215,6 +215,7 @@
"eslint-config-airbnb": "^19.0.0",
"eslint-config-prettier": "^8.0.0",
"eslint-import-resolver-alias": "^1.1.2",
+ "eslint-import-resolver-webpack": "^0.13.2",
"eslint-plugin-babel": "^5.3.0",
"eslint-plugin-compat": "^4.0.0",
"eslint-plugin-import": "^2.21.1",
@@ -287,6 +288,7 @@
"scrollama": "^3.0.0",
"semver": "^7.3.5",
"simple-git": "^3.0.0",
+ "size-limit": "^8.0.0",
"stylelint": "^14.9.0",
"stylelint-config-prettier": "^9.0.2",
"stylelint-config-rational-order": "^0.1.2",
@@ -310,6 +312,28 @@
"publishConfig": {
"registry": "https://npm.shopee.io/"
},
+ "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",
diff --git a/scripts/post-script.js b/scripts/post-script.js
index d9c21aad260b..e7a701f4444a 100644
--- a/scripts/post-script.js
+++ b/scripts/post-script.js
@@ -6,6 +6,23 @@ const inquirer = require('inquirer');
const chalk = require('chalk');
const { spawnSync } = require('child_process');
+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
@@ -28,16 +45,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: moment().diff(moment(time[version])),
- value: version,
- }));
+ const latestVersions = versionList
+ // Cut off
+ .slice(0, 30)
+ // Formatter
+ .map(version => ({
+ publishTime: time[version],
+ timeDiff: moment().diff(moment(time[version])),
+ value: version,
+ depreciated: matchDeprecated(version).match,
+ }));
+
+ const filteredLatestVersions = latestVersions
+ // Filter deprecated versions
+ .filter(({ depreciated }) => !depreciated);
- const startDefaultVersionIndex = latestVersions.findIndex(
+ 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;
@@ -57,26 +85,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 = moment(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));
diff --git a/site/bisheng.config.js b/site/bisheng.config.js
index b09cac63e6e6..84e4d9231b75 100644
--- a/site/bisheng.config.js
+++ b/site/bisheng.config.js
@@ -27,7 +27,7 @@ function alertBabelConfig(rules) {
});
}
-const port = process.env.DEV_PORT || 8001;
+const port = process.env.DEV_PORT || 8002;
module.exports = {
port,
diff --git a/site/theme/template/Content/Demo/index.jsx b/site/theme/template/Content/Demo/index.jsx
index 4782e046ecaf..72658a7d956f 100644
--- a/site/theme/template/Content/Demo/index.jsx
+++ b/site/theme/template/Content/Demo/index.jsx
@@ -1,5 +1,5 @@
/* eslint jsx-a11y/no-noninteractive-element-interactions: 0 */
-import { CheckOutlined, SnippetsOutlined, ThunderboltOutlined } from '@ant-design/icons';
+import { CheckOutlined, SnippetsOutlined, ThunderboltOutlined } from 'infra-design-icons';
import stackblitzSdk from '@stackblitz/sdk';
import { Alert, Badge, Tooltip } from 'antd';
import classNames from 'classnames';
diff --git a/site/theme/template/Layout/Footer.tsx b/site/theme/template/Layout/Footer.tsx
index d765ef7fcba8..aefde6b5f1bc 100644
--- a/site/theme/template/Layout/Footer.tsx
+++ b/site/theme/template/Layout/Footer.tsx
@@ -1,8 +1,6 @@
-import React from 'react';
-import { message } from 'antd';
+import React, { useMemo } from 'react';
import RcFooter from 'rc-footer';
import { Link } from 'bisheng/router';
-import { presetPalettes } from '@ant-design/colors';
import type { WrappedComponentProps } from 'react-intl';
import { FormattedMessage, injectIntl } from 'react-intl';
import {
@@ -19,21 +17,13 @@ import {
QuestionCircleOutlined,
BgColorsOutlined,
} from 'infra-design-icons';
-import ColorPicker from '../Color/ColorPicker';
-import { loadScript, getLocalizedPathname } from '../utils';
-
-class Footer extends React.Component {
- lessLoaded = false;
-
- state = {
- color: presetPalettes.blue.primary,
- };
-
- getColumns() {
- const { intl, location } = this.props;
+import type { FooterColumn } from 'rc-footer/lib/column';
+import { getLocalizedPathname } from '../utils';
+const Footer: React.FC = props => {
+ const { intl, location } = props;
+ const getColumns = useMemo(() => {
const isZhCN = intl.locale === 'zh-CN';
-
const getLinkHash = (path: string, hash: { zhCN: string; enUS: string }) => {
const pathName = getLocalizedPathname(path, isZhCN, location.query, hash);
const { pathname, query = {} } = pathName;
@@ -182,7 +172,7 @@ class Footer extends React.Component
enUS: 'JoinUs',
}),
LinkComponent: Link,
- } as any);
+ } as unknown as typeof col2['items'][number]);
}
const col3 = {
@@ -318,82 +308,23 @@ class Footer extends React.Component
},
],
};
-
return [col1, col2, col3, col4];
- }
-
- handleColorChange = (color: string) => {
- const {
- intl: { messages },
- } = this.props;
- message.loading({
- content: messages['app.footer.primary-color-changing'],
- key: 'change-primary-color',
- });
- const changeColor = () => {
- (window as any).less
- .modifyVars({
- '@primary-color': color,
- })
- .then(() => {
- message.success({
- content: messages['app.footer.primary-color-changed'],
- key: 'change-primary-color',
- });
- this.setState({ color });
- });
- };
-
- const lessUrl = 'https://gw.alipayobjects.com/os/lib/less/3.10.3/dist/less.min.js';
-
- if (this.lessLoaded) {
- changeColor();
- } else {
- (window as any).less = {
- async: true,
- javascriptEnabled: true,
- };
- loadScript(lessUrl).then(() => {
- this.lessLoaded = true;
- changeColor();
- });
- }
- };
-
- renderThemeChanger() {
- const { color } = this.state;
- const colors = Object.keys(presetPalettes).filter(item => item !== 'grey');
- return (
- presetPalettes[c][5]),
- ...colors.map(c => presetPalettes[c][4]),
- ...colors.map(c => presetPalettes[c][6]),
- ]}
- onChangeComplete={this.handleColorChange}
- />
- );
- }
+ }, [intl.locale, location.query]);
- render() {
- return (
-
- Made with ❤ by
- {/* eslint-disable-next-line react/jsx-curly-brace-presence */}{' '}
-
-
-
- >
- }
- />
- );
- }
-}
+ return (
+
+ Made with ❤ by
+ {/* eslint-disable-next-line react/jsx-curly-brace-presence */}{' '}
+
+
+
+ >
+ }
+ />
+ );
+};
export default injectIntl(Footer);
diff --git a/site/theme/template/Layout/Header/index.tsx b/site/theme/template/Layout/Header/index.tsx
index 7acdbd53c203..efe5571c9bba 100644
--- a/site/theme/template/Layout/Header/index.tsx
+++ b/site/theme/template/Layout/Header/index.tsx
@@ -1,9 +1,11 @@
-import React from 'react';
+import React, { useCallback, useContext, useEffect, useRef, useState, useMemo } from 'react';
+import type { WrappedComponentProps } from 'react-intl';
import { FormattedMessage, injectIntl } from 'react-intl';
import classNames from 'classnames';
import { Select, Row, Col, Popover, Button, Modal } from 'infrad';
import { MenuOutlined } from 'infra-design-icons';
import canUseDom from 'rc-util/lib/Dom/canUseDom';
+import type { DirectionType } from 'antd/es/config-provider';
import * as utils from '../../utils';
import packageJson from '../../../../../package.json';
import Logo from './Logo';
@@ -26,13 +28,11 @@ const { Option } = Select;
const antdVersion: string = packageJson.version;
export interface HeaderProps {
- intl: {
- locale: string;
- };
+ intl: { locale: string };
location: { pathname: string; query: any };
router: any;
themeConfig: { docVersions: Record };
- changeDirection: (direction: string) => void;
+ changeDirection: (direction: DirectionType) => void;
}
let docsearch: any;
@@ -61,7 +61,7 @@ function initDocSearch({ isZhCN, router }: { isZhCN: boolean; router: any }) {
transformData: AlgoliaConfig.transformData,
debug: AlgoliaConfig.debug,
// https://docsearch.algolia.com/docs/behavior#handleselected
- handleSelected: (input: any, _$1: unknown, suggestion: any) => {
+ handleSelected(input: any, _$1: unknown, suggestion: any) {
router.push(suggestion.url);
setTimeout(() => {
input.setVal('');
@@ -88,37 +88,44 @@ interface HeaderState {
showTechUIButton: boolean;
}
-class Header extends React.Component {
- static contextType = SiteContext;
-
- pingTimer: NodeJS.Timeout;
-
- state = {
+const Header: React.FC> = props => {
+ const { intl, router, location, themeConfig, changeDirection } = props;
+ const [headerState, setHeaderState] = useState({
menuVisible: false,
windowWidth: 1400,
searching: false,
showTechUIButton: false,
- };
-
- context: SiteContextProps;
-
- componentDidMount() {
- const { intl, router } = this.props;
- router.listen(this.handleHideMenu);
-
- initDocSearch({
- isZhCN: intl.locale === 'zh-CN',
- router,
- });
-
- window.addEventListener('resize', this.onWindowResize);
- this.onWindowResize();
-
- this.pingTimer = ping(status => {
+ });
+ const { direction } = useContext(SiteContext);
+ const pingTimer = useRef(null);
+
+ const handleHideMenu = useCallback(() => {
+ setHeaderState(prev => ({ ...prev, menuVisible: false }));
+ }, []);
+ const onWindowResize = useCallback(() => {
+ setHeaderState(prev => ({ ...prev, windowWidth: window.innerWidth }));
+ }, []);
+ const onTriggerSearching = useCallback((searching: boolean) => {
+ setHeaderState(prev => ({ ...prev, searching }));
+ }, []);
+ const handleShowMenu = useCallback(() => {
+ setHeaderState(prev => ({ ...prev, menuVisible: true }));
+ }, []);
+ const onMenuVisibleChange = useCallback((visible: boolean) => {
+ setHeaderState(prev => ({ ...prev, menuVisible: visible }));
+ }, []);
+ const onDirectionChange = useCallback(() => {
+ changeDirection(direction !== 'rtl' ? 'rtl' : 'ltr');
+ }, [direction]);
+
+ useEffect(() => {
+ router.listen(handleHideMenu);
+ initDocSearch({ isZhCN: intl.locale === 'zh-CN', router });
+ onWindowResize();
+ window.addEventListener('resize', onWindowResize);
+ pingTimer.current = ping(status => {
if (status !== 'timeout' && status !== 'error') {
- this.setState({
- showTechUIButton: true,
- });
+ setHeaderState(prev => ({ ...prev, showTechUIButton: true }));
if (
process.env.NODE_ENV === 'production' &&
window.location.host !== 'ant-design.antgroup.com' &&
@@ -128,86 +135,29 @@ class Header extends React.Component {
title: '提示',
content: '内网用户推荐访问国内镜像以获得极速体验~',
okText: '🚀 立刻前往',
- onOk: () => {
+ cancelText: '不再弹出',
+ closable: true,
+ onOk() {
window.open('https://ant-design.antgroup.com', '_self');
disableAntdMirrorModal();
},
- cancelText: '不再弹出',
- onCancel: () => {
+ onCancel() {
disableAntdMirrorModal();
},
- closable: true,
});
}
}
});
- }
-
- componentWillUnmount() {
- window.removeEventListener('resize', this.onWindowResize);
- clearTimeout(this.pingTimer);
- }
-
- onWindowResize = () => {
- this.setState({
- windowWidth: window.innerWidth,
- });
- };
-
- onTriggerSearching = (searching: boolean) => {
- this.setState({ searching });
- };
-
- handleShowMenu = () => {
- this.setState({
- menuVisible: true,
- });
- };
-
- handleHideMenu = () => {
- this.setState({
- menuVisible: false,
- });
- };
-
- onDirectionChange = () => {
- const { changeDirection } = this.props;
- const { direction } = this.context;
- if (direction !== 'rtl') {
- changeDirection('rtl');
- } else {
- changeDirection('ltr');
- }
- };
-
- getNextDirectionText = () => {
- const { direction } = this.context;
-
- if (direction !== 'rtl') {
- return 'RTL';
- }
- return 'LTR';
- };
-
- getDropdownStyle = (): React.CSSProperties => {
- const { direction } = this.context;
- if (direction === 'rtl') {
- return {
- direction: 'ltr',
- textAlign: 'right',
- };
- }
- return {};
- };
-
- onMenuVisibleChange = (visible: boolean) => {
- this.setState({
- menuVisible: visible,
- });
- };
+ return () => {
+ window.removeEventListener('resize', onWindowResize);
+ if (pingTimer.current) {
+ clearTimeout(pingTimer.current);
+ }
+ };
+ }, []);
// eslint-disable-next-line class-methods-use-this
- handleVersionChange = (url: string) => {
+ const handleVersionChange = useCallback((url: string) => {
const currentUrl = window.location.href;
const currentPathname = window.location.pathname;
if (/overview/.test(currentPathname) && /0?[1-39][0-3]?x/.test(url)) {
@@ -218,184 +168,168 @@ class Header extends React.Component {
return;
}
window.location.href = currentUrl.replace(window.location.origin, url);
- };
+ }, []);
- onLangChange = () => {
- const {
- location: { pathname, query },
- } = this.props;
+ const onLangChange = useCallback(() => {
+ const { pathname, query } = location;
const currentProtocol = `${window.location.protocol}//`;
const currentHref = window.location.href.slice(currentProtocol.length);
if (utils.isLocalStorageNameSupported()) {
localStorage.setItem('locale', utils.isZhCN(pathname) ? 'en-US' : 'zh-CN');
}
-
window.location.href =
currentProtocol +
currentHref.replace(
window.location.pathname,
utils.getLocalizedPathname(pathname, !utils.isZhCN(pathname), query).pathname,
);
- };
-
- render() {
- return (
-
- {({ isMobile }) => {
- const { menuVisible, windowWidth, searching, showTechUIButton } = this.state;
- const { direction } = this.context;
- const {
- location,
- themeConfig,
- intl: { locale },
- router,
- } = this.props;
- const docVersions: Record = {
- [antdVersion]: antdVersion,
- ...themeConfig.docVersions,
- };
- const versionOptions = Object.keys(docVersions).map(version => (
-
- ));
-
- const pathname = location.pathname.replace(/(^\/|\/$)/g, '');
-
- const isHome = ['', 'index', 'index-cn'].includes(pathname);
-
- const isZhCN = locale === 'zh-CN';
- const isRTL = direction === 'rtl';
- let responsive: null | 'narrow' | 'crowded' = null;
- if (windowWidth < RESPONSIVE_XS) {
- responsive = 'crowded';
- } else if (windowWidth < RESPONSIVE_SM) {
- responsive = 'narrow';
- }
-
- const headerClassName = classNames({
- clearfix: true,
- 'home-header': isHome,
- });
+ }, [location]);
+
+ const getNextDirectionText = useMemo(
+ () => (direction !== 'rtl' ? 'RTL' : 'LTR'),
+ [direction],
+ );
+
+ const getDropdownStyle = useMemo(
+ () => (direction === 'rtl' ? { direction: 'ltr', textAlign: 'right' } : {}),
+ [direction],
+ );
+
+ return (
+
+ {({ isMobile }) => {
+ const { menuVisible, windowWidth, searching, showTechUIButton } = headerState;
+ const docVersions: Record = {
+ [antdVersion]: antdVersion,
+ ...themeConfig.docVersions,
+ };
+ const versionOptions = Object.keys(docVersions).map(version => (
+
+ ));
+
+ const pathname = location.pathname.replace(/(^\/|\/$)/g, '');
+
+ const isHome = ['', 'index', 'index-cn'].includes(pathname);
+
+ const isZhCN = intl.locale === 'zh-CN';
+ const isRTL = direction === 'rtl';
+ let responsive: null | 'narrow' | 'crowded' = null;
+ if (windowWidth < RESPONSIVE_XS) {
+ responsive = 'crowded';
+ } else if (windowWidth < RESPONSIVE_SM) {
+ responsive = 'narrow';
+ }
- const sharedProps = {
- isZhCN,
- isRTL,
- };
-
- const navigationNode = (
-
- );
-
- let menu: (React.ReactElement | null)[] = [
- navigationNode,
- ,
- ,
- ,
- ,
- ,
- ];
-
- if (windowWidth < RESPONSIVE_XS) {
- menu = searching ? [] : [navigationNode];
- } else if (windowWidth < RESPONSIVE_SM) {
- menu = searching ? [] : menu;
- }
-
- const colProps = isHome
- ? [{ flex: 'none' }, { flex: 'auto' }]
- : [
- {
- xxl: 4,
- xl: 5,
- lg: 6,
- md: 6,
- sm: 24,
- xs: 24,
- },
- {
- xxl: 20,
- xl: 19,
- lg: 18,
- md: 18,
- sm: 0,
- xs: 0,
- },
- ];
-
- return (
-
- );
- }}
-
- );
- }
-}
+ const headerClassName = classNames({
+ clearfix: true,
+ 'home-header': isHome,
+ });
+
+ const sharedProps = {
+ isZhCN,
+ isRTL,
+ };
+
+ const navigationNode = (
+
+ );
+
+ let menu: (React.ReactElement | null)[] = [
+ navigationNode,
+ ,
+ ,
+ ,
+ ,
+ ,
+ ];
+
+ if (windowWidth < RESPONSIVE_XS) {
+ menu = searching ? [] : [navigationNode];
+ } else if (windowWidth < RESPONSIVE_SM) {
+ menu = searching ? [] : menu;
+ }
+
+ const colProps = isHome
+ ? [{ flex: 'none' }, { flex: 'auto' }]
+ : [
+ { xxl: 4, xl: 5, lg: 6, md: 6, sm: 24, xs: 24 },
+ { xxl: 20, xl: 19, lg: 18, md: 18, sm: 0, xs: 0 },
+ ];
+
+ return (
+
+ );
+ }}
+
+ );
+};
-export default injectIntl(Header as any);
+export default injectIntl(Header);
diff --git a/site/theme/template/Layout/SiteContext.tsx b/site/theme/template/Layout/SiteContext.tsx
index 9bb61f2e48bd..c002df15cb15 100644
--- a/site/theme/template/Layout/SiteContext.tsx
+++ b/site/theme/template/Layout/SiteContext.tsx
@@ -1,8 +1,9 @@
import * as React from 'react';
+import type { DirectionType } from 'antd/es/config-provider';
export interface SiteContextProps {
isMobile: boolean;
- direction: string;
+ direction: DirectionType;
}
const SiteContext = React.createContext({
diff --git a/tests/__mocks__/rc-trigger.js b/tests/__mocks__/rc-trigger.js
index 1c3add9e0dd5..813ac43067e7 100644
--- a/tests/__mocks__/rc-trigger.js
+++ b/tests/__mocks__/rc-trigger.js
@@ -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 ;
});
diff --git a/tests/shared/demoTest.tsx b/tests/shared/demoTest.tsx
index cdddbca8a83d..39683489a595 100644
--- a/tests/shared/demoTest.tsx
+++ b/tests/shared/demoTest.tsx
@@ -1,15 +1,14 @@
/* 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 moment from 'moment';
-import type { TriggerProps } from 'rc-trigger';
import { excludeWarning } from './excludeWarning';
+import { render, act } from '../utils';
+import { TriggerMockContext } from './demoTestContext';
-export const TriggerMockContext = React.createContext | undefined>(undefined);
-
-type CheerIO = ReturnType;
+type CheerIO = ReturnType;
type CheerIOElement = CheerIO[0];
// We should avoid use it in 4.0. Reopen if can not handle this.
const USE_REPLACEMENT = false;
@@ -52,6 +51,7 @@ function ariaConvert(wrapper: CheerIO) {
type Options = {
skip?: boolean | string[];
+ testingLib?: boolean;
};
function baseText(doInject: boolean, component: string, options: Options = {}) {
@@ -68,8 +68,9 @@ function baseText(doInject: boolean, component: string, options: Options = {}) {
doInject ? `renders ${file} extend context correctly` : `renders ${file} correctly`,
() => {
const errSpy = excludeWarning();
+ const mockDate = moment('2016-11-22').valueOf();
- MockDate.set(moment('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;
@@ -85,14 +86,29 @@ function baseText(doInject: boolean, component: string, options: Options = {}) {
);
}
- const wrapper = render(Demo);
+ if (options?.testingLib) {
+ jest.useFakeTimers().setSystemTime(mockDate);
- // Convert aria related content
- ariaConvert(wrapper);
+ const { container } = render(Demo);
+ act(() => {
+ jest.runAllTimers();
+ });
- expect(wrapper).toMatchSnapshot();
- MockDate.reset();
+ 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();
+ }
+
+ MockDate.reset();
errSpy();
},
);
diff --git a/tests/shared/demoTestContext.ts b/tests/shared/demoTestContext.ts
new file mode 100644
index 000000000000..df325ae27ba3
--- /dev/null
+++ b/tests/shared/demoTestContext.ts
@@ -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 | undefined>(undefined);
|